前置条件
- 一个 Lemon Squeezy 账号(lemonsqueezy.com) —— 注册是即时的,测试模式立刻就能跑真支付链路。
- 建一个 store(Settings → Stores → New store)。Store ID 后面要用。
- Postgres 跑起来,schema 已 push。
1. 选定当前 Provider
src/config/site.ts:
2. 建商品和 variant
Lemon Squeezy 的模型是**产品(product)**下面挂一个或多个 variant。真正 卖出去的是 variant(比如 “Vibestrap Promo 99” 可以是同一个产品下的两个 variant,也可以是两个各自只有一个 variant 的产品 —— 都行)。 进 Products → New product。每个 variant 有一个数字 ID,记下来要塞进 env。 注意:vibestrap 的 Lemon Squeezy Provider 期望priceId 是一个数字的
variant ID(用 parseInt 解析),不是字符串商品 slug。
3. 找到 store ID
Settings → Stores → [你的 store]。URL 里那串数字就是 store ID,贴到LEMON_SQUEEZY_STORE_ID。
4. 配环境变量
.env.local(变量名严格对齐 src/env.ts):
5. 配 webhook
Settings → Webhooks → New webhook:- URL:
https://your-domain.com/api/webhooks/lemonsqueezy - Events(至少订阅):
order_created、subscription_created、subscription_updated、subscription_cancelled、subscription_expired。 - Signing secret:自己随便编一个长随机字符串,同时贴到这里和环境变量
LEMON_SQUEEZY_WEBHOOK_SECRET。
localhost:3000。
验证
- 在 store 里打开 test mode,启动
pnpm dev。 - 打开
/pricing,点结账 —— Lemon Squeezy 的浮层会弹出来。 - 用测试卡
4242 4242 4242 4242,过期日填未来,CVC 随便。 - 看终端 webhook 日志。
- 查数据库:
常见坑
- Store ID 是账号级别的,不是 store 自己生成的 —— 一个账号下有多个 store
时必须显式选一个。
LEMON_SQUEEZY_STORE_ID不配的话 Provider 在建 checkout 时会立刻报错。 - Variant 和 product 容易搞混 —— 定价页常常链接到 product,但 checkout
需要 variant ID。Dashboard URL 里
/products/123是 product ID,不是 variant ID。要点进具体 variant 才能拿到它的 ID。 - 没有 customer portal API —— Lemon Squeezy 的客户 portal URL 是 checkout
时一次性下发的,会带在订单 webhook 里(
customer.urls.customer_portal)。 你得自己存下来复用,调paymentManager.createPortalLink会抛错(设计如此 —— 见src/payment/provider/lemonsqueezy.ts)。 - Webhook 密钥是你自己填的 —— 跟 Stripe 不一样,Lemon Squeezy 不替你生成
签名密钥。挑个长随机串(
openssl rand -hex 32)。 - Test mode 的订单 ID 会重置 —— 删掉 webhook 再重建后,旧的 test 事件不能 重投。重新发起一笔测试订单即可。
官方文档
- Lemon Squeezy 文档:docs.lemonsqueezy.com
- API 参考:docs.lemonsqueezy.com/api
- Webhooks 指南:docs.lemonsqueezy.com/help/webhooks
- 测试模式:docs.lemonsqueezy.com/help/getting-started/test-mode