如何测试 Android in-app subscription 机制?
结论
可以。Android 的 alpha/beta 测试轨道支持测试 in-app subscriptions(订阅商品)。static responses 主要用于模拟一次性应用内商品,无法完整验证订阅的购买、续订、取消和到期流程。
测试订阅时,需要先把应用上传到 Google Play 测试轨道,配置并启用订阅商品,再使用测试账号从 Google Play 安装应用并发起购买。直接安装本地 APK,订阅流程可能无法正常工作。
In-app Billing V3 已经是旧接口。维护现有项目时,仍可参考其中的
subs商品类型。新项目或准备重新发布的应用应使用当前 Google Play 要求的 Google Play Billing Library。版本要求可能发生变化,请以 Play Console 当前提示为准。
测试前的准备
测试前需要完成以下配置:
- 在 Play Console 中创建应用,确保应用的
applicationId与上传版本完全一致。 - 将使用发布证书签名的 AAB 或 APK 上传到内部测试、封闭测试或开放测试轨道。
- 在 Play Console 中创建订阅商品,完成基础方案、价格和销售区域等必要配置。
- 激活订阅商品。处于草稿状态或配置不完整的商品通常无法查询或购买。
- 将测试人员加入相应的测试轨道,并让他们通过测试邀请链接加入测试。
- 在 Play Console 的许可测试设置中添加用于购买的 Google 账号。
- 在 Android 设备的 Google Play 商店中登录该测试账号,然后从 Google Play 测试页面安装应用。
内部测试的发布和传播速度通常更快,因此更适合开发阶段。
使用 In-app Billing V3 发起订阅购买
Billing V3 使用商品类型 "subs" 标识订阅。以下是旧版 AIDL 接口的核心调用方式:
Bundle buyIntentBundle = billingService.getBuyIntent(
3,
getPackageName(),
subscriptionId,
"subs",
developerPayload
);
int responseCode = buyIntentBundle.getInt("RESPONSE_CODE");
if (responseCode == 0) {
PendingIntent pendingIntent =
buyIntentBundle.getParcelable("BUY_INTENT");
startIntentSenderForResult(
pendingIntent.getIntentSender(),
REQUEST_CODE_SUBSCRIPTION,
new Intent(),
0,
0,
0
);
}
其中:
subscriptionId必须与 Play Console 中配置的订阅商品 ID 完全一致。- 商品类型必须传入
"subs",不能使用"inapp"。 developerPayload属于旧接口设计,不能作为可靠的防伪机制。- 必须检查
RESPONSE_CODE,不能假定每次调用都会返回可用的BUY_INTENT。
购买完成后,还要处理返回结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != REQUEST_CODE_SUBSCRIPTION || data == null) {
return;
}
int responseCode = data.getIntExtra("RESPONSE_CODE", -1);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String signature = data.getStringExtra("INAPP_DATA_SIGNATURE");
if (resultCode == RESULT_OK && responseCode == 0) {
// 将 purchaseData 和 signature 发送到服务端校验。
// 校验成功后再授予订阅权益。
}
}
这段代码只展示了 Billing V3 的基本流程。生产环境不能仅根据客户端回调开通权益,购买令牌和签名应发送到服务端验证。
使用 Google Play Billing Library
如果项目已经迁移到 Billing Library,查询订阅商品时应使用订阅类型。不同版本的 Billing Library 在订阅、基础方案和优惠方面使用的对象结构有所不同,以下代码只展示核心思路:
val queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(
listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId("premium_monthly")
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
)
.build()
billingClient.queryProductDetailsAsync(queryProductDetailsParams) {
billingResult,
productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// 从 ProductDetails 中选择对应基础方案或优惠,
// 然后构造 BillingFlowParams 发起购买。
}
}
发起订阅购买时,通常还需要从 subscriptionOfferDetails 中选择合适的 offerToken:
val offer = productDetails.subscriptionOfferDetails
?.firstOrNull()
?: return
val productDetailsParams =
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.setOfferToken(offer.offerToken)
.build()
val billingFlowParams =
BillingFlowParams.newBuilder()
.setProductDetailsParamsList(listOf(productDetailsParams))
.build()
billingClient.launchBillingFlow(activity, billingFlowParams)
如果能够查询到订阅商品,却无法发起购买,应重点检查基础方案是否启用、优惠是否适用于当前测试账号,以及选择的 offerToken 是否正确。
如何验证续订、取消和到期
购买成功只是订阅测试的第一步。完整测试至少应覆盖:
- 首次订阅;
- 自动续订;
- 用户取消续订;
- 当前计费周期结束后权益到期;
- 付款失败或进入宽限期;
- 账号保留;
- 恢复购买;
- 套餐升级、降级或替换;
- 重复购买和“已拥有该商品”;
- 应用重装或更换设备后的权益恢复。
测试购买的订阅周期通常会缩短,自动续订次数也可能受到限制。具体周期和行为可能随 Play Billing 规则调整,业务代码不应依赖测试环境中的加速时长。
取消测试订阅后,订阅权益一般不会立即失效,而会持续到当前测试计费周期结束。应用应根据服务端查询到的订阅状态和到期时间决定是否保留权益,不能在用户点击取消后立即关闭服务。
为什么不应只依赖 static responses
Static responses 适合快速测试客户端如何处理固定结果,例如购买成功、取消或商品不可用,但不能完整模拟真实的订阅生命周期,包括:
- 周期性续订;
- 到期时间变化;
- 用户取消自动续订;
- 宽限期和账号保留;
- 套餐变更;
- Play 后台订阅状态;
- 服务端购买令牌校验和状态同步。
因此,static responses 可以用于测试局部逻辑,但订阅功能最终仍要通过 Google Play 测试轨道和测试账号验证。
常见问题排查
如果查询不到订阅商品,可以依次检查:
- 安装包是否来自 Google Play 测试轨道;
- 当前账号是否已经加入相应测试;
- 当前账号是否同时配置为许可测试人员;
applicationId是否与 Play Console 中的应用一致;- 订阅商品 ID 是否完全匹配,包括大小写;
- 商品类型是否使用
subs或BillingClient.ProductType.SUBS; - 订阅及其基础方案是否已经激活;
- 上传版本是否使用正确的签名和版本号;
- Play 商店是否还登录着其他账号;
- Play Console 的配置是否仍在同步。
如果测试账号已经拥有该订阅,再次购买时可能返回“已拥有商品”。此时应先查询现有购买并恢复权益,或者取消订阅,等待测试周期结束后再重新购买。
服务端验证不可省略
客户端收到购买成功结果,不代表订阅之后仍然有效。可靠的实现需要保存购买令牌,并由服务端向 Google Play 查询订阅状态。
服务端至少需要判断:
- 购买令牌是否属于自己的应用和商品;
- 订阅是否已经生效;
- 当前是否仍在有效期内;
- 是否已取消自动续订;
- 是否处于宽限期、暂停或账号保留状态;
- 是否已经到期或被撤销;
- 是否发生套餐替换。
如果业务包含付费会员、云端资源或跨设备权益,更不能只在本地保存订阅状态。客户端状态可能被清除或篡改,也可能因为回调丢失而与 Google Play 的实际状态不一致。
备注:内容仅供参考。
