苹果为开发者提供了两种获取订阅状态的关键途径:App Store服务器通知(Server Notifications)和服务端收据验证(verifyReceipt)。前者在状态变更时即时推送,后者需开发者主动调用。然而,仅靠通知事件的类型往往无法完整还原用户的订阅生命周期,例如升级、降级或计费暂停等复杂情况必须结合收据验证响应中的pending_renewal_info和latest_receipt_info数组进行分析。这两个结构分别描述了「下一次续订是否会发生」以及「当前有效的订阅交易记录」,读懂它们的字段是杜绝权益漏洞和误收费的核心。

pending_renewal_info:预测下一次续订行为
pending_renewal_info是一个数组,通常只包含一个元素,它针对当前生效的订阅而提供,描述的是订阅未来是否会自动续订以及如果不会,背后的原因是什么。这个结构中最重要的字段是auto_renew_status,取值为“0”表示用户已关闭自动续订,“1”表示当前订阅将在到期时自动续订。如果你发现auto_renew_status为“0”,需要通过expiration_intent进一步判断用户主动取消的原因:例如“1”代表用户自行取消,“2”代表由于计费问题导致取消,“3”表示用户不同意最近的价格上涨等等。
另一个不可忽视的字段是is_in_billing_retry_period,该字段的值为“1”时说明苹果正在尝试向用户二次收费,但之前的一次扣款因余额不足等原因失败了。此时用户的订阅并不会立即过期,而是处于一个临时的“宽限期”,如果在这个阶段将用户权限直接收回,会造成非常差的体验和投诉。因此,在服务端逻辑中,遇到is_in_billing_retry_period = "1"的场景必须继续保留订阅权益,直到expiration_intent明确为“2”且宽限期耗尽。
当用户升级或降级订阅时,pending_renewal_info会指出下一次续订的目标产品:auto_renew_product_id字段列出了即将生效的新产品ID。若该字段与当前latest_receipt_info中的product_id不同,说明存在跨等级的变更,开发者可以据此提前准备权益切换,而无需等到新交易产生。对于需要检查价格变更确认状态的场景,price_consent_status字段指示用户是否同意新的价格(“1”为同意)。
下面是一段典型的pending_renewal_info JSON片段,帮助直观理解字段布局:
{
"pending_renewal_info": [
{
"auto_renew_product_id": "com.example.premium_monthly",
"auto_renew_status": "1",
"expiration_intent": "",
"is_in_billing_retry_period": "0",
"original_transaction_id": "1000000123456789",
"product_id": "com.example.premium_monthly",
"price_consent_status": "1"
}
]
}latest_receipt_info:解析当前订阅的真实状态
latest_receipt_info数组包含了用户账户下所有自动续订订阅的最新交易信息,每一条记录代表了一个特定产品的最近一次购买或续订凭证。与pending_renewal_info关注“下一次”不同,latest_receipt_info直接给出了当前权利的有效时间窗口。最重要的字段是expires_date_ms(或expires_date_pst),它标记了如果不再续订,订阅将在哪个时刻过期。开发者应当以该时间戳作为是否授予访问权限的核心判断依据。
当用户执行升级、降级或跨产品迁移时,latest_receipt_info中可能会出现同一个original_transaction_id对应多条记录的情况,各自有不同的product_id。例如,用户从月订阅升级到年订阅,数组里会同时存在月订阅的过期信息和年订阅的最新交易,而年订阅的expires_date_ms更远。此时不应简单地取最早或最晚的过期时间,而应根据业务规则确定用户当前应享有的最高权益产品,并以其expires_date_ms为准;对于已经被替换掉的低权益订阅记录,要通过is_upgraded字段识别——苹果会在被升级的交易上标记is_upgraded = "true",防止重复授予权限。
订阅取消和退款的信息也封装在latest_receipt_info中。如果用户申请退款并获得通过,对应交易的cancellation_date_ms字段会填充一个时间戳,同时cancellation_reason会说明退款原因(例如“1”为用户主动发起,“0”为其他)。一旦检测到退款记录,即使该交易的expires_date_ms尚未来临,也必须立即终止用户权益,否则可能因提供未付费内容而被苹果拒绝。另外,通过StoreKit 2的Transaction对象同样可以获知这些信息,但服务端收据验证仍然是对历史凭证进行批量处理的可靠方式。
下面是一个简化的Python解析示例,演示如何从收据响应中提取关键字段并判断订阅有效性:
def get_active_subscription(receipt_data):
latest_info = receipt_data.get('latest_receipt_info', [])
pending_info = receipt_data.get('pending_renewal_info', [])
now = int(time.time() * 1000)
active_products = []
for item in latest_info:
# 跳过已被升级的交易
if item.get('is_upgraded') == 'true':
continue
# 检查退款
if item.get('cancellation_date_ms'):
continue
expires = int(item['expires_date_ms'])
if expires > now:
active_products.append((item['product_id'], expires))
return active_products
订阅生命周期场景处理策略
基于上述两个字段的组合,我们可以针对订阅的各类状态变更制定清晰的管理策略。当触发升级(Upgrade)时,新等级的订阅立即生效,苹果会在latest_receipt_info中生成新的交易同时将旧交易标记为is_upgraded = "true",而pending_renewal_info中的auto_renew_product_id会指向新产品。开发者应在收到通知或验证收据后,立即将用户权益切换到新产品,并根据新产品的有效期刷新权限。如果服务端同时维护多个订阅层级,只需要选择当前未过期且未被升级覆盖的最高等级产品即可。
降级(Downgrade)的情况则有所不同:降级不会立即生效,而是在当前订阅周期结束后才切换到较低等级。因此,在pending_renewal_info中会显示auto_renew_product_id为低档产品,但当前有效的latest_receipt_info条目仍然是高档产品,且expires_date_ms不变。此时不能在降级请求发出后立即降低用户权限,必须等到高档产品过期之后,再根据下一笔续订交易自动转为低档订阅。正确处理降级的关键是信任过期时间和auto_renew_product_id的预告,而非提前干预。
对于暂停和恢复场景,常见于计费问题引起的暂停。前面提到,pending_renewal_info中的is_in_billing_retry_period = "1"表示苹果正在尝试重新扣款。此时latest_receipt_info内的expires_date_ms可能仍然在未来,即使实际扣款失败,用户依然处于宽限期内。苹果会在一定天数后停止重试并将expiration_intent设为“2”。开发者需要在服务端记录一个中间状态“billing_grace”,并持续使用收据验证来跟踪最终结果。一旦扣款恢复,auto_renew_status会重新变为“1”,权益得以延续;如果最终失败,expiration_intent明确且过期时间到达,则收回权限。切勿在宽限期内擅自中断服务。
取消与退款的处理相对直接但容错率极低。用户自主取消后,auto_renew_status变为“0”,且expiration_intent通常为“1”,此时应允许用户继续使用直到原订阅周期结束。退款则会直接添加cancellation_date_ms,要求服务端立即取消权益,无论原过期时间还剩多久。鉴于退款状态只能从latest_receipt_info中检测,建议服务端采用定时轮询或结合服务器通知V2的REFUND事件类型,确保在退款发生的几分钟内就同步更新用户权限,避免损失。
构建全生命周期管理时,最佳实践是将收据验证结果与通知事件结合起来,以latest_receipt_info中无退款且未过期的最高等级产品作为权限授予的唯一依据,同时参考pending_renewal_info处理续订异常和等级迁移。每一次通知到达后执行一次收据验证,使用最新收据覆盖本地状态,可以有效规避通知漏发、乱序等问题。通过精确同步苹果的权威数据,你的订阅管理才能既保证用户享受到应有的付费服务,又充分保护业务收入。
pending_renewal_infolatest_receipt_infoiOS订阅管理修改时间:2026-08-12 15:01:04