跳转到内容

Apple App Store

Kasskeep 校验 StoreKit 2 签名交易,接收 App Store Server Notifications V2,并调用 App Store Server API。渠道 id:app_store,平台:ios。

  • 校验 JWS 证书链一直到 Apple 根证书(内置 Apple Root CA G2 和 G3),并校验签名。
  • 检查 bundleId 与 App 一致;正式环境下还会检查 App Store 的 app id。
  • 如果带了 appAccountToken,检查它与提交方的 app_user_id 一致(不区分大小写)。
  • 每笔交易在 App、平台、环境和交易 id 的组合内只记一次。较新的签名数据总是覆盖较旧的,按 signedDate 比较。

在 App Store Connect 打开你的 App,进入 App Information → App Store Server Notifications:

  • 选择 Version 2。
  • 把正式和沙盒服务器地址都设为:
https://api.kasskeep.com/v1/stores/apple/<app_id>/notifications

每个环境只能填一个地址。用 App Store Server API 的 Request a Test Notification 确认连通;Kasskeep 会记录这条 TEST 通知。

打开 Users and Access → Integrations → In-App Purchase:

  1. 生成一个 In-App Purchase 密钥,下载 .p8 文件。它只能下载一次。
  2. 记下页面顶部的 Key ID 和 Issuer ID。
  3. 在 App Information → General Information 下记下 App 的 Apple ID。

上传:

终端窗口
npx @kasskeep/cli apps update --id app_xxx \
--apple-issuer-id 57246542-96fe-1a63-e053-0824d011072a \
--apple-key-id 2X9R4HXF34 \
--apple-private-key-file ./AuthKey_2X9R4HXF34.p8 \
--apple-app-apple-id 1234567890 \
--apple-environment production

也可以用管理 API:

PUT /v1/admin/tenants/:tenant_id/apps/:app_id/credentials/apple
{"issuer_id": "...", "key_id": "...", "private_key": "-----BEGIN PRIVATE KEY-----...", "app_apple_id": 1234567890, "environment": "production"}

凭据只能写入:GET .../credentials 返回 issuer id、key id 和私钥指纹,永远不返回密钥本身。

没有 API 密钥,收据和通知照样能用。有了密钥,还能用上:

App Store Server API 用途
Get All Subscription Statuses 提交订阅时获取最新续订信息
Get Transaction History 恢复购买时补全用户的历史记录
Get Notification History 每小时对账(首次回溯 7 天,之后每次重叠一小时)
Set App Account Token 把不带 appAccountToken 的交易(比如在 App 外兑换的优惠码)绑定到第一个提交它的用户
Send Consumption Information 在 12 小时内答复 CONSUMPTION_REQUEST
设置 CLI 参数 取值
apple_environment --apple-environment production(默认)接受正式和沙盒数据,并分开存储;sandbox 只接受沙盒数据
apple_refund_preference --apple-refund-preference DECLINE、GRANT_FULL 或 GRANT_PRORATED。设置后,Kasskeep 会以 customerConsented: true 自动答复消费信息请求,所以只有在你的用户条款涵盖这一点时才设置。留空则只发送 refund.consumption_requested 事件
  • 使用 StoreKit 2,把 appAccountToken 设为用户的 UUID。SDK 会替你做。
  • 把交易提交给 Kasskeep,收到成功响应后才调用 finish()。每次启动都扫描未结束的交易。
通知 结果
SUBSCRIBED、DID_RENEW、OFFER_REDEEMED 写入交易和订阅;发出 subscription.started、subscription.renewed 或 subscription.product_changed
DID_CHANGE_RENEWAL_PREF 升级立即切换商品;降级则更新下次续订的商品
DID_CHANGE_RENEWAL_STATUS subscription.canceled 或 subscription.uncanceled
DID_FAIL_TO_RENEW、GRACE_PERIOD_EXPIRED、EXPIRED 扣费重试、宽限期和到期
PRICE_INCREASE 记录同意状态,不发事件
REFUND、REVOKE、REFUND_REVERSED refund、revoked、refund.reversed
CONSUMPTION_REQUEST refund.consumption_requested
ONE_TIME_CHARGE 记录一次性购买
RENEWAL_EXTENDED 更新到期时间,不发事件
REFUND_DECLINED、RENEWAL_EXTENSION、TEST、METADATA_UPDATE、MIGRATION、PRICE_CHANGE、RESCIND_CONSENT、EXTERNAL_PURCHASE_TOKEN 仅记录

通知按 notificationUUID 去重。处理失败的通知会在 Apple 重试时再次处理。同一个原始交易 id 的处理通过 Postgres advisory lock 串行执行。签名无效时接口返回 400,处理失败时返回 500,好让 Apple 重试。