Android Google支付接入

语言: CN / TW / HK

在App的开发中,支付功能也是比较常见的,国内通常是集成支付宝和微信,但公司的App要上架GooglePlay,所以集成的是Google支付。本篇文章主要介绍一下如何接入Google支付。

之前在SDK项目中接入过4.1.0的Billing库,最近Billing库升级到了5.0.0,看到后我也是升级了。但是发现,如果手机上的GooglePlay商店版本太低,会导致无法正常使用新的API,例如无法正常获取商品信息。根据目前测试的情况来看,商店版本在30以下的都无法通过5.0.0的API获取商品信息。因此,本篇文章会同时介绍4.1.0和5.0.0两个版本的API。

官方文档传送门

集成Billing库

在项目app module的build.gradle中的dependencies中添加依赖:

``` dependencies { //4.1.0和5.0.0选择一种即可 implementation("com.android.billingclient:billing:4.1.0")

implementation("com.android.billingclient:billing:5.0.0") } ```

初始化与连接GooglePlay

这一部分两个版本之间的API没有变化,代码如下:

``` //购买交易更新监听,需要在初始化BillingClient时设置 //所有购买都会回调此监听 PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() { @Override public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchases) { switch (billingResult.getResponseCode()) { case BillingClient.BillingResponseCode.OK: //购买商品成功 break; case BillingClient.BillingResponseCode.USER_CANCELED: //取消购买 break; default: //购买失败,具体异常码可以到BillingClient.BillingResponseCode中查看 break; } } };

//初始化BillingClient BillingClient billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) //支持待处理的交易 .enablePendingPurchases() .build();

//与GooglePlay连接状态监听 BillingClientStateListener stateListener = new BillingClientStateListener() { @Override public void onBillingSetupFinished(@NonNull BillingResult billingResult) { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { //连接成功,可以进行查询商品等操作 } }

@Override
public void onBillingServiceDisconnected() {
    //连接已经断开,重新连接
    billingClient.startConnection(this);
}

};

//与GooglePlay建立连接 billingClient.startConnection(stateListener); ```

获取可用的商品

Google支付的商品分为内购(INAPP)和订阅(SUBS)两种类型。获取商品的API在两个版本之间有所不同,下面分别介绍一下。

4.1.0 获取可用商品

代码如下: ``` //获取商品详情回调 SkuDetailsResponseListener skuDetailsResponseListener=new SkuDetailsResponseListener() { @Override public void onSkuDetailsResponse(@NonNull BillingResult billingResult, @Nullable List list) { //list为可用商品的集合 } };

//查询内购类型的商品 //productId为产品ID(从谷歌后台获取) ArrayList inAppSkuInfo = new ArrayList<>(); inAppSkuInfo.add("productId"); SkuDetailsParams skuParams = SkuDetailsParams.newBuilder() .setType(BillingClient.SkuType.INAPP) .setSkusList(inAppSkuInfo) .build(); billingClient.querySkuDetailsAsync(skuParams, skuDetailsResponseListener);

//查询订阅类型的商品 //productId为产品ID(从谷歌后台获取) ArrayList subscriptionSkuInfo = new ArrayList<>(); subscriptionSkuInfo.add("productId"); SkuDetailsParams skuParams = SkuDetailsParams.newBuilder() .setType(BillingClient.SkuType.SUBS) .setSkusList(subscriptionSkuInfo) .build(); billingClient.querySkuDetailsAsync(skuParams, skuDetailsResponseListener); ```

5.0.0 获取可用商品

代码如下:

``` ProductDetailsResponseListener productDetailsResponseListener = new ProductDetailsResponseListener() { @Override public void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List productDetailsList) { //productDetailsList为可用商品的集合 } };

//查询内购类型的商品 //设置查询参数方式有所更改,productId为产品ID(从谷歌后台获取) ArrayList inAppProductInfo = new ArrayList<>(); inAppProductInfo.add(QueryProductDetailsParams.Product.newBuilder() .setProductId("productId") .setProductType(BillingClient.ProductType.INAPP) .build()); QueryProductDetailsParams productDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList(inAppProductInfo) .build(); billingClient.queryProductDetailsAsync(productDetailsParams, productDetailsResponseListener);

//查询订阅类型的商品 //设置查询参数方式有所更改,productId为产品ID(从谷歌后台获取) ArrayList subscriptionProductInfo = new ArrayList<>(); subscriptionProductInfo.add(QueryProductDetailsParams.Product.newBuilder() .setProductId("productId") .setProductType(BillingClient.ProductType.SUBS) .build()); QueryProductDetailsParams productDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList(subscriptionProductInfo) .build(); billingClient.queryProductDetailsAsync(productDetailsParams, productDetailsResponseListener); ```

