IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【Unity】Google内购 -> 正文阅读

[游戏开发]【Unity】Google内购

目录

一、创建空安卓库工程

二、Unity配置

三、注意事项


https://developer.android.com/google/play/billing/getting-ready

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、需要通过邮箱测试验证。

?要填写你的测试邮箱到测试列表里,反馈邮箱无所谓,然后复制下面的链接,传给测试人员去打开这个网页 进行验证邮箱 确认进行测试。(这一步很的重要!!!!

这里只有服务器方面的东西我没有说明,因为哇不是服务器!

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-11-11 13:02:12  更:2021-11-11 13:02:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/28 0:36:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码