Highlight
App Store Server API 今年新增 ONE_TIME_CHARGE 通知覆盖所有一次性购买,CONSUMPTION_REQUEST 扩展到自动续期订阅退款,Get Transaction History V2 返回客户完整交易记录。
核心内容
很多开发者的内购验证还停留在客户端:StoreKit 拿到交易,本地 finish 掉,就算完事。问题在于,客户端数据可以被篡改,设备可能离线,退款发生在用户不使用 App 的时候——你的服务器对这些事件一无所知。结果就是:用户退款了你没收回权益,续期了你没及时开通,消耗型商品余额对不上。
App Store Server 正是为了解决这个信息差。它包含三个部分:App Store Server API 让你的服务器主动查询 App Store(12 个端点,涵盖交易历史、消费信息提交、通知管理);App Store Server Notifications V2 让 App Store 主动通知你的服务器(订阅续期、退款、价格变更等事件);App Store Server Library(Java / Python / Node.js / Swift)把签名验证、数据解码、promotional offer 签名等逻辑封装成开箱即用的接口,开源在 GitHub 上。
今年的更新集中在三个方向。第一,购买生命周期:新增 ONE_TIME_CHARGE 通知类型,覆盖消耗型、非消耗型和非续期订阅的一次性购买,配合已有的订阅通知,你的服务器现在能收到 App 内每一笔交易的通知。第二,退款参与:CONSUMPTION_REQUEST 通知从消耗型扩展到自动续期订阅,新增 consumptionRequestReason 字段表示用户退款原因,你还可以在 Send Consumption Information 时提交 refundPreference 表达是否同意退款。第三,交易历史:Get Transaction History 端点升级到 V2,返回客户所有交易,不再按产品类型或完成状态过滤。
详细内容
消费信息提交流程(07:02)
当用户对消耗型内购申请退款时,App Store 会向你的服务器发送 CONSUMPTION_REQUEST 通知。你需要在 12 小时内调用 Send Consumption Information 端点提交消费数据。以下是使用 App Store Server Library (Java) 的实现:
// 1. 创建验证器和 API 客户端
SignedDataVerifier verifier = new SignedDataVerifier(/* 配置参数 */);
AppStoreServerAPIClient apiClient = new AppStoreServerAPIClient(/* 配置参数 */);
// 2. 验证并解码收到的通知
DecodedSignedData notification = verifier.verifyAndDecodeNotification(signedNotification);
// 3. 判断通知类型
if (notification.getNotificationType() == NotificationTypeV2.CONSUMPTION_REQUEST) {
// 4. 提取并验证交易信息
String signedTransactionInfo = notification.getData().getSignedTransactionInfo();
JWSVerificationHeader header = verifier.verifyAndDecodeTransaction(signedTransactionInfo);
String transactionId = header.getTransactionId();
// 5. 构建消费信息请求(今年新增 refundPreference 字段)
ConsumptionRequest consumptionRequest = new ConsumptionRequest();
consumptionRequest.setCustomerConsumedValue("SAMPLE_CONTENT_PROVIDED");
consumptionRequest.setDeliveryStatus("DELIVERED");
consumptionRequest.setRefundPreference(determineRefundPreference(
notification.getData().getConsumptionRequestReason(), header));
// 6. 提交消费信息
apiClient.sendConsumptionInformation(transactionId, consumptionRequest);
}
关键点:
SignedDataVerifier统一处理所有签名数据的验证和解码,无论数据来源是设备、API 还是通知(02:59)consumptionRequestReason是今年新增字段,包含用户声明的退款原因(如 “UNINTENTIONAL_PURCHASE”),可以据此决定refundPreference(11:15)refundPreference允许你表达PREFER_GRANT(同意退款)或PREFER_DENY(拒绝退款)的倾向,Apple 会将其纳入最终退款决策(12:06)- 今年起,CONSUMPTION_REQUEST 通知也会对自动续期订阅的退款请求发送,不再仅限于消耗型内购(11:02)
获取交易历史 V2(16:42)
// 使用任意一个 transactionId 获取客户的完整交易历史
String transactionId = "existing_transaction_id";
GetTransactionHistoryRequest request = new GetTransactionHistoryRequest()
.setSort(Order.ASCENDING)
.setProductTypes(/* 可选过滤 */);
HistoryResponse response = apiClient.getTransactionHistory(transactionId, null, request, "v2");
// 版本参数 ↑
关键点:
- V1 版本只返回已退款、已撤销或设备上未完成的消耗型交易;V2 返回客户所有交易,不论产品类型、退款状态或完成状态(18:52)
- 端点是分页的,通过
revision参数追踪上次请求位置,后续请求只拉取新增或更新的交易(17:24) - 迁移只需将 URL 路径版本改为 V2,并准备好处理已完成的消耗型交易(19:50)
- V1 已被标记为 deprecated(19:44)
交易和续期信息新增字段(25:25)
交易信息(TransactionInfo)新增字段:
price:购买时的展示价格,以货币的 milliunits 表示(如 4990 = $4.99)currency:购买时使用的货币代码(如 “USD”)offerDiscountType:如果购买使用了 offer,标识其付款模式:FREE_TRIAL、PAY_UP_FRONT或PAY_AS_YOU_GO
续期信息(RenewalInfo)新增字段:
renewalPrice:下次续期时的预期展示价格currency:续期价格的货币代码offerDiscountType:下次续期时预期生效的 offer 付款模式
这些字段让你在服务端就能判断用户当前支付的价格和下次续期的价格,无需再查阅 App Store Connect。例如,一个 PAY_UP_FRONT offer 的初始价格为 $4.99,续期后 renewalPrice 显示为 $9.99——你可以据此决定是否向该用户推送 promotional offer。
核心启发
-
做什么:接入 ONE_TIME_CHARGE 通知,在服务端跟踪所有一次性购买 为什么值得做:以前消耗型和非消耗型购买没有服务端通知,你只能依赖客户端上报,设备离线或被篡改就会漏单 怎么开始:在 App Store Connect 中开启 Server Notifications V2,然后在通知处理逻辑中增加
ONE_TIME_CHARGE类型的分支,用通知中的appAccountToken直接关联用户并发放内容 -
做什么:实现 CONSUMPTION_REQUEST 全流程,包含订阅退款的消费信息提交和 refundPreference 为什么值得做:现在订阅退款也会触发 CONSUMPTION_REQUEST,你可以根据
consumptionRequestReason和用户历史消费数据决定是否同意退款,直接影响退款决策结果 怎么开始:在现有的消费信息提交代码中增加对订阅交易的处理分支,设置refundPreference字段(PREFER_GRANT/PREFER_DENY),并用determineRefundPreference方法综合退款原因和交易数据做出判断 -
做什么:迁移到 Get Transaction History V2 为什么值得做:V1 只返回部分消耗型交易,V2 返回完整历史,可以做购买记录展示、服务端权益审计、消耗型余额对账 怎么开始:将 API 路径中的版本从 V1 改为 V2,存储每次请求返回的
revision用于增量同步,注意处理已完成的消耗型交易(之前不会出现) -
做什么:利用 price / currency / offerDiscountType 新字段构建精准的 promotional offer 定向策略 为什么值得做:这些字段让你知道用户当前和下次续期的实际支付价格,你可以据此判断哪些用户适合推送打折 offer(比如一直在低价的用户可以尝试升级到高级产品) 怎么开始:在 transactionInfo 和 renewalInfo 解码后提取这三个新字段,建立用户价格画像,在收到
DID_CHANGE_RENEWAL_STATUS/AUTO_RENEW_DISABLED通知时触发 offer 评估逻辑
关联 Session
- What’s new in StoreKit and In-App Purchase — StoreKit 客户端侧的内购新功能,与本文服务端视角互补
- Meet AdAttributionKit — 新的隐私保护广告归因框架,与内购转化追踪相关
- What’s new in App Store Connect — App Store Connect 新功能,包括推荐提名和营销素材生成
评论
GitHub Issues · utterances