rc-cancellations-pauses-winback

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cancellations, Pauses, and Winback

订阅取消、暂停与赢回

Detect cancellation, pause, and winback states on Android by reading
CustomerInfo
. Most of these events are passive: your app observes them rather than initiates them. Open Google Play's manage screen with
managementURL
. Look up the pause resume date from the REST API.
通过读取
CustomerInfo
在Android平台检测订阅的取消、暂停及赢回状态。这些事件大多是被动的:你的应用只需监听而非主动触发。使用
managementURL
打开Google Play的订阅管理界面,通过REST API查询暂停恢复日期。

Phase 1: Scope

阶段1:确定范围

Decide what state you need to surface to the user.
StateSignal in CustomerInfoNotes
Canceled, still has access
entitlement.unsubscribeDetectedAt != null
and
isActive == true
Show "ends on [expirationDate]"
Billing issue (grace or hold)
entitlement.billingIssuesDetectedAt != null
Payment failed, recovery in progress
Paused
entitlement.isActive == false
while product is still owned
Resume date requires REST API
Active and renewing
entitlement.willRenew == true
Normal state
Backend win back campaign segmentation uses the
CANCELLATION
webhook
cancel_reason
field (
UNSUBSCRIBE
,
BILLING_ERROR
,
DEVELOPER_INITIATED
,
PRICE_INCREASE
).
决定需要向用户展示哪些状态。
状态CustomerInfo中的信号说明
已取消但仍有访问权限
entitlement.unsubscribeDetectedAt != null
isActive == true
显示“将于[expirationDate]到期”
账单问题(宽限期或保留期)
entitlement.billingIssuesDetectedAt != null
支付失败,正在恢复中
已暂停
entitlement.isActive == false
但仍拥有该产品
恢复日期需通过REST API获取
活跃且自动续订
entitlement.willRenew == true
正常状态
后端赢回活动的细分需使用
CANCELLATION
webhook的
cancel_reason
字段(可选值:
UNSUBSCRIBE
BILLING_ERROR
DEVELOPER_INITIATED
PRICE_INCREASE
)。

Phase 2: Prepare

阶段2:准备工作

Confirm you already have a fetched
CustomerInfo
from
Purchases.sharedInstance.getCustomerInfo(...)
or a listener. You do not need to call
BillingClient.queryPurchasesAsync
with
setIncludeSuspendedSubscriptions(true)
. RevenueCat resolves pause state from the server side subscription state.
Rules:
  • managementURL
    is already
    Uri?
    . Pass it straight to the Intent. Do not call
    Uri.parse(url.toString())
    .
  • Do not compute access manually from
    expirationDate
    . Read
    isActive
    and
    willRenew
    .
  • Pause resume date is not in the SDK. Fetch it from
    /v1/subscribers/{app_user_id}
    in your backend.
确认你已通过
Purchases.sharedInstance.getCustomerInfo(...)
或监听器获取了
CustomerInfo
。无需调用
BillingClient.queryPurchasesAsync
并设置
setIncludeSuspendedSubscriptions(true)
。RevenueCat会从服务器端的订阅状态解析暂停状态。
规则:
  • managementURL
    的类型已为
    Uri?
    ,可直接传递给Intent,无需调用
    Uri.parse(url.toString())
  • 不要通过
    expirationDate
    手动计算访问权限,直接读取
    isActive
    willRenew
    字段。
  • 暂停恢复日期不在SDK中,需从后端调用
    /v1/subscribers/{app_user_id}
    接口获取。

Phase 3: Execute

阶段3:执行步骤

Detect cancellation with remaining access

检测已取消但仍有访问权限的状态

kotlin
val entitlement = customerInfo.entitlements["pro_access"]
if (entitlement?.unsubscribeDetectedAt != null && entitlement.isActive) {
    entitlement.expirationDate?.let { expiry ->
        showCancellationBanner(expiry)
    }
}
unsubscribeDetectedAt
is set when RevenueCat receives the
SUBSCRIPTION_CANCELED
RTDN.
isActive
stays
true
until the billing period ends.
kotlin
val entitlement = customerInfo.entitlements["pro_access"]
if (entitlement?.unsubscribeDetectedAt != null && entitlement.isActive) {
    entitlement.expirationDate?.let { expiry ->
        showCancellationBanner(expiry)
    }
}
当RevenueCat收到
SUBSCRIPTION_CANCELED
RTDN时,会设置
unsubscribeDetectedAt
字段。
isActive
会保持
true
直到计费周期结束。

Detect pause

检测暂停状态

kotlin
val entitlement = customerInfo.entitlements["pro_access"]
val hasAccess = entitlement?.isActive == true
// When paused, isActive == false. Pause resume date is not in the SDK.
kotlin
val entitlement = customerInfo.entitlements["pro_access"]
val hasAccess = entitlement?.isActive == true
// 暂停时,isActive == false。暂停恢复日期不在SDK中。

