【Unity】Google内购
目录
一、创建空安卓库工程
二、Unity配置
三、注意事项
版本更新注意事项
服务器相关(相关文章如下)
支付相关错误码
https://developer.android.com/google/play/billing/integrate
一、创建空安卓库工程
应用gradle文件添加依赖
dependencies {def billing_version = "4.0.0"implementation "com.android.billingclient:billing:$billing_version"
}
源码:
package com.test.googlebillingandroidproj;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;import android.app.Activity;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesResponseListener;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.unity3d.player.UnityPlayer;import java.util.ArrayList;
import java.util.List;public class MainActivity extends UnityPlayerActivity {//初始化Google监听支付成功失败private PurchasesUpdatedListener purchasesUpdatedListener;private BillingClient billingClient;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//初始化Google监听支付成功失败purchasesUpdatedListener = new PurchasesUpdatedListener() {@Overridepublic void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {//支付回调int responseCode = billingResult.getResponseCode();String debugMsg = billingResult.getDebugMessage();Log.d("GooglePay", "responseCode: " + responseCode + ", debugMsg: " + debugMsg);if(null != purchases && BillingClient.BillingResponseCode.OK == responseCode) {for(Purchase purchase : purchases) {// TODO 通知服务端发货,发货成功后,把订单关闭Log.d("Unity", "通知服务端发货,发货成功后,把订单关闭");ToaskMakeTest("订单成功支付 准备进行确认操作 服务器下发奖励" + purchase.getPurchaseToken());//测试用例 当做成功 正式需服务器通知再handlePurchasehandlePurchase(purchase); // 注意,必须确保服务器发货成功后再执行handlePurchase}} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {// Handle an error caused by a user cancelling the purchase flow.Log.d("Unity", "11111111111111111 用户关闭订单");ToaskMakeTest("用户关闭订单");} else {// Handle any other error codes.//TODO E Unity : 支付其他错误码:4 (成乔测试) ITEM_UNAVAILABLE = 4; 物品不可用?// 解决方法:测试人员必须通过链接登录邮箱点击接受测试..Log.e("Unity", "支付其他错误码:" + responseCode);ToaskMakeTest("支付其他错误码:" + responseCode);}}};billingClient = BillingClient.newBuilder(MainActivity.this).setListener(purchasesUpdatedListener).enablePendingPurchases().build();InitGooglePay();}public boolean IsConnectGoogleServer(){return billingClient.isReady();}//初始化Google支付public void InitGooglePay(){//连接google服务器billingClient.startConnection(new BillingClientStateListener() {@Overridepublic void onBillingSetupFinished(BillingResult billingResult) {if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {// The BillingClient is ready. You can query purchases here.Log.i("Unity", "Google connect successfully!");ToaskMakeTest("Google connect successfully!");SearchGood(); //查询出所有现有的货品详情 //测试..}else{//TODO 问题一直无法进行连接谷歌服务器 返回3, 支付无效 可能是手机谷歌框架不匹配手机本身 或者 必须用release发布版并且等成功后//解决方法:VPN换美国线路 ***美国账号Google邮箱*** 测试人员需经过测试链接确认进入测试模式Log.e("Unity", "Google connect server fail! code:" + billingResult.getResponseCode());ToaskMakeTest("Google connect server fail! code:" + billingResult.getResponseCode());}}@Overridepublic void onBillingServiceDisconnected() {// Try to restart the connection on the next request to// Google Play by calling the startConnection() method.Log.i("Unity", "Google disconnect!!");ToaskMakeTest("Google disconnect!!");}});}//消耗型商品订单确认(订单关闭)//订单关闭(完成支付成功后 通知服务器发送货物成功后执行的行为 或者 客户端主动取消支付执行关闭订单)【消耗性商品确认流程】//purchase参数从PurchasesUpdatedListener支付成功回调拿到//或者 从玩家登陆服务器时发起补单BillingClient#queryPurchases行为获取到直接处理关闭订单(补单也是要确认服务器发货成功)void handlePurchase(Purchase purchase) {ConsumeParams consumeParams =ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();ConsumeResponseListener listener = new ConsumeResponseListener() {@Overridepublic void onConsumeResponse(BillingResult billingResult, String purchaseToken) {if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {// Handle the success of the consume operation.//订单关闭完毕Log.i("Unity", "订单确认完毕!");ToaskMakeTest("流程跑通 订单确认完毕!");}else{Log.e("Unity", "订单确认异常, code:" + billingResult.getResponseCode());ToaskMakeTest("订单确认异常, code:" + billingResult.getResponseCode());}}};billingClient.consumeAsync(consumeParams, listener);}private void ToaskMakeTest(String str) {
// if (Looper.myLooper() == Looper.getMainLooper()) { // UI主线程
// Toast.makeText(MainActivity.this, str, Toast.LENGTH_LONG).show();
// } else { // 非UI主线程
// Looper.prepare();
// Toast.makeText(MainActivity.this, str, Toast.LENGTH_LONG).show();
// Looper.loop();
// }}//通过id查询并购买商品(可行)public void SearchAndPurchaseById(String id) {Log.i("Unity","准备购买商品:" + id + " 已连接Google服务器? " + IsConnectGoogleServer());List<String> skuList = new ArrayList<>();skuList.add(id);SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);billingClient.querySkuDetailsAsync(params.build(),new SkuDetailsResponseListener() {@Overridepublic void onSkuDetailsResponse(BillingResult billingResult,List<SkuDetails> skuDetailsList) {// Process the result.int resultCode = billingResult.getResponseCode();if (BillingClient.BillingResponseCode.OK != resultCode) {Log.e("Unity", String.format("购买商品时,查询失败。错误代码:%s", resultCode));ToaskMakeTest(String.format("购买商品时,查询失败。错误代码:%s", resultCode));} else if (skuDetailsList != null && skuDetailsList.size() > 0) {for (SkuDetails skuDetails : skuDetailsList) {Log.i("Unity", "查询出物品列表 物品id:" + skuDetails.getSku() + ", title:" + skuDetails.getTitle());if(id.equals(skuDetails.getSku())){Activity activity = MainActivity.this;BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();// .setObfuscatedAccountId("10000001") //直接塞入用户IDint responseCode = billingClient.launchBillingFlow(activity, billingFlowParams).getResponseCode();Log.i("Unity", "支付[" + id + "] 开始Start launchBillingFlow >> responseCode:" + responseCode);ToaskMakeTest("支付[" + id + "] 开始Start launchBillingFlow >> responseCode:" + responseCode);}}} else {Log.e("Unity", "购买商品异常失败, SkuDetailsList = " + skuDetailsList);ToaskMakeTest("购买商品异常失败, SkuDetailsList = " + skuDetailsList);}}});}///连上服务器时缓存所有商品并展览public void SearchGood() {Log.i("Unity", "开始查询所有物品!");List<String> skuList = new ArrayList<>();skuList.add("good_1004");skuList.add("good_10022");skuList.add("good_1003");SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);billingClient.querySkuDetailsAsync(params.build(),new SkuDetailsResponseListener() {@Overridepublic void onSkuDetailsResponse(BillingResult billingResult,List<SkuDetails> skuDetailsList) {// Process the result.int resultCode = billingResult.getResponseCode();if (BillingClient.BillingResponseCode.OK != resultCode) {Log.e("Unity", String.format("购买商品时,查询失败。错误代码:%s", resultCode));ToaskMakeTest(String.format("购买商品时,查询失败。错误代码:%s", resultCode));} else if (skuDetailsList != null && skuDetailsList.size() > 0) {for (SkuDetails skuDetails : skuDetailsList) {Log.i("Unity", "查询出物品列表 物品id:" + skuDetails.getSku() + ", title:" + skuDetails.getTitle());ToaskMakeTest("查询出物品列表 物品id:" + skuDetails.getSku() + ", title:" + skuDetails.getTitle());}} else {Log.e("Unity", "购买商品异常失败, SkuDetailsList = " + skuDetailsList);ToaskMakeTest("购买商品异常失败, SkuDetailsList = " + skuDetailsList);}}});}//客户端登陆服务器成功后,补发货物public void Replenishment(){ToaskMakeTest("补发货物");//异步请求补发订单billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, new PurchasesResponseListener() {@Overridepublic void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> list) {if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {for(Purchase purchase : list) {if (Purchase.PurchaseState.PURCHASED == purchase.getPurchaseState()) {// TODO 通知服务端补发货,发货完成后,客户端关闭订单。//purchase.getSkus() 订单货物ID列表?String strIds = "";for(String skuId : purchase.getSkus()){strIds += skuId + "|";Log.i("Unity", "skuId:" + skuId);}ToaskMakeTest("补发货物id:" + strIds + ", 订单token:" + purchase.getPurchaseToken() + ", order id:" + purchase.getOrderId());//TODO 需通知服务器补发货物,根据商品ID,若服务器已经发货,则直接回调客户端确认订单purchase, 否则发货 再确认,都是要通知客户端处理handlePurchase(purchase); // 注意,必须确保服务器发货成功后再执行handlePurchase}}}}});}//非消耗型商品订单确认(订单关闭)void handleOtherPurchase(Purchase purchase) {BillingClient client = billingClient;if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {if (!purchase.isAcknowledged()) {AcknowledgePurchaseParams acknowledgePurchaseParams =AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();client.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {@Overridepublic void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) {}});}}}
}
主要Unity需调用
SearchAndPurchaseById函数
进行购买具体商品,传递商品ID,商品ID的配置在Google Console的内部应用商品里配置。
商品token验证上面源码是客户端进行的,正常要服务器进行验证是否有效token。
https://developer.android.com/google/play/billing/security
AndroidManifest.xml配置
需添加uses-permission和meta-data如下所示。
<uses-permission android:name="com.android.vending.BILLING" /><application><activityandroid:name="com.test.googlebillingandroidproj.MainActivity"android:exported="true" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><meta-dataandroid:name="unityplayer.UnityActivity"android:value="true" /></activity></application>
最终打包成aar 获取里面的classes.jar和AndroidManifest 移动到Plugins/Android下。
注意:classes.jar 有一个文件 BuildConfig.classes删掉。
二、Unity配置
https://mvnrepository.com/
进入这个网址下载billing-4.0.0 arr包,导入Plugins/Android下。它是sdk的依赖包
紧接着Unity的C#代码
using UnityEngine;public class GoogleBillingComponent : MonoBehaviour
{AndroidJavaClass jc;AndroidJavaObject jo;// Start is called before the first frame updatevoid Start(){jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");jo = jc.GetStatic<AndroidJavaObject>("currentActivity");}private void OnDestroy(){jo.Dispose();jc.Dispose();jo = null;jc = null;}public void SearchAndPurchaseById(){Debug.Log("Unity ~~RequestAndroidGoogleLogin~~~~~~~~~~~~~~~~~");jo.Call("SearchAndPurchaseById", "good_1003");}
}
三、配置Google项目
https://play.google.com/console/
按步骤创建你的应用,要全部!全部!都填写好!比如 应用的图标 等各种无关紧要的东西 都要填写好,不然可能会出点奇奇怪怪的问题。
创建内部商品
测试时如果没有支付google的银行卡 可以用
创建免费的code去直接买商品(测试能创建100个)注意它的时间设定是在我们北京时间的-8小时。 即你想在早上10点 到 中午12点,开放code, 那应该是 填写 2点 到 4点。(格林尼治标准时间 (GMT))也就是说 北京时间-8小时 才是我们要填写的时间值,我们早上10点,就是格林尼治时间的2点,所以应该填2点!
三、注意事项
1、手机必须要能连外网
2、手机必须要有Google三件套
3、需要一个国外的Google邮箱(最好是漂亮国的)且不能是开发者账号。
4、需要通过邮箱测试验证。
要填写你的测试邮箱到测试列表里,反馈邮箱无所谓,然后复制下面的链接,传给测试人员去打开这个网页 进行验证邮箱 确认进行测试。(这一步很的重要!!!!
2022年2月11日更新:
正式测试支付功能需要添加许可测试,将你测试支付的google账号添加进去。
如果发现报错code:3 无法拉起支付窗口,则说明你的账号或网络有问题,需要换一个网络代理或换到美服账号。
版本更新注意事项
更新aab时若没有变更后台数据(例如:充值项)直接在下面这里提交最新的aab更新即可
服务器相关(相关文章如下)
http://www.wangwenyong.com/?p=306
支付相关错误码
错误码支付返回CODE(BillingResponseCode)
public @interface BillingResponseCode {undefined
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;//未能消费,因为尚未拥有此商品
}
2022年11月10日更新
许可测试:加入该测试的邮箱账号都能在所有测试渠道都可免费购买内购商品
开放式测试、内部测试邮箱列表和测试链接并无太多作用,如果内部测试时有报错可添加,尝试解决。
关于将aab包给谷歌发布后会修改密钥SHA的问题,其实是没有升级密钥操作导致的。
升级密钥操作:谷歌后台页面-选中你的应用-设置-应用完整性-升级应用签名密钥,按照文档提供的说明进行操作,执行一个命令即可完成操作,完成后应用签名密钥证书的SHA-1就会变回你的签名文件(keystore文件)对应的SHA-1了。
【Unity】Google内购相关推荐
- google内购-订阅模式
1.订单有变化接收google推送的接口,据此可以实现续订订单 /*** 接收google推送接口* @param body* @param request* @param response* @re ...
- Python Google内购服务端验证
Google内购完成后,服务端需要校验订单的状态是否正确(是否已经成功付款). 一.申请认证 参考https://developers.google.cn/android-publisher/gett ...
- Unity iOS内购
前言:最近项目需要切换到iOS平台做一些提交审核和支付对接相关的工作,上一篇刚分享了最新的iOS10提交审核的一些坑,这篇分享一些内购相关的流程. Unity iOS内购 思路: Unity调用iOS ...
- Google 内购总结
Google 内购坑之总结 最近项目中增加了 Google 内购的内容,接入并不难,在这里总结下接入过程中的细节和坑的地方. 内购接入过程 如何接入官方的教程写的很详细(传送门),并且官方也提供了一个 ...
- google内购In-App Billing
本帖地址:http://blog.csdn.net/jinjian2009/article/details/9140891 这周做了google的内购,没搞过google的内购还是觉得比较繁琐的 go ...
- unity 配置内购_内购推荐 (IAP Promo)
应用商店扩展 内购推荐 (IAP Promo) 集成 内购推荐 (IAP Promo) 概述 内购推荐 (IAP Promo) 让开发者可以轻松促进应用内购.使用内购推荐可以在游戏中的不同位置向不同玩 ...
- Google内购--封装版
最近老大提出了一个需求,在应用里面加一个内购.由于之前没做过这块,所以百度一番.网上都是讲的使用一大堆的utils.还要加一个aidl文件.感觉挺麻烦的.最终让我找到了:com.android.bil ...
- Google 内购 - Android
1. 添加依赖 implementation "com.android.billingclient:billing:5.0.0" 2.支付相关的代码 /*** 连接**/publi ...
- java集成Google Pay内购
挺简单的直接上代码: api入参 @Data @ApiModel("google支付表单信息") public class GooglePayForm {/*** 包名*/@Api ...
最新文章
- 总结一下对buffer的学习体会
- 《深入剖析NGINX》学习记录
- 用GDB排查Python程序故障
- 网站运营需要注意什么?
- astr在python_python学习之初识字符串
- 超全面的JavaWeb笔记day11JSPSessionCookieHttpSession
- 使用阿里云接口进行银行卡三四要素实名认证(阿里云api接口java)
- HCIE学习笔记(2)之ISIS Overload
- 黑色星期五 问题描述 有些西方人比较迷信,如果某个月的13号正好是星期五,他们就会觉得不太吉利,用古人的说法,就是“诸事不宜”。请你编写一个程序,统计出在某个特定的年份中,出现了多少次既是13号又
- 【Java实验】文件中单词重复字母对的查找
- Distributing Ballot Boxes HDU - 4190【详细翻译】【贪心、二分】
- VS2010中水晶报表插件下载安装方法 详细出处参考:http://www.jb51.net/softjc/88860.html
- acme + acme-dns + google domains 签发泛域名证书
- 人工智能计算机技术图片,这是人工智能眼中它自己的样子
- 小程序商城制作一个需要多少钱?
- 迭代器(list迭代器的实现)
- WF2011 Chips Challenge
- 震惊!!十五天开发出一款安卓打卡app,并且成功发布!
- No core dump will be written. Minidumps are not enabled by default on client versions of Windows
- 地图切图工具:批量标注功能需要安装Firebird数据库
热门文章
- PROBOT_G603双臂GAZEBO+MoveIt!仿真中配置手眼相机和夹爪
- css如何将图片调成合适大小,如何利用CSS自动调整图片的大小
- 强化学习(一):概述
- 互联网如何再定义古老的眼镜行业?
- Oracle 语句大全,确实不错,赶紧转走!
- 小游戏“程序猿大战产品*那啥”
- echarts (二) 之canvas设置地图背景图
- 判断ssh远程命令是否执行结束
- 医院预约挂号系统(Java+SSM+MySQL+Maven)
- java强引用不会被回收_强引用(Strong Reference)-不回收