应用内集成指南:面向用户选择的替代性结算

本指南介绍了如何在应用中集成 API,以便提供面向用户选择的替代性结算。

Play 结算库设置

将 Play 结算库依赖项添加到您的 Android 应用中。如需使用替代性结算 API,您需要使用 5.2 或更高版本。如果您需要从早期版本迁移,请在尝试实现替代性结算之前按照迁移指南中的说明进行操作。

连接到 Google Play

集成过程的第一步与Google Play 结算集成指南中介绍的步骤相同,但在初始化 BillingClient 时有一些修改

以下示例演示了如何通过这些修改初始化 BillingClient

Kotlin

val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // Handle new Google Play purchase.
   }

val userChoiceBillingListener =
   UserChoiceBillingListener { userChoiceDetails ->
       // Handle alternative billing choice.
   }

var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   .enablePendingPurchases()
   .enableUserChoiceBilling(userChoiceBillingListener)
   .build()

Java

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        // Handle new Google Play purchase.
    }
};

private UserChoiceBillingListener userChoiceBillingListener = new UserChoiceBillingListener() {
    @Override
    public void userSelectedAlternativeBilling(
        UserChoiceDetails userChoiceDetails) {
        // Handle new Google Play purchase.
    }
};

private BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases()
    .enableUserChoiceBilling(userChoiceBillingListener)
    .build();

初始化 BillingClient 后,您需要按照集成指南中所述建立与 Google Play 的连接

显示可用商品

您可以向用户显示可用商品,其方式与 Google Play 结算系统集成相同。当您的用户看到可供购买的商品并选择其中一项进行购买时,按照下一部分所述启动用户选择结算流程。

启动用户选择结算流程

通过调用 launchBillingFlow() 启动用户选择结算流程。这与通过 Google Play 结算系统集成启动购买流程的工作方式相同:您提供一个 ProductDetails 实例以及与用户想要获取的商品和优惠相对应的 offerToken。如果用户选择 Google Play 的结算系统,则使用此信息继续购买流程。

当开发者调用 launchBillingFlow() 时,Google Play 结算系统会执行以下检查

  • 系统检查用户的 Google Play 国家/地区是否是支持包含用户选择的替代性结算的国家/地区(即受支持的国家/地区)。如果用户的 Google Play 国家/地区受支持,Google Play 会根据 BillingClient 的配置检查是否已启用替代性结算。
    • 如果已启用包含用户选择的替代性结算,购买流程会显示用户选择界面
    • 如果启用包含用户选择的替代性结算,购买流程会显示标准的 Google Play 结算系统界面,不含用户选择。
  • 如果用户的 Google Play 国家/地区不是受支持的国家/地区,购买流程会显示标准的 Google Play 结算系统界面,不含用户选择。

用户所在的 Play 国家/地区为受支持的国家/地区

用户所在的 Play 国家/地区不受支持

在 BillingClient 设置期间调用 enableUserChoiceBilling

用户看到用户选择界面

用户看到标准的 Google Play 结算系统界面

在 BillingClient 设置期间未调用 enableUserChoiceBilling

用户看到标准的 Google Play 结算系统界面

用户看到标准的 Google Play 结算系统界面

处理用户选择

您如何处理剩余的购买流程取决于用户选择了 Google Play 的结算系统还是替代性结算系统。

当用户选择替代性结算系统时

如果用户选择替代性结算系统,Google Play 会调用 UserChoiceBillingListener 通知应用需要在替代性结算系统中启动购买流程。特别是会调用 userSelectedAlternativeBilling() 方法。

UserChoiceDetails 对象中提供的外部事务令牌代表用户选择进入替代性结算流程的签名。正如后端集成指南中所述,使用此令牌上报由该选择产生的任何事务。

应执行以下操作 UserChoiceBillingListener

  • 获取用户正在购买的商品,以便在替代性结算系统的购买流程中呈现这些商品。
  • 收集作为外部事务令牌接收到的字符串,并将其发送到您的后端以进行持久化存储。如果用户完成了这项特定的购买,此令牌稍后用于向 Google Play 上报外部事务。
  • 启动开发者的替代性购买流程。

如果用户使用替代性结算系统完成了购买,您必须在 24 小时内从后端调用 Google Play Developer API 向 Google Play 上报该事务,并提供 externalTransactionToken 和其他事务详情。如需了解更多详细信息,请参阅后端集成指南

以下示例演示了如何实现 UserChoiceBillingListener

Kotlin

private val userChoiceBillingListener =
    UserChoiceBillingListener { userChoiceDetails ->
        // Get the products being purchased by the user.
        val products = userChoiceDetails.products

        // Send external transaction token to developer backend server
        // this devBackend object is for demonstration purposes,
        // developers can implement this step however best fits their
        // app to backend communication.
        devBackend.sendExternalTransactionStarted(
            userChoiceDetails.externalTransactionToken,
            user
        )

        // Launch alternative billing
        // ...
        // The developer backend handles reporting the transaction
        // to Google Play's backend once the alternative billing
        // purchase is completed.
    }

