阅读 320

谷歌结算库(谷歌支付)接入总结

1. 前言

这段时间在做安卓的海外项目,需要用到谷歌的结算库。这里对整个集成流程、重要的点和踩过的坑做一个总结。


2. 介绍

谷歌支付是Google Paly 提供的对应用内商品结算的服务。可通过Gradle远程依赖谷歌结算库后使用。


3. 使用

3.1 前置条件准备

在集成结算库之前,需要准备好以下东西:

  • 能够通过VPN等方式科学上网。
  • 谷歌开发者账号
  • 发布应用(正式版或者内测版)并创建好应用内商品。

发布应用和创建商品步骤较多, 这里详细说下。 我们最终的目的是能够将应用发布到测试版或者是正式版。只有当要发布的应用的所有信息填写完毕,所有对勾都变绿以后。才能发布版本。填写没有顺序,比如填写定价和分发范围的时候,要先上传一个apk才能继续填写, 那就先在应用版本传一个apk再去继续填写,耐心点按照提示来就可以了。 创建商品在应用内商品 里面添加。 内购型商品在受管理商品创建,订阅型产品在订阅里面创建。 所以信息填写好,就可以到 应用版本->管理->查看->发布到XX版来发布。

3.2 集成谷歌结算库

主要参考官方文档,如果可以科学上网尽量多看官方文档,耐心看下去会少踩很多坑。 集成很简单, 直接依赖就好了:

implementation 'com.android.billingclient:billing:2.0.3'
复制代码

3.3 使用

整个支付过程在安卓客户端这里分为5步:

  1. 建立连接
  2. 查询商品
  3. 调起支付
  4. 支付结果回调
  5. 消费商品

整个过程走下来就是, 先与Google Play 建立连接, 通过商品id来查询本应用是否存在该商品,存在则去支付,如果支付成功了,就消费此次商品。 消费成功后客户端需要去和服务端校验此次支付。

支付工具类封装如下:

public class GooglePayUtils implements PurchasesUpdatedListener {
    String TAG = ".GooglePayUtils";

    private BillingClient mBillingClient = null;

    //是否已经建立连接
    private boolean isClientInit = false;

    //支付结果回调接口
    private MyListener mListener = null;

    private Context mContext = null;

    private String orderId = "";

    private String purchaseId = "";

    //购买类型:内购、 订阅,   默认为内购
    private String purchaseType = BillingClient.SkuType.INAPP;

    public GooglePayUtils(Context context, MyListener mListener, String orderId, String purchaseId, String purchaseType) {
        this.mContext = context;
        this.mListener = mListener;
        this.orderId = orderId;
        this.purchaseId = purchaseId;
        this.purchaseType = purchaseType;
    }


