事件流
在 Adapty 中,您将在用户使用应用的整个历程中收到各种订阅事件。以下订阅流程涵盖了常见场景,帮助您了解 Adapty 在用户订阅、取消或重新激活订阅时生成的事件。
请注意,Apple 会在实际开始/续订时间前数小时处理订阅付款。为保持图表简洁,以下流程图将订阅开始/续订与付款扣除显示为同时发生。 此外,与同一操作相关的事件会同时发生,在 Event Feed 中的显示顺序可能不固定,与我们图示中的顺序也可能有所不同。
订阅生命周期
初次购买流程
当用户首次购买订阅且没有试用期时,会触发以下事件:
- Subscription started
- Access level updated:授予用户访问权限
当订阅到达续期日期时,订阅将自动续期,并触发以下事件:
- Subscription renewal:开始新一个订阅周期
- Access level updated:更新订阅到期日期,将访问权限延长至下一个周期 付款失败或用户取消续订的情况分别在账单问题结果流程和订阅取消流程中描述。
订阅取消流程
当用户取消订阅时,系统会创建以下事件:
- Subscription renewal canceled:表示订阅在当前周期结束前仍保持有效,之后用户将失去访问权限
- Access level updated:用于禁用该访问等级的自动续费功能
订阅到期后,系统会触发 Subscription expired (churned) 事件,标志着订阅的结束。
如果退款申请获批,以下事件将替代 Subscription expired (churned):
- Subscription refunded:终止订阅并提供退款详情
对于 Stripe,订阅可以立即取消,跳过剩余的订阅周期。在这种情况下,所有事件会同时创建:
- Subscription renewal cancelled
- Subscription expired (churned)
- Access Level updated(用于移除用户的访问等级) 如果退款申请获批,系统还会触发 Subscription refunded 事件。
订阅重新激活流程
如果用户取消订阅后,订阅到期,之后又重新购买了同一订阅,系统将创建一个 Subscription renewed 事件。即使中间存在访问中断,Adapty 也会通过 vendor_original_transaction_id 将其视为同一交易链,因此此次重购被视为续订。
Access level updated 事件将被创建两次:
- 在订阅结束时,撤销用户的访问权限
- 在订阅重新购买时,授予访问权限
订阅暂停流程(仅限 Android)
此流程适用于用户在 Android 上暂停并随后恢复订阅的情况。
暂停订阅会产生延迟效果。如果用户在订阅续期前将其暂停,订阅仍保持有效,用户在当前计费周期剩余时间内继续享有付费访问权限。
-
当用户暂停订阅时,会触发 Subscription paused (Android only) 事件。
-
订阅周期结束时,Adapty 会触发 Access level updated 事件以撤销用户的访问权限。
-
当用户恢复订阅时,将触发以下事件:
- Subscription renewed
- Access level updated(用于恢复用户的访问权限)
这些订阅将属于同一交易链,并通过相同的 vendor_original_transaction_id 关联。
试用流程
如果您在应用中使用试用功能,您将收到额外的试用相关事件。
试用期成功转化流程
最常见的流程是:用户开始试用、绑定信用卡,并在试用期结束后成功转化为标准订阅。在此场景中,试用开始时会生成以下事件:
- Trial started:标记试用开始
- Access level updated:授予访问权限
当标准订阅正式生效时,系统会生成 Trial converted 事件。
试用未成功转化的流程
如果用户在试用期转化为订阅之前取消,系统会在取消时创建以下事件:
- Trial renewal cancelled:禁用试用期自动转化为订阅
- Access level updated:禁用访问等级续订
用户仍可使用至试用期结束,届时系统会创建 Trial expired 事件,标记试用期正式结束。
试用期到期后重新激活订阅的流程
如果试用期因账单问题或取消而到期,用户后续购买订阅时,系统将创建以下事件:
- 访问等级已更新,为用户授予访问权限
- 试用已转化
即使试用期与订阅之间存在时间间隔,Adapty 也会通过 vendor_original_transaction_id 将两者关联起来。此次转化被视为一条连续交易链的一部分,该链从零价格的试用期开始。这就是系统创建 试用已转化 事件而非 订阅已开始 事件的原因。
产品变更
本节涵盖对活跃订阅所做的各类调整,例如升级、降级,或购买其他组合中的产品。
立即生效的产品变更流程
用户变更产品后,系统可以在订阅结束前立即完成切换(通常发生在升级或替换产品的情况下)。此时,在产品变更的瞬间:
- 访问等级发生变更,系统创建两个 Access level updated 事件:
- 撤销第一个产品的访问权限。
- 授予第二个产品的访问权限。
- 旧订阅结束,并退款(系统创建 Subscription refunded 事件,
cancellation_reason=upgraded)。请注意,此时不会创建 Subscription expired (churned) 事件;Subscription refunded 事件将替代它。 - 新订阅开始(系统为新产品创建 Subscription started 事件)。
如果用户降级订阅,第一个订阅将持续到已付费周期结束,届时将被新的低级别订阅替换。在这种情况下,系统会立即创建 Access level updated 事件以禁用自动续订访问权限。所有其他事件将在订阅实际发生替换时创建:
- 另一个 Access level updated 事件被创建,以授予对第二个产品的访问权限。
- Subscription expired (churned) 事件被创建,以结束第一个产品的订阅。
- Subscription started 事件被创建,以开始新产品的新订阅。
延迟产品变更流程
还有一种情况:用户在订阅续费时更改产品。这种情况与前一种非常相似:系统会立即创建一个 Access level updated 事件,以禁用旧产品的访问等级自动续费。所有其他事件将在用户更改订阅且变更生效于系统时创建:
- 另一个 Access level updated 事件被创建,以授予对第二个产品的访问权限。
- Subscription expired (churned) 事件被创建,以结束第一个产品的订阅。
- Subscription started 事件被创建,以启动新产品的新订阅。
账单问题结果流程
如果试用转换或订阅续费因账单问题失败,后续流程取决于是否启用了宽限期。
启用宽限期时,若付款成功,试用将完成转换或订阅将完成续费。若付款失败,应用商店会继续尝试向用户收取订阅费用,如仍失败,则应用商店将自行终止试用或订阅。
因此,在账单问题发生时,Adapty 中会创建以下事件:
- 检测到账单问题
- 已进入宽限期(如果已启用宽限期)
- 访问等级已更新,将访问权限延续至宽限期结束
如果后续付款成功,Adapty 会记录 Trial converted 或 Subscription renewed 事件,用户不会失去访问权限。
如果付款最终失败且应用商店取消了订阅,Adapty 将生成以下事件:
- Trial expired 或 Subscription expired (churned),附带
cancellation_reason: billing_error - 访问等级已更新,撤销用户的访问权限
如果没有宽限期,账单重试期(应用商店持续尝试向用户收费的时段)将立即开始。 如果在宽限期结束前付款始终未能成功,流程相同:当应用商店自动终止订阅时,会生成相同的事件:
-
Trial expired 或 Subscription expired (churned) 事件,其
cancellation_reason为billing_error -
Access level updated 事件,用于撤销用户的访问等级
跨用户账户共享购买的流程
当一个 Customer User ID iOS、Android、React Native、Flutter 和 Unity 尝试恢复或续期已绑定到另一个 Customer User ID iOS、Android、React Native、Flutter 和 Unity 的订阅时,Adapty 的 Sharing paid access between user accounts 设置将决定如何管理访问权限。具体流程会因所选选项而有所不同。
对于 Apple 家庭共享交易(in_app_ownership_type=FAMILY_SHARED),只会触发 Access level updated 事件——下方各产品的订阅事件不会触发。完整的事件矩阵请参阅 Apple 家庭共享。
如果用户点击 Restore Purchases 时,当前用户画像已拥有访问权限,则此次恢复操作无效,不会触发任何 Webhook 事件。本节中的事件仅在访问权限实际在用户画像之间转移时才会触发。
要快速了解第二个用户画像领取现有订阅时会触发哪些事件,请参阅下方矩阵。后续各节将展示每种流程的完整 JSON 载荷。
| 事件 | 已启用(默认) | 将访问等级转移至新用户 | 已禁用 |
|---|---|---|---|
新用户画像:Access level updated(is_active=true) | 触发 | 触发 | 不触发 |
旧用户画像:Access level updated(is_active=false) | 不触发——两个用户画像均保留访问等级 | 当新识别设备传播交易时触发 | 不触发——原始用户画像保留访问等级 |
新事件中的 profiles_sharing_access_level 字段 | 列出共享该访问等级的其他用户画像 | null | 不适用——不触发任何事件 |
已转移订阅的续订、退款和到期事件,将继续在当前持有该访问等级的用户画像上触发 subscription_renewed、subscription_refunded 和 subscription_expired 事件。转移事件本身不会触发 subscription_started 事件,因为没有记录新的交易——只有归因关系发生了变化。 |
有关各模式的详细约定,请参阅实用参考。
将访问等级转移给新用户的流程
推荐的做法是将访问等级转移给新用户。这样可以保留原始用户的交易记录,确保分析数据的一致性。整个过程只会产生 2 个 Access level updated 事件:
- 移除第一个用户的访问等级
- 授予第二个用户访问等级
以下是此场景中生成的事件里,与访问等级分配及转移相关字段的说明:
- 用户 A:访问等级已更新(当用户 A 在应用内购买订阅时发送)
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": UserA,
"event_properties": {
"profile_has_access_level": true,
},
"profiles_sharing_access_level": null
}
- 用户 A:访问等级已更新(当应用重新安装并由用户 B 登录,撤销用户 A 的访问权限时发送)
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": UserA,
"event_properties": {
"profile_has_access_level": false,
},
"profiles_sharing_access_level": null
}
-
用户 B:访问等级已更新(当用户 B 登录并获得访问权限时发送)
{ "profile_id": "00000000-0000-0000-0000-000000000001", "customer_user_id": UserB, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": null }
用户间共享访问流程
此选项允许多个用户共享同一访问等级,前提是他们的设备登录了相同的 Apple/Google ID。当用户重新安装应用并使用不同的邮箱登录时,仍可访问之前的购买内容,此选项非常适合这种场景。启用此选项后,多个已识别用户可以共享同一访问等级。在共享访问等级期间,所有交易记录均归属于原始 Customer User ID iOS、Android、React Native、Flutter 和 Unity ,以确保完整的交易历史记录和分析数据。 因此,只会创建 1 个事件:Access level updated,用于向第二个用户授予访问权限。
以下是该场景中生成的事件里,与访问等级分配和共享相关的字段说明:
用户 B:Access level updated(当用户 B 登录并获得访问权限时发送)
{
"profile_id": "00000000-0000-0000-0000-000000000000",
"customer_user_id": UserA,
"event_properties": {
"profile_has_access_level": true,
},
"profiles_sharing_access_level": [
{
"profile_id": "00000000-0000-0000-0000-000000000001,
"customer_user_id": UserB
}
]
}
用户之间访问权限不共享的流程
使用此选项时,只有第一个获得该访问等级的用户画像能永久保留它。如果购买需要绑定到唯一的 Customer User ID iOS、Android、React Native、Flutter 和 Unity ,这是最理想的选择。