导读:本期聚焦于小雨创作的《iOS内购订阅升级降级后如何获取用户的选择信息?upgrade_downgrade_choice字段与订阅组级别变更逻辑详解》,敬请观看详情。订阅升级或降级后,服务器端如何知道用户在弹窗中选了哪个档位?App Store服务器通知中的pending_renewal_info数组里,包含了一个容易被忽略的upgrade_downgrade_choice字段,它记录了用户在下一次续订时将要生效的降级选择。本文围绕这个字段展开,讲解订阅组内不同档位之间的级别变更规则,分析升级立即生效与降级延迟生效的区别,说明跨订阅组购买的处理方式,并给出服务端解析通知、同步订阅状态的完整处理思路和代码示例,帮助开发者避开状态不一致的常见坑。

在iOS订阅体系中,同一订阅组内的用户可以在不同档位之间切换。当用户发起升级或降级操作时,App Store会在服务器通知的pending_renewal_info数组中记录相关状态,其中upgrade_downgrade_choice字段专门用于描述用户做出的降级选择。很多服务端开发者在处理订阅状态同步时只关注auto_renew_statusexpiration_intent,却忽略了级别变更的细节,导致用户权益在切换档位后出现错乱。本文将系统解析这一字段以及订阅组内级别变更的完整逻辑。

iOS内购订阅升级降级后如何获取用户的选择信息?upgrade_downgrade_choice字段与订阅组级别变更逻辑详解

一、订阅组内升级与降级的基本规则

苹果规定,功能相似的订阅必须放在同一个订阅组里,同一订阅组内同一时刻用户只能拥有一个有效订阅。用户在不同档位之间切换时,行为规则并不相同,这是理解后续字段的前提。

升级(Upgrade)指切换到更高级别的订阅,例如从月度标准版切到月度高级版。升级会立即生效:旧订阅被立即取消,剩余价值按比例折算,新订阅马上开始计费周期。此时服务器会收到一个DID_CHANGE_RENEWAL_PREF类型的通知,通知中不包含upgrade_downgrade_choice,因为升级已经即时完成,无需等待。

降级(Downgrade)则完全不同。用户从高级版切到标准版时,切换不会立即生效,而是要等到当前计费周期结束后才按新档位续订。苹果这样做是为了保护已支付的费用。在这段等待期内,用户的高级版权益依然有效,但服务端需要提前知道用户已经选择了降级,以便在到期时正确切换权益——这正是upgrade_downgrade_choice存在的意义。跨订阅组购买则不属于升级降级,相当于独立的订阅交易,两个订阅可以并存。

二、upgrade_downgrade_choice字段结构解析

upgrade_downgrade_choice出现在App Store服务器通知(v1和v2)以及验证收据接口返回的JSON中,它位于pending_renewal_info数组内。下面是v1通知中一个典型片段:

{
  "notification_type": "DID_CHANGE_RENEWAL_PREF",
  "latest_receipt_info": [ ... ],
  "pending_renewal_info": [
    {
      "product_id": "com.example.premium.monthly",
      "auto_renew_product_id": "com.example.standard.monthly",
      "auto_renew_status": "1",
      "expiration_intent": "0",
      "upgrade_downgrade_choice": {
        "product_id": "com.example.standard.monthly",
        "promotion_offer_id": ""
      }
    }
  ]
}

该字段包含两个子键:product_id表示用户选择在下一个周期切换到的目标产品ID,promotion_offer_id表示该切换是否与促销优惠关联,如果没有关联则为空字符串。需要注意,只有当用户选择了降级(或同级别切换)且尚未生效时,这个字段才会出现。一旦新周期开始、降级实际生效,后续通知中此字段会消失,auto_renew_product_id则变更为新的产品ID。

判断用户操作的思路可以归纳为:若通知为DID_CHANGE_RENEWAL_PREFpending_renewal_info中存在upgrade_downgrade_choice,说明用户选择了降级;若该通知中不存在此字段但auto_renew_product_id已变化且生效时间立即,则说明是升级。服务端必须把这两种情况分开处理,否则容易出现降级用户提前失去高级权益的问题。

三、服务端处理级别变更的完整流程

建议服务端维护一张订阅状态表,至少包含用户ID、当前生效产品ID、待生效产品ID和生效时间四个字段。收到DID_CHANGE_RENEWAL_PREF通知时的处理伪代码如下:

func handleRenewalPrefChange(notice *AppStoreNotification) {
    info := notice.PendingRenewalInfo[0]
    if info.UpgradeDowngradeChoice != nil {
        // 降级:记录待生效档位,当前权益保持不变
        targetProduct := info.UpgradeDowngradeChoice.ProductID
        SavePendingChange(notice.UserID, targetProduct,
            notice.CurrentPeriodEnd)
        // 不修改当前生效的产品,继续提供服务
    } else {
        // 升级:立即切换生效档位
        newProduct := info.AutoRenewProductID
        ApplyProductImmediately(notice.UserID, newProduct)
    }
}

等到下一个续订周期开始时,苹果会推送DID_RENEW通知,此时latest_receipt_info中的product_id已经是降级后的新档位。服务端收到后应将待生效记录落盘为当前生效产品,并清理upgrade_downgrade_choice相关的临时状态。这个两阶段确认机制能有效防止用户在等待期内反悔(可以再次切换档位)导致的状态错乱。

另外要处理边界情况:用户在降级等待期内取消订阅(DID_CHANGE_RENEWAL_STATUS通知且auto_renew_status为0),此时降级永远不会生效,服务端应清除待生效记录,让用户在当前周期内继续享受高级版权益直到到期。对于App Store服务器通知v2版本,对应的是subscription_renewal_preference变化,语义与v1一致,逻辑可以复用。

四、常见误区与验证建议

最常见的错误是收到降级通知后立即切换权益,这会让付费到月底的用户瞬间丢失高级功能,引发大量客诉。正确做法是降级只记录不生效。其次是误以为auto_renew_product_id等于当前生效产品,实际上它表示下次续订时将使用的产品,两者在降级等待期内是不同的。

在沙盒环境测试时,可以用一个订阅组内配置两个不同价位的档位,先购买高档位再降级,观察DID_CHANGE_RENEWAL_PREF通知中的字段变化。沙盒续订周期被大幅压缩(月订阅约5分钟),方便快速验证完整的降级生效链路。建议将每次收到的通知原始JSON都落库存档,便于排查状态不一致问题时回溯完整的事件序列。

iOS内购pending_renewal_info订阅升级降级修改时间:2026-08-31 04:18:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。