返回
Featured image of post 就问你Apple订阅乱不乱?

就问你Apple订阅乱不乱?

深入解析Apple应用内订阅系统的复杂性与常见问题

DigitalOcean Referral Badge DigitalOcean Referral Badge DigitalOcean Referral Badge

一、概念说明

开始前,我们先明确两个重要概念:

  • 订阅单:代表一个订阅组的初始交易,也是 Apple 的originTransactionID
  • 交易单:代表一笔实际的收费交易,也是 Apple 的transactionID

二、Apple 订阅系统问题

2.1 一个交易单存在多个交易 ID

Apple 订阅系统中,同一笔交易单可能对应多个交易 ID(如 120012745728737、120012745728738),它们拥有相同的购买时间戳和过期时间戳,表明属于同一笔交易。

graph LR
  订阅单[订阅单: 120011342464464<br>originTransactionID]
  交易单[交易单<br>transactionID]
  交易ID0[交易ID: 120011342464464<br>购买时间戳: 2024-02-01<br>过期时间戳: 2024-03-01]
  交易ID1[交易ID: 120012745728737<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
  交易ID2[交易ID: 120012745728738<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
  交易ID3[交易ID: 120012564738459<br>购买时间戳: 2024-04-01<br>过期时间戳: 2024-05-01]

  订阅单 --> 交易单
  交易单 --> 交易ID0
  交易单 --> 交易ID1
  交易单 --> 交易ID2
  交易单 --> 交易ID3

  classDef transaction fill:#f9f,stroke:#333,stroke-width:2px
  classDef ids fill:#bbf,stroke:#333,stroke-width:1px
  class 订阅单,交易单 transaction
  class 交易ID1,交易ID2 ids

2.2 多数据源数据不一致

苹果提供了多种 API 来获取交易信息,但不同接口返回的数据可能存在差异,给开发者带来困扰:

2.2.1 Get Transaction History

  • 接口:https://api.storekit.itunes.apple.com/inApps/v2/history/{transactionId}
  • 参数: 120012745728737 和 120012745728738 返回结果是一样的
graph LR
  订阅单[订阅单: 120011342464464<br>originTransactionID]
  交易单[交易单<br>transactionID]
  交易ID0[交易ID: 120011342464464<br>购买时间戳: 2024-02-01<br>过期时间戳: 2024-03-01]
  交易ID1[交易ID: 120012745728737<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
  交易ID3[交易ID: 120012564738459<br>购买时间戳: 2024-04-01<br>过期时间戳: 2024-05-01]

  订阅单 --> 交易单
  交易单 --> 交易ID0
  交易单 --> 交易ID1
  交易单 --> 交易ID3

  classDef transaction fill:#f9f,stroke:#333,stroke-width:2px
  classDef ids fill:#bbf,stroke:#333,stroke-width:1px
  class 订阅单,交易单 transaction
  class 交易ID1 ids

2.2.2 Get Transaction Info

  • 接口:https://api.storekit.itunes.apple.com/inApps/v1/transactions/{transactionId}
  • 参数: 120012745728737
graph LR
  订阅单[订阅单: 120011342464464<br>originTransactionID]
  交易单[交易单<br>transactionID]
  交易ID1[交易ID: 120012745728737<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]

  订阅单 --> 交易单
  交易单 --> 交易ID1

  classDef transaction fill:#f9f,stroke:#333,stroke-width:2px
  classDef ids fill:#bbf,stroke:#333,stroke-width:1px
  class 订阅单,交易单 transaction
  class 交易ID1 ids
  • 参数: 120012745728738
graph LR
  订阅单[订阅单: 120011342464464<br>originTransactionID]
  交易单[交易单<br>transactionID]
  交易ID2[交易ID: 120012745728738<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]

  订阅单 --> 交易单
  交易单 --> 交易ID2

  classDef transaction fill:#f9f,stroke:#333,stroke-width:2px
  classDef ids fill:#bbf,stroke:#333,stroke-width:1px
  class 订阅单,交易单 transaction
  class 交易ID2 ids

2.2.3 Get All Subscription Statuses

  • 接口:https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{transactionId}
  • 特点:返回用户所有订阅的状态
  • 参数: 120012745728737 和 120012745728738 返回结果是一样的
graph LR
 订阅单[订阅单: 120011342464464<br>originTransactionID]
 交易单[交易单<br>transactionID]
 交易ID0[交易ID: 120011342464464<br>购买时间戳: 2024-02-01<br>过期时间戳: 2024-03-01]
 交易ID1[交易ID: 120012745728737<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
 交易ID3[交易ID: 120012564738459<br>购买时间戳: 2024-04-01<br>过期时间戳: 2024-05-01]

 订阅单 --> 交易单
 交易单 --> 交易ID0
 交易单 --> 交易ID1
 交易单 --> 交易ID3

 classDef transaction fill:#f9f,stroke:#333,stroke-width:2px
 classDef ids fill:#bbf,stroke:#333,stroke-width:1px
 class 订阅单,交易单 transaction
 class 交易ID1 ids

2.2.4 Look Up Order ID

  • 接口:https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId}
  • 特点:通过订单 ID 查询交易
  • 未测试, 数据未知

2.2.5 App Store Server Notifications V2

  • 特点:推送交易状态变化通知

2.2.6 结论

  • Get Transaction Info: 对于重复的交易 ID 都可以查询到数据
  • Get All Subscription StatusesGet Transaction InfoApp Store Server Notifications V2 都只返回其中一个交易 ID
  • 注意: 交易 ID 120012745728737 和 120012745728738 相差 1, 但有时候不在 +1/-1 范围, 有可能是 +2/-2 范围, 也有可能超出这个范围……

三、一个订阅组多个订阅单

按照之前苹果的约定, 同一个订阅组, 只会有一个订阅单,但最近出现了一些不符合以往逻辑的情况:

3.1 生成新的订阅单问题

大概在 2025 年 2、3 月的时候, 发现苹果竟然对一些很久没有续费的订阅单, 生成了新的订阅单号(originTransactionID)

graph LR
 交易单[交易单<br>transactionID]

 订阅单1[订阅单1: 120011342464464<br>originTransactionID]
 交易ID0[交易ID: 120011342464464<br>购买时间戳: 2024-02-01<br>过期时间戳: 2024-03-01]
 交易ID1[交易ID: 120012745728737<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
 交易ID2[交易ID: 120012745728738<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
 交易ID3[交易ID: 120012564738459<br>购买时间戳: 2024-04-01<br>过期时间戳: 2024-05-01]

 订阅单1 --> 交易单
 交易单 --> 交易ID0
 交易单 --> 交易ID1
 交易单 --> 交易ID2
 交易单 --> 交易ID3

 订阅单2[订阅单2: 120012563452138<br>originTransactionID]
 交易ID4[交易ID: 120012563452138<br>购买时间戳: 2025-02-20<br>过期时间戳: 2025-03-20]

 交易单2[交易单<br>transactionID]

 订阅单2 --> 交易单2
 交易单2 --> 交易ID4

 classDef transaction fill:#f9f,stroke:#333,stroke-width:2px
 classDef ids fill:#bbf,stroke:#333,stroke-width:1px
 class 订阅单,交易单,交易单2 transaction
 class 交易ID1,交易ID2 ids

3.2 重复的订阅单问题

其中还发现存在一些重复的订阅单, 类似最开始描述的多个交易 ID 情况

graph LR
交易单[交易单<br>transactionID]

订阅单1[订阅单1: 120011342464464<br>originTransactionID]
交易ID0[交易ID: 120011342464464<br>购买时间戳: 2024-02-01<br>过期时间戳: 2024-03-01]
交易ID1[交易ID: 120012745728737<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
交易ID2[交易ID: 120012745728738<br>购买时间戳: 2024-03-01<br>过期时间戳: 2024-04-01]
交易ID3[交易ID: 120012564738459<br>购买时间戳: 2024-04-01<br>过期时间戳: 2024-05-01]

订阅单1 --> 交易单
交易单 --> 交易ID0
交易单 --> 交易ID1
交易单 --> 交易ID2
交易单 --> 交易ID3

订阅单2[订阅单2: 120012563452138<br>originTransactionID]
交易ID4[交易ID: 120012563452138<br>购买时间戳: 2025-02-20<br>过期时间戳: 2025-03-20]

交易单2[交易单<br>transactionID]

订阅单2 --> 交易单2
交易单2 --> 交易ID4

订阅单3[订阅单3: 120012563452139<br>originTransactionID]
交易ID5[交易ID: 120012563452139<br>购买时间戳: 2025-02-20<br>过期时间戳: 2025-03-20]

交易单3[交易单<br>transactionID]

订阅单3 --> 交易单3
交易单3 --> 交易ID5

classDef transaction fill:#f9f,stroke:#333,stroke-width:2px
classDef ids fill:#bbf,stroke:#333,stroke-width:1px
class 订阅单,交易单,交易单2,交易单3 transaction
class 交易ID1,交易ID2 ids

3.3 新订阅单回滚到旧订阅单问题

交易关联回原来的订阅单
交易关联回原来的订阅单

注意: 发生在 2025-03-17 之后,所有的回调、查询交易、查询状态的返回数据,对应交易关联全部被回滚到旧的订阅单。你说奇不奇葩? 跟闹着玩似的…

四、苹果订阅支付系统的演进历程

4.1 通知部分演进时间线

  • 2019-11: App Store Server Notifications 初版发布
  • 2021-10: App Store Server Notifications V2 发布
  • 2022-06: 支持发送 TEST 通知
  • 2023-06: App Store Server Notifications V1 被废弃
  • 2023-10: JWSTransactionDecodedPayload 对象新增属性:price, currency 和 offerDiscountType
  • 2024-04: 新增 CONSUMPTION_REQUEST 通知类型,用于自动续期订阅的退款请求
  • 2025-03: JWSTransactionDecodedPayload 中新增 previousOriginalTransactionId 字段 (就是这个最奇葩的改动)

五、问题反馈过程

发起 issuer

填写 feedback

回复时间真久不说, 等来的只是几个字回复….. 就问你服不服 ? 好歹我也提供了那么多数据啊, apple 老大哥!!! 关键是这回复, 回了跟没回有什么区别?

  • 问题原因是什么? — 没说
  • 怎么解决的? — 没说
  • 异常的订单怎么处理? — 没说
  • 去死吧 垃圾apple — 我说

六、相关官方讨论链接

Purchase information for users wit… | Apple Developer Forums

original_transaction_id associated… | Apple Developer Forums

StoreKit2 and Subscription Receipt… | Apple Developer Forums

StoreKit.product.purchase returns … | Apple Developer Forums

Issue with App Store Server Notifi… | Apple Developer Forums

Behavior of the “get all subscript… | Apple Developer Forums

© 2020 - 2025    去去的博客
运行: 0天    书写: 605.5k字     80篇文章
总访客数:     总访问量:    

备案号: 赣ICP备2022002813号-2 |