Unity客户端接入原生Google支付

1. Google后台配置

  1. 找到内部测试(这个测试轨道过审最快),打包上传,这个包不需要接入支付,如果已经有上传过包了那就跳过这一步
    在这里插入图片描述

  2. 在许可测试里添加测试人员,勾选测试人员列表,并且设置许可相应为LICENSED,这样才可以使用测试卡测试支付
    在这里插入图片描述

  3. 确认已经添加了付款方式,以及开放地区有香港,否则可能需要挂VPN才能进行支付流程测试
    在这里插入图片描述
    在这里插入图片描述

  4. 流程图
    在这里插入图片描述

2. 开始接入

确保Unity Plugins/Android里有com.android.billingclient.billing,并且是v3版本以上,这里用的5.0.0版本
在这里插入图片描述

Java部分

java文件,也放到Plugins/Android下,开头package需要根据项目而定
GoogleBillingManager.java

package com.xxx.xxx;
 
import android.app.Activity;
import android.content.Context;
import android.util.Log;
 
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.unity3d.player.UnityPlayer;
 
import java.util.List;
 
public class GoogleBillingManager {
    private static GoogleBillingManager instance;
    private static BillingClient billingClient;
    private static GoogleBillingListener billingListener;
    public static boolean isConnection = false;
 
    private GoogleBillingManager() {
        instance = this;
        createClient(UnityPlayer.currentActivity);
    }
 
    public static GoogleBillingManager getInstance() {
        if (instance == null) {
            synchronized (GoogleBillingManager.class) {
                if (instance == null) {
                    instance = new GoogleBillingManager();
                }
            }
        }
        return instance;
    }
 
    /**
     * 创建支付客户端
     */
    public static void createClient(Activity activity) {
        if (isReady()) {
            return;
        }
        if (null == activity) {
            Log.e("TAG","谷歌支付CreateClient, activity = null");
            return;
        }
        billingClient = BillingClient.newBuilder(activity)
                .enablePendingPurchases()
                .setListener(new PurchasesUpdatedListener() {
                    @Override
                    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
                        if (null != billingListener) {
                            billingListener.onPurchasesUpdated(billingResult, purchases);
                        }
                    }
                })
                .build();
        //启动支付连接
        startConn();
    }
 
    public BillingClient getBillingClient() {
        return billingClient;
    }
    /**
     * 添加监听事件
     */
    public void setBillingListener(GoogleBillingListener listener) {
        billingListener = listener;
    }
 
    /**
     * 是否准备好了
     *
     * @return
     */
    public static boolean isReady() {
        return !(null == billingClient || !billingClient.isReady());
    }
 
    /**
     * 启动连接
     */
    private static void startConn() {
        if (isReady()) {
            return;
        }
 
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    isConnection = true;
                    Log.e("TAG", "连接成功,可以开始操作了~~~");
                }
            }
 
            @Override
            public void onBillingServiceDisconnected() {
                isConnection = false;
                //连接失败。 可以尝试调用 startConnection 重新建立连接
                Log.e("TAG", "连接失败");
            }
        });
 
    }
 
    /**
     * 结束连接
     */
    public void endConn() {
        if (null != billingClient) {
            billingClient.endConnection();
            isConnection = false;
        }
    }
}

GoogleBillHelper.java

package com.xxx.xxx;
 
import android.app.Activity;
import android.util.Log;
 
import com.android.billingclient.api.BillingClient;
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.ProductDetails;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesResponseListener;
import com.android.billingclient.api.QueryProductDetailsParams;
import com.android.billingclient.api.QueryPurchasesParams;
import com.unity3d.player.UnityPlayer;
 
import java.util.ArrayList;
import java.util.List;
 
import io.reactivex.annotations.NonNull;
 
/**
 * Desc:支付的具体操作
 * 1.查询
 * 2.购买
 * 3.消费
 */
public class GoogleBillHelper {
    public static final String TAG = GoogleBillHelper.class.getSimpleName();
 