Java

private userChoiceBillingListener userChoiceBillingListener = new UserChoiceBillingListener() {
    @Override
    public void userSelectedAlternativeBilling(
           UserChoiceDetails userChoiceDetails) {
       // Get the products being purchased by the user.
       List<Product> products =
              userChoiceDetails.getProducts();

       // Send external transaction token to developer backend server
       // this devBackend object is for demonstration purposes,
       // developers can implement this step however best fits their
       // app to backend communication.
       devBackend.sendExternalTransactionStarted(
              userChoiceDetails.getExternalTransactionToken(),
              user
       );

       // Launch alternative billing
       // ...
       // The developer backend handles reporting the transaction
       // to Google Play's backend once the alternative billing
       // purchase is completed.
    }
};

当用户选择 Google Play 的结算系统时

如果用户选择 Google Play 的结算系统,他们将通过 Google Play 继续完成购买。

  • 如需了解如何通过 Google Play 的结算系统处理新的应用内购买,请参阅结算库集成指南中的处理购买
  • 如需获取有关订阅购买的额外指导,请参阅订阅管理指南中的新订阅

处理订阅变更

对于使用包含用户选择的替代性结算的开发者,购买需要通过 Google Play 的结算系统处理,或者根据用户的选择通过 externalTransactionId 上报。通过用户选择流程处理的现有订阅的变更可以在同一结算系统进行,直到订阅到期。

本节介绍了如何处理一些常见的订阅变更场景。

升级和降级流程

订阅方案变更(包括升级和降级流程)应根据订阅最初是通过 Google Play 结算系统购买还是通过替代性结算系统购买而区别对待。

依赖于现有订阅、共享相同付款方式并对齐周期性扣费的附加服务将作为升级处理。对于其他附加服务,用户应能够选择他们想要使用的结算系统。如启动用户选择结算流程中所述,使用 launchBillingFlow() 启动新的购买体验。

通过替代性结算系统购买的订阅

对于用户选择后最初通过开发者替代性结算系统购买的订阅,请求升级或降级的用户应通过开发者的替代性结算系统进行,而无需再次经历用户选择体验。

为此,当用户请求升级或降级时,调用 launchBillingFlow()。在参数中,不指定 SubscriptionUpdateParams 对象,而是使用 setOriginalExternalTransactionId,提供原始购买的外部事务 ID。由于升级和降级保留了原始购买的用户选择,因此这不会显示用户选择屏幕。在这种情况下,调用 launchBillingFlow() 会为该事务生成一个新的外部事务令牌,您可以从回调中检索到该令牌。

Kotlin

// The external transaction ID from the current
// alternative billing subscription.
val externalTransactionId = //... ;

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(
        listOf(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                // Fetched via queryProductDetailsAsync.
                .setProductDetails(productDetailsNewPlan)
                // offerIdToken can be found in
                // ProductDetails=>SubscriptionOfferDetails.
                .setOfferToken(offerTokenNewPlan)
                .build()
        )
    )
    .setSubscriptionUpdateParams(
        BillingFlowParams.SubscriptionUpdateParams.newBuilder()
            .setOriginalExternalTransactionId(externalTransactionId)
            .build()

val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

// When the user selects the alternative billing flow,
// the UserChoiceBillingListener is triggered.

Java

// The external transaction ID from the current
// alternative billing subscription.
String externalTransactionId = //... ;

BillingFlowParams billingFlowParams =
        BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(
                ImmutableList.of(
                    ProductDetailsParams.newBuilder()
                        // Fetched via queryProductDetailsAsync.
                        .setProductDetails(productDetailsNewPlan)
                        // offerIdToken can be found in
                        // ProductDetails=>SubscriptionOfferDetails
                        .setOfferToken(offerTokenNewPlan)
                    .build()
                )
            )
            .setSubscriptionUpdateParams(
                SubscriptionUpdateParams.newBuilder()
                    .setOriginalExternalTransactionId(externalTransactionId)
                    .build()
            )
            .build();

BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

// When the user selects the alternative billing flow,
// the UserChoiceBillingListener is triggered.

在替代性结算系统中完成升级或降级后,您需要使用通过之前对新订阅购买的调用获得的外部事务令牌,上报新事务

通过 Google Play 的结算系统购买的订阅

类似地,用户选择后通过 Google Play 的结算系统购买当前订阅的用户,应看到Google Play 结算系统中的升级或降级流程。以下说明介绍了如何通过 Google Play 的结算系统启动升级或降级的购买流程

  1. 识别新方案所选优惠的 offerToken

val offerTokenNewPlan = productDetailsNewPlan
             .getSubscriptionOfferDetails(selectedOfferIndex)
             .getOfferToken()

String offerTokenNewPlan = productDetailsNewPlan
                     .getSubscriptionOfferDetails(selectedOfferIndex)
                     .getOfferToken();

  1. 将正确的信息发送到 Google Play 的结算系统以处理新购买,包括现有订阅的购买令牌

val billingFlowParams =
    BillingFlowParams.newBuilder().setProductDetailsParamsList(
        listOf(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(productDetailsNewPlan)
                .setOfferToken(offerTokenNewPlan)
                .build()
        )
    )
    .setSubscriptionUpdateParams(
        BillingFlowParams.SubscriptionUpdateParams.newBuilder()
            .setOldPurchaseToken(oldToken)
            .setReplaceProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE)
            .build()
        )
        .build()

