一次性商品的多个购买选项和优惠

本文档详细介绍了如何将一次性商品 (OTP) 与 Play 结算库集成。其中进一步介绍了如何集成与您的一次性商品相关的各种购买选项和优惠。

您可以为一次性商品配置多个购买选项和优惠。例如,您可以为同一款一次性商品配置“立即购买”购买选项和“预订”优惠。

前提条件

如需为一次性商品配置多个优惠,您必须使用 queryProductDetailsAsync() API。已废弃的 querySkuDetailsAsync() API 不受支持。如需了解如何使用 queryProductDetailsAsync() 以及接受 ProductDetailsParams 作为输入的 launchBillingFlow() 版本,请参阅迁移步骤

查询商品详情

如果您为一次性商品配置了多个优惠或购买选项,则 queryProductDetailsAsync() 方法返回的 ProductDetails 对象中,每件一次性商品可以有多个可用的购买和/或租赁选项。如需获取每个 ProductDetails 对象的所有符合条件的优惠的列表,请使用 getOneTimePurchaseOfferDetailsList() 方法。此列表中仅会返回用户符合条件的优惠和购买选项。onProductDetailsResponse() 方法中的代码应处理返回的优惠。

启动结算流程

如需从应用发起购买请求,请从应用的主线程调用 launchBillingFlow() 方法。此方法接受对 BillingFlowParams 对象的引用,该对象包含通过调用 queryProductDetailsAsync() 获取的相关 ProductDetails 对象。如需创建 BillingFlowParams 对象,请使用 BillingFlowParams.Builder 类。请注意,在创建 BillingFlowParams 对象时,您必须设置与用户选择的优惠对应的优惠令牌。

以下示例展示了如何为包含多个优惠的一次性商品启动购买流程:

Java

    
// An activity reference from which the billing flow will launch.
Activity activity = ...;
ImmutableList<ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // retrieve a value for &quot;productDetails&quot; by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // to get an offer token, call
            // ProductDetails.getOneTimePurchaseOfferDetailsList() 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);
    
    

offerToken 可在 OneTimePurchaseOfferDetails 中找到。向用户显示商品时,请务必使用正确的商品令牌(可从 oneTimePurchaseOfferDetails.getOfferToken() 方法获取)配置结算流程参数。

购买选项和优惠

借助购买选项,您可以定义向用户授予使用权的方式、价格以及产品在哪些地区提供。一件商品可以有多个购买选项,这些选项可以表示您销售商品的地点和方式。

Google Play 支持一次性商品的以下购买选项:

  • “购买”购买选项
  • “租借”购买选项

优惠是指您可以为一次性商品创建的定价方案。 例如,您可以为一次性商品创建折扣优惠。

Google Play 支持一次性商品的以下购买优惠:

  • 预订优惠(仅适用于“购买”购买选项)
  • 折扣优惠(支持“买断”和“租借”购买选项)

“购买”购买选项

“购买”购买选项代表一次性商品的标准一次性购买交易。它有一个可选的 legacyCompatible 字段,用于指示在不支持新模型的旧版 Play 结算库(版本 7 或更低版本)流程中,是否提供此购买选项。为了实现向后兼容性,至少应将一个“购买”购买选项标记为“与旧版兼容”。

将购买和租赁购买选项与 PBL 集成的步骤相同。如需了解如何将“租”购买选项与 PBL 集成,请参阅将“租”购买选项与 PBL 集成

“租借”购买选项

借助租赁购买选项,用户可以指定租赁一次性商品的时长。您可以指定租赁期和到期时间。本文档介绍了将租借购买选项与 Play 结算库 (PBL) 集成的步骤。

将租借购买选项与 PBL 集成

本部分介绍了如何将租借购买选项与 Play 结算库 (PBL) 集成。本文假定您熟悉初始 PBL 集成步骤,例如向应用添加 PBL 依赖项、初始化 BillingClient连接到 Google Play。本部分重点介绍了与租购选项相关的 PBL 集成方面。