Open the subscription management screen

打开订阅管理界面

kotlin
customerInfo.managementURL?.let { url ->
    startActivity(Intent(Intent.ACTION_VIEW, url))
}
managementURL
is typed as
Uri?
. Use it directly. Wrapping it with
Uri.parse(url.toString())
is redundant and error prone.
kotlin
customerInfo.managementURL?.let { url ->
    startActivity(Intent(Intent.ACTION_VIEW, url))
}
managementURL
的类型为
Uri?
,可直接使用。用
Uri.parse(url.toString())
包装是多余且容易出错的。

Look up pause resume date via REST

通过REST API查询暂停恢复日期

From your backend, call the subscribers endpoint and read
paused_expiration_time_ms
on the subscription:
bash
curl -H "Authorization: Bearer $RC_SECRET_API_KEY" \
  https://api.revenuecat.com/v1/subscribers/$APP_USER_ID
The subscription object contains
paused_expiration_time_ms
when paused. Expose this to your client through your own endpoint.
在后端调用订阅者接口,读取订阅对象中的
paused_expiration_time_ms
字段:
bash
curl -H "Authorization: Bearer $RC_SECRET_API_KEY" \
  https://api.revenuecat.com/v1/subscribers/$APP_USER_ID
当订阅处于暂停状态时,订阅对象会包含
paused_expiration_time_ms
字段。可通过你自己的后端接口将该值暴露给客户端。

Resubscribe before expiry

到期前重新订阅

Google's resubscribe before expiry flow fires
SUBSCRIPTION_RESTARTED
. RevenueCat clears
unsubscribeDetectedAt
and sets
willRenew = true
. No extra code required, just re-read
customerInfo
.
Google的到期前重新订阅流程会触发
SUBSCRIPTION_RESTARTED
事件。RevenueCat会清除
unsubscribeDetectedAt
并设置
willRenew = true
。无需额外代码,只需重新读取
customerInfo
即可。

Resubscribe after expiry

到期后重新订阅

A resubscribe after expiry fires a
RENEWAL
webhook, not
INITIAL_PURCHASE
. Grant entitlement access on both events in your backend handler.
到期后重新订阅会触发
RENEWAL
webhook,而非
INITIAL_PURCHASE
。在后端处理程序中,需为这两个事件都授予权益访问权限。

Phase 4: Verify

阶段4:验证

CheckHow
Cancellation banner showsCancel in Play Store, pull fresh
CustomerInfo
, confirm
unsubscribeDetectedAt != null
and
isActive == true
Management deep link opensTap the link, verify Play subscriptions screen opens for the correct product
Pause is reflectedPause in Play Store sandbox, confirm
entitlement.isActive == false
Pause resume dateCall
/v1/subscribers/{id}
, confirm
paused_expiration_time_ms
is present
Win back acceptedAccept a Play configured win back offer, confirm the purchase surfaces as a normal subscription in
CustomerInfo
检查项验证方式
取消提示横幅显示在Play Store中取消订阅,拉取最新的
CustomerInfo
,确认
unsubscribeDetectedAt != null
isActive == true
管理深度链接打开点击链接,验证Play订阅界面是否打开并显示正确的产品
暂停状态已同步在Play Store沙盒环境中暂停订阅,确认
entitlement.isActive == false
暂停恢复日期调用
/v1/subscribers/{id}
接口,确认
paused_expiration_time_ms
字段存在
赢回优惠已接受接受Play配置的赢回优惠,确认该购买在
CustomerInfo
中显示为正常订阅

Notes

注意事项

  • Deferral and revocation are done through the Google Play Developer API (
    purchases.subscriptionsv2.defer
    ,
    purchases.subscriptionsv2.revoke
    ) from your backend. RevenueCat processes the resulting RTDN and updates
    CustomerInfo
    automatically.
  • Win back campaigns need no SDK code. They are configured in Play Console or the RevenueCat dashboard.
  • RevenueCat does not currently emit a dedicated pause webhook. Pause state appears in
    CustomerInfo
    once the Google Play RTDN is processed.
  • 延期和撤销操作需通过后端调用Google Play Developer API(
    purchases.subscriptionsv2.defer
    purchases.subscriptionsv2.revoke
    )完成。RevenueCat会处理生成的RTDN并自动更新
    CustomerInfo
  • 赢回活动无需SDK代码,可在Play Console或RevenueCat控制台中配置。
  • RevenueCat目前不会发送专门的暂停webhook。处理完Google Play的RTDN后,暂停状态会显示在
    CustomerInfo
    中。

References

参考资料