将 Google Play 结算库集成到您的应用中

本主题描述如何将 Google Play 结算库集成到您的应用中以开始销售产品。

购买的生命周期

以下是针对一次性购买或订阅的典型购买流程。

  1. 向用户展示他们可以购买的内容。
  2. 启动购买流程,让用户接受购买。
  3. 在您的服务器上验证购买。
  4. 向用户提供内容。
  5. 确认内容已送达。对于消耗性产品,请消耗购买,以便用户可以再次购买该商品。

订阅会自动续订,直到取消。订阅可以经历以下状态

  • 有效: 用户处于良好状态,并且可以访问订阅。
  • 已取消: 用户已取消,但仍可以访问订阅,直到过期。
  • 宽限期内: 用户遇到了付款问题,但在 Google 重试付款方式时仍可以访问订阅。
  • 暂停: 用户遇到了付款问题,并且在 Google 重试付款方式时无法访问订阅。
  • 暂停: 用户已暂停访问订阅,并且在恢复之前无法访问订阅。
  • 过期: 用户已取消订阅,并且无法访问订阅。用户在过期时被视为流失

初始化与 Google Play 的连接

与 Google Play 的结算系统集成的第一步是将 Google Play 结算库添加到您的应用中并初始化连接。

添加 Google Play 结算库依赖项

将 Google Play 结算库依赖项添加到您的应用的 build.gradle 文件中,如下所示

Groovy

dependencies {
    def billing_version = "7.0.0"

    implementation "com.android.billingclient:billing:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "7.0.0"

    implementation("com.android.billingclient:billing:$billing_version")
}

如果您使用的是 Kotlin,Google Play 结算库 KTX 模块包含 Kotlin 扩展和协程支持,使您能够在使用 Google Play 结算库时编写惯用的 Kotlin 代码。要将这些扩展包含在您的项目中,请将以下依赖项添加到您的应用的 build.gradle 文件中,如下所示

Groovy

dependencies {
    def billing_version = "7.0.0"

    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "7.0.0"

    implementation("com.android.billingclient:billing-ktx:$billing_version")
}

初始化 BillingClient

在您添加对 Google Play 结算库的依赖项后,您需要初始化一个 BillingClient 实例。 BillingClient 是 Google Play 结算库与您的应用其他部分之间通信的主要接口。 BillingClient 提供了许多常见结算操作的同步和异步便捷方法。强烈建议您一次只打开一个活动的 BillingClient 连接,以避免针对单个事件出现多个 PurchasesUpdatedListener 回调。

要创建一个 BillingClient,请使用 newBuilder()。您可以将任何上下文传递给 newBuilder()BillingClient 会使用它来获取应用程序上下文。这意味着您无需担心内存泄漏。要接收有关购买的更新,您还必须调用 setListener(),并传递对 PurchasesUpdatedListener 的引用。此侦听器会接收应用中所有购买的更新。

Kotlin

private val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // To be implemented in a later section.
   }

private var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   // Configure other settings.
   .build()

Java

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        // To be implemented in a later section.
    }
};

private BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    // Configure other settings.
    .build();

连接到 Google Play

创建 BillingClient 后,您需要建立与 Google Play 的连接。

要连接到 Google Play,请调用 startConnection()。连接过程是异步的,您必须实现 BillingClientStateListener 以在客户端设置完成后并准备好进行进一步请求时接收回调。

您还必须实现重试逻辑以处理与 Google Play 的连接丢失。要实现重试逻辑,请覆盖 onBillingServiceDisconnected() 回调方法,并确保 BillingClient 调用 startConnection() 方法以在进行进一步请求之前重新连接到 Google Play。

以下示例演示如何启动连接并测试它是否可以使用

Kotlin

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

Java

billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    @Override
    public void onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
});

显示可购买的产品

在建立与 Google Play 的连接后,您就可以查询可用的产品并将其显示给用户。

在向用户显示产品之前,查询产品详细信息是一个重要步骤,因为它会返回本地化的产品信息。对于订阅,请确保您的产品展示 符合所有 Play 政策

要查询应用内产品详细信息,请调用 queryProductDetailsAsync()

要处理异步操作的结果,您还必须指定一个实现 ProductDetailsResponseListener 接口的侦听器。然后,您可以覆盖 onProductDetailsResponse(),它会在查询完成后通知侦听器,如以下示例所示

Kotlin

val queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build()

billingClient.queryProductDetailsAsync(queryProductDetailsParams) {
    billingResult,
    productDetailsList ->
      // check billingResult
      // process returned productDetailsList
}

Java