    /**
     * 查询商品详情
     *
     * @param billingListener : 接口监听
     * @param productIds      :商品id 。对应Google 后台的
     * @param productType     :取值
     *                        BillingClient.ProductType.INAPP(一次性商品)
     *                        BillingClient.ProductType.SUBS(订阅)
     */
    public static void onQuerySkuDetailsAsync(GoogleBillingListener billingListener, String productType, String productIds, String orderId) {
        if (null == productIds){
            return;
        }
        String[] productList = productIds.split(",");
        Log.e("TAG", "onQuerySkuDetailsAsync: " + productIds + "   ----->" + productList[0]);
        if (productList.length == 0 || !GoogleBillingManager.getInstance().isReady()) {
            Log.e("TAG", "productList.length:" + productList.length + ",client:" + GoogleBillingManager.getInstance().isReady());
            return;
        }
        List<QueryProductDetailsParams.Product> skuList = new ArrayList<>();
        for (String productId : productList) {
            QueryProductDetailsParams.Product product = QueryProductDetailsParams
                    .Product.newBuilder()
                    .setProductId(productId)
                    .setProductType(productType)
                    .build();
            //添加对应的 产品id 去查询详情
            skuList.add(product);
        }
 
        QueryProductDetailsParams params = QueryProductDetailsParams
                .newBuilder()
                .setProductList(skuList)
                .build();
 
        GoogleBillingManager.getInstance().getBillingClient().queryProductDetailsAsync(params, (billingResult, list) -> {
            if (null != billingListener) {
                billingListener.onProductDetailsSus(billingResult, list, orderId);
            }
        });
    }
 
    /**
     * 打开支付面板
     *
     * @param billingListener
     * @param activity
     * @param details
     */
    public static void onOpenGooglePlay(GoogleBillingListener billingListener, Activity activity, ProductDetails details, String orderId) {
        if (null == details) {
            return;
        }
        List<BillingFlowParams.ProductDetailsParams> params = new ArrayList<>();
        //添加购买数据
        BillingFlowParams.ProductDetailsParams productDetailsParams = BillingFlowParams.ProductDetailsParams
                .newBuilder()
                .setProductDetails(details)
                .build();
        params.add(productDetailsParams);
 
        BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                .setProductDetailsParamsList(params)
                .setObfuscatedAccountId(orderId)
                .build();
        //添加购买监听
        GoogleBillingManager.getInstance().setBillingListener(billingListener);
        //响应code 码
        GoogleBillingManager.getInstance().getBillingClient().launchBillingFlow(activity, billingFlowParams).getResponseCode();
    }
    /**
     * 消费商品
     * 对于购买类型的商品需要手动调用一次消费方法 (目的:用户可以再次购买此商品)
     *
     * @param billingListener
     * @param purchase
     */
    public static void onConsumeAsync(GoogleBillingListener billingListener, Purchase purchase) {
        if (!GoogleBillingManager.getInstance().isReady()) {
            return;
        }
        ConsumeParams consumeParams =
                ConsumeParams.newBuilder()
                        .setPurchaseToken(purchase.getPurchaseToken())
                        .build();
 
        ConsumeResponseListener listener = (billingResult, purchaseToken) -> {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                String result = "消费code : " + billingResult.getResponseCode() + " message : " + billingResult.getDebugMessage();
                if (null == billingListener) {
                    Log.e(TAG, result);
                }
                billingListener.onConsumeSus(billingResult.getResponseCode(), result, purchaseToken, purchase);
            }
        };
        GoogleBillingManager.getInstance().getBillingClient().consumeAsync(consumeParams, listener);
    }
 
    /**
    * 检查补单
    *
    * @param billingListener
    * @param productType
    */
    public static void queryPurchases(String productType, GoogleBillingListener billingListener){
        PurchasesResponseListener mPurchasesResponseListener = new PurchasesResponseListener() {
            @Override
            public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> purchasesResult) {
                if(billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK || purchasesResult == null){
                    return;
                }
                for (Purchase purchase : purchasesResult) {
                    if(purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED){
                        continue;
                    }
                    billingListener.onQueryPurchases(purchase.getAccountIdentifiers().getObfuscatedAccountId());
                    onConsumeAsync(billingListener, purchase);
//                    这里处理已经支付过的订单,通知服务器去验证
 
                }
            }
        };
        QueryPurchasesParams params =
                QueryPurchasesParams.newBuilder()
                        .setProductType(productType)
                        .build();
        GoogleBillingManager.getInstance().getBillingClient().queryPurchasesAsync(params, mPurchasesResponseListener);
    }
}