如需配置可供租赁的商品,您需要使用 Play Developer API 的新 monetization.onetimeproducts 服务或 Play 管理中心界面。如需使用该服务,您可以直接调用 REST API,也可以使用 Java 客户端库

启动租借选项的购买流程

如需为租赁商品启动购买流程,请执行以下步骤:

  1. 使用 ProductDetails.oneTimePurchaseOfferDetails.getRentalDetails() 方法提取租赁购买选项元数据。

    以下示例展示了如何获取租借购买交易元数据:

    Java

    billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
      public void onProductDetailsResponse(
          BillingResult billingResult, QueryProductDetailsResult productDetailsResult) {
        // check billingResult
        // …
        // process productDetailsList returned by QueryProductDetailsResult
        for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
          for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails :
              productDetails.getOneTimePurchaseOfferDetailsList()) {
            // Checks if the offer is a rent purchase option.
            if (oneTimePurchaseOfferDetails.getRentalDetails() != null) {
              // process the returned RentalDetails
              OneTimePurchaseOfferDetails.RentalDetails rentalDetails =
                  oneTimePurchaseOfferDetails.getRentalDetails();
              // Get rental period in ISO 8601 format.
              String rentalPeriod = rentalDetails.getRentalPeriod();
              // Get rental expiration period in ISO 8601 format, if present.
              if (rentalDetails.getRentalExpirationPeriod() != null) {
                String rentalExpirationPeriod = rentalDetails.getRentalExpirationPeriod();
              }
              // Get offer token
                String offerToken = oneTimePurchaseOfferDetails.getOfferToken();
              // Get the associated purchase option ID
              if (oneTimePurchaseOfferDetails.getPurchaseOptionId() != null) {
                String purchaseOptionId = oneTimePurchaseOfferDetails.getPurchaseOptionId();
              }
            }
          }
        }
      }
    });
  2. 启动结算流程。

    如需从应用发起购买请求,请从应用的主线程调用 launchBillingFlow() 方法。此方法接受对 BillingFlowParams 对象的引用,该对象包含通过调用 queryProductDetailsAsync() 获取的相关 ProductDetails 对象。如需创建 BillingFlowParams 对象,请使用 BillingFlowParams.Builder 类。请注意,在创建 BillingFlowParams 对象时,您必须设置与用户选择的优惠对应的优惠令牌。如果用户符合租赁购买选项的条件,则会收到包含 RentalDetails 和 offerId 的优惠 queryProductDetailsAsync()

    以下示例展示了如何启动结算流程:

    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)
            // Get the offer token:
            // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList()
            // for a list of offers that are available to the user.
            // b. For subscriptions, 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)
                // Get the offer token:
                // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList()
                // for a list of offers that are available to the user.
                // b. For subscriptions, 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);

    offerToken 可在 OneTimePurchaseOfferDetails 中找到。 向用户显示商品时,请务必使用正确的商品令牌(可从 oneTimePurchaseOfferDetails.getOfferToken() 方法获取)配置结算流程参数。

预订优惠

借助预订功能,您可以设置一次性商品,以便在商品发布之前购买。用户预订商品即表示同意在商品发布时付款,除非用户在发布日期之前取消预订。在发布日期当天,系统会向买家收取费用,Play 会通过电子邮件通知买家商品已发布。

本文档介绍了将预订购买商品与 Play 结算库 (PBL) 集成的步骤。

将预订优惠与 PBL 集成

本部分介绍了如何将预订商品与 Play 结算库 (PBL) 集成。本文假定您熟悉初始 PBL 集成步骤,例如向应用添加 PBL 依赖项、初始化 BillingClient连接到 Google Play。本部分重点介绍了与预订优惠相关的 PBL 集成方面。

为预订优惠启动购买流程