    // 1. 建立连接
    public GooglePayUtils pay() {

        mBillingClient = BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build();
        mBillingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                Log.e(TAG, "onBillingSetupFinished code = " + billingResult.getResponseCode() + " ,  msg = " + billingResult.getDebugMessage());
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    isClientInit = true;
                    queryAndPayPurchases(purchaseId);
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                isClientInit = false;
            }
        });
        return this;
    }

    // 2. 查询商品信息
    private void queryAndPayPurchases(@NonNull final String purchaseId) {
        if (!isClientInit) {
            if (mListener != null) {
                mListener.onError(mContext.getResources().getString(R.string.not_connect));
            }
            return;
        }
        List<String> skuList = new ArrayList<>();
        skuList.add(purchaseId);
//        skuList.add("xxx");  // 这个参数不能为空,值随便传
        SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
        params.setSkusList(skuList).setType(purchaseType);
        mBillingClient.querySkuDetailsAsync(params.build(),
                new SkuDetailsResponseListener() {
                    @Override
                    public void onSkuDetailsResponse(BillingResult billingResult,
                                                     List<SkuDetails> skuDetailsList) {
                        Log.e(TAG, "onSkuDetailsResponse code = " + billingResult.getResponseCode() + " ,  msg = " + billingResult.getDebugMessage() + " , skuDetailsList = " + skuDetailsList);
                        // Process the result.
                        if (billingResult.getResponseCode()!= BillingClient.BillingResponseCode.OK) {
                            onFail(mContext.getResources().getString(R.string.no_goods));
                            return;
                        }


                        if (skuDetailsList == null || skuDetailsList.isEmpty()) {
                            if (mListener != null) {
                                mListener.onError(mContext.getResources().getString(R.string.no_goods));
                            }
                            return;
                        }
                        SkuDetails skuDetails = null;
                        for (SkuDetails details : skuDetailsList) {
                            Log.e(TAG, "onSkuDetailsResponse skuDetails = " + details.toString());
                            if (purchaseId.equals(details.getSku())) {
                                skuDetails = details;
                            }
                        }
                        if (skuDetails != null) {
                            pay(skuDetails);
                        } else {
                            if (mListener != null) {
                                mListener.onError(mContext.getResources().getString(R.string.no_goods));
                            }
                        }
                    }
                });
    }

    //3. 调起支付
    private void pay(SkuDetails skuDetails) {
        BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                .setSkuDetails(skuDetails)
                .build();
        int code = mBillingClient.launchBillingFlow((Activity) mContext, flowParams).getResponseCode();

        if (BillingClient.BillingResponseCode.OK!=code) {
            onFail(mContext.getResources().getString(R.string.dump_pay_faile));
        }

    }


    //4. 支付回调
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        Log.e(TAG, "onPurchasesUpdated code = " + billingResult.getResponseCode() + " ,  msg = " + billingResult.getDebugMessage());
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
            if (mListener != null) {
                mListener.onError(mContext.getResources().getString(R.string.pay_cancle));
            }
        } else {
            // Handle any other error codes.
            if (mListener != null) {
                mListener.onError(mContext.getResources().getString(R.string.pay_faile));
            }
        }
    }

    //5. 支付成功, 去消费此次支付, 支付成功后 不消费会自动退款。
    private void handlePurchase(Purchase purchase) {

//        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
//        }


        if (!purchase.isAcknowledged()) {

            if(BillingClient.SkuType.INAPP.equals(purchaseType)){
                ConsumeParams consumeParams =
                        ConsumeParams.newBuilder()
                                .setPurchaseToken(purchase.getPurchaseToken())
                                .setDeveloperPayload(orderId)
                                .build();

                mBillingClient.consumeAsync(consumeParams, new ConsumeResponseListener() {
                    @Override
                    public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
                        Log.e(TAG, "onConsumeResponse code = " + billingResult.getResponseCode() + " ,  msg = " + billingResult.getDebugMessage() + " , purchaseToken = " + purchase.getPurchaseToken());              // 消费成功  处理自己的流程,我选择先存入数据库
                        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                            if (mListener != null) {
                                mListener.onSuccess(purchase.getPurchaseToken());
                            }
                        } else {
                            // 消费失败,后面查询消费记录后再次消费,否则,就只能等待退款
                            if (mListener != null) {
                                mListener.onError(mContext.getResources().getString(R.string.pay_success_comfirm_faile));
                            }
                        }
                    }
                });
            }else {

            AcknowledgePurchaseParams acknowledgePurchaseParams =
                    AcknowledgePurchaseParams.newBuilder()
                            .setPurchaseToken(purchase.getPurchaseToken()).setDeveloperPayload(orderId)
                            .build();
            mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
                @Override
                public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
                    Log.e(TAG, "onConsumeResponse code = " + billingResult.getResponseCode() + " ,  msg = " + billingResult.getDebugMessage() + " , purchaseToken = " + purchase.getPurchaseToken());              // 消费成功  处理自己的流程,我选择先存入数据库
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                        if (mListener != null) {
                            mListener.onSuccess(purchase.getPurchaseToken());
                        }
                    } else {
                        // 消费失败,后面查询消费记录后再次消费,否则,就只能等待退款
                        if (mListener != null) {
                            mListener.onError(mContext.getResources().getString(R.string.pay_success_comfirm_faile));
                        }
                    }
                }
            });}
        } else {
            if (mListener != null) {
                mListener.onError(mContext.getResources().getString(R.string.pay_success_comfirm_faile));
            }
        }

    }


    public interface MyListener {
        void onSuccess(String purchaseToken);
        void onError(String msg);

    }

    private void onFail(String str){
        if (mListener != null) {
            mListener.onError(str);
        }
    }


}
复制代码

3.4 测试

代码编写完了,要做的就是测试。 测试购买分两种, 一种是消耗性的也就是购买类型是内购。一种是非消耗性型,购买类型是订阅。 要测试内购,直接传结算库里面几个预设好拿来测试的商品id就行了,在官方文档中查看。如果是订阅, 则需要应用发布到正式版才能够测试。第一次发布后,审核的时间会比较久,3-4天左右。 页面上显示正在处理更新就是还在审核中。

  • 添加测试账号

只有在控制台添加谷歌测试账号才能够用这个账号来测试支付。在这里添加测试账号: 控制台->开发者账号->账号详情->可用于测试的 Gmail帐号

这些都准备好了, 就可以传商品id和商品类型到上面的支付工具类中测试支付了。

测试过程中有哪些坑:

  • 没有科学上网, 导致查询不到商品。

  • 手机登录的不是刚刚添加的测试账号,导致调起支付框,但提示无法支付之类的信息。在手机->设置->账号/同步 -> Google账号 里查看。

  • 版本号verionCode 、应用标识applicationId、签名等要保持和控制台上传的一致。

  • 订阅后是自动续订的, 要通过谷歌发送的续订邮件中找到退订入口, 退订后才能继续测试订阅支付。 不然提示,系统正在处理你的订单,稍后再试。


4.总结

  • 在Google Play 控制台创建应用、应用内商品。
  • 发布应用到内测版或者正式版(测试订阅需要)。
  • 参照官方文档集成结算库、封装好支付工具类。
  • 在Google Play 控制台添加测试账号,用这个账号来测试支付。

5. 历史文章目录

关注下面的标签,发现更多相似文章
评论