Google内购支付最新版本 8.3 版本接入文档横空出世

78 阅读6分钟

Google Play Billing Library 8.3 接入文档

基于项目现有代码整理,包含 8.3 新版写法旧版写法 对比


一、Gradle 依赖配置

✅ 8.3 版本写法(推荐)

dependencies {
    // Google 支付
    def billing_version = "8.3.0"
    api "com.android.billingclient:billing:$billing_version"
}

❌ 旧版写法(5.0 及以下)

dependencies {
    // 旧版本号示例
    def billing_version = "4.1.0"  // 或 5.0.0
    implementation "com.android.billingclient:billing:$billing_version"
}

变化说明:8.x 版本继续使用 apiimplementation 均可,但建议升级到 8.3.0 以获取最新特性与修复。


二、BillingClient 初始化

✅ 8.3 版本写法(推荐)

// 8.3 版本需要显式构建 PendingPurchasesParams
PendingPurchasesParams pendingPurchasesParams = PendingPurchasesParams.newBuilder()
        .enableOneTimeProducts()  // 启用一次性商品(内购)
        .build();

billingClient = BillingClient.newBuilder(context)
        .setListener(purchasesUpdatedListener)
        .enablePendingPurchases(pendingPurchasesParams)  // 必须传入参数
        .enableAutoServiceReconnection()  // 自动重连服务(新增)
        .build();

billingClient.startConnection(billingClientStateListener);

❌ 旧版写法(5.0 及以下)

// 旧版不需要参数,直接调用 enablePendingPurchases()
billingClient = BillingClient.newBuilder(context)
        .setListener(purchasesUpdatedListener)
        .enablePendingPurchases()  // 无参数调用
        .build();

变化说明

  • 6.0 开始,enablePendingPurchases() 必须传入 PendingPurchasesParams 参数
  • PendingPurchasesParams.newBuilder().enableOneTimeProducts() 用于显式声明支持的一次性商品
  • enableAutoServiceReconnection() 为新增 API,建议添加以提升连接稳定性

三、查询商品详情(ProductDetails)

✅ 8.3 版本写法(推荐)

private void toGooglePay(String productId) {
    List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
    productList.add(QueryProductDetailsParams.Product.newBuilder()
            .setProductId(productId)
            .setProductType(BillingClient.ProductType.INAPP)  // 一次性商品
            // .setProductType(BillingClient.ProductType.SUBS) // 订阅商品
            .build());

    QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
            .setProductList(productList)
            .build();

    billingClient.queryProductDetailsAsync(queryProductDetailsParams, 
            new ProductDetailsResponseListener() {
        @Override
        public void onProductDetailsResponse(
                @NonNull BillingResult billingResult, 
                @NonNull QueryProductDetailsResult queryProductDetailsResult) {  // ← 注意参数变化

            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                    && queryProductDetailsResult.getProductDetailsList() != null
                    && queryProductDetailsResult.getProductDetailsList().size() > 0) {

                ProductDetails productDetails = queryProductDetailsResult
                        .getProductDetailsList().get(0);

                // 构建购买流程参数
                List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = new ArrayList<>();
                productDetailsParamsList.add(BillingFlowParams.ProductDetailsParams.newBuilder()
                        .setProductDetails(productDetails)
                        .build());

                BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                        .setProductDetailsParamsList(productDetailsParamsList)
                        .build();

                billingClient.launchBillingFlow((Activity) context, billingFlowParams);
            } else {
                Toast.makeText(context, "商品ID无效", Toast.LENGTH_SHORT).show();
            }
        }
    });
}

❌ 旧版写法(5.0 及以下)

billingClient.queryProductDetailsAsync(
        queryProductDetailsParams,
        new ProductDetailsResponseListener() {
            public void onProductDetailsResponse(
                    BillingResult billingResult,
                    List<ProductDetails> productDetailsList) {  // ← 旧版直接返回 List

                if (productDetailsList != null && productDetailsList.size() > 0) {
                    ProductDetails productDetails = productDetailsList.get(0);
                    // ... 后续流程相同
                } else {
                    Toast.makeText(context, "商品ID无效", Toast.LENGTH_SHORT).show();
                }
            }
        }
);

变化说明

  • 6.0 开始,onProductDetailsResponse 回调参数从 List<ProductDetails> 改为 QueryProductDetailsResult
  • 需要通过 queryProductDetailsResult.getProductDetailsList() 获取商品列表

