• Android_Google Play结算库(应用内支付)billing 3.0接入实战


    一、接入摘要
    时间:2021-04-19
    版本:billing 3.0
    语言:java
    内容:一次性消耗型商品
    老版本比较:当前客户端接入版本对比aidl方式区别巨大,支付透传字段也被废弃,需要开发者做好订单和google订单关联;服务端支付验证也改了流程,开发者需要做更多的操作。
    关于google 支付新版本,了解到好多开发者还是用的老版本,为什么不更新?哈哈,因为太坑了。不过现在强制更新,给了具体的时间限制


    二、接入流程
    Google Play开发者后台创建应用(Google Play)
    开发者后台对应项目配置相关信息
    安卓端接入(结算库接入文档)
    后端商品验证
    三、客户端接入
    集成依赖库
    module的 build.gradle 添加下面代码

    dependencies {
    ...
    implementation "com.android.billingclient:billing:3.0.3"
     

    dependencies {
        ...
        implementation "com.android.billingclient:billing:3.0.3"
        ...
    }


    支付流程
    初始化 BillingClient
    与 Google Play 建立连接
    自己服务端生成订单再调起 google 购买操作
    购买成功拿到相关信息去服务端验证购买合法性
    服务端验证商品后进行发货并返回客户端信息,客户端消耗商品
    初始化 BillingClient
    /**
     

    /**
     * 初始化billingClient
     */
    private void initBillingClient() {
        mBillingClient = BillingClient.newBuilder(this)
                .setListener(new PurchasesUpdatedListener() {
                    @Override
                    public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<Purchase> purchases) {
                        //交易更新将会在这里回调
                        int responseCode = billingResult.getResponseCode();
                        if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
                            for (Purchase purchase : purchases) {
                                String googlePayOrderId = purchase.getOrderId();
                                String purchaseToken = purchase.getPurchaseToken();
                                //服务器验证
                                verifyPayment(orderId, googlePayOrderId, productId, purchaseToken);
                            }
                        } else if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
                            //取消支付
                        } else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
                            //已存在这个未完成订单,查询订单验证然后消耗掉
                            queryPurchases();
                        } else {
                            //还有很多其他状态,判断进行相应处理
                        }
                    }
                })
                .enablePendingPurchases()
                .build();
    
    }
     

    与 Google Play 建立连接
    /**
    * 与Google Play建立连接
    */

    private void startConnection() {
    mBillingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
    //链接成功最好去查询订单,做掉单处理
    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
    queryPurchases();
    }
    }
    @Override
    public void onBillingServiceDisconnected() {
    // Try to restart the connection on the next request to
    // Google Play by calling the startConnection() method.
    //建议断开时重连或在使用时判断连接状态,没有连接就手动再调一次 startConnection,确保在执行任何方法时都与 BillingClient 保持连接。
    }
    });

    发起购买
    先展示商品再发起购买

    /**
    * 购买商品
    */

    private void purchase() {
    //先展示商品
    List<String> skuList = new ArrayList<>();
    skuList.add(productId);
    SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
    params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);//INAPP应用内支付
    mBillingClient.querySkuDetailsAsync(params.build(),
    new SkuDetailsResponseListener() {
    @Override
    public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
    for (SkuDetails skuDetails : skuDetailsList) {
    String sku = skuDetails.getSku();
    if (productId.equals(sku)) {
    //启动购买
    BillingFlowParams purchaseParams =
    BillingFlowParams.newBuilder()
    .setSkuDetails(skuDetails)
    .build();
    mBillingClient.launchBillingFlow(TestBilling.this, purchaseParams);
    //购买状态将在PurchasesUpdatedListener.onPurchasesUpdated返回
    }
    }
    }
    }
    });
    //
    }

    服务端验证
    这一步骤具体逻辑在服务端,接入是跟服务端人员沟通配合完成

    /**
    * 验证支付,需要后端处理
    *
    * @param orderId 我们自己的订单号,一般客户端调起支付前会在自己服务端下单
    * @param gpOrderId google商品订单号
    * @param productId 商品id
    * @param purchaseToken 商品token
    */
    private void verifyPayment(String orderId, String gpOrderId, String productId, String purchaseToken) {
    //这一步操作就是写个网络请求,把支付相关信息传到后端进行验证合法性,后端验证返回客户端,验证成功将消耗商品

    }
     
    查询购买
    /**
    * 查询购买交易,以确保所有购买交易都得到成功处理,如购买未发货,或者未消耗
    */
    private void queryPurchases() {
    if (mBillingClient != null && mBillingClient.isReady()) {
    Purchase.PurchasesResult result = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
    List<Purchase> purchasesList = result.getPurchasesList();
    if (purchasesList != null) {
    for (int i = 0; i < purchasesList.size(); i++) {
    if (purchasesList.get(i).isAcknowledged()) {
    //已确认/已验证,消耗即可
    consume(purchasesList.get(i).getPurchaseToken());
    } else {
    //TODO 因google支付新版没有透传字段,所以我们的订单号需要手动关联,
    // 数据库查询gp订单对应的我方订单号或者服务端进行订单关联
    // 关于这一块后续看是否google有新的改动优化
    Purchase purchase = purchasesList.get(i);
    String gpOrderId = purchase.getOrderId();
    String orderId = "";//我们自己的订单号,如果是程序刚启动进来补单的,那么我们的订单就拿不到,因为google不携带透传,自己做处理吧
    String sku = purchase.getSku();
    String purchaseToken = purchase.getPurchaseToken();
    verifyPayment(orderId, gpOrderId, sku, purchaseToken);
    }
    }
    }

    }
    }
     
    消耗商品
    /**
    * 消耗商品
    *
    * @param purchaseToken 商品token
    */
    private void consume(String purchaseToken) {
    if (mBillingClient != null && mBillingClient.isReady()) {
    ConsumeParams consumeParams =
    ConsumeParams.newBuilder()
    .setPurchaseToken(purchaseToken)
    .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.
    }
    }
    };
    mBillingClient.consumeAsync(consumeParams, listener);
    }
    }
     
    断开连接
    @Override
    public void onDestroy(Activity activity) {
    if (mBillingClient!= null && mBillingClient.isReady()) {
    mBillingClient.endConnection();
    }
    }
     

    四、服务端验证流程
    初次接入新版本也是相当麻烦,如不懂流程在官方文档上会摸不着头脑
    总结有几个步骤

    1、创建 API 项目
    Google Play 开发者后台对应项目新建API项目(Google Play Developer API)并启动API服务和关联到Google Play 项目

    参考地址:https://developers.google.cn/android-publisher/getting_started

    2、创建 OAuth 客户端
    API 项目下新建OAuth 客户端,应用类型选择网页应用,其他的看情况填写,确认创建之后获取到客户端ID(client_id)和客户端秘钥(client_secret)还有client_secret_xxx.json文件,内容如下:

    {
    "web":{
    "client_id":"916683014888-479gobska5u85hho2hnb03296lcr9pii.apps.googleusercontent.com",
    "project_id":"api-8972885819834575872-739232",
    "auth_uri":"https://accounts.google.com/o/oauth2/auth",
    "token_uri":"https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
    "client_secret":"5J8jjMVx4o5bvvkqzku1DzMl",
    "redirect_uris":[
    "xxx"
    ],
    "javascript_origins":[
    "xxx"
    ]
    }
    }
     
    以上参数都是用在服务端调用google api,客户端ID和客户端秘钥并非Android客户端参数,服务端这里的所以参数都跟Android端没有啥关系

    3、获取code
    这一步的操作是需要开发者账号登录网页,然后在网页打开这个链接(https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=xxx&client_id=xxx)进行授权,redirect_uri就是创建 OAuth 客户端填写的重定向链接。授权成功后会把code通过redirect_uri返回。如果redirect_uri是随便填的,访问出现404页面或者无法访问的提示,这时候请将地址栏中的链接地址复制出来,把code=4/xxx的值取出来,这里就获得了code的值4/wtedvcqw-yui5CNNb-m2iI83KQx1d.yp6198ti5Zc7dJ3UXOl0T3aRLxWrtgbn

    4、 通过 code 获取 refresh_token
    重要的事情说三遍 保存 保存 保存 第一次授权的时候才会返回refresh_token(长令牌,一般情况下永久有效) ,请妥善保存。
    不过在调试阶段没保存的小伙伴也不用太过担心,可以在 OAuth 客户端选择重置秘钥或者新建 OAuth 客户端,记得服务端把参数替换哦。这时候再走一遍流程就可以再次授权获得refresh_token

    POST请求到https://accounts.google.com/o/oauth2/token

    请求参数:

    grant_type:authorization_code
    code:步骤 3 获取到的code
    client_id:客户端id
    client_secret:客户端秘钥
    redirect_uri:重定向地址

    结果返回:

    {
    "access_token":"xxx",
    "expires_in":3599,
    "refresh_token":"1//xxx",
    "scope":"https://www.googleapis.com/auth/androidpublisher",
    "token_type":"Bearer",
    "created":16193255555
    }
     
    5、通过 refresh_token 获取 access_token
    POST方式调用https://accounts.google.com/o/oauth2/token

    请求参数:

    grant_type:refresh_token
    client_id:客户端id
    client_secret:客户端秘钥
    refresh_token:步骤 4 获取的refresh_token值

    结果返回:

    {
    "access_token":"xxx",
    "token_type":"Bearer",
    "expires_in":3600
    }
     
    6、查询订单信息
    其实就是所谓的验证
    一次性消耗型商品参考官网地址:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products/get

    GET方法调用以下接口: https://www.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{token}?access_token=access_token

    packageName:该应用的包名, 如com.google.demo

    productId:商品ID

    token:Android端充值获取的token值

    access_token:步骤 5 获取的access_token

    如果订单有效会返回相关信息

    {
    "kind": “androidpublisher#productPurchase”,
    "purchaseTimeMillis": “支付时间”,
    "purchaseState": 0,// 是否付费: 0 已支付, 1 取消
    "consumptionState": 0, // 是否被消费: 0 未消费, 1 已消费,
    "developerPayload": "",//透传字段,新版这个客户端没法传了
    "orderId": “GPA-XXX”,//google的订单号
    "purchaseType": 0, // 支付类型: 0 测试, 1 真实
    "acknowledgementState": 0,//商品的确认状态, 0 尚未被确认, 1 确认
    "purchaseToken": "token",//购买令牌,即客户端支付商品token
    "productId": “sss”,//商品id
    "quantity": 1,//数量
    "obfuscatedExternalAccountId": “”,
    "obfuscatedExternalProfileId": “”,
    "regionCode": “”
    }

     
    可根据返回的信息作相应的验证

    如果订单无效会返回400错误

    如果返回403错误
    1、检查api项目启动状态
    2、检查api项目关联状态
    3、OAuth 客户端参数与服务端使用是否一致
    4、谷歌服务的 bug,这时候只要在该应用的商店内,随意增加一个内购商品,再试一次看看是否可行,新增的内购商品可以删除。

    {
    "error": {
    "errors": [{
    "domain": "androidpublisher",
    "reason": "projectNotLinked",
    "message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
    }],
    "code": 403,
    "message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console."
    }
    }
     
    五、收工
    ————————————————
    版权声明:本文为CSDN博主「kincai」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/hqiong208/article/details/116162203

  • 相关阅读:
    24. Swap Nodes in Pairs
    23. Merge k Sorted Lists
    shell脚本报错:"[: =: unary operator expected"
    一种用 数组元素 指定 所调函数 的方法
    阻塞 非阻塞
    Linux open() 一个函数,两个函数原型
    QT 执行windows cmd 命令并读取结果
    Qt5 escape spaces in path
    获取磁盘的 总容量,空余容量,已用容量 【windows】
    通过进程名称,获取其路径
  • 原文地址:https://www.cnblogs.com/xiaogou/p/15568381.html
Copyright © 2020-2023  润新知