跳转到内容

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.txt
  • secret_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。

在 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 和租户。

把你的每个商品 id 映射到一个 Stripe 价格。商品 id 可以和你在 App Store、Google Play 用的相同。

终端窗口
npx @kasskeep/cli products set --id app_xxx --product-id pro_monthly --external-id price_1Q... --type subscription
npx @kasskeep/cli products set --id app_xxx --product-id gems_100 --external-id price_1Q... --type one_time
npx @kasskeep/cli products list --id app_xxx

type 为 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 失败。这是为了防止接口被当作开放重定向使用。

在客户端用公钥调用:

POST /v1/checkout
X-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_id metadata,会被忽略。要测完整流程,请从 POST /v1/checkout 开始;用 Stripe test clock 快进续订。