四、发起购买流程(launchBillingFlow)

✅ 8.3 版本写法 / 旧版写法(此部分基本一致)

List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = new ArrayList<>();
productDetailsParamsList.add(BillingFlowParams.ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .build());

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .build();

billingClient.launchBillingFlow((Activity) context, billingFlowParams);

说明:购买流程参数构建在 8.3 中保持不变。如需支持优惠,ProductDetailsParams 还可通过 .setOfferToken(token) 设置优惠 Token。


五、处理购买回调(PurchasesUpdatedListener)

✅ 8.3 版本写法 / 旧版写法(此部分基本一致)

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
            // 用户取消购买
            Log.d(TAG, "User canceled the purchase");
        } else {
            // 其他错误
            Log.e(TAG, "Purchase failed: " + billingResult.getResponseCode());
        }
    }
};

六、消耗商品(Consume)

✅ 8.3 版本写法 / 旧版写法(此部分基本一致)

private void handlePurchase(final 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) {
                // 消耗成功,商品可以再次购买
                Log.e(TAG, "Consume success, token: " + purchaseToken);
            } else {
                // 消耗失败
                Log.e(TAG, "Consume failed: " + billingResult.getResponseCode());
            }
        }
    };

    if (billingClient != null) {
        billingClient.consumeAsync(consumeParams, listener);
    }
}

注意:对于 一次性商品(INAPP),必须在购买成功后调用 consumeAsync,否则用户无法再次购买同一商品。对于 订阅(SUBS),不需要消耗。


七、查询已购买商品(queryPurchasesAsync)

✅ 8.3 版本写法 / 旧版写法(此部分基本一致)

private void queryPurchasesAsync() {
    if (billingClient != null) {
        if (!billingClient.isReady()) {
            Toast.makeText(context, "BillingClient is not ready", Toast.LENGTH_SHORT).show();
            return;
        }

        billingClient.queryPurchasesAsync(
                QueryPurchasesParams.newBuilder()
                        .setProductType(BillingClient.ProductType.INAPP)
                        .build(),
                new PurchasesResponseListener() {
                    public void onQueryPurchasesResponse(
                            BillingResult billingResult,
                            List<Purchase> purchases) {
                        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                            if (purchases != null && purchases.size() > 0) {
                                for (Purchase purchase : purchases) {
                                    handlePurchase(purchase);  // 处理未消耗的购买
                                }
                            }
                        }
                    }
                }
        );
    }
}

最佳实践:建议在 onResume() 中调用 queryPurchasesAsync(),以处理在购买流程外完成的交易(例如支付在应用外完成的情况)。

@Override
protected void onResume() {
    super.onResume();
    queryPurchasesAsync();
}

八、完整 MainActivity 示例代码(8.3 版本)

