Apple App Store
Kasskeep 校验 StoreKit 2 签名交易,接收 App Store Server Notifications V2,并调用 App Store Server API。渠道 id:app_store,平台:ios。
Kasskeep 做了什么
Section titled “Kasskeep 做了什么”- 校验 JWS 证书链一直到 Apple 根证书(内置 Apple Root CA G2 和 G3),并校验签名。
- 检查
bundleId与 App 一致;正式环境下还会检查 App Store 的 app id。 - 如果带了
appAccountToken,检查它与提交方的app_user_id一致(不区分大小写)。 - 每笔交易在 App、平台、环境和交易 id 的组合内只记一次。较新的签名数据总是覆盖较旧的,按
signedDate比较。
1. 通知地址
Section titled “1. 通知地址”在 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 通知。
2. API 密钥
Section titled “2. API 密钥”打开 Users and Access → Integrations → In-App Purchase:
- 生成一个 In-App Purchase 密钥,下载
.p8文件。它只能下载一次。 - 记下页面顶部的 Key ID 和 Issuer ID。
- 在 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 |
App 设置
Section titled “App 设置”| 设置 | 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 重试。