如需为预订商品启动购买流程,请按以下步骤操作:

  1. 使用 ProductDetails.oneTimePurchaseOfferDetails.getPreorderDetails() 方法提取预订商品元数据。以下示例展示了如何获取预订优惠的元数据:

    Java

    billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
      public void onProductDetailsResponse(
          BillingResult billingResult, QueryProductDetailsResult productDetailsResult) {
        // check billingResult
        // …
        // process productDetailsList returned by QueryProductDetailsResult
        for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
          for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails :
              productDetails.getOneTimePurchaseOfferDetailsList()) {
            // Checks if the offer is a preorder offer.
            if (oneTimePurchaseOfferDetails.getPreorderDetails() != null) {
              // process the returned PreorderDetails
              OneTimePurchaseOfferDetails.PreorderDetails preorderDetails =
                  oneTimePurchaseOfferDetails.getPreorderDetails();
              // Get preorder release time in millis.
              long preorderReleaseTimeMillis = preorderDetails.getPreorderReleaseTimeMillis();
              // Get preorder presale end time in millis.
              long preorderPresaleEndTimeMillis = preorderDetails.getPreorderPresaleEndTimeMillis();
              // Get offer ID
                String offerId = oneTimePurchaseOfferDetails.getOfferId();
              // Get the associated purchase option ID
              if (oneTimePurchaseOfferDetails.getPurchaseOptionId() != null) {
                String purchaseOptionId = oneTimePurchaseOfferDetails.getPurchaseOptionId();
              }
            }
          }
        }
      }
    });

  2. 启动结算流程。

    如需从应用发起购买请求,请从应用的主线程调用 launchBillingFlow() 方法。此方法接受对 BillingFlowParams 对象的引用,该对象包含通过调用 queryProductDetailsAsync() 获取的相关 ProductDetails 对象。如需创建 BillingFlowParams 对象,请使用 BillingFlowParams.Builder class。请注意,在创建 BillingFlowParams 对象时,您必须设置与用户选择的优惠对应的优惠令牌。如果用户符合预订优惠的条件,则会在 queryProductDetailsAsync() 方法中收到包含 PreorderDetails 和 offerId 的优惠。

    以下示例展示了如何启动结算流程:

    Java

    // An activity reference from which the billing flow will launch.
    Activity activity = ...;
    ImmutableList productDetailsParamsList =
        ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // to get an offer token, call
            // ProductDetails.getOneTimePurchaseOfferDetailsList() 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);

    offerToken 可在 OneTimePurchaseOfferDetails 中找到。 向用户显示商品时,请务必使用正确的商品令牌(可从 oneTimePurchaseOfferDetails.getOfferToken() 方法获取)配置结算流程参数。

折扣优惠

本部分介绍了如何为一次性商品配置折扣优惠。

您可以在一次性商品折扣优惠中配置四个不同的参数:

  • 折扣优惠价格:指定与原价相比的折扣百分比或绝对折扣金额的详细信息。

  • 适用的国家或地区:指定一次性商品优惠在某个国家或地区是否可用。

  • 购买次数限制(可选):可让您确定用户可以兑换同一优惠的次数。如果用户超出购买限额,则不符合优惠条件。

  • 限时优惠(可选):指定优惠的有效期。超出该时间段后,您将无法购买该优惠。

检索折扣优惠价格信息

对于折扣优惠,您可以检索折扣百分比或提供的绝对折扣金额。

示例 1:检索折扣优惠的折扣百分比

以下示例展示了如何获取折扣优惠的原始全价和折扣百分比。请注意,系统仅针对有折扣的商品返回折扣百分比信息。