package com.example.myapplication;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.android.billingclient.api.*;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends Activity {
    private static final String TAG = "-----MainActivity-----";
    private Context context = MainActivity.this;
    private BillingClient billingClient;

    private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
        @Override
        public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                    && purchases != null) {
                for (Purchase purchase : purchases) {
                    handlePurchase(purchase);
                }
            } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
                Log.d(TAG, "User canceled purchase");
            } else {
                Log.e(TAG, "Purchase error: " + billingResult.getResponseCode());
            }
        }
    };

    private void handlePurchase(final 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) {
                    Log.e(TAG, "Consume success: " + purchaseToken);
                }
            }
        };

        if (billingClient != null) {
            billingClient.consumeAsync(consumeParams, listener);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initgooglePlay();
    }

    private void initgooglePlay() {
        // ===== 8.3 版本写法 =====
        PendingPurchasesParams pendingPurchasesParams = PendingPurchasesParams.newBuilder()
                .enableOneTimeProducts()
                .build();

        billingClient = BillingClient.newBuilder(context)
                .setListener(purchasesUpdatedListener)
                .enablePendingPurchases(pendingPurchasesParams)
                .enableAutoServiceReconnection()
                .build();

        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingServiceDisconnected() {
                // 服务断开,可尝试重连
            }

            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    Log.d(TAG, "BillingClient setup success");
                }
            }
        });
    }

    private void toGooglePay(String productId) {
        List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
        productList.add(QueryProductDetailsParams.Product.newBuilder()
                .setProductId(productId)
                .setProductType(BillingClient.ProductType.INAPP)
                .build());

        QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
                .setProductList(productList)
                .build();

        billingClient.queryProductDetailsAsync(queryProductDetailsParams,
                new ProductDetailsResponseListener() {
                    @Override
                    public void onProductDetailsResponse(
                            @NonNull BillingResult billingResult,
                            @NonNull QueryProductDetailsResult result) {

                        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                                && result.getProductDetailsList() != null
                                && result.getProductDetailsList().size() > 0) {

                            ProductDetails productDetails = result.getProductDetailsList().get(0);
                            List<BillingFlowParams.ProductDetailsParams> paramsList = new ArrayList<>();
                            paramsList.add(BillingFlowParams.ProductDetailsParams.newBuilder()
                                    .setProductDetails(productDetails)
                                    .build());

                            BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                                    .setProductDetailsParamsList(paramsList)
                                    .build();

                            billingClient.launchBillingFlow(MainActivity.this, billingFlowParams);
                        } else {
                            Toast.makeText(context, "商品ID无效", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
    }

    private void queryPurchasesAsync() {
        if (billingClient != null && billingClient.isReady()) {
            billingClient.queryPurchasesAsync(
                    QueryPurchasesParams.newBuilder()
                            .setProductType(BillingClient.ProductType.INAPP)
                            .build(),
                    new PurchasesResponseListener() {
                        @Override
                        public void onQueryPurchasesResponse(
                                BillingResult billingResult,
                                List<Purchase> purchases) {
                            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                                    && purchases != null) {
                                for (Purchase purchase : purchases) {
                                    handlePurchase(purchase);
                                }
                            }
                        }
                    }
            );
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        queryPurchasesAsync();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (billingClient != null) {
            billingClient.endConnection();
        }
    }
}

九、关键变更总结表

功能点旧版写法(≤5.0)8.3 新版写法
Gradle 依赖implementation 'com.android.billingclient:billing:4.x'api 'com.android.billingclient:billing:8.3.0'
PendingPurchases.enablePendingPurchases() 无参数.enablePendingPurchases(PendingPurchasesParams) 必须传参
自动重连无此 API.enableAutoServiceReconnection() 建议添加
商品详情回调onProductDetailsResponse(BillingResult, List<ProductDetails>)onProductDetailsResponse(BillingResult, QueryProductDetailsResult)
获取商品列表直接使用 List<ProductDetails>result.getProductDetailsList()
服务断开手动处理重连可配合 enableAutoServiceReconnection()

十、注意事项

  1. 测试环境:Google Play 内购必须在真实设备上测试,且需使用发布了内购商品的 Google Play 开发者账号。
  2. 签名文件:Release 包需使用上传到 Google Play 的签名文件。
  3. 后台验证:生产环境建议在服务器端验证购买凭证(purchase.getOriginalJson() + purchase.getSignature())。
  4. 订阅商品:如需接入订阅,请将 ProductType.INAPP 改为 ProductType.SUBS,订阅不需要调用 consumeAsync
  5. 内存管理:Activity 销毁时建议调用 billingClient.endConnection() 释放资源。

需要注意的点

我们的google 支付初始化不要多次初始化 不然会出现多次回调的 会影响到我们支付回调里面进行数据上报的逻辑 。支付消耗查询也可以放在每次支付之前先查询后再去进行下一笔支付 ,还有示例中部分底和官方写法不一样是我改过的 ImmutableList.of 这个其实Java里面的 声明一个非空集合 因为我这边一直导入不了 所以就改掉了 其余代码跟官方一模一样

支付调不起来的几个原因

1 保证拿到商品ID 是正确的可以在google play后台查看到

2 保证手机里面 Google账号是可以支付的 商店点击游戏然后付费有内容查看

image.png

3、安装的app包的versionName、versionCode 和 Google Play Console上传的不一样 Google Play Console - 所有应用 - 查看应用 (上传安装的app或者修改版本号)

4、安装的app包的签名和上传到Google Play Console的包签名不一致

Google Play Console - 所有应用 - 查看应用 -设置 - 应用完整性 (查看签名是否一致) 5 保证vpn 翻墙工具稳定

最后总结:

如果你是6.0.0版本的结算库 请服务查看 从 Google Play 结算库版本 6 或 57迁移到版本 8.3 说明里面有些api 还是有改动 最好用最新方法 不要用过时的方法 如果你是第一次接入就按照次文档或者官方最新文档接入即可 最后呢 希望我都文章能帮助到各位同学工作和学习 如果你觉得文章还不错麻烦给我三连 关注点赞和转发 谢谢