GoogleBillingListener.java

package com.xxx.xxx;
 
import android.util.Log;
 
import com.android.billingclient.api.AccountIdentifiers;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ProductDetails;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.unity3d.player.UnityPlayer;
 
import java.util.List;
 
public class GoogleBillingListener implements PurchasesUpdatedListener {
    public final String objectName;
    public final String paySuccessMethodName;
    public final String detailsSusMethodName;
    public final String payFailMethodName;
    public final String payEndMethodName;
    public final String queryPurchasesMethodName;
    public final String detailsFailMethodName;
    public ProductDetails.OneTimePurchaseOfferDetails productDetails;
 
    public GoogleBillingListener(String objectName, String successMethodName, String processingMethodName,
                                 String failMethodName, String payEndMethodName, String queryPurchasesMethodName, String detailsFailMethodName) {
        this.objectName = objectName;
        this.paySuccessMethodName = successMethodName;
        this.detailsSusMethodName = processingMethodName;
        this.payFailMethodName = failMethodName;
        this.payEndMethodName = payEndMethodName;
        this.queryPurchasesMethodName = queryPurchasesMethodName;
        this.detailsFailMethodName = detailsFailMethodName;
    }
    /**
     * 购买监听
     *
     * @param result
     * @param purchases
     */
    @Override
    public void onPurchasesUpdated(BillingResult result, List<Purchase> purchases) {
        Log.e("TAG", result.toString());
        if (null == purchases || purchases.size() == 0) {
            Log.e("TAG", "not purchases");
            UnityPlayer.UnitySendMessage(this.objectName, this.payEndMethodName, "not purchases;BillingResult:" + result.toString());
            return;
        }
        for (Purchase purchase : purchases) {
            AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();
            String resultStr = accountIdentifiers.getObfuscatedAccountId() + "," + purchase.getPurchaseToken() + "," + purchase.getPurchaseState();
            UnityPlayer.UnitySendMessage(this.objectName, this.payEndMethodName, resultStr);
            if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED){
                GoogleBillHelper.onConsumeAsync(this, purchase);
            }
        }
    }
 
    /**
     * 查询商品详情成功
     *
     * @param list
     */
    public void onProductDetailsSus(BillingResult result, List<ProductDetails> list, String orderId) {
        if (result.getResponseCode() != BillingClient.BillingResponseCode.OK){
            String msg = "Get Details Fails, code:" + result.getResponseCode() + ",msg:" + result.getDebugMessage();
            UnityPlayer.UnitySendMessage(this.objectName, this.detailsFailMethodName, msg);
            return;
        }
        if (null == list || list.size() <= 0) {
            Log.e("TAG", "没有查询到相关产品~~~~");
            UnityPlayer.UnitySendMessage(this.objectName, this.detailsFailMethodName, "Not Search Product, Please check ProductID!");
            return;
        }
        if (orderId != null && orderId.length() > 0){
            GoogleBillHelper.onOpenGooglePlay(this, UnityPlayer.currentActivity, list.get(0), orderId);
            productDetails = list.get(0).getOneTimePurchaseOfferDetails();
        }
        String infoList = "";
        for (ProductDetails details: list) {
            ProductDetails.OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails = details.getOneTimePurchaseOfferDetails();
            //注意:如果手机语言是法语,获取的商品价格是以 , 作为分隔符
            String info = details.getProductId() + "|-|" + oneTimePurchaseOfferDetails.getFormattedPrice() + "|-|" +
                    oneTimePurchaseOfferDetails.getPriceCurrencyCode() + "|-|" + oneTimePurchaseOfferDetails.getPriceAmountMicros();
            if (infoList.isEmpty()){
                infoList = info;
            }else{
                infoList = infoList + ";" + info;
            }
        }
        UnityPlayer.UnitySendMessage(this.objectName, this.detailsSusMethodName, infoList);
    }
 
    /**
     * 商品消费成功
     *
     * @param code
     * @param purchaseToken
     */
    public void onConsumeSus(int code, String result, String purchaseToken, Purchase purchase) {
        AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();
        String itemId = purchase.getProducts().get(0);
        String msg = code + "," + result + "," + purchaseToken + "," + accountIdentifiers.getObfuscatedAccountId() + "," + itemId;
        if (productDetails != null){
            msg = msg + "," + productDetails.getPriceCurrencyCode() + "," + productDetails.getPriceAmountMicros();
        }
        if (code == BillingClient.BillingResponseCode.OK) {
            UnityPlayer.UnitySendMessage(this.objectName, this.paySuccessMethodName, msg);
        }else{
            UnityPlayer.UnitySendMessage(this.objectName, this.payFailMethodName, msg);
        }
    }
 
    public void onQueryPurchases(String txnid){
        UnityPlayer.UnitySendMessage(this.objectName, this.queryPurchasesMethodName, txnid);
    }
}

