1. 简介
在此 Codelab 中,您将重点学习如何创建一次性商品、将应用与 Play 结算库 (PBL) 集成,以及分析购买流失的原因。
注意:如需成功完成此 Codelab,您需要能够使用一次性商品的多种购买选项和优惠功能。此功能属于抢先体验计划 (EAP)。EAP 中的产品和功能“按原样”提供,可能只能获得有限的支持。如要使用 EAP 功能,请填写一次性商品 EAP 意向调查表提交申请。不过,如果您只是想了解如何使用 Play 结算服务响应代码分析购买流失情况,请直接前往此 Codelab 的分析购买流失情况部分。
观众群
此 Codelab 面向使用 Play 结算库 (PBL) 或想要使用 PBL 将一次性商品变现的 Android 应用开发者。
学习内容…
- 如何在 Google Play 管理中心内创建一次性商品。
- 如何将应用与 PBL 集成。
- 如何在 PBL 中处理消耗型和非消耗型一次性商品的购买交易。
- 如何分析购买流失情况。
所需条件…
- 拥有开发者账号,能够访问 Google Play 管理中心 。如果您没有开发者账号,则需要创建一个账号。
- 此 Codelab 的示例应用,您可以从 GitHub 下载。
- Android Studio。
2. 构建示例应用
示例应用旨在成为一个功能齐全的 Android 应用,其中包含完整的源代码,展示了以下方面:
- 将应用与 PBL 集成
- 提取一次性商品
- 启动一次性商品的购买流程
- 导致以下结算响应的购买场景:
BILLING_UNAVAILABLEUSER_CANCELLEDOKITEM_ALREADY_OWNED
以下演示视频展示了示例应用在部署和运行后的外观和行为。
前提条件
在构建和部署示例应用之前,请执行以下操作:
- 创建 Google Play 管理中心开发者账号。如果您已有开发者账号,请跳过此步骤。
- 在 Play 管理中心内创建新应用。创建应用时,您可以为示例应用指定任何应用名称。
- 安装 Android Studio。
构建
此构建步骤的目标是生成示例应用的已签名 Android App Bundle 文件。
如需生成 Android App Bundle,请执行以下步骤:
- 从 GitHub 下载示例应用。
- 构建示例应用。在构建之前,请更改示例应用的软件包名称,然后进行构建。如果您的 Play 管理中心内有其他应用的软件包,请确保为示例应用提供的软件包名称是唯一的。
注意:构建示例应用只会创建一个 APK 文件,您可以使用该文件进行本地测试。不过,运行该应用不会提取商品和价格,因为您尚未在 Play 管理中心内配置商品,而您将在本 Codelab 中进一步完成此操作。 - 生成已签名的 Android App Bundle。
下一步是将 Android App Bundle 上传到 Google Play 管理中心。
3. 在 Play 管理中心内创建一次性商品
如需在 Google Play 管理中心内创建一次性商品,您需要在 Play 管理中心内有一个应用。在 Play 管理中心内创建一个应用,然后上传之前创建的已签名 App Bundle。
创建应用
如需创建应用,请执行以下操作:
- 使用您的开发者账号登录 Google Play 管理中心 。
- 点击创建应用 。系统随即会打开创建应用 页面。
- 输入应用名称、选择默认语言,以及其他与应用相关的详细信息。
- 点击创建应用 。系统随即会在 Google Play 管理中心内创建一个应用。
现在,您可以上传示例应用的已签名 App Bundle 了。
上传已签名的 App Bundle
- 将已签名的 App Bundle 上传到 Google Play 管理中心的内部测试轨道。只有在上传后,您才能在 Play 管理中心内配置与创收相关的功能。
- 依次点击测试和发布 > 测试 > 内部版本 > 创建新版本 。
- 输入版本名称,然后上传已签名的 App Bundle 文件。
- 点击下一步 ,然后点击保存并发布 。
现在,您可以创建一次性商品了。
创建一次性商品
如需创建一次性商品,请执行以下操作:
- 在 Google Play 管理中心 内,从左侧导航菜单中依次前往 Monetize with Play > Products > One-time products 。
- 点击创建一次性商品 。
- 输入以下商品详情:
- 商品 ID :输入唯一的商品 ID。输入
one_time_product_01。 - (可选)标签 :添加相关标签。
- 名称 :输入商品名称。例如,
Product name。 - 说明 :输入商品说明。例如,
Product description。 - (可选)添加图标图片 :上传代表您产品的图标。
- 商品 ID :输入唯一的商品 ID。输入
- 点击下一步 。
- 添加购买选项并配置其地区供应情况。一次性商品需要至少一个购买选项,该选项用于定义授予使用权的方式、价格和地区供应情况。 对于此 Codelab,我们将为商品添加标准的购买 选项。在购买选项 部分中,输入以下详细信息:
- 购买选项 ID :输入购买选项 ID。例如,
buy。 - 购买类型 :选择购买 。
- (可选)标签 :添加特定于此购买选项的标签。
- (可选)点击高级选项 以配置高级选项。出于此 Codelab 的目的,您可以跳过高级选项配置。
- 购买选项 ID :输入购买选项 ID。例如,
- 在供应情况和定价 部分中,依次点击设置价格 > 批量修改价格 。
- 选择国家 / 地区 选项。系统随即会选择所有地区。
- 点击继续 。系统随即会打开一个对话框,供您输入价格。输入 10 美元,然后点击应用 。
- 点击保存 ,然后点击启用 。系统随即会创建并启用购买选项。
出于此 Codelab 的目的,请使用以下商品 ID 创建 3 个额外的一次性商品:
- consumable_product_01
- consumable_product_02
- consumable_product_03
示例应用已配置为使用这些商品 ID。您可以提供不同的商品 ID,在这种情况下,您必须修改示例应用以使用您提供的商品 ID。
在 Google Play 管理中心 内打开示例应用,然后依次前往 Monetize with Play > Products > One-time products 。然后点击创建一次性商品 ,并重复执行第 3 到 9 步。
一次性商品创建视频
以下示例视频展示了之前介绍的一次性商品创建步骤。
4. 与 PBL 集成
现在,我们将了解如何将应用与 Play 结算库 (PBL) 集成。本部分介绍了集成的简要步骤,并为每个步骤提供了代码段。您可以将这些代码段作为指南来实现实际集成。
如需将应用与 PBL 集成,请执行以下步骤:
- 将 Play 结算库依赖项添加到示例应用。
dependencies { val billing_version = "8.0.0" implementation("com.android.billingclient:billing-ktx:$billing_version") } - 初始化 BillingClient。BillingClient 是驻留在应用中并与 Play 结算库通信的客户端 SDK。以下代码段展示了如何初始化结算客户端。
protected BillingClient createBillingClient() { return BillingClient.newBuilder(activity) .setListener(purchasesUpdatedListener) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .enableAutoServiceReconnection() .build(); } - 连接到 Google Play。以下代码段展示了如何连接到 Google Play。
public void startBillingConnection(ImmutableList<Product> productList) { Log.i(TAG, "Product list sent: " + productList); Log.i(TAG, "Starting connection"); billingClient.startConnection( new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // Query product details to get the product details list. queryProductDetails(productList); } else { // BillingClient.enableAutoServiceReconnection() will retry the connection on // transient errors automatically. // We don't need to retry on terminal errors (e.g., BILLING_UNAVAILABLE, // DEVELOPER_ERROR). Log.e(TAG, "Billing connection failed: " + billingResult.getDebugMessage()); Log.e(TAG, "Billing response code: " + billingResult.getResponseCode()); } } @Override public void onBillingServiceDisconnected() { Log.e(TAG, "Billing Service connection lost."); } }); } - 提取一次性商品详情。将应用与 PBL 集成后,您必须将一次性商品详情提取到应用中。以下代码段展示了如何在应用中提取一次性商品详情。
提取private void queryProductDetails(ImmutableList<Product> productList) { Log.i(TAG, "Querying products for: " + productList); QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(productList).build(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { @Override public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) { // check billingResult Log.i(TAG, "Billing result after querying: " + billingResult.getResponseCode()); // process returned productDetailsList Log.i( TAG, "Print unfetched products: " + productDetailsResponse.getUnfetchedProductList()); setupProductDetailsMap(productDetailsResponse.getProductDetailsList()); billingServiceClientListener.onProductDetailsFetched(productDetailsMap); } }); }ProductDetails后,您会收到类似于如下所示的响应:{ "productId": "consumable_product_01", "type": "inapp", "title": "Shadow Coat (Yolo's Realm | Play Samples)", "name": "Shadow Coat", "description": "A sleek, obsidian coat for stealth and ambushes", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 1990000, "priceCurrencyCode": "USD", "formattedPrice": "$1.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] }, { "productId": "consumable_product_02", "type": "inapp", "title": "Emperor Den (Yolo's Realm | Play Samples)", "name": "Emperor Den", "description": "A fair lair glowing with molten rock and embers", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 2990000, "priceCurrencyCode": "USD", "formattedPrice": "$2.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] } - 启动结算流程。
public void launchBillingFlow(String productId) { ProductDetails productDetails = productDetailsMap.get(productId); if (productDetails == null) { Log.e( TAG, "Cannot launch billing flow: ProductDetails not found for productId: " + productId); billingServiceClientListener.onBillingResponse( BillingResponseCode.ITEM_UNAVAILABLE, BillingResult.newBuilder().setResponseCode(BillingResponseCode.ITEM_UNAVAILABLE).build()); return; } ImmutableList<ProductDetailsParams> productDetailsParamsList = ImmutableList.of( ProductDetailsParams.newBuilder().setProductDetails(productDetails).build()); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); billingClient.launchBillingFlow(activity, billingFlowParams); } - 检测并处理购买交易。在此步骤中,您需要执行以下操作:
- 验证购买交易
- 向用户授予使用权
- 通知用户
- 将购买流程通知 Google
private void handlePurchase(Purchase purchase) { // Step 1: Send the purchase to your secure backend to verify the purchase following // https://developer.android.com/google/play/billing/security#verify // Step 2: Update your entitlement storage with the purchase. If purchase is // in PENDING state then ensure the entitlement is marked as pending and the // user does not receive benefits yet. It is recommended that this step is // done on your secure backend and can combine in the API call to your // backend in step 1. // Step 3: Notify the user using appropriate messaging. if (purchase.getPurchaseState() == PurchaseState.PURCHASED) { for (String product : purchase.getProducts()) { Log.d(TAG, product + " purchased successfully! "); } } // Step 4: Notify Google the purchase was processed. // For one-time products, acknowledge the purchase. // This sample app (client-only) uses billingClient.acknowledgePurchase(). // For consumable one-time products, consume the purchase // This sample app (client-only) uses billingClient.consumeAsync() // If you have a secure backend, you must acknowledge purchases on your server using the // server-side API. // See https://developer.android.com/google/play/billing/security#acknowledge if (purchase.getPurchaseState() == PurchaseState.PURCHASED && !purchase.isAcknowledged()) { if (shouldConsume(purchase)) { ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(); billingClient.consumeAsync(consumeParams, consumeResponseListener); } else { AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); billingClient.acknowledgePurchase( acknowledgePurchaseParams, acknowledgePurchaseResponseListener); } } }
5. 分析购买流失情况
到目前为止,Codelab 中的 Play 结算服务响应侧重于有限的场景,例如 USER_CANCELLED、BILLING_UNAVAILABLE、OK 和 ITEM_ALREADY_OWNED 响应。不过,Play 结算服务可以返回 13 种不同的响应代码,这些代码可能会由各种实际因素触发。
本部分详细介绍了 USER_CANCELLED 和 BILLING_UNAVAILABLE 错误响应的原因,并提出了您可以实施的可能纠正措施。
USER_CANCELED 响应错误代码
此响应代码表示用户在完成购买之前放弃了购买流程界面。
可能的原因 | 您可以执行哪些操作? |
|
|
BILLING_UNAVAILABLE 响应错误代码
此响应代码表示由于用户支付服务机构或其选择的支付方式存在问题,因此无法完成购买交易。例如,用户的信用卡已过期,或者用户所在的国家/地区不受支持。此代码并不表示 Play 结算服务本身存在错误。
可能的原因 | 您可以执行哪些操作? |
|
|
响应错误代码的重试策略
Play 结算库 (PBL) 中可恢复错误 的有效重试策略因上下文而异,例如用户会话期间的互动(如购买期间)与后台操作 (如在应用恢复时查询购买交易)。务必实施这些策略,因为某些 BillingResponseCode 值表示可以通过重试解决的暂时性问题,而其他值是永久性的,不需要重试。
对于用户处于会话状态时 遇到的错误,建议采用简单的重试策略,并设置最大尝试次数,以尽可能减少对用户体验的干扰。相反,对于后台操作 (例如确认新购买交易,这些操作不需要立即执行),建议采用指数退避算法 。
如需详细了解特定响应代码及其对应的建议重试策略,请参阅 处理 BillingResult 响应代码。
6. 后续步骤
参考文档
7. 恭喜!
恭喜!您已成功浏览 Google Play 管理中心,创建了新的应用内一次性商品,测试了结算响应代码,并分析了购买流失情况。
调查问卷
我们非常重视您对此 Codelab 的反馈。请考虑抽出几分钟时间填写我们的调查问卷。