一.接入前准备
1.申请一个google play开发者账号,这里我是有google play开发账号的,毕竟我们的APP是发谷歌市场的
2.提前准备好一个apk(不需要集成支付sdk,占位用),在google play控制台上传你的apk,这里你可以发封闭测试里面去
3.发布一个alpha或者beta的版本,发布之前需要点亮以下选项(提交商品详情内容)(确定内容分级)(选择发布范围)等,之后才能正常发布(选择内部测试即可,alpha测试还需要)
4.添加测试人员,等应用审核通过之后,会得到一个地址,把地址发给对方,让对方点击同意加入测试即可
5.需要创建应用内商品(商品id,商品描述,定价),按提示填就可以了,注意确保商品id唯一
6.在账户详细信息里面,添加许可测试的邮箱账号,许可测试响应改为 “RESPOND_NORMALLY或者LICENSED”(一般选择LICENSED),点击保存,需要一两分钟生效,记得弄这一步,这个很坑,你不弄,你测试人员就一直不会出现测试卡测试的模式
7.检查你的包名和签名文件是否和Google Console 上面上传的apk包是否一致(网站:https://console.cloud.google.com/apis/credentials?pli=1&project=api-project-68117872)
8.检查版本号是否和Google console发布的apk版本是否一致
9.检查你是否可以购买,是否绑定了银行卡,手机支不支持Google支付,手机是否有Google服务(实际支付才需要绑定,测试支付不用绑定也可以)
10.测试登录和支付都需要vpn
二.打包配置
1.firebase创建项目,配置包名和打包使用的Cert的SHA-1密钥
2.下载配置json文件,放在Assets\Plugins\Android下(网址:https://console.cloud.google.com/apis/credentials?project=api-project-68117872),
3.baseProjectTemplate.gradle的repositories增加mavenCentral(),dependencies增加classpath 'com.google.gms:google-services:4.3.10'
4. launcherTemplate.gradle增加apply plugin: 'com.google.gms.google-services',
dependencies增加implementation project(':unityLibrary')
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation platform('com.google.firebase:firebase-bom:29.0.1')
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.android.gms:play-services-auth:19.2.0'
implementation "com.android.billingclient:billing:4.0.0"
implementation "com.android.billingclient:billing-ktx:4.0.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
- android段增加
viewBinding {
enabled = true
}
buildFeatures {
dataBinding = true
viewBinding = true
}
6.文件末尾加上task copyJsonFile {
copy {
from('../../../Assets/Plugins/Android/')
into('./')
include(‘google-services.json’)
}
}
preBuild.dependsOn copyJsonFile
7.AndroidManifest.xml所有android:debuggable="false",且增加
<activity android:name="com.ycy.googleplugin.MainActivity" android:label="googleplugin">
增加支付权限:
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR" />
8.proguard-user.txt增加-keep classmembers class com.ycy.googleplugin.MainActivity {public*;}
9.添加googleplugin.aar文件到Assets\Plugins\Android(aar文件在谷歌支付登录相关插件。-Unity3D文档类资源-CSDN下载)
10.检查一下工程中是否含有字节跳动、巨量引擎等第三方收集玩家手机隐私信息的sdk,被谷歌后台检测到没有及时处理应用会被下架处理,处理之后得等审核之后才能继续更新测试轨道的应用
10.2021版本的In App Purchasing插件打包的时候可能跟引用的两个库
implementation "com.android.billingclient:billing:4.0.0"和
implementation "com.android.billingclient:billing-ktx:4.0.0"冲突,打包的时候请注意移除这个插件
三.客户端代码
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ycynet.AccountSDK;
public abstract class SDKGoogle
{
public class LoginResult
{
public int success = -1;//-1进行中0失败1成功
public string err_msg = "";
public string id = "";
public string token = "";
public override string ToString()
{
return "success==" + success.ToString() + "err_msg: " + err_msg + "id: " + id + " token: " + token;
}
}
public abstract void Init(string serverClientId);
public abstract void Login();
public abstract LoginResult GetLoginResult();
public abstract string PayForOrder(string accountId, string serverOrderId, string productId);
[Serializable]
public class SDKGoogle_Consumes
{
public List<string> lst;
}
[Serializable]
public class SDKGoogle_ConsumeReq
{
public long tm;
public string tokenAccOrders;
}
/// <summary>
/// 消费通知,要在全局存在对象中有相应方法,如SDKMgr中定义相同方法
/// </summary>
/// <param name="consumeInfo"></param>
public void OnNotifyGooglePlayBuyConsumeSuccess(string consumeInfo)
{
SDKGoogle_Consumes ss = JsonUtility.FromJson<SDKGoogle_Consumes>(string.Format("{ \"lst\" : {0}}", consumeInfo));
string consumeUrl = "http://ycygamesupport.ycyhero.com/api/payment/PaymentRcvResponse?pp=googleplay";
SDKGoogle_ConsumeReq _postData = new SDKGoogle_ConsumeReq();
_postData.tm = TimeUtil.getNowInt();
_postData.tokenAccOrders = string.Join("|", ss.lst);
Debug.Log("pay..tm==" + _postData.tm + " tokenOrder==" + _postData.tokenAccOrders);
HttpConnect.Post(consumeUrl, _postData, OnNotifyGooglePlayBuyConsumeSuccessCb);
}
public void OnNotifyGooglePlayBuyConsumeSuccessCb(string data)
{
Debug.Log(data);
}
}
#if UNITY_ANDROID
public class SDKGoogle_AND : SDKGoogle
{
private static SDKGoogle_AND _instance = null;
public static SDKGoogle_AND Instance { get { if (_instance == null) _instance = new SDKGoogle_AND(); return _instance; } }
public AndroidJavaObject ClassObject = new AndroidJavaObject("com.ycy.googleplugin.MainActivity");
private AndroidJavaObject getApplicationContext()
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
using (AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"))
{
return jo.Call<AndroidJavaObject>("getApplicationContext");
}
}
}
private AndroidJavaObject getCurrentActivity()
{
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
return jc.GetStatic<AndroidJavaObject>("currentActivity");
}
}
/// <summary>
/// 谷歌初始化
/// </summary>
/// <param name="serverClientId">googleConsle 后台的客户端ID,类型是web应用</param>
public override void Init(string serverClientId)
{
ClassObject.Call("sdkInit", serverClientId, getCurrentActivity());
}
public override void Login()
{
ClassObject.Call("signIn");
}
public override LoginResult GetLoginResult()
{
return JsonUtility.FromJson<LoginResult>(ClassObject.Call<string>("getLoginResult"));
}
public override string PayForOrder(string accountId, string serverOrderId, string productId)
{
return ClassObject.Call<string>("payForOrder",accountId,serverOrderId,productId);
}
}
#endif
#if UNITY_IOS
public class SDKGoogle_IOS
{
private static SDKGoogle_IOS _instance = null;
public static SDKGoogle_IOS Instance { get { if (_instance == null) _instance = new SDKGoogle_IOS(); return _instance; } }
#region DllImport
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalInitWithAppKeyAndChannel_Tracking(string appKey, string channelId);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetRegisterWithAccountID_Tracking(string account);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetLoginWithAccountID_Tracking(string account);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetRyzf_Tracking(string ryTID, string ryzfType, string hbType, float hbAmount);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetDD_Tracking(string ryTID, string hbType, float hbAmount);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetEvent_Tracking(string EventName);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetTrackViewDuration_Tracking(string viewID, long duration);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetAdShow_Tracking(string adPlatform, string adid);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetAdClick_Tracking(string adPlatform, string adid);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetTrackAppDuration_Tracking(long duration);
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern string _internalGetDeviceId_Tracking();
[System.Runtime.InteropServices.DllImport("__Internal")]
private static extern void _internalSetPrintLog_Tracking(bool printLog);
#endregion
public void init(string appKey, string channelId)
{
_internalInitWithAppKeyAndChannel_Tracking(appKey, channelId);
}
public void register(string account)
{
_internalSetRegisterWithAccountID_Tracking(account);
}
public void login(string account)
{
_internalSetLoginWithAccountID_Tracking(account);
}
public void setryzfStart(string ryTID, string ryzfType, string hbType, float hbAmount) { }
public void setryzf(string ryTID, string ryzfType, string hbType, float hbAmount)
{
_internalSetRyzf_Tracking(ryTID, ryzfType, hbType, hbAmount);
}
public void setEvent(string eventName)
{
_internalSetEvent_Tracking(eventName);
}
public void setTrackAppDuration(long duration)
{
_internalSetTrackAppDuration_Tracking(duration);
}
}
#endif
四.上传到谷歌后台
五.开始测试
- 上传后一般需要20分钟之后,手机打开之前的那个测试链接才会更新,如果没有更新,尝试清除谷歌商店的缓存,并重新打开测试链接
- 测试的支付的时候一定要注意,最好清除浏览器的缓存和谷歌商店的缓存,并清除所有登录的谷歌账号,安卓机退出谷歌账号是在设置里面,确保本机只有一个谷歌账号,不同的账号可能会出现唤起不了支付的状况(被坑的很惨一个),哪怕你的账号都是可以支付的
- 测试的时候注意看应用版本,有时候应用可能没有更新
六.注意事项
1在"APK"页面里,有一个“选择使用网址”,把这个网址给你的测试人员,让你的测试人员用他的google账号点进去,点那个“成为测试人员”(前提是你把他加进了测试人员列表), 只有这样才能测试商品支付。
2保证 versionCode 和版本号与你上传的apk的包的一样。(每次上传的包versioncode必须大于一次上传的包否则谷歌会提示异常)
- 保证后台和你传入的购买商品的 id 一致,请确认打包apk的包名以及签名皆一致。
- 可能封闭式测试、内部测试同时开启造成测试用户乱序。(测试的时候最好关暂停其中一个轨道)
- 检查账号所在地是不是在开启地区/国家范围内(正常情况不需要)
- 清空谷歌市场APP缓存和浏览器缓存重新登录(必须确保测试连接登录的账号和googlePlay登录的账号一致否则可能出现支付唤起不了支付界面的情况)
- 服务端和客户端都需要vpn
七.支付返回CODE(BillingResponseCode)
// * int SERVICE_TIMEOUT = -3;//服务超时
// * int FEATURE_NOT_SUPPORTED = -2;//不支持功能
// * int SERVICE_DISCONNECTED = -1;//服务单元已断开
// * int OK = 0;//成功
// * int USER_CANCELED = 1;//用户按上一步或取消对话框
// * int SERVICE_UNAVAILABLE = 2;//网络连接断开
// * int BILLING_UNAVAILABLE = 3;//所请求的类型不支持 Google Play 结算服务 AIDL 版本
// * int ITEM_UNAVAILABLE = 4;//请求的商品已不再出售。
// * int DEVELOPER_ERROR = 5;//提供给 API 的参数无效。此错误也可能说明应用未针对结算服务正确签名或设置,或者在其清单中缺少必要的权限。
// * int ERROR = 6;//API 操作期间出现严重错误
// * int ITEM_ALREADY_OWNED = 7;//未能购买,因为已经拥有此商品
// * int ITEM_NOT_OWNED = 8;//未能消费,因为尚未拥有此商品