将 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 = "6.2.0"

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

Kotlin

dependencies {
    val billing_version = "6.2.0"

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

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

Groovy

dependencies {
    def billing_version = "6.2.0"

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

Kotlin

dependencies {
    val billing_version = "6.2.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)
   .enablePendingPurchases()
   .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)
    .enablePendingPurchases()
    .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 显示了一项订阅的购买界面:

Google Play 购买界面显示了一项可供购买的订阅
图 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 的购买成功界面
图 2. Google Play 的购买成功界面。

如果成功购买商品,系统还会生成购买令牌,它是一个唯一标识符,表示用户及其所购应用内商品的商品 ID。应用可以在本地存储购买令牌,不过我们建议您将令牌传递到安全的后端服务器,您随后可以在该服务器上验证购买交易及防范欺诈行为。下一部分对此过程进行了详细说明。

用户还会收到包含交易收据的电子邮件,收据内含订单 ID 或交易的唯一 ID。用户每次购买一次性商品时,都会收到包含唯一订单 ID 的电子邮件。此外,用户最初购买订阅时以及后续定期自动续订时,也会收到这样的电子邮件。您可以在 Google Play 管理中心内使用订单 ID 来管理退款。

标明个性化价格

如果应用可能会面向欧盟用户分发,请使用 setIsOfferPersonalized() 方法向用户披露您的商品价格已通过自动化决策进行了个性化设置。

Google Play 购买界面指明系统已为用户提供自定义价格。
图 3. Google Play 购买屏幕指明系统已为用户提供自定义价格。

您必须参阅《欧盟消费者权益指令》(2011/83/EU) 6 (1) (ea) CRD 条款,确定您向用户提供的价格是否进行了个性化设置。

setIsOfferPersonalized() 接受布尔值输入。当该值为 true 时,Play 界面会包含披露声明。当该值为 false 时,Play 界面会忽略披露声明。默认值为 false

如需了解详情,请参阅消费者帮助中心

处理购买交易

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

此外,如果您在安全后端中有实时开发者通知客户端,则可以通过接收 subscriptionNotificationoneTimeProductNotification(仅适用于待处理的购买交易)来注册新购买交易,这两者都会在有新的购买交易时提醒您。收到这些通知后,请调用 Google Play Developer API 以获取完整状态并更新您自己的后端状态。

应用应按以下方式处理购买交易:

  1. 验证购买交易。
  2. 向用户提供内容,并确认内容已传送给用户。另外,也可以将商品标记为已消耗,以便用户可以再次购买该商品。

如需验证购买交易,请先检查购买交易的状态是否为 PURCHASED。如果购买交易的状态为 PENDING,则您应按照处理待处理的交易中的说明处理购买交易。对于通过 onPurchasesUpdated()queryPurchasesAsync() 接收的购买交易,您应在应用授予使用权之前进一步验证购买交易,以确保其合法性。如需了解如何正确验证购买交易,请参阅在授予权利前验证购买交易

一旦您验证了购买交易,您的应用就可以向用户授予使用权了。若要确认与购买交易相关联的用户账号,您可以使用 Purchases.products:get 返回的 ProductPurchase.obfuscatedExternalAccountId(适用于应用内商品的购买交易)和 Purchases.subscriptions:get 返回的 SubscriptionPurchase.obfuscatedExternalAccountId(适用于服务器端的订阅),或者 Purchase.getAccountIdentifiers() 在客户端返回的 obfuscatedAccountId(如果在交易时已使用 setObfuscatedAccountId 设置该 ID)。

授予使用权后,您的应用必须确认购买交易。此确认会告知 Google Play 您已授予购买交易的权利。

授予权利并确认购买交易的流程取决于购买的是消耗型商品、非消耗型商品,还是订阅。

消耗型商品

对于消耗型商品,如果您的应用具有安全后端,我们建议您使用 Purchases.products:consume 可靠地消耗所购商品。若要确保所购商品未被消耗掉,请查看 Purchases.products:get 调用结果中的 consumptionState。如果您的应用只有客户端而没有后端,请使用 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 可靠地确认购买交易。若要确保购买交易尚未确认,请查看 Purchases.products:get 调用结果中的 acknowledgementState

如果您的应用只有客户端,请在应用中使用 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 Developer API 中的 Purchases.subscriptions.acknowledge 通过安全后端可靠地确认购买交易。若要验证购买交易尚未确认,请通过 Purchases.subscriptions:get 查看购买资源中的 acknowledgementState。另外,您也可以在查看 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 purchases) {
        // check billingResult
        // process returned purchase list, e.g. display the plans user owns

      }
    }
);

提取交易记录

queryPurchaseHistoryAsync() 会返回用户对每个商品进行的最近一笔购买交易,即使该购买交易已过期、已取消或已消耗,也仍会返回相关记录。