BillingClient.launchBillingFlow(activity, billingFlowParams)

BillingFlowParams billingFlowParams =
        BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(
                ImmutableList.of(
                    ProductDetailsParams.newBuilder()
                        // Fetched via queryProductDetailsAsync
                        .setProductDetails(productDetailsNewPlan)
                        // offerIdToken can be found in
                        // ProductDetails=>SubscriptionOfferDetails.
                        .setOfferToken(offerTokenNewPlan)
                        .build()
                )
            )
            .setSubscriptionUpdateParams(
                SubscriptionUpdateParams.newBuilder()
                    // purchaseToken can be found in
                    // Purchase#getPurchaseToken
                    .setOldPurchaseToken("old_purchase_token")
                    .setReplaceProrationMode(ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE)
                    .build()
            )
            .build();

BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

此购买在 Google Play 的结算系统中进行,您的应用会收到 PurchasesUpdatedListener.onPurchaseUpdated 调用,其中包含购买结果。如果购买成功,onPurchaseUpdated() 方法也会收到新的购买信息,并且您的后端会收到 SUBSCRIPTION_PURCHASED 实时开发者通知。在拉取新购买的状态时,linkedPurchaseToken 属性会链接到旧的订阅购买,以便您可以按照建议将其停用。

订阅取消和恢复

用户应能够随时取消订阅。当用户取消订阅时,权益终止可能会推迟到付费期结束。例如,如果用户在一个月的中途取消了月度订阅,他们可能会继续访问该服务大约 2 周,直到其访问权限被移除。在此期间,订阅在技术上仍然处于活动状态,因此用户可以使用该服务。

用户在此活动期间决定撤销取消的情况并不少见。在本指南中,这称为恢复。以下部分介绍了如何在替代性结算 API 集成中处理恢复场景。

通过替代性结算系统购买的订阅

如果您的已取消订阅有外部事务 ID,则无需调用 launchBillingFlow() 来恢复订阅,因此不应将其用于此类激活。如果用户在已取消订阅的活动期内恢复其订阅,此时不会发生事务;当当前周期到期并发生下一次续订时,您只需继续上报续订即可。这包括用户在恢复过程中收到赠金或特殊续订价格的情况(例如,鼓励用户继续订阅的促销活动)。

通过 Google Play 的结算系统购买的订阅

通常,用户可以在 Google Play 的结算系统上恢复订阅。对于最初在 Google Play 的结算系统上购买的已取消订阅,用户可以在订阅处于活动状态时通过 Google Play 的重新订阅功能撤销取消。在这种情况下,您的后端会收到 SUBSCRIPTION_RESTARTED 实时开发者通知,并且不会颁发新的购买令牌 - 而是使用原始令牌继续订阅。如需了解如何在 Google Play 的结算系统中管理恢复,请参阅订阅管理指南中的恢复

您也可以通过在应用中调用 launchBillingFlow() 来触发 Google Play 结算系统中的恢复。有关如何执行此操作的说明,请参阅订阅到期前 - 应用内。对于通过原始购买的用户选择流程(已取消但仍处于活动状态)的用户,系统会自动检测到他们的选择,并显示恢复这些购买的用户界面。他们会被要求确认通过 Google Play 重新购买订阅,但无需再次经过用户选择流程。在这种情况下,会为用户颁发新的购买令牌。您的后端会收到 SUBSCRIPTION_PURCHASED 实时开发者通知,并且新购买状态的 linkedPurchaseToken 值会像升级或降级的情况一样设置,其中包含已取消订阅的旧购买令牌。

重新订阅

如果订阅完全到期,无论是由于取消还是付款失败且无法恢复(账户冻结已过期),用户若要重新获得权益,则必须重新订阅

也可以通过在应用中处理,使其类似于标准注册流程来启用重新订阅。用户应能够选择他们想要使用的结算系统。在这种情况下,可以调用 launchBillingFlow(),如启动用户选择结算流程中所述。

测试替代性结算

应使用许可测试人员测试您的替代性结算集成。对于由许可测试人员账号发起的事务,您不会被收取费用。如需详细了解如何配置许可测试人员,请参阅使用应用授权测试应用内结算功能

后续步骤

完成应用内集成后,您就可以集成后端了。