【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;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //初始化Google监听支付成功失败
        purchasesUpdatedListener = new PurchasesUpdatedListener() {
            @Override
            public 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());
                        //测试用例 当做成功 正式需服务器通知再handlePurchase
                        handlePurchase(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() {
            @Override
            public 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());
                }
            }
            @Override
            public 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() {
            @Override
            public 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() {
                    @Override
                    public 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") //直接塞入用户ID
                                    int 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() {
                    @Override
                    public 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() {
            @Override
            public 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() {
                    @Override
                    public 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>
        <activity
            android: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-data
                android: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 update
    void 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;//未能消费,因为尚未拥有此商品
}

猜你喜欢

转载自blog.csdn.net/qq_39574690/article/details/121252342