我们通常说的“苹果支付IAP”其实是指 In-App Purchase (IAP),即“应用内购买”。而 Apple Pay 是苹果的支付工具,可以在 App、网页或实体店里用来付款,两者是不同的体系。我们这里主要讨论 IAP。
客户端只负责“请求购买”和“展示商品”,而“校验票据”和“发放商品”的权威必须是你的服务器。
1. 流程
用户操作: 用户在 App 中点击“购买”按钮。
客户端获取商品信息: App 通过 StoreKit 向苹果服务器请求商品信息(价格、名称等),并展示给用户。
客户端发起支付: 用户确认购买后,App 通过 StoreKit 创建一个支付请求。
Apple 处理支付: iOS 系统接管,弹出 Apple ID 登录和支付确认界面(包括 Touch ID/Face ID)。
Apple 返回交易结果:
成功: 苹果服务器记录交易,更新设备上的 App Receipt,并通过 StoreKit 回调通知你的 App 交易成功。
失败/取消: 通过 StoreKit 回调通知你的 App 交易失败或被用户取消。
客户端处理成功交易:
App 收到成功回调,从设备中获取加密的 Receipt Data。
App 将这份 receipt-data 和一些关联信息(如用户ID、交易ID)发送到你自己的服务器。
服务器验证收据:
你的服务器收到 receipt-data 后,向苹果的 verifyReceipt 服务器接口发起验证请求。
苹果服务器返回解码后的 JSON 格式的收据信息,其中包含所有购买记录。
服务器更新用户权益:
你的服务器解析苹果返回的 JSON 数据,找到对应的购买记录。
验证该交易是否已处理过(防止重复发货)。
如果验证通过且是新交易,服务器更新数据库,为该用户解锁相应的功能或发放物品(例如:标记为Pro用户,增加金币数量,记录订阅到期时间)。
服务器返回结果给客户端: 服务器告知客户端“发货”成功。
客户端结束交易: 客户端收到服务器的成功确认后,调用 StoreKit 的 finishTransaction 方法。这一步至关重要,它告诉苹果:“这笔交易我们已经处理完毕,请不要再重复通知我了。”
客户端更新 UI: 客户端刷新界面,让用户立即看到购买的商品或功能已生效。
2. 介绍
2.1 支付 id 的区别
在苹果支付系统中,transaction_id 和 original_transaction_id 的主要区别如下:
transaction_id(交易ID)
- 每次交易都会生成一个唯一的 transaction_id
- 代表当前这笔具体的交易
- 对于首次购买和后续续订,都会生成新的 transaction_id
original_transaction_id(原始交易ID)
- 表示首次订阅时的交易ID
- 对于订阅类型的商品,续订时会保持相同的 original_transaction_id
- 用于追踪整个订阅周期的所有相关交易
同组级别
- 同组升级:保持original_transaction_id 不变,便于追踪
- 跨组升级:生成新的original_transaction_id,视为新订阅
2.2 订阅的升级与降级
类型为: DID_CHANGE_RENEWAL_PREF,配合 subtype
如果 subtype 为 UPGRADE ,则用户升级了订阅。升级立即生效,开启新的计费周期,用户会收到上一周期未使用部分的按比例退款。
如果 subtype 为 DOWNGRADE ,则用户降级了订阅。降级将在下一个续订日期生效,不会影响当前有效的计划。
2.3 订单续期
续期失败怎么办
- https://developer.apple.com/app-store/subscriptions/#keeping-subscribers
- https://developer.apple.com/documentation/StoreKit/reducing-involuntary-subscriber-churn#Enable-Billing-Grace-Period
与账单相关的问题会导致订阅自动进入账单重试状态,在此期间 App Store 会尝试恢复该订阅。
- App Store 服务器通知会发送 DID_FAIL_TO_RENEW 通知类型。
- 当订阅续订失败时,Apple 会在 60 天内尝试恢复。如果你在此期间选择暂停用户访问服务或内容,一旦问题解决,你需要重新恢复访问权限。
- 如果订阅在 60 天内续订,付费服务天数将从续订日期起继续计算。
- 你可以选择将账单宽限期应用于所有续订(包括现有付费续订和从免费优惠转为付费续订的情况),或仅应用于现有付费续订。你还可以将宽限期时长设为 3 天、16 天或 28 天。
续期少税
自动续期订阅的净收入结构与 App Store 上的其他商业模式不同。在订阅者的第一年服务期间,你在每个计费周期可获得订阅价格的 70%,并扣除相应税费。
当订阅者累计付费满一年后,你的净收入将提升至订阅价格的 85%,并扣除相应税费。
2.4 服务器验证API
- 订阅状态API: “/inApps/v2/subscriptions/{originalTransactionId}” 视频里用这个的多
- 历史记录API: “/inApps/v1/history/{originalTransactionId}”
- 查询id信息API: “/inApps/v1/transactions/{transactionId}”,经测试某些情况下查不到,但是 subscriptions 可以追踪到。
3. 问题
商品升级是按比例抵扣还是退款?
https://developer.apple.com/app-store/subscriptions/#ranking
https://developer.apple.com/documentation/appstoreservernotifications/notificationtype
退款。用户购买了一个比当前订阅更高级别的订阅。他们会立即升级,并获得原订阅按比例退款的金额。
1 | 一种通知类型,与其 subtype 一起表示客户对其订阅计划进行了更改。如果 subtype 为 UPGRADE ,则用户升级了订阅。升级立即生效,开启新的计费周期,用户会收到上一周期未使用部分的按比例退款。如果 subtype 为 DOWNGRADE ,则用户降级了订阅。降级将在下一个续订日期生效,不会影响当前有效的计划。 |
订单续期失败
DID_FAIL_TO_RENEW
- 一种通知类型,与其
subtype
一起表示由于账单问题导致订阅续订失败。订阅进入账单重试期。如果subtype
为GRACE_PERIOD
,请在宽限期内继续提供服务。如果subtype
为空,则订阅不在宽限期内,你可以停止提供订阅服务。
- 一种通知类型,与其
GRACE_PERIOD_EXPIRED
- 表示账单宽限期已结束且订阅未续订的通知类型,因此你可以关闭对服务或内容的访问。告知客户其账单信息可能存在问题。
- App Store 会继续重试扣费 60 天,或直到客户解决账单问题或取消订阅,以先到者为准。
苹果宽限期到了还是扣款失败,服务器会收到哪些类型通知?
https://developer.apple.com/videos/play/wwdc2021/10174/?time=1030
https://developer.apple.com/documentation/appstoreservernotifications/notificationtype
续订失败 -> DID_FAIL_TO_RENEW
自己设置的宽限期结束(28 天) -> GRACE_PERIOD_EXPIRED
过了账单重试期间 (60天)-> EXPIRED (子 BILLING_RETRY)
EXPIRED,附带 subtype
如果 subtype
为 VOLUNTARY
,则订阅因用户关闭自动续订而过期。
如果 subtype
为 BILLING_RETRY
,则订阅因账单重试期结束且未成功完成账单交易而过期。
如果 subtype
为 PRICE_INCREASE
,则订阅因用户未同意需要用户确认的价格上调而过期。
如果 subtype
为 PRODUCT_NOT_FOR_SALE
,则订阅因订阅尝试续订时产品不可购买而过期。
客户端首次购买成功后获得一个transaction_id,如果换设备,恢复购买的transaction_id会变吗?
文档里说是会变的。
- https://developer.apple.com/documentation/storekit/restoring-purchased-products
- https://developer.apple.com/documentation/appstorereceipts/transaction_id
- https://developer.apple.com/documentation/appstoreservernotifications/transactionid
1 | 每当订阅自动续期或用户在新设备上恢复订阅时,App Store 都会为该交易生成一个新的交易标识符。 |