Cocos2dx-Lua游戏接入GooglePlay SDK支付

最近项目中需要接入海外SDK,进行支付,故选择Google Play支付。

1.首先要注意的是,Google官方控制台的项目创建和配置,配置出现问题的话会导致,支付调不起来。(配置教程,网上很多暂时就不搞了)

2.创建好项目不要忘了设置测试账号,测试账号很重要,测试账号最好是绑定国外的卡。如果是绑定中国大陆的信用卡,可能测试的时候你会发现,你这个账号好像并不能使用,因为谷歌在中国是不可以进行支付的。(原因是你的卡会有地址信息展示,国内的不可以,你可以试着改到国外)

3.谷歌账号的地区,一定要是国外的,中国不允许谷歌支付,会出现 code码为3 的情况(也可以切换地区)

4.目前谷歌官方推荐的是google play结算  方法,(网上很多都是之前的那种老方法) 

下边是调用支付时返回的code码,看这个code码对比自己的错误。2020-12-04说下 code码为6 的情况,一般是由于网络原因。先检测网络是否翻墙,再Google Play商店是否能正常打开

//BILLING_RESPONSE_RESULT_OK	0	成功
//BILLING_RESPONSE_RESULT_USER_CANCELED	1	用户按上一步或取消对话框
//BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE	2	网络连接断开
//BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE	3	所请求的类型不支持 Google Play 结算服务 AIDL 版本
//BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE	4	请求的商品已不再出售
//BILLING_RESPONSE_RESULT_DEVELOPER_ERROR	5	提供给 API 的参数无效。此错误也可能说明应用未针对 Google Play 结算服务正确签名或设置,或者在其清单中缺少必要的权限。
//BILLING_RESPONSE_RESULT_ERROR	6	API 操作期间出现严重错误
//BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED	7	未能购买,因为已经拥有此商品
//BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED	8	未能消费,因为尚未拥有此商品

5.弹出  “无法购买您想要的商品” ,“此版本的应用未配置为通过google play结算”  这种错误提示,原因有:一、由于 versionCode版本 与Google后台上传的安装包的版本不一致(这个千万要注意,上传的包和你本地测试的那个包不一致就会出现问题),二、签名不一致(部分人喜欢让谷歌来管理自己的证书,好多就会导致本地打包测试出现错误,将自己修改后的东西重新上传下包,以后再进行测试就不需要了)

6.支付验证时(支付 验证 json文件和谷歌离线推送的json文件是两个不同的json文件),需要将 Server account 所对应的邮件添加到测试组上

下面我就贴下支付管理类的代码,

public class AppGooglePay implements PurchasesUpdatedListener {

    private static String TAG = "AppGooglePay";
    //支付权限 key 
    private static final String BASE64_PUBLIC_KEY = "改为你自己的值BASE64";
    //未初始化标记
    public static final int BILLING_MANAGER_NOT_INIT  = -1;
    /*客户端*/
    private BillingClient billingClient;

    private final Activity mActivity;

    //监听
    private final BillingUpdatesListener mBillingUpdatesListener;

    //是否连接成功
    private boolean mIsServiceConnected;

    //客户端当前状态
    private @BillingClient.BillingResponseCode
    int curBillingClientResponseCode = BillingClient.BillingResponseCode.SERVICE_DISCONNECTED;

    //商品列表
    private final List<Purchase> PurchaseList = new ArrayList<>();

    //消耗令牌
    private Set<String> mTokensToBeConsumed;

    //监听 接口
    public interface BillingUpdatesListener {
        void onBillingClientSetupFinished();
        void onConsumeFinished(String token, @BillingClient.BillingResponseCode int result);
        void onPurchasesUpdated(List<Purchase> purchases);
        void onFailedHandle(@BillingClient.BillingResponseCode int result);
    }