QueryProductDetailsParams queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build();

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
        public void onProductDetailsResponse(BillingResult billingResult,
                List<ProductDetails> productDetailsList) {
            // check billingResult
            // process returned productDetailsList
        }
    }
)

查询产品详细信息时,请传递 QueryProductDetailsParams 的实例,该实例指定在 Google Play 管理中心创建的产品 ID 字符串列表以及 ProductTypeProductType 可以是 ProductType.INAPP(一次性产品)或 ProductType.SUBS(订阅)。

使用 Kotlin 扩展进行查询

如果您正在 使用 Kotlin 扩展,则可以通过调用 queryProductDetails() 扩展函数来查询应用内产品详细信息。

queryProductDetails() 利用 Kotlin 协程,因此您无需定义单独的侦听器。相反,该函数会暂停,直到查询完成,之后您可以处理结果

suspend fun processPurchases() {
    val productList = listOf(
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("product_id_example")
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    )
    val params = QueryProductDetailsParams.newBuilder()
    params.setProductList(productList)

    // leverage queryProductDetails Kotlin extension function
    val productDetailsResult = withContext(Dispatchers.IO) {
        billingClient.queryProductDetails(params.build())
    }

    // Process the result.
}

很少情况下,某些设备无法支持 ProductDetailsqueryProductDetailsAsync(),通常是由于 Google Play 服务 版本过旧。要确保对这种情况提供适当的支持,请了解如何在 Play 结算库 5 迁移指南 中使用向后兼容性功能。

处理结果

Google Play 结算库将查询结果存储在 ProductDetails 对象的 List 中。然后,您可以对列表中的每个 ProductDetails 对象调用各种方法以查看有关应用内产品的相关信息,例如其价格或描述。要查看可用的产品详细信息,请参阅 ProductDetails 类中方法的列表。

在提供商品出售之前,请检查用户是否已经拥有该商品。如果用户拥有其商品库中仍然存在的消耗性商品,则必须在再次购买之前消耗该商品。

在提供订阅之前,请验证用户是否尚未订阅。另请注意以下事项

  • queryProductDetailsAsync() 会返回订阅产品详细信息和每个订阅最多 50 个优惠。
  • queryProductDetailsAsync() 仅返回用户有资格获得的优惠。如果用户尝试购买他们没有资格获得的优惠(例如,如果应用正在显示过时的合格优惠列表),Play 会通知用户他们没有资格,并且用户可以选择购买基本计划。

启动购买流程

要从您的应用中启动购买请求,请从您的应用的主线程调用 launchBillingFlow() 方法。此方法接受对 BillingFlowParams 对象的引用,该对象包含从调用 queryProductDetailsAsync() 获得的相关 ProductDetails 对象。要创建 BillingFlowParams 对象,请使用 BillingFlowParams.Builder 类。

Kotlin

// An activity reference from which the billing flow will be launched.
val activity : Activity = ...;

val productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
        .setProductDetails(productDetails)
        // For One-time product, "setOfferToken" method shouldn't be called.
        // For subscriptions, to get an offer token, call ProductDetails.subscriptionOfferDetails()
        // for a list of offers that are available to the user
        .setOfferToken(selectedOfferToken)
        .build()
)

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

// Launch the billing flow
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

Java

// An activity reference from which the billing flow will be launched.
Activity activity = ...;

ImmutableList<ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // For one-time products, "setOfferToken" method shouldn't be called.
            // For subscriptions, to get an offer token, call
            // ProductDetails.subscriptionOfferDetails() for a list of offers
            // that are available to the user.
            .setOfferToken(selectedOfferToken)
            .build()
    );

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

// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

launchBillingFlow() 方法会返回 BillingClient.BillingResponseCode 中列出的几种响应代码之一。请务必检查此结果,以确保启动购买流程时没有错误。 BillingResponseCodeOK 表示成功启动。

在成功调用 launchBillingFlow() 后,系统会显示 Google Play 购买屏幕。图 1 显示了一个订阅的购买屏幕

the google play purchase screen shows a subscription that is
            available for purchase
图 1. Google Play 购买屏幕显示一个可供购买的订阅。

Google Play 会调用 onPurchasesUpdated() 将购买操作的结果传递给实现 PurchasesUpdatedListener 接口的侦听器。侦听器是使用 setListener() 方法在您 初始化客户端 时指定的。

您必须实现 onPurchasesUpdated() 以处理可能的响应代码。以下示例显示了如何覆盖 onPurchasesUpdated()

Kotlin

override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
   if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
       for (purchase in purchases) {
           handlePurchase(purchase)
       }
   } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user cancelling the purchase flow.
   } else {
       // Handle any other error codes.
   }
}