Java

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
      public void onProductDetailsResponse(
          BillingResult billingResult, QueryProductDetailsResult productDetailsResult){
        // check billingResult
        // …
        // process productDetailsList returned by QueryProductDetailsResult
        for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
          for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails :
              productDetails.getOneTimePurchaseOfferDetailsList()) {
            long discountedOfferPriceMicros =
                oneTimePurchaseOfferDetails.getPriceAmountMicros();
            // process the returned fullPriceMicros and percentageDiscount.
            if (oneTimePurchaseOfferDetails.getFullPriceMicros() != null) {
              long fullPriceMicros = oneTimePurchaseOfferDetails.getFullPriceMicros();
            }
            if (oneTimePurchaseOfferDetails.getDiscountDisplayInfo() != null) {
              long percentageDiscount =
                  oneTimePurchaseOfferDetails
                      .getDiscountDisplayInfo()
                      .getPercentageDiscount();
            }
            // …
          }
        }
      }
    });
    
示例 2:检索折扣优惠的绝对折扣

以下示例展示了如何获取折扣商品的原始全价和绝对折扣(以微秒为单位)。请注意,微商品信息中的绝对折扣仅针对有折扣的商品返回。必须为折扣优惠指定绝对折扣或百分比折扣。

Java

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
      public void onProductDetailsResponse(
          BillingResult billingResult, QueryProductDetailsResult productDetailsResult) {
        // check billingResult
        // …
        // process productDetailsList returned by QueryProductDetailsResult
        for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
          for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails :
              productDetails.getOneTimePurchaseOfferDetailsList()) {
            long discountedOfferPriceMicros =
                oneTimePurchaseOfferDetails.getPriceAmountMicros();
            // process the returned fullPriceMicros and absolute DiscountAmountMicros.
            if (oneTimePurchaseOfferDetails.getFullPriceMicros() != null) {
              long fullPriceMicros = oneTimePurchaseOfferDetails.getFullPriceMicros();
            }
            if (oneTimePurchaseOfferDetails.getDiscountDisplayInfo() != null) {
              long discountAmountMicros =
                  oneTimePurchaseOfferDetails
                      .getDiscountDisplayInfo()
                      .getDiscountAmount()
                      .getDiscountAmountMicros();
            }
            // …
          }
        }
      }
    });
    

获取优惠的有效时间范围

您可以使用 OneTimePurchaseOfferDetails.getValidTimeWindow() 方法获取商品的有效时间范围。此对象包含时间范围的开始时间和结束时间(以毫秒为单位)。

以下示例展示了如何获取优惠的有效时间范围:

Java

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
      public void onProductDetailsResponse(
          BillingResult billingResult, QueryProductDetailsResult productDetailsResult) {
        // check billingResult
        // …
        // process productDetailsList returned by QueryProductDetailsResult
        for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
          for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails :
              productDetails.getOneTimePurchaseOfferDetailsList()) {
            if (oneTimePurchaseOfferDetails.getValidTimeWindow() != null) {
              // process the returned startTimeMillis and endTimeMillis.
              ValidTimeWindow validTimeWindow =
                  oneTimePurchaseOfferDetails.getValidTimeWindow();
              long startTimeMillis = validTimeWindow.getStartTimeMillis();
              long endTimeMillis = validTimeWindow.getEndTimeMillis();
              // …
            }
          }
        }
      }
    });
    

折扣优惠级别的数量有限

您可以在折扣优惠一级指定数量上限,该上限仅应用于优惠一级。下面举例说明:

  1. “超级屏保”针对屏保商品提供了 2 种优惠:购买选项屏保和折扣屏保。
    1. 购买选项屏保未设置数量上限。
    2. 折扣屏保的商品级别允许的数量上限设为 3。
  2. 屏保产品没有产品级别的允许购买数量上限,因此用户可以无限量购买此产品。
  3. 用户拥有 1 个折扣屏保,并计划再购买一个折扣屏保。
  4. 检索可用优惠时,购买选项屏保的 LimitedQuantityInfo 为 null,而折扣屏保的剩余数量值为 2。

以下示例展示了如何在折扣优惠级别获取限量数量:

Java

billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
      public void onProductDetailsResponse(
          BillingResult billingResult, QueryProductDetailsResult productDetailsResult) {
        // check billingResult
        // …
        // process productDetailsList returned by QueryProductDetailsResult
        for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
          for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails :
              productDetails.getOneTimePurchaseOfferDetailsList()) {
            if (oneTimePurchaseOfferDetails.getLimitedQuantityInfo() != null) {
              // process the returned maximumQuantity and remainingQuantity.
              LimitedQuantityInfo limitedQuantityInfo =
                  oneTimePurchaseOfferDetails.getLimitedQuantityInfo();
              int maximumQuantity = limitedQuantityInfo.getMaximumQuantity();
              int remainingQuantity = limitedQuantityInfo.getRemainingQuantity();
              // …
            }
          }
        }
      }
    });
    

当用户兑换优惠的数量达到上限时,getOneTimePurchaseOfferDetailsList() 方法将不会返回该优惠。

计算兑换限额

以下示例展示了如何获取特定折扣优惠的限量数量信息。您可以获取当前用户的允许数量上限和剩余数量。请注意,限量功能适用于消耗型和非消耗型一次性商品优惠。此功能仅在商品级别受支持。

Google Play 会将用户拥有的数量从您设置的允许数量上限中减去,以此计算剩余数量。在统计用户拥有的数量时,Google Play 会考虑已消耗的购买交易或待处理的购买交易。已取消、退款或退单的购买交易不会计入用户拥有的数量。例如:

  1. 超级屏保设置的折扣优惠允许的数量上限为 1,因此用户最多只能购买 1 个享受折扣的屏保。

  2. 用户购买了某个享受折扣的屏保。然后,如果用户尝试购买第二个享受折扣的屏保,系统会出错,PurchasesUpdatedListener 会收到 ITEM_UNAVAILABLE 响应代码。

  3. 用户申请退还原来购买的打折屏保程序,并成功收到退款。用户尝试购买某个享受折扣的屏保,购买交易将会成功。

国家/地区资格条件

您可以选择向用户提供购买选项优惠或折扣优惠的国家/地区。Google Play 会根据用户的 Play 国家/地区来评估其是否符合资格条件。为商品配置地区性库存状况后,只有当用户位于目标国家/地区时,系统才会在 getOneTimePurchaseOfferDetailsList() 中返回该商品;否则,该商品不会包含在您调用 queryProductDetailsAsync() 时返回的商品列表中。

商品标记

以下示例展示了如何检索与商品关联的商品标记。

Java

    
billingClient.queryProductDetailsAsync(
    queryProductDetailsParams,
    new ProductDetailsResponseListener() {
      public void onProductDetailsResponse(
          BillingResult billingResult, QueryProductDetailsResult productDetailsResult) {
        // check billingResult
        // …
        // process productDetailsList returned by QueryProductDetailsResult
        for (ProductDetails productDetails : productDetailsResult.getProductDetailsList()) {
          for (OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails :
              productDetails.getOneTimePurchaseOfferDetailsList()) {
            // process the returned offer tags.
            ImmutableList<String> offerTags =
                oneTimePurchaseOfferDetails.getOfferTagsList();
            // …
          }
        }
      }
    });
    
    

优惠代码的继承

您可以为商品、购买选项或折扣优惠设置优惠代码。折扣优惠会继承其购买选项优惠的优惠代码。 同样,如果在商品级别指定了商品标签,则购买选项商品和折扣商品都会继承商品商品标签。

例如,Super 屏保针对屏保商品提供了两种优惠:购买选项屏保和折扣屏保。

  • 超级屏保包含商品优惠代码 SSProductTag
  • 购买选项屏保包含优惠代码 SSPurchaseOptionTag
  • 折扣屏保带有优惠代码 SSDiscountOfferTag

在此示例中,购买选项优惠的 oneTimePurchaseOfferDetails.getOfferTagsList() 方法会返回 SSProductTagSSPurchaseOptionTag。对于折扣优惠,该方法会返回 SSProductTagSSPurchaseOptionTagSSDiscountOfferTag