商品价格

获取商品返回的集合中,商品的价格会根据所在的区域换算,但货币符号仍然为$。

1655518028.png

例如上图中的商品,后台配置的价格为1.99美元。在测试机获取商品时,Google支付SDK判定我所在的地区是台湾省,所以返回的金额根据当时美元与台币的汇率进行了计算,但是返回的货币符号仍然是美元符号。如果直接用返回的价格显示,可能会对用户造成困扰。

从上面的图片中可以看到,返回的信息中有货币码(CurrencyCode),我们可以根据货币码来对价格进行优化。

Android SDK中提供了Currency类,可以获取所有的货币码信息,代码如下:

``` //用map存储,便于匹配 ArrayMap currencyArrayMap = new ArrayMap<>();

for (Currency availableCurrency : Currency.getAvailableCurrencies()) { //currentcyCode为key,货币符号为value //对于没有特定符号的货币,symbol与currencyCode相同。 currencyArrayMap.put(availableCurrency.getCurrencyCode(), availableCurrency.getSymbol()); } ```

获取商品价格的API两个版本有所差异,下面分别介绍一下。

4.1.0 获取商品价格

在4.1.0版本,内购商品和订阅商品获取商品价格使用统一的API,代码如下:

``` String replaceCurrencySymbol(String priceStr, String currencyCode, String currencySymbol) { if (priceStr.startsWith("$")) { if (currencySymbol != null) { if (currencySymbol.equals(currencyCode)) { //没有货币符号的情况,把货币码拼接到前面 priceStr = currencySymbol + priceStr; } else { if (!priceStr.startsWith(currencySymbol)) { priceStr = priceStr.replace("$", currencySymbol); } } } } return priceStr; }

for (SkuDetails skuDetail : skuInfoList) { String googleProductPrice = skuDetail.getPrice(); String googleCurrencyCode = skuDetail.getPriceCurrencyCode(); String currencySymbol = currencyArrayMap.get(googleCurrencyCode) == null ? googleCurrencyCode : currencyArrayMap.get(googleCurrencyCode);

String replacePrice = replaceCurrencySymbol(googleProductPrice, googleCurrencyCode, currencySymbol);

} ```

5.0.0 获取商品价格

在5.0.0版本,内购商品和订阅商品获取商品价格使用不同的API, 代码如下:

``` String replaceCurrencySymbol(String priceStr, String currencyCode, String currencySymbol) { if (priceStr.startsWith("$")) { if (currencySymbol != null) { if (currencySymbol.equals(currencyCode)) { //没有货币符号的情况,把货币码拼接到前面 priceStr = currencySymbol + priceStr; } else { if (!priceStr.startsWith(currencySymbol)) { priceStr = priceStr.replace("$", currencySymbol); } } } } return priceStr; }

for (ProductDetails productDetail : productDetails) { if (BillingClient.ProductType.INAPP.equals(productDetail.getProductType()) && productDetail.getOneTimePurchaseOfferDetails() != null) { String googleProductPrice = productDetail.getOneTimePurchaseOfferDetails().getFormattedPrice(); String googleCurrencyCode = productDetail.getOneTimePurchaseOfferDetails().getPriceCurrencyCode(); String currencySymbol = currencyArrayMap.get(googleCurrencyCode) == null ? googleCurrencyCode : currencyArrayMap.get(googleCurrencyCode);

    String replacePrice = replaceCurrencySymbol(googleProductPrice, googleCurrencyCode, currencySymbol);
} else if (BillingClient.ProductType.SUBS.equals(productDetail.getProductType()) && productDetail.getSubscriptionOfferDetails() != null) {
    String googleProductPrice = productDetail.getSubscriptionOfferDetails().get(0).getPricingPhases().getPricingPhaseList().get(0).getFormattedPrice();
    String googleCurrencyCode = productDetail.getSubscriptionOfferDetails().get(0).getPricingPhases().getPricingPhaseList().get(0).getPriceCurrencyCode();
    String currencySymbol = currencyArrayMap.get(googleCurrencyCode) == null ? googleCurrencyCode : currencyArrayMap.get(googleCurrencyCode);

    String replacePrice = replaceCurrencySymbol(googleProductPrice, googleCurrencyCode, currencySymbol);
}

} ```

启动购买

需要在主线程中调用launchBillingFlow,启动购买的API在两个版本之间有一些不同,下面分别介绍一下。

4.1.0 启动购买