Java

@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponseCode.OK
        && purchases != null) {
        for (Purchase purchase : purchases) {
            handlePurchase(purchase);
        }
    } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
        // Handle an error caused by a user cancelling the purchase flow.
    } else {
        // Handle any other error codes.
    }
}

成功的购买会生成一个 Google Play 购买成功屏幕,类似于图 2。

google play's purchase success screen
图 2. Google Play 的购买成功屏幕。

成功的购买还会生成一个购买令牌,它是一个唯一的标识符,代表用户和他们购买的应用内产品的产品 ID。您的应用可以将购买令牌存储在本地,但我们建议将令牌传递到您的安全后端服务器,以便您可以在那里验证购买并防止欺诈。此过程在下一节中进行了更详细的描述。

用户还会收到一封包含订单 ID 或交易唯一 ID 的交易收据电子邮件。用户会收到包含每个一次性产品购买的唯一订单 ID 的电子邮件,以及首次订阅购买和后续自动续订的电子邮件。您可以使用订单 ID 在 Google Play 管理中心管理退款。

指示个性化价格

如果您的应用可以分发给欧盟的用户,请使用 setIsOfferPersonalized() 方法向用户披露商品价格是使用自动化决策制定的。

The Google Play purchase screen indicating that the price was customized for the user.
图 3. Google Play 购买屏幕,指示价格已针对用户定制。

您必须咨询消费者权利指令 2011/83/EU 的第 6 条第 1 款第 (ea) 项,以确定您向用户提供的价格是否个性化。

setIsOfferPersonalized() 接受布尔值输入。当 true 时,Play UI 包含披露信息。当 false 时,UI 不会显示披露信息。默认值为 false

有关更多信息,请参阅 消费者帮助中心

处理购买

用户完成购买后,您的应用需要处理该购买。在大多数情况下,您的应用会通过您的 PurchasesUpdatedListener 收到购买通知。但是,在某些情况下,您的应用将通过调用 BillingClient.queryPurchasesAsync() 来获知购买情况,如 获取购买 中所述。

此外,如果您在安全的后台拥有 实时开发者通知 客户端,则可以通过接收 subscriptionNotificationoneTimeProductNotification 来注册新的购买,这些通知会提醒您有新的购买。收到这些通知后,请调用 Google Play 开发者 API 获取完整状态并更新您自己的后台状态。

您的应用应按照以下方式处理购买

  1. 验证购买。
  2. 向用户提供内容,并确认内容已送达。可以选择性地将项目标记为已消费,以便用户可以再次购买该项目。

要验证购买,首先检查 购买状态 是否为 PURCHASED。如果购买状态为 PENDING,则应按照 处理挂起的交易 中的说明处理购买。对于从 onPurchasesUpdated()queryPurchasesAsync() 收到的购买,您应该进一步验证购买以确保合法性,然后您的应用才授予权利。要了解如何正确验证购买,请参阅 在授予权利之前验证购买

验证购买后,您的应用就可以授予用户权利了。与购买相关的用户帐户可以使用 ProductPurchase.obfuscatedExternalAccountId 来识别,该标识由 Purchases.products:get 返回,用于应用内产品购买,以及 SubscriptionPurchase.obfuscatedExternalAccountId,由 Purchases.subscriptions:get 返回,用于服务器端的订阅,或者 obfuscatedAccountId 来自 Purchase.getAccountIdentifiers() 客户端,如果一个是在购买时使用 setObfuscatedAccountId 设置的。

授予权利后,您的应用必须确认购买。此确认通知 Google Play 您已为购买授予权利。

授予权利和确认购买的过程取决于购买是消耗型产品、非消耗型产品还是订阅。

消耗型产品

对于消耗型产品,如果您的应用有安全的后台,建议您使用 Purchases.products:consume 来可靠地消费购买。通过检查 consumptionState(来自调用 Purchases.products:get 的结果),确保购买尚未被消费。如果您的应用是仅客户端的,没有后台,请使用 Google Play 结算库的 consumeAsync()。这两种方法都满足确认要求,并表明您的应用已向用户授予权利。这些方法还使您的应用能够使与输入购买令牌对应的的一次性产品可供重新购买。使用 consumeAsync(),您还必须传递一个实现 ConsumeResponseListener 接口的对象。此对象处理消费操作的结果。您可以覆盖 onConsumeResponse() 方法,Google Play 结算库在操作完成时会调用此方法。

以下示例说明了如何使用 Google Play 结算库和关联的购买令牌来消费产品

Kotlin

