Creem
Creem 商品映射、沙箱联调与 webhook 接入
最近更新 2026 年 11 月 3 日
Creem 是 Merchant of Record:以自己名义收款、处理税费与退款,站点拿到的是净额。订阅与一次性付款都支持。
步骤
- 在 Creem 后台建商品:订阅用 recurring 商品(按月 / 按年各建一个),买断用 one-time 商品。
- 填环境变量:
``dotenv VITE_PAYMENT_PROVIDER=creem CREEM_API_KEY=creem_xxx CREEM_WEBHOOK_SECRET=whsec_xxx CREEM_PRODUCTS=pro_monthly=prod_xxx,pro_yearly=prod_yyy,lifetime=prod_zzz ``
CREEM_PRODUCTS 左边是 src/config/products.ts 里 prices 的右侧引用,右边是 Creem 的商品 ID(prod_*),逗号分隔。同一个引用只能映射一个商品 ID。
- 部署:
pnpm run deploy。 - 在 Creem 后台添加 webhook 端点
<站点域名>/api/webhooks/creem,签名密钥即CREEM_WEBHOOK_SECRET,并订阅下面这些事件。
端点只在当前 provider 是 Creem 时才响应:站点选的是别的渠道时该地址返回 404,Creem 会停止重试。
事件
| 事件 | 作用 |
|---|---|
checkout.completed | 结账完成;一次性付款的订单在这里记录 |
subscription.paid | 订阅付款的权威事件,首笔与每次续费都会发 |
subscription.* | 订阅生命周期,scheduled_cancel 归一化为「有效但期末取消」 |
请求头 creem-signature 是请求原文的 HMAC-SHA256 十六进制串,签名不合法返回 400。归属写在 checkout 的 metadata.userId / productId / offerId 上,订阅自带 metadata 时优先用它;两者都缺失时写入失败并让 Creem 重试,不会把付款记到猜出来的用户身上。
沙箱联调
CREEM_DEBUG=true 时请求打到沙箱地址 test-api.creem.io(生产是 api.creem.io)。联调用沙箱的 API key(creem_test_ 开头)与沙箱商品 ID;上线前改回 false,换成生产 key 与生产商品 ID。
与 Stripe 的差异
| 维度 | Creem | Stripe |
|---|---|---|
| 商品与价格 | 商品即价格,没有 lookup key,结账传 product_id | Price 与 Product 分开,靠 lookup_key 关联 |
| 取价 | 逐个 GET /v1/products/{id} 读取 | 按 lookup key 批量查 Prices |
| 取消回跳 | 没有 cancel_url,取消只能回 Creem 自己的页面 | 有 cancel_url,回到发起购买的页面 |
| 金额 | 税前价,结账页会加税,展示价与实付可能不同 | 展示价即实付 |
| 订阅状态 | expired 记为 past_due(仍可恢复),未知状态记为 incomplete,暂不授予权益 | 状态直接取自 Stripe |
| 退款 | 站内没有退款入口,退款在 Creem 后台发起 | 站内没有退款入口,退款在 Stripe 后台发起,charge.refunded 会回传 |
已有的订阅不会随渠道切换迁移:换渠道等于换收款主体,存量要自己处理。
验证
pnpm run check:env通过,说明 provider、凭据与商品映射都齐了。- Creem 后台的 webhook 记录里能看到本站发起的投递(返回
200即签名校验通过)。