PAYATHON 2026

Google Play 的 in-app billing for subscriptions 如何延长免费期?

支付阿杰

结论

可以。Google Play Developer API 提供 purchases.subscriptions.defer,可以延后自动续订订阅的到期时间和下一次扣款时间。

如需补偿 30 天服务,可以按以下步骤处理:

  1. 获取用户当前订阅的 purchaseToken。
  2. 查询并确认订阅当前的到期时间。
  3. 在该到期时间上增加 30 天。
  4. 调用 purchases.subscriptions.defer 完成延期。

不要只修改应用自己的数据库,因为这不会改变 Google Play 的实际扣款时间,还会造成应用权益状态与 Google Play 订阅状态不一致。

调用地址

REST API:

POST https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/subscriptions/{subscriptionId}/tokens/{token}:defer

请求体:

{
  "deferralInfo": {
    "expectedExpiryTimeMillis": "CURRENT_EXPIRY_TIME_MILLIS",
    "desiredExpiryTimeMillis": "NEW_EXPIRY_TIME_MILLIS"
  }
}

字段说明:

  • expectedExpiryTimeMillis:服务器认为该订阅当前的到期时间。
  • desiredExpiryTimeMillis:延期后的新到期时间。
  • 两个字段都是 Unix Epoch 毫秒时间戳,在 JSON 中以字符串表示。

expectedExpiryTimeMillis 用于并发保护。如果查询完成后,订阅发生了续订、取消恢复或其他状态变化,原来的到期时间可能已经失效。此时 API 会拒绝修改,以免覆盖最新状态。

延长 30 天的计算方式

应在当前到期时间上增加 30 天,不要直接使用”当前时间加 30 天”:

desiredExpiryTimeMillis =
    expectedExpiryTimeMillis + 30 × 24 × 60 × 60 × 1000

也就是:

desiredExpiryTimeMillis =
    expectedExpiryTimeMillis + 2592000000

例如,当前到期时间是:

1790000000000

延期 30 天后是:

1792592000000

实际开发时,最好使用日期时间库计算,避免混淆时间单位或发生整数溢出。

REST 调用示例

curl -X POST \
  "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/com.example.app/purchases/subscriptions/monthly_plan/tokens/PURCHASE_TOKEN:defer" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "deferralInfo": {
      "expectedExpiryTimeMillis": "1790000000000",
      "desiredExpiryTimeMillis": "1792592000000"
    }
  }'

成功响应类似:

{
  "newExpiryTimeMillis": "1792592000000"
}

ACCESS_TOKEN 必须通过有权访问相应 Google Play Console 应用的服务账号获取,并使用 Android Publisher API 所需的 OAuth 权限:

https://www.googleapis.com/auth/androidpublisher

Java 调用示例

使用 Google API Java Client 时,可以构造 SubscriptionPurchasesDeferRequest:

long currentExpiryTimeMillis = 1790000000000L;
long thirtyDaysMillis = 30L * 24 * 60 * 60 * 1000;
long desiredExpiryTimeMillis =
        currentExpiryTimeMillis + thirtyDaysMillis;

SubscriptionDeferralInfo deferralInfo =
        new SubscriptionDeferralInfo()
                .setExpectedExpiryTimeMillis(currentExpiryTimeMillis)
                .setDesiredExpiryTimeMillis(desiredExpiryTimeMillis);

SubscriptionPurchasesDeferRequest request =
        new SubscriptionPurchasesDeferRequest()
                .setDeferralInfo(deferralInfo);

SubscriptionPurchasesDeferResponse response =
        androidPublisher.purchases()
                .subscriptions()
                .defer(
                        "com.example.app",
                        "monthly_plan",
                        purchaseToken,
                        request
                )
                .execute();

System.out.println(
        "New expiry time: " + response.getNewExpiryTimeMillis()
);

具体的类名和方法签名可能会随 Google API Java Client 版本变化。使用较新的客户端时,应以该版本生成的 Android Publisher API 类型为准。

后台补偿流程

管理员工具可以采用以下流程:

  1. 由客服确认用户身份和订单。
  2. 从服务器保存的订阅记录中获取 purchaseToken。
  3. 调用 Google Play Developer API 查询最新的订阅状态。
  4. 确认订阅仍然有效,并且属于正确的应用和订阅商品。
  5. 读取 Google Play 返回的当前到期时间。
  6. 在这个时间上增加 30 天。
  7. 调用 purchases.subscriptions.defer。
  8. 保存延期原因、操作人员、原到期时间、新到期时间和 API 响应。
  9. 再次查询订阅状态,确认本地权益与 Google Play 保持一致。

应用应在购买流程中将 purchaseToken 上传到可信服务器。不要让管理员直接使用用户提交的 token 执行操作,也不要把服务账号凭据放入 Android application。

免费试用配置需要注意

初始的 30 天或 60 天免费试用,应通过 Google Play Console 中的订阅 base plan 和 offer 配置,让 Google Play 管理试用资格和首次扣款时间。

如果所谓的”免费试用”只是应用在本地实现的计时逻辑,Google Play 不会知道这段试用期。此时,defer 只能延期由 Google Play 管理的订阅周期,无法修正纯本地的试用状态。

另外,defer 只能延期现有订阅,不能为任意账号创建一段新订阅。用户必须已经拥有 Google Play API 能识别的订阅购买记录。

其他方案

如果订阅类型、账号状态或 API 限制导致 defer 无法调用,还可以考虑:

  • 在应用服务器中添加独立的补偿权益,补偿到期后再恢复以 Google Play 状态为准。
  • 对已经产生费用的订单进行全额或部分退款,但退款和延长服务是两回事。
  • 创建专用优惠。不过,优惠通常适用于一类符合条件的用户,不适合临时补偿单个客户。

本地补偿权益不会改变 Google Play 的下一次扣款日期。如果需求明确要求”30 天后再扣款”,应优先使用 purchases.subscriptions.defer。调用前还要查阅当前的 Google Play Developer API:purchases.subscriptions.defer,确认目标订阅类型、延期范围和账号权限仍符合最新限制。

备注:内容仅供参考。