C#部分

IAPMangaer.cs
namespace根据自己项目决定要不要写

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
namespace xxx.Sdk
{
    public enum BillingResponseCode
    {
        SERVICE_TIMEOUT = -3,
        FEATURE_NOT_SUPPORTED = -2,
        SERVICE_DISCONNECTED = -1,
        OK = 0,
        USER_CANCELED = 1,
        SERVICE_UNAVAILABLE = 2,
        BILLING_UNAVAILABLE = 3,
        ITEM_UNAVAILABLE = 4,
        DEVELOPER_ERROR = 5,
        ERROR = 6,
        ITEM_ALREADY_OWNED = 7,
        ITEM_NOT_OWNED = 8,
    }
 
    public class IAPManager
    {
        private bool initialize;
#if UNITY_ANDROID
        private AndroidJavaClass billingManager;
        private AndroidJavaClass billingHelper;
#endif
        public event Action<bool, string> OnPayEndResult;
        public event Action<bool, string> OnPayResult;
        public event Action<bool, string> OnDetailsSus;
        public event Action<bool, string> OnQueryPurchasesResult;
 
        public void Initialize()
        {
            if (initialize)
            {
                return;
            }
             
#if UNITY_ANDROID
            if (billingManager == null)
            {
                billingManager = new AndroidJavaClass("com.dorocat.bombman.GoogleBillingManager");
            }
 
            if (billingHelper == null)
            {
                billingHelper = new AndroidJavaClass("com.dorocat.bombman.GoogleBillHelper");
            }
             
            if (SdkMgr.currentActivity == null) return;
            SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
            {
                billingManager.CallStatic("createClient", SdkMgr.currentActivity);
            }));
#endif
            initialize = true;
 
        }
 
        public void StartConnection()
        {
#if UNITY_ANDROID
            if (SdkMgr.currentActivity == null) return;
            SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
            {
                billingManager.CallStatic("startConn");
            }));
#endif
        }
         
        public void endConnection()
        {
#if UNITY_ANDROID
            if (billingManager != null)
            {
                if (SdkMgr.currentActivity == null) return;
                SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
                {
                    billingManager.CallStatic("endConn");
                }));
            }
#endif
        }
 
        public void pay(string itemId, string productType, string orderId)
        {
#if UNITY_ANDROID
            if (SdkMgr.currentActivity == null) return;
            SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
            {
                var listener = new AndroidJavaObject("com.dorocat.bombman.GoogleBillingListener",
                    SdkMgr.GameObjectName,
                    "OnPaySuccess",
                    "OnProductDetailsSus",
                    "OnPayFail",
                    "OnPayEnd",
                    "OnQueryPurchases",
                    "OnProductDetailsSusFail");
                billingHelper.CallStatic("onQuerySkuDetailsAsync", listener, productType, itemId, orderId);
            }));
#endif
        }
 
        public void getProductsDetail(string itemId, string productType)
        {
#if UNITY_ANDROID
            if (SdkMgr.currentActivity == null) return;
            SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
            {
                var listener = new AndroidJavaObject("com.dorocat.bombman.GoogleBillingListener",
                    SdkMgr.GameObjectName,
                    "OnPaySuccess",
                    "OnProductDetailsSus",
                    "OnPayFail",
                    "OnPayEnd",
                    "OnQueryPurchases",
                    "OnProductDetailsSusFail");
                billingHelper.CallStatic("onQuerySkuDetailsAsync", listener, productType, itemId, "");
            }));
