Google In-app billing 无法查询订阅详情
结论
在 queryInventoryAsync(true, additionalSkus, listener) 中,additionalSkus 会按一次性商品(ITEM_TYPE_INAPP)的 SKU 查询,不会按订阅(ITEM_TYPE_SUBS)处理。
因此,返回的 SkuDetails 中不会包含尚未购买的订阅。购买后能够查到,是因为 IabHelper 在查询用户已有订阅时取得了该 SKU,随后又加载了对应的商品详情。
如果需要查询尚未购买的订阅及其本地化价格,应通过支持订阅列表的重载方法,将订阅 SKU 作为 moreSubsSkus 传入,或者直接按订阅商品类型查询。
原因
Google Play Billing 将商品分为两种类型:
IabHelper.ITEM_TYPE_INAPP
IabHelper.ITEM_TYPE_SUBS
当前调用:
iabHelper.queryInventoryAsync(true, additionalSkus, listener);
通常对应以下方法:
queryInventoryAsync(
boolean querySkuDetails,
List<String> moreItemSkus,
QueryInventoryFinishedListener listener
)
其中,moreItemSkus 只用于补充查询 inapp 商品。即使列表中放的是订阅 SKU,IabHelper 仍会按错误的商品类型发起查询,所以 Google Play 不会返回对应的订阅详情。
已经购买的订阅属于用户的现有库存。IabHelper 查询库存时,会单独查询用户已有的 subscriptions,因此购买过的 SKU 可能会自动进入后续的详情查询。取消订阅甚至重装应用后仍能查到,也与此有关:取消通常只是停止续费,并不会立即删除购买记录或当前订阅权益。Google Play 账号也可能继续保留相关购买信息。
正确查询方式
使用同时接收一次性商品 SKU 和订阅 SKU 的重载方法,并把订阅列表放在第三个参数中:
List<String> subscriptionSkus = new ArrayList<>();
for (Subscription subscription : subscriptions) {
subscriptionSkus.add(subscription.getSku());
}
iabHelper.queryInventoryAsync(
true,
null,
subscriptionSkus,
new IabHelper.QueryInventoryFinishedListener() {
@Override
public void onQueryInventoryFinished(IabResult result, Inventory inv) {
if (iabHelper == null) {
return;
}
if (result.isFailure()) {
Log.i("subs", result.getMessage());
return;
}
for (Subscription subscription : subscriptions) {
SkuDetails skuDetails =
inv.getSkuDetails(subscription.getSku());
if (skuDetails != null) {
subscription.setLocalizedPrice(skuDetails.getPrice());
Log.i("subs", subscription.getLocalizedPrice());
} else {
Log.i(
"subs",
"No SkuDetails returned for "
+ subscription.getSku()
);
}
}
}
}
);
如果还要同时查询 managed products,可以分别准备两个列表:
List<String> inAppSkus = new ArrayList<>();
List<String> subscriptionSkus = new ArrayList<>();
iabHelper.queryInventoryAsync(
true,
inAppSkus,
subscriptionSkus,
inventoryListener
);
两个参数分别对应:
moreItemSkus // managed products / inapp
moreSubsSkus // subscriptions
订阅 SKU 不应放入 moreItemSkus。
如果使用较新的 Google Play Billing Library
IabHelper 是早期示例中的封装。现代 Google Play Billing Library 使用 ProductDetails 查询,并要求明确指定订阅商品类型:
QueryProductDetailsParams.Product product =
QueryProductDetailsParams.Product.newBuilder()
.setProductId(subscriptionSku)
.setProductType(BillingClient.ProductType.SUBS)
.build();
QueryProductDetailsParams params =
QueryProductDetailsParams.newBuilder()
.setProductList(Collections.singletonList(product))
.build();
billingClient.queryProductDetailsAsync(
params,
(billingResult, queryProductDetailsResult) -> {
if (billingResult.getResponseCode()
!= BillingClient.BillingResponseCode.OK) {
Log.i("subs", billingResult.getDebugMessage());
return;
}
for (ProductDetails details
: queryProductDetailsResult.getProductDetailsList()) {
List<ProductDetails.SubscriptionOfferDetails> offers =
details.getSubscriptionOfferDetails();
if (offers == null) {
continue;
}
for (ProductDetails.SubscriptionOfferDetails offer : offers) {
List<ProductDetails.PricingPhase> phases =
offer.getPricingPhases().getPricingPhaseList();
for (ProductDetails.PricingPhase phase : phases) {
Log.i("subs", phase.getFormattedPrice());
}
}
}
}
);
具体的类名和返回结构由项目使用的 Billing Library 版本决定。旧项目无需为了修复这个问题立即迁移,但新项目不建议继续使用 IabHelper。
注意事项
- 本地化价格应直接使用 Google Play 返回的
getPrice()或getFormattedPrice(),不要自行拼接货币符号。 - 查询失败后应立即
return。原代码只记录了错误,之后仍会调用inv.getSkuDetails(),如果inv无效,可能引发异常。 - 确认当前设备登录的 Google 账号有权访问测试版本,同时检查安装包的 application ID、签名和发布轨道配置是否一致。
- 订阅必须已在 Google Play Console 中正确激活,并配置可购买的基础方案或报价。具体要求由所用 Billing Library 版本和 Play Console 的订阅模型决定。
- 取消订阅不等于立即失效。在当前计费周期结束前,订阅通常仍可能处于有效状态。
- 如果
SkuDetails为null,还应检查 SKU 拼写、商品类型、应用包名和商品发布状态。不过在本例中,主要原因是订阅 SKU 被放进了inapp查询参数。
备注:内容仅供参考。
