如何修改 Android 应用中订阅产品的价格?
结论
订阅价格应在 Google Play Console 中修改,而不是通过 In-App Billing API 调整。原则上,能否改价与应用使用新版还是旧版 Billing API 无关。Billing API 用于查询商品、发起购买和处理交易,商品价格则由 Play Console 管理。
需要分别考虑两类用户:
- 新订阅用户:新价格生效后,通常会按新价格购买。
- 现有订阅用户:是否保留旧价格、何时迁移到新价格,以及是否需要用户确认,取决于 Play Console 当前提供的价格迁移选项、用户所在地区和 Google Play 的订阅价格变更政策。
旧版 In-App Billing API 可能无法完整支持现行的价格变更确认流程。因此,除了修改后台价格,还要检查现有订阅者的迁移方式,并尽快升级到当前受支持的 Google Play Billing Library。
操作步骤
1. 在 Play Console 中修改价格
进入对应应用,找到订阅产品的价格配置。Play Console 的菜单名称可能随界面更新而变化,相关设置通常位于:
应用 → 变现产品 → 订阅
选择目标订阅或相应的基础方案,修改各个国家或地区的价格并保存。
修改前需要确认:
- 新价格何时生效;
- 新价格是否仅适用于新订阅者;
- 现有订阅者是否保留旧价格;
- 迁移现有订阅者时,是否需要用户明确同意;
- 用户未同意价格变更时,订阅是否会被取消。
不要根据后台显示的默认价格推断所有地区的实际售价。Google Play 可能按照税费、币种和当地定价规则生成不同的区域价格。
2. 检查应用是否动态读取价格
应用界面中的价格应来自 Google Play 返回的商品信息,不要写死在代码或资源文件中,例如:
priceTextView.setText("$49.99/year");
否则,在 Play Console 改价后,购买页面可能已经采用新价格,而应用自己的界面仍然显示旧价格。
旧版 In-App Billing API 一般通过 getSkuDetails() 查询商品详情。示意代码如下:
ArrayList<String> skuList = new ArrayList<>();
skuList.add("annual_subscription");
Bundle querySkus = new Bundle();
querySkus.putStringArrayList("ITEM_ID_LIST", skuList);
Bundle skuDetails = billingService.getSkuDetails(
3,
getPackageName(),
"subs",
querySkus
);
int responseCode = skuDetails.getInt("RESPONSE_CODE");
if (responseCode == 0) {
ArrayList<String> detailsList =
skuDetails.getStringArrayList("DETAILS_LIST");
if (detailsList != null) {
for (String detailsJson : detailsList) {
JSONObject details = new JSONObject(detailsJson);
String productId = details.getString("productId");
String formattedPrice = details.getString("price");
if ("annual_subscription".equals(productId)) {
priceTextView.setText(formattedPrice);
}
}
}
}
重点是直接使用 Google Play 返回的格式化价格 price,不要自行拼接货币符号,也不要用浮点数重新计算显示价格。
这段代码只用于说明旧接口的查询方式。实际字段和返回结构应以项目当前使用的 Billing API 版本为准。
3. 处理现有订阅者
改价时,已经订阅的用户最容易被忽略。
如果 Play Console 允许现有订阅者继续使用旧价格,应用一般不需要立即处理价格确认,但必须能够接受同一个订阅产品对不同用户显示不同价格。
如果要将现有订阅者迁移到新价格,需要先在 Play Console 中查看迁移规则。部分涨价场景可能要求用户确认,而旧版 Billing API 对相关确认流程的支持可能有限。此时需要:
- 确认 Google Play 是否会通过 Play Console 直接通知用户;
- 检查应用当前使用的 Billing API 是否支持相应的价格变更确认流程;
- 如果旧接口无法满足要求,先升级 Billing Library,再迁移现有用户;
- 在测试环境中验证用户同意、拒绝、未响应以及订阅取消等状态。
不要通过删除旧订阅商品,再创建同 ID 商品的方式强制改价。删除或停用订阅可能影响现有订阅关系,而且商品 ID 通常不能按预期重复使用。
是否必须升级 Billing API
仅修改 Play Console 中的商品价格,通常不需要先升级 Billing API。不过,继续使用旧版接口会遇到一些实际问题:
- 可能无法使用新的订阅模型和价格确认流程;
- 旧接口的数据结构与当前 Billing Library 不同;
- Google Play 会持续调整 Billing Library 的支持范围和发布要求;
- 后续提交应用更新时,旧版本可能不再符合 Play Console 的要求。
可以分两步处理:
- 先在 Play Console 中配置目标价格和现有用户的迁移策略;
- 将应用升级到当前受支持的 Google Play Billing Library,并改为动态查询订阅价格。
新版 Billing Library 通常通过 queryProductDetailsAsync() 查询订阅信息,例如:
val product = QueryProductDetailsParams.Product.newBuilder()
.setProductId("annual_subscription")
.setProductType(BillingClient.ProductType.SUBS)
.build()
val params = QueryProductDetailsParams.newBuilder()
.setProductList(listOf(product))
.build()
billingClient.queryProductDetailsAsync(params) { billingResult, queryResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
val productDetails = queryResult.productDetailsList.firstOrNull()
val pricingPhase = productDetails
?.subscriptionOfferDetails
?.firstOrNull()
?.pricingPhases
?.pricingPhaseList
?.firstOrNull()
val formattedPrice = pricingPhase?.formattedPrice
if (formattedPrice != null) {
priceTextView.text = formattedPrice
}
}
}
真实项目中可能有多个基础方案、优惠和计费阶段,不能一律直接取第一个 subscriptionOfferDetails。应根据 basePlanId、offerId、资格条件以及购买时使用的 offerToken,选择正确的报价。
注意事项
- 在 Play Console 改价,不会自动更新应用中写死的价格文案。
- 新用户和现有订阅者的价格可能在一段时间内不同。
- 涨价和降价的处理规则可能不同。
- 用户是否需要确认,不能只看 Billing API 版本,还要考虑地区、迁移方式和 Google Play 当前政策。
- 不要通过自定义支付接口修改 Google Play 订阅的续费金额。
- 改价后,应使用测试账号验证商品查询、购买页面、续订状态和服务端订单处理。
- 如果服务端会校验订阅,不要只根据价格判断订阅是否有效。还应校验购买令牌、产品 ID、订阅状态和有效期。
备注:内容仅供参考。
