Stripe
Stripe 渠道面向你的 App 的网页版和桌面版售卖。顾客在 Stripe 托管的 Checkout 页面付款,钱直接进你自己的 Stripe 账户。Kasskeep 用你的私钥创建 Checkout Session 并回读对象,记录结果并发出事件。渠道 id:stripe,平台:web。
支持:订阅、一次性购买、退款、退款查询、拒付、对账和测试模式。退款在 Stripe Dashboard 里发起;Kasskeep 不会主动退款。
curl -X PUT https://api.kasskeep.com/v1/admin/tenants/ten_xxx/apps/app_xxx/credentials/stripe \ -H "Authorization: Bearer kkp_..." -H "Content-Type: application/json" \ -d '{"values": {"secret_key": "sk_test_...", "webhook_secret": "whsec_..."}}'也可以用 CLI 从文件读取,避免密钥留在 shell 历史里:
npx @kasskeep/cli apps update --id app_xxx --stripe-secret-key-file ./stripe-sk.txt --stripe-webhook-secret-file ./stripe-whsec.txtsecret_key:sk_live_...或sk_test_...。受限密钥(rk_live_...、rk_test_...)也可以,只要它能写 Checkout Sessions,并能读 Subscriptions、Invoices、Charges、Disputes 和 Events。前缀决定环境:sk_test→sandbox,sk_live→production。webhook_secret:下一步里拿到的端点签名密钥whsec_...。- 其他键名或前缀会以 400 拒绝。测试密钥和正式密钥请分别用两个 Kasskeep App。
2. 回调地址
Section titled “2. 回调地址”在 Stripe Dashboard 里进入 Developers → Webhooks → Add endpoint:
- URL:
https://api.kasskeep.com/v1/stores/stripe/<app_id>/webhook(即 App 的stripe_webhook_path)。 - API 版本不限。Kasskeep 只从事件里取对象 id,再从 API 读取当前状态。
- 事件:
| 事件 | 用途 |
|---|---|
checkout.session.completed |
一次性购买已付款;订阅开始 |
checkout.session.async_payment_succeeded |
延迟到账的支付方式 |
invoice.paid |
首个周期、续订、恢复 |
invoice.payment_failed |
扣款失败、宽限期 |
customer.subscription.created、customer.subscription.updated、customer.subscription.deleted |
更换方案、取消和恢复、到期 |
charge.refunded |
全额和部分退款 |
charge.dispute.created、charge.dispute.closed |
拒付 |
显示该端点的 Signing secret,保存为 webhook_secret。
对每个请求,Kasskeep 会:
- 检查
Stripe-Signature(v1= 对t + "." + body做 HMAC-SHA256,容差 300 秒),不通过则返回400 invalid_signature; livemode与密钥不一致的事件以400 livemode_mismatch拒绝;- 按事件 id 去重;
- 从 Stripe API 读取对象,只信任读到的结果,所以重试和乱序事件都不会让状态倒退(API 调用失败返回 502,Stripe 会重试);
- 忽略
metadata.kasskeep_app_id属于其他 App 的对象,所以一个 Stripe 账户可以同时服务多个 App 和租户。
3. 映射商品和回跳域名
Section titled “3. 映射商品和回跳域名”把你的每个商品 id 映射到一个 Stripe 价格。商品 id 可以和你在 App Store、Google Play 用的相同。
npx @kasskeep/cli products set --id app_xxx --product-id pro_monthly --external-id price_1Q... --type subscriptionnpx @kasskeep/cli products set --id app_xxx --product-id gems_100 --external-id price_1Q... --type one_timenpx @kasskeep/cli products list --id app_xxxtype 为 subscription(周期价格,Checkout mode=subscription)或 one_time(mode=payment)。一个价格只能属于一个商品 id(409 external_id_in_use)。
设置 Checkout 允许回跳的域名。每一项是小写域名,可带端口;*. 匹配任意子域名,但不匹配裸域名本身:
npx @kasskeep/cli apps update --id app_xxx --checkout-return-hosts "play.example.com,*.preview.example.com"success_url 和 cancel_url 必须是这些域名下的 https 地址;只有 localhost 和 127.0.0.1 可以用 http。列表为空时,所有结账都会以 422 return_hosts_not_configured 失败。这是为了防止接口被当作开放重定向使用。
4. 发起结账
Section titled “4. 发起结账”在客户端用公钥调用:
POST /v1/checkoutX-Kasskeep-Key: pk_...
{"app_user_id": "6f1c...", "channel": "stripe", "product_id": "pro_monthly", "success_url": "https://play.example.com/done?session={CHECKOUT_SESSION_ID}", "cancel_url": "https://play.example.com/shop", "quantity": 1}返回 {"url": "https://checkout.stripe.com/...", "session_id": "cs_..."}。把浏览器跳转到 url。回到 success_url 并不代表已付款;请根据回调或 GET /v1/customers 发放权限。SDK 封装了这个调用:checkout() 和 KasskeepWeb.checkout()。
- 一次性购买:交易 id 是 PaymentIntent id(
pi_...)。 - 订阅:
original_transaction_id是 Subscription id(sub_...);每个周期是一笔交易,以 Invoice id(in_...)为键。 - 金额按原币种存储,
price_micros以百万分之一单位计(JPY 这类无小数位的币种也能正确换算)。
| Stripe 状态 | status |
|---|---|
active、trialing |
active(cancel_at_period_end 时为 canceled) |
past_due |
下次扣款尝试前为 grace_period,Stripe 停止重试后为 billing_retry |
unpaid、paused |
billing_retry |
canceled、incomplete_expired |
expired |
incomplete |
不记录 |
| 事件 | 对应 Stripe 的情况 |
|---|---|
purchase |
Checkout 完成且已付款 |
subscription.started |
首张账单已付 |
subscription.renewed |
续订账单已付;扣款失败后恢复时带 data.recovered |
subscription.product_changed |
价格变更,立即生效 |
subscription.canceled、subscription.uncanceled |
cancel_at_period_end 打开或关闭 |
subscription.billing_issue |
invoice.payment_failed |
subscription.grace_period_expired |
宽限期结束仍未付款 |
subscription.expired |
订阅已取消;data.expiration_reason 为 VOLUNTARY、BILLING_RETRY 或 DISPUTE |
refund |
全额退款会收回权限;部分退款只做通知(data.partial: true) |
chargeback |
发起拒付。不会收回权限 |
chargeback.closed |
拒付结案,带 data.outcome。结果为 lost 时收回权限,并发送一条 data.reason: "chargeback_lost" 的 refund |
对配置了私钥的 App,Kasskeep 每小时从 Stripe 读取一次 GET /v1/events(首次回溯 3 天,之后每次重叠一小时,最多 29 天),把每个事件交给回调处理逻辑。
- 使用
sk_test_密钥。卡号4242 4242 4242 4242支付成功,4000 0000 0000 0341能绑卡但续订时扣款失败,4000 0000 0000 0259会产生拒付。 - 在 Stripe 测试模式下创建回调端点,并给测试 App 单独配一个
whsec_密钥。 stripe trigger创建的对象没有kasskeep_app_idmetadata,会被忽略。要测完整流程,请从POST /v1/checkout开始;用 Stripe test clock 快进续订。