#endif
        }
         
        public void queryPurchases(string productType)
        {
#if UNITY_ANDROID
            if (SdkMgr.currentActivity == null) return;
            SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>
            {
                var listener = new AndroidJavaObject("com.dorocat.bombman.GoogleBillingListener",
                    SdkMgr.GameObjectName,
                    "OnPaySuccess",
                    "OnProductDetailsSus",
                    "OnPayFail",
                    "OnPayEnd",
                    "OnQueryPurchases",
                    "OnProductDetailsSusFail");
                billingHelper.CallStatic("queryPurchases", productType, listener);
            }));
#endif
        }
         
        public void onPaySuccess(string msg)
        {
            OnPayResult?.Invoke(true, msg);
        }
 
        public void onPayFail(string msg)
        {
            OnPayResult?.Invoke(false, msg);
        }
 
        public void onProductDetailsSus(string msg)
        {
            OnDetailsSus?.Invoke(true, msg);
        }
 
        public void onPayEnd(string msg)
        {
            OnPayEndResult?.Invoke(true, msg);
        }
 
        public void onQueryPurchases(string msg)
        {
            OnQueryPurchasesResult?.Invoke(true, msg);
        }
 
        public void onDeatilSusFail(string msg)
        {
            OnDetailsSus?.Invoke(false, msg);
        }
         
        public bool getConnectionState()
        {
#if UNITY_ANDROID
            return billingManager.GetStatic<bool>("isConnection");
#else
            return false;
#endif
        }
    }
}

自行定义一个SdkManager.cs,在这里面初始化,包括在java层定义的回调函数名也要在这里实现

public static IAPManager billingManager = null;
public static IAPManager CreateBillingClient()
{
    billingManager = new IAPManager();
    billingManager.Initialize();
    return billingManager;
}
 
 
public void OnPaySuccess(string result)
{
    if (billingManager != null)
    {
        billingManager.onPaySuccess(result);
    }
    else
    {
        current?.auth.OnPaySuccess(result);
    }
}
 
 
public void OnPayFail(string message)
{
    if (billingManager != null)
    {
        billingManager.onPayFail(message);
    }
    else
    {
        current?.auth.OnPayFail(message);
    }
}
 
 
public void OnPayEnd(string result)
{
    if (billingManager != null)
    {
        billingManager.onPayEnd(result);
    }
}
 
 
public void OnProductDetailsSus(string result)
{
    if (billingManager != null)
    {
        billingManager.onProductDetailsSus(result);
    }
}
 
 
public void OnProductDetailsSusFail(string result)
{
    if (billingManager != null)
    {
        billingManager.onDeatilSusFail(result);
    }
}
 
public void OnQueryPurchases(string result)
{
    if (billingManager != null)
    {
        billingManager.onQueryPurchases(result);
    }
}

Lua部分

初始化支付SDK

---@type xxx.Sdk.IAPManager
App.billingSdk = CS.BombMan.Sdk.SdkMgr.CreateBillingClient()

调用支付

local billingProductType =
{
    INAPP = "inapp",
    SUBS = "subs",
}
 
 
---sdk支付---
function ShopMgr:pay(itemId, payCallBack, addInfo)
    if LuaClass.Application.isMobilePlatform then
        local callback
        callback = function(result,str)
            App.billingSdk:OnPayResult("-", callback)
            print("onPayResult",result,str)
            if payCallBack then
                payCallBack(result,str)
            end
        end
        if not App.billingSdk:getConnectionState() then
            --如果没有连上Google支付服务器,开始连接
            App.billingSdk:StartConnection()
            return
        end
        local payEnd
        payEnd = function(result, msg)
            App.billingSdk:OnPayEndResult("-", payEnd)
            print("payEnd", msg)
            self.starPay = false
            local infoList = string.split(msg, ",")
            self:requestPayEnd(infoList[1], infoList[2], tonumber(infoList[3]), self.priceStrGoogle and self.priceStrGoogle[itemId][2] or nil, self.priceStrGoogle and tonumber(self.priceStrGoogle[itemId][3]) or nil, infoList[4], itemId, payCallBack)
        end
        local detailFail
        detailFail = function(result, msg)
            print("detailFail", result, msg)
            if not result then
                App.billingSdk:OnPayEndResult("-", payEnd)
            end
            self.starPay = false
            App.billingSdk:OnDetailsSus("-", detailFail)
        end
        self:requestPayStart(itemId, addInfo, function ()
            App.billingSdk:OnPayEndResult("+", payEnd)
            App.billingSdk:OnDetailsSus("+", detailFail)
            App.billingSdk:pay(itemId, billingProductType.INAPP, StringUtil.obfuscate(App.playerMgr.data.id, "pay"))
            self.starPay = false
        end)
    end