    /*
    *   初始化
    * */
    public AppGooglePay(Activity activity, final BillingUpdatesListener updatesListener){
        mActivity = activity;
        mBillingUpdatesListener = updatesListener;
        billingClient = BillingClient.newBuilder(mActivity).enablePendingPurchases().setListener(this).build();
        Log.d(TAG, "开始设置信息");
        startServiceConnection(new Runnable() { //连接 GooglePlay服务器
            @Override
            public void run() {
                mBillingUpdatesListener.onBillingClientSetupFinished();
                Log.d(TAG, "设置客户端成功,开始请求商品库存");
                OnQueryPurchases();
            }
        });

    }
    //连接GooglePlay服务器
    public void startServiceConnection(final Runnable executeOnSuccess){
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                Log.d(TAG, "Setup finished. Response code: " + billingResult.getResponseCode());
                if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
                    mIsServiceConnected = true;
                    if(executeOnSuccess != null){
                        executeOnSuccess.run();
                    }
                }
                curBillingClientResponseCode = billingResult.getResponseCode();
            }

            @Override
            public void onBillingServiceDisconnected() {
                mIsServiceConnected = false;
            }
        });
    }

    //请求查询商品库存
    public void  OnQueryPurchases() {
        Runnable queryToExecute = new Runnable() {
            @Override
            public void run() {
                //系统当前时间
                long time = System.currentTimeMillis();
                //请求内购商品
                Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
                Log.i(TAG, "请求请内购商品花费时间:" + (System.currentTimeMillis() - time) + "ms");

                //支持订阅
                if(areSubscriptionsSupported()){
                    Purchase.PurchasesResult subscriptionResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);

                    Log.i(TAG, "请求订阅商品后花费的时间: "
                            + (System.currentTimeMillis() - time) + "ms");

                    if(subscriptionResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
                        Log.i(TAG, "请求订阅消息返回 Code: "
                                + subscriptionResult.getResponseCode()
                                + " res: " + subscriptionResult.getPurchasesList().size());

                        purchasesResult.getPurchasesList().addAll(subscriptionResult.getPurchasesList());
                    }
                    else {
                        Log.e(TAG, "获取订阅商品失败请见Code");
                    }
                }
                else if (purchasesResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
                    Log.i(TAG, "跳过请求订阅商品,因为设备不支持");
                }
                else{
                    Log.w(TAG, "请求商品失败返回: "
                            + purchasesResult.getResponseCode());
                }
                onQueryPurchasesFinished(purchasesResult);    //请求商品信息
            }
        };

        executeServiceRequest(queryToExecute);
    }


    /*是否支持订阅*/
    public boolean areSubscriptionsSupported(){
        int responseCode = billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS).getResponseCode();
        if(responseCode != BillingClient.BillingResponseCode.OK)
        {
            Log.w(TAG, "areSubscriptionsSupported() got an error response: " + responseCode);
        }
        return responseCode == BillingClient.BillingResponseCode.OK;
    }
    //开始执行服务请求(不要在主线程中搞)
    private void executeServiceRequest(Runnable runnable) {
        if(mIsServiceConnected){
            runnable.run();
        }
        else{
            startServiceConnection(runnable);
        }
    }

    //请求商品信息完成
    private void  onQueryPurchasesFinished(Purchase.PurchasesResult result){
        if(billingClient == null || result.getResponseCode() != BillingClient.BillingResponseCode.OK){
            Log.w(TAG, "billingClient is null or result code (" + result.getResponseCode()
                    + ") was bad - quitting");
            return;
        }
        Log.d(TAG, "请求商品信息完成");
        PurchaseList.clear();
        onPurchasesUpdated(result.getBillingResult(),result.getPurchasesList());
    }


    // 更新商品内容
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null ){
            for (Purchase purchase : purchases){
                Log.i(TAG, "onPurchasesUpdated: -->"+ purchase.getOrderId() + ",--->"+purchase.getPurchaseToken() + ",-->" + purchase.getOriginalJson());
                HandlePurchase(purchase);
            }
            mBillingUpdatesListener.onPurchasesUpdated(PurchaseList);
        }
        else{
            if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED){
                Log.i(TAG, "onPurchasesUpdated() - 用户取消购买当前商品");
            }
            else{
                Log.w(TAG, "onPurchasesUpdatedconsumeAsync() got unknown resultCode: " + billingResult.getResponseCode());
            }
            mBillingUpdatesListener.onFailedHandle(billingResult.getResponseCode());
        }
    }


    /*商品处理*/
    private void HandlePurchase(Purchase purchase){
        //验证签名数据
        Log.i(TAG,"getSignature => "+ purchase.getSignature());
        if(!VerifyValidSignature(purchase.getOriginalJson(),purchase.getSignature())){
            Log.i(TAG, "Got a purchase: " + purchase + "; but signature is bad. Skipping...");
            return;
        }
        Log.d(TAG, "Got a verified purchase: " + purchase);
        PurchaseList.add(purchase);
    }


    //验证签名
    private boolean VerifyValidSignature(String signedData,String signature){

        try{
            return AppGooglePaySec.verifyPurchase(BASE64_PUBLIC_KEY,signedData,signature);
        }
        catch (IOException e){
            Log.e(TAG, "Got an exception trying to validate a purchase: " + e);
            return false;
        }
    }

    //商品消耗
    public void consumeAsync(final String purchaseToken) {
        if (mTokensToBeConsumed == null) {
            mTokensToBeConsumed = new HashSet<>();
        } else if (mTokensToBeConsumed.contains(purchaseToken)) {
            Log.i(TAG, "Token was already scheduled to be consumed - skipping...");
            return;
        }
        mTokensToBeConsumed.add(purchaseToken);

        //消耗监听
        final ConsumeResponseListener onConsumeListener = new ConsumeResponseListener() {
            @Override
            public void onConsumeResponse(BillingResult  responseCode, String purchaseToken) {
                // If billing service was disconnected, we try to reconnect 1 time
                // (feel free to introduce your retry policy here).

                mBillingUpdatesListener.onConsumeFinished(purchaseToken, responseCode.getResponseCode());
                Log.i("luaAppGG", "消耗监听 --- >>> " + responseCode.getResponseCode());
            }
        };

        final ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build();
        Runnable consumeRequest = new Runnable() {
            @Override
            public void run() {
                // 进行消耗
                billingClient.consumeAsync(consumeParams, onConsumeListener);
            }
        };
        executeServiceRequest(consumeRequest);
    }

    //查询内购商品详情
    public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType, final List<String> skuList,
                                     final SkuDetailsResponseListener listener) {
        Runnable queryRequest = new Runnable(){
            @Override
            public void run() {
                SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
                params.setSkusList(skuList).setType(itemType);
                billingClient.querySkuDetailsAsync(params.build(),
                        new SkuDetailsResponseListener() {
                            @Override
                            public void onSkuDetailsResponse(BillingResult billingResult,
                                                             List<SkuDetails> skuDetailsList) {
                                listener.onSkuDetailsResponse(billingResult, skuDetailsList);
                            }
                        });
            }
        };
        executeServiceRequest(queryRequest);
    }

