1. 简介
在此 Codelab 中,您将重点学习如何创建一次性商品 (OTP),并为该商品添加预订优惠。
注意:在开始此 Codelab 之前,您需要填写一次性商品 EAP 意向调查表,申请使用预订功能。
观众
本 Codelab 适用于熟悉一次性商品并希望了解如何向一次性商品添加预订优惠的 Android 应用开发者。
前提条件
如果您刚开始使用一次性商品,建议您完成 利用地区商品价格开拓新市场 Codelab。
学习内容…
- 如何使用 Google Play 管理中心为一次性商品创建预订优惠。
- 如何使用 Play Billing 库 API 查询一次性商品和相应的预订优惠详情。
所需条件…
- 使用开发者账号访问 Google Play 管理中心。如果您没有开发者账号,则需要创建账号。
- 此 Codelab 的示例应用,您可以从 GitHub 下载。
- Android Studio。
2. 构建示例应用
此 Codelab 使用一个示例 Android 应用来教您如何管理一次性商品。此示例应用旨在成为一个功能齐全的 Android 应用,其中包含完整的源代码,可展示以下方面:
- 将应用与 PBL 集成。
- 获取一次性商品和相关的预订优惠。
- 针对地区定价执行购买流程。
以下演示视频展示了示例应用在部署和运行后的外观和行为。
如果您已熟悉一次性商品和 Play 结算库 (PBL),可以下载示例应用并试用。
前提条件
在构建和部署示例应用之前,请执行以下操作:
- 创建 Google Play 管理中心开发者账号。如果您已有开发者账号,请跳过此步骤。
- 在 Play 管理中心内创建新应用。创建应用时,您可以为示例应用指定任意应用名称。
- 安装 Android Studio。
构建
此构建步骤的目标是生成示例应用的已签名 Android App Bundle 文件。
如需生成 Android App Bundle,请执行以下步骤:
- 从 GitHub 下载示例应用。
- 构建示例应用。在构建之前,请更改示例应用的软件包名称,然后进行构建。如果您的 Play 管理中心中还有其他应用的软件包,请确保您为示例应用提供的软件包名称是唯一的。
注意:构建示例应用只会创建一个可用于本地测试的 APK 文件。不过,运行应用不会提取商品和价格,因为尚未在 Play 管理中心内配置商品。 - 生成已签名的 Android App Bundle。
下一步是将 Android app bundle 上传到 Google Play 管理中心。
3. 在 Play 管理中心内创建包含预订的 OTP
如需在 Google Play 管理中心内创建一次性商品 (OTP),您需要在 Play 管理中心内拥有一个应用。在 Play 管理中心内创建应用,然后上传之前创建的已签名 app bundle。
创建应用
如需创建应用,请执行以下操作:
- 使用您的开发者账号登录 Google Play 管理中心。
- 点击创建应用。系统随即会打开创建应用页面。
- 输入应用名称,选择默认语言,以及其他与应用相关的详细信息。
- 点击创建应用。这会在 Google Play 管理中心内创建一个应用。
现在,您可以上传示例应用的已签名 app bundle。
上传已签名的 app bundle
- 将已签名的 app bundle 上传到 Google Play 管理中心的内部测试轨道。只有在上传后,您才能在 Play 管理中心内配置与创收相关的功能。
- 依次点击测试和发布 > 测试 > 内部版本 > 创建新的发布版本。
- 输入版本名称,然后上传已签名的 APK 文件。
- 点击下一步,然后点击保存并发布。
现在,您可以创建一次性商品了。
创建一次性商品
现在,创建您希望用户购买的一次性商品。
- 在 Google Play 管理中心内打开示例应用,然后依次前往通过 Play 创收 > 商品 > 一次性商品。
- 点击创建一次性商品。
- 输入以下商品详情:
- 商品 ID:输入唯一 ID。例如
upcoming_movie_1。 - (可选)标记:添加相关标记。
- 名称:输入商品名称。例如
Product Movie。 - 说明:输入商品说明。例如
Product Description。 - (可选)添加图标图片:上传可代表您产品的图标。
- 商品 ID:输入唯一 ID。例如
- 点击下一步。
- 添加购买选项并配置其地区供应情况。一次性商品需要至少一个购买选项,用于定义使用权的授予方式、价格和地区供应情况。在此 Codelab 中,我们将为商品添加标准的购买选项。在购买选项部分中,输入以下详细信息:
- 购买选项 ID:输入唯一 ID。例如
buy-movie。 - 购买类型:选择购买。
- (可选)标记:添加特定于此购买选项的标记。
- (可选)点击高级选项以配置高级选项。在此 Codelab 中,您可以跳过高级选项配置。
- 购买选项 ID:输入唯一 ID。例如
- 接下来,您必须为购买选项配置地区性库存状况和价格。在“地区供应情况”中,您可以指定要在哪些地区销售商品,甚至包括您的应用尚未发布到的地区。默认情况下,购买选项在所有地区均供应。在适用范围和价格部分中,点击修改适用范围和访问权限。
- 选择设为不供应。
- 仅取消选择
United States国家/地区,然后点击设为不供应。现在,一次性商品将仅在United States中提供。 - 在所有地区下拉菜单中,选择支持的国家和地区。您应该只会看到
United States。 - 点击价格图标。系统随即会显示一个用于设置价格的对话框。
- 输入 10 美元,然后点击保存。
- 点击保存为草稿。
注意:暂时请勿激活此购买选项。我们会在配置预订优惠后激活它。这是因为您无法向已设置地区供应情况的有效购买选项添加预订优惠。
添加预订优惠
现在,您将为之前创建的“购买”购买选项添加预订优惠。借助预订优惠,用户可在商品正式发布之前购买您的商品。请注意,预订优惠仅适用于购买类型的购买选项,并且只能针对某个地区的新商品进行配置。
添加预订优惠需要完成以下 2 个步骤:
- 为预订优惠准备购买购买选项。
- 为购买选项添加预订优惠。
为预订优惠准备“购买”购买选项
- 在 Google Play 管理中心内打开示例应用,然后依次前往通过 Play 创收 > 商品 > 一次性商品。
- 在一次性商品页面中,点击相应商品的向右箭头 (
upcoming_movie_1)。系统随即会打开修改一次性商品页面。 - 点击您之前创建的
buy-movie购买选项对应的向右箭头。系统随即会打开修改购买选项页面。 - 点击修改供应状态和可预订状态,然后选择设为供应并允许用户预订。
- 从所有地区下拉菜单中,选择可用的国家和地区。此列表应仅显示您之前配置的
United States。 - 选择相应国家/地区,然后点击设置为仅可预订。
- 点击保存。
请注意,您尚未为购买选项添加预订优惠。下一步是添加预订优惠。
添加预订优惠
- 在 Google Play 管理中心内打开示例应用,然后依次前往通过 Play 创收 > 商品 > 一次性商品。
- 在一次性商品页面中,针对您的商品点击添加优惠 > 预订 (
upcoming_movie_1)。系统随即会打开添加预订页面。 - 输入预订详细信息:
- 预订 ID:输入
preorder-offer-1。 - (可选)添加折扣:您可以选择无、百分比或绝对折扣。在本 Codelab 中,请选择无。
- (可选)标记:添加相关标记。
- 开始日期和时间:设置至少 3 天后的日期。
- 结束日期和时间:设置的日期必须比开始日期晚至少 24 小时。
- 预订后的供应情况:选择商品是在预订期结束后立即开始供应,还是在稍后的特定日期/时间开始供应。
- (可选)低价保证:如果您希望用户按预订价格与发布价格两者中的较低价格付费,请选择此选项。这可以成为吸引早期买家的有力激励措施。
- 预订 ID:输入
- 点击保存。
- 打开一次性商品 (upcoming_movie_1) 的修改一次性商品页面。
- 点击购买购买选项 (
buy-movie) 对应的启用。 - 点击“购买”购买选项下预订优惠 (
preorder-offer-1) 对应的启用。这会激活预订优惠,并使其在您之前在预订详细信息中配置的日期生效。
预订优惠创建视频
以下视频展示了前文所述的预订优惠创建步骤。
4. 与 PBL 集成
如需将应用与 Play 结算库 (PBL) 集成,请按以下步骤操作:
- 向示例应用添加 Play 结算库依赖项。
dependencies { val billing_version = "8.1.0" implementation("com.android.billingclient:billing-ktx:$billing_version") } - 初始化 BillingClient。BillingClient 是位于应用中并与 Play 结算库通信的客户端 SDK。以下代码段展示了如何初始化结算客户端。
private BillingClient createBillingClient() { return BillingClient.newBuilder(activity) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) // For one-time products, add a listener to process and acknowledge the purchases. This will notify // Google the purchase was processed. // For client-only apps, use billingClient.acknowledgePurchase(). // 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 // In this sample snippet purchases aren't processed. You must // implement your business logic to process and acknowledge the purchases. .setListener((billingResult, purchases) -> {}) .enableAutoServiceReconnection() .build(); } - 连接到 Google Play。以下代码段展示了如何连接到 Google Play。
/** * Starts the billing connection with Google Play. This method should be called exactly once * before any other methods in this class. * * @param productList The list of products to query for after the connection is established. */ public void startBillingConnection(List<Product> productList) { billingClient.startConnection( new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { Log.d(TAG, "Billing Client Connection Successful"); queryProductDetails(productList); } else { Log.e(TAG, "Billing Client Connection Failed: " + billingResult.getDebugMessage()); listener.onBillingSetupFailed(billingResult); // Propagate the error to the listener to show a message to the user. } } @Override public void onBillingServiceDisconnected() { Log.e(TAG, "Billing Client Connection Lost"); listener.onBillingError("Billing Connection Lost"); } }); } - 获取一次性商品详情。将应用与 PBL 集成后,您必须将一次性商品详情提取到应用中。以下代码段展示了如何在应用中提取一次性商品详情。
在private void queryProductDetails(List<Product> productList) { QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(productList).build(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { @Override public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { List<ProductDetails> productDetailsList = productDetailsResponse.getProductDetailsList(); listener.onProductDetailsResponse(productDetailsList); } else { Log.e(TAG, "QueryProductDetailsAsync Failed: " + billingResult.getDebugMessage()); listener.onBillingError("Query Products Failed: " + billingResult.getResponseCode()); } } }); }ProductDetails中提取一次性商品(在本例中为upcoming_movie_1)会得到类似于以下内容的响应: 请注意,预订优惠详情可在{ "productId": "upcoming_movie_1", "type": "inapp", "title": "Purrfect Mayhem: The Final Playback (Movies All Day | Play Samples)", "name": "Purrfect Mayhem: The Final Playback", "description": "Yolo and Thorne must reach the original broadcasting site to initiate the \"Final Playback\" and save the timeline. Follow them through their race against the Clockinators.", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 8500000, "priceCurrencyCode": "USD", "formattedPrice": "$8.50", "offerIdToken": "<---offerIdToken--->", "offerId": "preorder", "purchaseOptionId": "buy-option", "offerTags": [], "validTimeWindow": { "startTimeMillis": 1756771200000, "endTimeMillis": 1785542400000 }, "preorderDetails": { "preorderReleaseTimeMillis": 1785542400000, "preorderPresaleEndTimeMillis": 1785542400000 } } ] }oneTimePurchaseOfferDetailsList中查看。此列表包含 1 个购买选项 (buy-option),该选项的预订优惠已在 Play 管理中心内配置。您可以通过每个购买选项的 offerIdToken 来唯一标识相应购买选项。 - 获取优惠令牌以及预订优惠详细信息。您需要优惠令牌才能在第 6 步中启动结算流程。
@Override public void onProductDetailsResponse(List<ProductDetails> productDetailsList) { if (productDetailsList != null && !productDetailsList.isEmpty()) { // 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(); } } } } } else { Log.e(TAG, "No product details found for " + productId); } } - 启动结算流程。
/** * Launches the billing flow for the product with the given offer token. * * @param activity The activity instance from which the billing flow will be launched. * @param productDetails The product details of the product to purchase. * @param offerToken The offer token of the product to purchase. * @return The result of the billing flow. */ public void launchPurchase(Activity activity, ProductDetails productDetails, String offerToken) { ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = ImmutableList.of( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build()); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); billingClient.launchBillingFlow(activity, billingFlowParams); }
5. 测试购买选项
在正式版应用中提供一次性商品之前,您可以使用许可测试人员和 Play Billing Lab 测试 PBL 集成。
如需了解如何使用 Play Billing Lab 测试购买选项,请参阅利用区域性商品价格开拓新市场 Codelab。
6. 后续步骤
- 了解如何分析商品购买流程中的放弃情况。
- 了解如何利用区域定价开拓新市场。
- 了解如何最大限度地利用 Play 结算集成。
- 请务必遵循验证和处理购买交易的最佳实践,在用户开始购买这些商品后,在您的安全后端执行这些操作。
参考文档
7. 恭喜!
恭喜!您已成功在 Google Play 管理中心内为一次性商品创建了预订优惠。现在,您对 Google Play 灵活的一次性购买商品目录有了更深入的了解。
调查问卷
我们非常重视您对此 Codelab 的反馈。不妨抽出几分钟时间填写我们的调查问卷。