suspend fun handlePurchase(purchase: Purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    val purchase : Purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    val consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build()
    val consumeResult = withContext(Dispatchers.IO) {
        client.consumePurchase(consumeParams)
    }
}

Java

void handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    Purchase purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Handle the success of the consume operation.
            }
        }
    };

    billingClient.consumeAsync(consumeParams, listener);
}

非消耗型产品

要确认非消耗型产品购买,如果您的应用有安全的后台,建议您使用 Purchases.products:acknowledge 来可靠地确认购买。通过检查 acknowledgementState(来自调用 Purchases.products:get 的结果),确保购买尚未被确认。

如果您的应用是仅客户端的,请在您的应用中使用 Google Play 结算库的 BillingClient.acknowledgePurchase()。在确认购买之前,您的应用应使用 Google Play 结算库中的 isAcknowledged() 方法检查购买是否已被确认。

以下示例说明了如何使用 Google Play 结算库来确认购买

Kotlin

val client: BillingClient = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ...

suspend fun handlePurchase() {
    if (purchase.purchaseState === PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged) {
            val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
            val ackPurchaseResult = withContext(Dispatchers.IO) {
               client.acknowledgePurchase(acknowledgePurchaseParams.build())
            }
        }
     }
}

Java

BillingClient client = ...
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
        }
    }
}

订阅

订阅的处理方式类似于非消耗型产品。如果可能,请使用 Google Play 开发者 API 的 Purchases.subscriptions.acknowledge 来自您的安全后台可靠地确认购买。通过检查 acknowledgementState(来自 Purchases.subscriptions:get 中的购买资源),验证购买是否尚未被确认。否则,您可以在检查 isAcknowledged() 后,使用 Google Play 结算库的 BillingClient.acknowledgePurchase() 来确认订阅。所有初始订阅购买都需要确认。订阅续订不需要确认。有关何时需要确认订阅的更多信息,请参阅 销售订阅 主题。

获取购买

使用 PurchasesUpdatedListener 监听购买更新不足以确保您的应用处理所有购买。您的应用可能不知道用户进行的所有购买。以下是一些您的应用可能无法跟踪或不知道购买的场景

  • 购买期间出现网络问题:用户成功购买并收到来自 Google 的确认,但他们的设备在通过 PurchasesUpdatedListener 收到购买通知之前失去了网络连接。
  • 多个设备:用户在一台设备上购买了某个项目,然后希望在切换设备时看到该项目。
  • 处理在您的应用之外进行的购买:某些购买(例如促销兑换)可以在您的应用之外进行。

要处理这些情况,请确保您的应用在 onResume() 方法中调用 BillingClient.queryPurchasesAsync(),以确保按照 处理购买 中的说明成功处理所有购买。

以下示例说明了如何获取用户的订阅购买情况。请注意,queryPurchasesAsync() 只返回活动的订阅和未消耗的一次性购买。

Kotlin

val params = QueryPurchasesParams.newBuilder()
               .setProductType(ProductType.SUBS)

// uses queryPurchasesAsync Kotlin extension function
val purchasesResult = billingClient.queryPurchasesAsync(params.build())

// check purchasesResult.billingResult
// process returned purchasesResult.purchasesList, e.g. display the plans user owns

Java

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
      .setProductType(ProductType.SUBS)
      .build(),
    new PurchasesResponseListener() {
      public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> purchases) {
        // check billingResult
        // process returned purchase list, e.g. display the plans user owns

      }
    }
);

处理在您的应用之外进行的购买

某些购买(例如促销兑换)可能在您的应用之外进行。当用户在您的应用之外进行购买时,他们希望您的应用显示应用内消息,或使用某种通知机制来让用户知道应用已正确收到并处理了购买。以下是一些可接受的机制

  • 显示应用内弹出窗口。
  • 将消息传递到应用内消息框,并明确说明应用内消息框中有新消息。
  • 使用操作系统通知消息。

请记住,您的应用在识别购买时可能处于任何状态。当购买完成时,您的应用甚至可能尚未安装。用户希望在恢复应用时收到购买,无论应用处于何种状态。

您必须检测购买,无论应用在购买完成时处于何种状态。但是,在某些情况下,可以不立即通知用户已收到项目。例如

  • 在游戏的动作部分,显示消息可能会分散用户的注意力。在这种情况下,您必须在动作部分结束后通知用户。
  • 在过场动画中,显示消息可能会分散用户的注意力。在这种情况下,您必须在过场动画结束后通知用户。
  • 在游戏的初始教程和用户设置部分。建议您在新用户打开游戏或在初始用户设置期间立即通知他们奖励。但是,可以等到主游戏序列可用后再通知用户。