//   商品确认
    public void acknowledgePurchase(AcknowledgePurchaseParams acknowledgePurchaseParams, AcknowledgePurchaseResponseListener Listener){
        billingClient.acknowledgePurchase(acknowledgePurchaseParams,Listener);
    }

    //开始  购买,
    public void initiatePurchaseFlow(final SkuDetails skuDetails) {
        Runnable purchaseFlowRequest = new Runnable() {
            @Override
            public void run() {

                BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
                        .setSkuDetails(skuDetails).build();
                billingClient.launchBillingFlow(mActivity, purchaseParams);
            }
        };
        executeServiceRequest(purchaseFlowRequest);
    }


    /*断开、 释放连接*/
    public void destroy(){
        Log.d(TAG, "Destroying the manager.");
        if (billingClient != null && billingClient.isReady()) {
            billingClient.endConnection();
            billingClient = null;
        }
    }



}

还有一个管理类,这样写确实比较方便以后的改变(比如说,我要在其他项目中使用这个谷歌支付,就可以直接将我这些类,拷贝到相应的项目下,调用相应的方法即可,耦合性很低)

管理类

public class AppGooglePaySec {

    private static final String TAG = "AppGooglePaySec";
    private static final String KEY_FACTORY_ALGORITHM = "RSA";
    private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
    //商品 签名 验证
    public static boolean verifyPurchase(String base64PublicKey, String signedData,
                                         String signature) throws IOException {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
                || TextUtils.isEmpty(signature)) {
            Log.w(TAG,"购买验证失败,数据丢失"+base64PublicKey);
            return false;
        }
        PublicKey key = generatePublicKey(base64PublicKey);
        return verify(key, signedData, signature);
    }

    public static PublicKey generatePublicKey(String encodedPublicKey) throws IOException {
        try {
            byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
            return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
        } catch (NoSuchAlgorithmException e) {
            // "RSA" is guaranteed to be available.
            throw new RuntimeException(e);
        } catch (InvalidKeySpecException e) {
            String msg = "Invalid key specification: " + e;
            BillingHelper.logWarn(TAG, msg);
            throw new IOException(msg);
        }
    }

    public static boolean verify(PublicKey publicKey, String signedData, String signature) {
        byte[] signatureBytes;
        try {
            signatureBytes = Base64.decode(signature, Base64.DEFAULT);
        } catch (IllegalArgumentException e) {
            BillingHelper.logWarn(TAG, "Base64 decoding failed.");
            return false;
        }
        try {
            Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
            signatureAlgorithm.initVerify(publicKey);
            signatureAlgorithm.update(signedData.getBytes());
            if (!signatureAlgorithm.verify(signatureBytes)) {
                BillingHelper.logWarn(TAG, "Signature verification failed.");
                return false;
            }
            return true;
        } catch (NoSuchAlgorithmException e) {
            // "RSA" is guaranteed to be available.
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            BillingHelper.logWarn(TAG, "Invalid key specification.");
        } catch (SignatureException e) {
            BillingHelper.logWarn(TAG, "Signature exception.");
        }
        return false;
    }

}

其实也可以跟着谷歌支付官方走,相对来说比较详细和真实,具有参考性。(官方文档看着就是有点乱)

再记录一下,商品消耗,个人认为,谷歌支付有点麻烦,购买后还需要先进行对商品的确认,如果不确认的话,谷歌平台会在三天后,将充值的返还给玩家,切记一定要确认订单;再一个就是必须要进行对商品消耗,若是不进行消耗,则会造成玩家再次购买不成功(本人项目中跟后端订单确认过之后,进行立即消耗)。对了,在每次购买之前都需要先将商品列表进行初始化一次,也就是将商品Id添加到谷歌平台,相当于做了一次校验一样。

还有一点:谷歌支付确认订单针对非消耗型的,像游戏中的金币解锁关卡之类的属于消耗型 的直接使用消耗订单即可

注意:这里面的方法有的是需要在 主Activity里的生命周期方法中调用 的 哟!!!莫要忘记!!!

 

以上大概就是我遇到的问题,特此记录!!!

记住,我不定时更新!!!

参考链接:https://blog.csdn.net/qq_35431588/article/details/105514168

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值