如果您使用的是 Kotlin 扩展,则可以使用 queryPurchaseHistory() 扩展函数。

Kotlin

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

// uses queryPurchaseHistory Kotlin extension function
val purchaseHistoryResult = billingClient.queryPurchaseHistory(params.build())

// check purchaseHistoryResult.billingResult
// process returned purchaseHistoryResult.purchaseHistoryRecordList, e.g. display purchase

Java

billingClient.queryPurchaseHistoryAsync(
    QueryPurchaseHistoryParams.newBuilder()
        .setProductType(ProductType.SUBS)
        .build(),
    new PurchaseHistoryResponseListener() {
      public void onPurchaseHistoryResponse(
        BillingResult billingResult, List purchasesHistoryList) {
          // check billingResult
          // process returned purchase history list, e.g. display purchase history
        }
    }
);

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

某些购买交易(如促销活动兑换)可能发生在您的应用外。当在您的应用外进行购买交易时,用户希望您的应用显示应用内消息,或希望应用能使用某种通知机制告知其应用已正确接收并处理该购买交易。下面是一些可接受的机制:

  • 显示应用内弹出式消息。
  • 将消息传送到应用内消息箱,并清楚地指出应用内消息箱中有新消息。
  • 使用操作系统通知消息。

请注意,当您的应用识别出购买交易时,它有可能处于任何状态。甚至有可能在用户进行购买交易时根本没有安装您的应用。无论应用处于什么状态,用户都希望在继续使用应用时收到其所购商品。

无论用户进行购买交易时应用处于什么状态,您都必须检测购买交易。不过,在一些例外情况下,不立即通知用户已收到商品或许是可以接受的。例如:

  • 当用户在玩游戏时,显示消息可能会让用户分心。在这种情况下,必须待游戏结束后再通知用户。
  • 在出现过场动画时,显示消息可能会让用户分心。在这种情况下,必须待过场动画结束后再通知用户。
  • 在游戏的初始教程和用户设置期间。我们建议您在新用户打开游戏后立即通知用户有奖励,或在用户进行初始设置期间通知。不过,也可以等到主要游戏序列出现后通知用户。

在斟酌何时以及如何通知用户在您的应用外进行的购买交易时,一定要把用户放在心上。如果用户没有立即收到通知,他们可能会感到困惑,并且可能会停止使用您的应用,与用户支持团队联系,或在社交媒体上抱怨。注意:PurchasesUpdatedListener 已向您的应用上下文注册,以处理购买交易更新,包括从您的应用外部发起的购买交易。这意味着,如果您的应用进程不存在,您的 PurchasesUpdatedListener 将不会收到通知。因此,您的应用应在 onResume() 方法中调用 BillingClient.queryPurchasesAsync()(如提取购买交易中所述)。

处理待处理的交易

Google Play 支持待处理的交易,即从用户发起购买交易到购买交易的付款方式得到处理期间需要执行一个或多个额外步骤的交易。在 Google 通知您已通过用户的付款方式成功扣款之前,您的应用不得授予对这些类型的购买交易的权利。

例如,用户可以选择现金作为付款方式来创建应用内商品的 PENDING 购买交易。然后,用户可以选择在一家实体店完成交易,并通过通知和电子邮件收到一个代码。当用户到达实体店时,可以在收银员处兑换该代码并用现金支付。Google 随后会通知您和用户现金已收到。您的应用随后就可以授予用户使用权了。

在初始化应用的过程中,应用必须通过调用 enablePendingPurchases() 来支持待处理的交易。

当应用通过 PurchasesUpdatedListener 或由于调用 queryPurchasesAsync() 而收到新的购买交易时,使用 getPurchaseState() 方法确定购买交易的状态是 PURCHASED 还是 PENDING

如果应用在用户完成购买交易时正在运行,系统会再次调用 PurchasesUpdatedListener,并且 PurchaseState 现在为 PURCHASED。此时,您的应用可以使用处理一次性购买的标准方法处理购买交易。此外,应用还应在其 onResume() 方法中调用 queryPurchasesAsync(),以处理在应用未运行时已转换为 PURCHASED 状态的购买交易。

您的应用还可以通过监听 OneTimeProductNotifications,将实时开发者通知与待处理的购买交易一起使用。当购买交易从“PENDING”状态转换为“PURCHASED”状态时,您的应用会收到 ONE_TIME_PRODUCT_PURCHASED 通知。如果购买交易被取消,您的应用会收到 ONE_TIME_PRODUCT_CANCELED 通知。如果客户没有在规定的时间范围内完成付款,就会发生这种情况。当收到这些通知时,您可以使用 Google Play Developer API,该 API 包含 Purchases.productsPENDING 状态。

如需了解有关如何测试此场景的详细步骤,请参阅测试待处理的购买交易

处理多件购买交易

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
        }
      }
    });