end

检查补单

function ShopMgr:queryPurchases()
    if not self.isQuery then
        local addCallBack = function(result, str)
            print("onQueryPurchases", str)
            local infoList = string.split(str, ",")
            local price = self.priceStrGoogle and infoList[5] and self.priceStrGoogle[infoList[5]]
            self:requestPayEnd(infoList[1], infoList[3], tonumber(infoList[4]),
                        price and price[2] or nil, price and tonumber(price[3]) or nil, infoList[2], infoList[5])
        end
        App.billingSdk:OnQueryPurchasesResult("+", addCallBack)
    end
    App.billingSdk:queryPurchases(billingProductType.INAPP)
    self.isQuery = true
end

获取谷歌商店内价格

function ShopMgr:getProductsByGoogle()
    if LuaClass.Application.platform == LuaClass.RuntimePlatform.IPhonePlayer or
            not isValid(App.billingSdk) then
        return
    end
    if self.priceStrGoogle == nil then
        self.priceStrGoogle = {}

        local templates = LuaClass.DirectpurchaseDatatable:getAll()
        local idstr = ""
        for i = 1,#templates do
            idstr = idstr..templates[i].ID..","
        end
        if idstr then
            local callback
            callback = function(result,str)
                print("getProductsByGoogle:",result,str)
                App.billingSdk:OnDetailsSus("-", callback)
                if result then
                    local strSP = string.split(str,";")
                    for i = 1, #strSP do
                        local productInfo = string.split(strSP[i], "|-|")
                        self.priceStrGoogle[productInfo[1]] = {
                            --格式化后的价格 如:HK$8.00
                            [1] = productInfo[2],
                            --货币代码,如HKD
                            [2] = productInfo[3],
                            --微单位价格,1,000,000 微单位等于 1 货币单位
                            [3] = productInfo[4],
                        }
                    end
                    print("productInfo", self.priceStrGoogle)
                    self:queryPurchases()
                end
            end
            App.billingSdk:OnDetailsSus("+", callback)
            App.billingSdk:pay(idstr, billingProductType.INAPP, "")
        end
    end
end

3. 导出工程打包测试

注意要导apk,并且要带有调试标签(直连手机Build即可),包名和版本号要和Google Play后台上传的包一致,确保测试机只登陆了一个谷歌测试账号
在这里插入图片描述

参考

https://blog.51cto.com/kenkao/5989952
https://www.cnblogs.com/fnlingnzb-learner/p/16385685.html

踩坑注意

1.手机语言是法语的话价格会用逗号代替小数点,注意自己使用的分隔符,例如 $1234,56
2.关闭订单这一步操作最好由后端处理,以防客户端因为网络等原因关闭订单后无法通知后端发货
3.在拉起支付时如果需要设置ObfuscatedAccountId的话,请确保每次传输的值都是一样的,否则会出现用户支付遭拒的情况
在这里插入图片描述

相关推荐

  1. ES客户接入方式

    2024-07-20 00:34:01       40 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-20 00:34:01       52 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-20 00:34:01       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-20 00:34:01       45 阅读
  4. Python语言-面向对象

    2024-07-20 00:34:01       55 阅读

热门阅读

  1. 新手教程---python-函数(新添加)

    2024-07-20 00:34:01       19 阅读
  2. Leetcode226.翻转二叉树

    2024-07-20 00:34:01       17 阅读