``` //将要购买商品的商品详情配置到参数中 BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setSkuDetails("SkuDetails") .setObfuscatedAccountId(availableGoods.getOrderNum()) .build();

//启动购买,会BillingResult。 BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams); ```

5.0.0 启动购买

``` ArrayList params = new ArrayList<>(); //将要购买商品的商品详情配置到参数中,两种类型的商品有所区别 if (BillingClient.ProductType.SUBS.equals(productDetailInfo.getProductType()) && productDetailInfo.getSubscriptionOfferDetails() != null) { params.add(BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetailInfo) .setOfferToken(productDetailInfo.getSubscriptionOfferDetails().get(0).getOfferToken()) .build()); } else { params.add(BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetailInfo) .build()); }

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(params) .setObfuscatedAccountId(availableGoods.getOrderNum()) .build();

//启动购买,会BillingResult。 BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams); ```

核销订单

完成购买之后,还需要核销订单。如果三天之内没有核销订单的话, GooglePlay会取消交易,退款给用户。并且商品如果不核销则无法再次购买。

在调用谷歌的核销API之前,还可以通过服务器验证一下交易。

核销订单根据商品类型有不同的API,可重复购买的内购型商品使用consumeAsync,不可重复购买的内购型商品和订阅型商品使用acknowledgePurchase

如果网络通畅,可以在初始化时配置的purchasesUpdatedListener中收到回调并进行核销订单的处理,具体使用代码如下:

``` //核销回调 ConsumeResponseListener consumeResponseListener=new ConsumeResponseListener() { @Override public void onConsumeResponse(@NonNull BillingResult billingResult, @NonNull String purchaseToken) { //核销完成后回调 } };

//核销回调 AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener=new AcknowledgePurchaseResponseListener() { @Override public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) { //核销完成后回调 } };

PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() { @Override public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List purchases) { switch (billingResult.getResponseCode()) { case BillingClient.BillingResponseCode.OK: //购买商品成功 for (Purchase purchase : purchases) { if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { //通过服务器验证订单,此处省略 //可重复购买的内购商品核销 consumePurchase(purchase.getPurchaseToken()); //不可重复购买的内购商品、订阅商品核销 acknowledgedPurchase(purchase.getPurchaseToken()); } } break; case BillingClient.BillingResponseCode.USER_CANCELED: //取消购买 break; default: //购买失败,具体异常码可以到BillingClient.BillingResponseCode中查看 break; } } };

void consumePurchase(String purchaseToken) { if (billingClient != null && billingClient.isReady()) { ConsumeParams consumeParams = ConsumeParams.newBuilder() .setPurchaseToken(purchaseToken) .build(); billingClient.consumeAsync(consumeParams, consumeResponseListener); } }

void acknowledgedPurchase(String purchaseToken) { if (billingClient != null && billingClient.isReady()) { AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchaseToken) .build(); billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener); } } ```

查询购买交易

为了避免在异常情况下(例如网络出现问题),无法通过purchasesUpdatedListener核销订单,可以使用queryPurchasesAsync来查询购买交易。

查询购买交易的API在两个版本之间有一些不同,下面分别介绍一下。

4.1.0 查询购买交易

``` PurchasesResponseListener purchasesResponseListener =new PurchasesResponseListener() { @Override public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List list) { //list为购买交易的集合 } };

//内购商品交易查询 billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, purchasesResponseListener);

//订阅商品交易查询 billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS, purchasesResponseListener); ```

5.0.0 查询购买交易

``` PurchasesResponseListener purchasesResponseListener =new PurchasesResponseListener() { @Override public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List list) { //list为购买交易的集合 } };

//内购商品交易查询 QueryPurchasesParams inAppPurchasesQurey = QueryPurchasesParams.newBuilder() .setProductType(BillingClient.ProductType.INAPP) .build() billingClient.queryPurchasesAsync(inAppPurchasesQurey, inAppPurchasesResponseListener);

//订阅商品交易查询 QueryPurchasesParams inAppPurchasesQurey = QueryPurchasesParams.newBuilder() .setProductType(BillingClient.ProductType.SUBS) .build() billingClient.queryPurchasesAsync(inAppPurchasesQurey, inAppPurchasesResponseListener); ```

测试支付

到GooglePlay后台创建商品,需要注意的是要创建商品必须先发布一个带有Billing库的aab到GooglePlay(测试渠道即可)。

1655525847226.png

获取并配置好商品id之后,将测试用的aab发布到内部测试,并通过连接分享给测试人员。

1655526170422.png

测试人员接受邀请后,就可以测试支付。

1655526274391.png

如果想要用测试卡支付,还需要在许可测试中添加测试人员的谷歌账号。

1655526424651.png