在决定何时以及如何通知用户在应用外部进行的购买时,始终牢记用户的感受。如果用户没有立即收到通知,他们可能会感到困惑,并可能停止使用您的应用,联系用户支持或在社交媒体上抱怨。注意:PurchasesUpdatedListener 与您的应用 context 注册,以处理购买更新,包括在您的应用外部发起的购买。这意味着,如果您的应用进程不存在,您的 PurchasesUpdatedListener 将不会收到通知。这就是为什么您的应用应该在 onResume() 方法中调用 BillingClient.queryPurchasesAsync(),如 获取购买 中所述。

处理挂起交易

Google Play 支持挂起交易,或需要用户发起购买和处理购买支付方式之间一个或多个额外步骤的交易。在 Google 通知您用户支付方式已成功收费之前,您的应用不应为这些类型的购买授予权利。

例如,用户可以通过选择一家他们稍后将现金支付的实体店来发起交易。用户通过通知和电子邮件收到一个代码。当用户到达实体店时,他们可以使用代码向收银员兑换,并用现金支付。然后,Google 会通知您和用户支付已收到。然后,您的应用可以为用户授予权利。

在初始化 BillingClient 时,调用 enablePendingPurchases() 以启用应用的挂起交易。您的应用必须为一次性产品启用和支持挂起交易。在添加支持之前,请确保您了解挂起交易的 购买生命周期

当您的应用通过 PurchasesUpdatedListener 或通过调用 queryPurchasesAsync() 收到新的购买时,使用 getPurchaseState() 方法确定购买状态是 PURCHASED 还是 PENDING。您应该**仅**在状态为 PURCHASED 时授予权利。

如果您的应用在用户完成购买时正在运行,您的 PurchasesUpdatedListener 将再次被调用,并且 PurchaseState 现在为 PURCHASED。此时,您的应用可以使用用于 处理购买 的标准方法来处理购买。您的应用还应在应用的 onResume() 方法中调用 queryPurchasesAsync(),以处理在您的应用未运行时已转换为 PURCHASED 状态的购买。

当购买从 PENDING 转换为 PURCHASED 时,您的 实时开发者通知 客户端会收到 ONE_TIME_PRODUCT_PURCHASEDSUBSCRIPTION_PURCHASED 通知。如果购买被取消,您将收到 ONE_TIME_PRODUCT_CANCELEDSUBSCRIPTION_PENDING_PURCHASE_CANCELED 通知。如果您的客户未能在规定时间内完成付款,就会发生这种情况。请注意,您始终可以使用 Google Play 开发者 API 检查购买的当前状态。

处理多数量购买

在 Google Play 结算库版本 4.0 及更高版本中受支持,Google Play 允许客户通过在购买购物车中指定数量,在一个交易中购买多个相同应用内商品。您的应用应处理多数量购买,并根据指定的购买数量授予权利。

为了尊重多数量购买,您的应用配置逻辑需要检查商品数量。您可以从以下 API 之一访问 quantity 字段

添加了处理多数量购买的逻辑后,您需要在 Google Play 开发者控制台的应用内商品管理页面上为相应商品启用多数量功能。

查询用户的结算配置

getBillingConfigAsync() 提供用户用于 Google Play 的国家/地区。

您可以在 创建 BillingClient 后查询用户的结算配置。以下代码片段描述了如何调用 getBillingConfigAsync()。通过实现 BillingConfigResponseListener 来处理响应。此侦听器接收来自您应用的所有结算配置查询的更新。

如果返回的 BillingResult 不包含任何错误,则可以检查 BillingConfig 对象中的 countryCode 字段,以获取用户的 Play 国家/地区。

Kotlin

// Use the default GetBillingConfigParams.
val getBillingConfigParams = GetBillingConfigParams.newBuilder().build()
billingClient.getBillingConfigAsync(getBillingConfigParams,
    object : BillingConfigResponseListener {
        override fun onBillingConfigResponse(
            billingResult: BillingResult,
            billingConfig: BillingConfig?
        ) {
            if (billingResult.responseCode == BillingResponseCode.OK
                && billingConfig != null) {
                val countryCode = billingConfig.countryCode
                ...
            } else {
                // TODO: Handle errors
            }
        }
    })

Java

// Use the default GetBillingConfigParams.
GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build();
billingClient.getBillingConfigAsync(getBillingConfigParams,
    new BillingConfigResponseListener() {
      public void onBillingConfigResponse(
          BillingResult billingResult, BillingConfig billingConfig) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK
            && billingConfig != null) {
            String countryCode = billingConfig.getCountryCode();
            ...
         } else {
            // TODO: Handle errors
        }
      }
    });