跳转到主要内容
Paddle 是 Merchant of Record(MoR)—— 他们卖给你的客户,你卖给 Paddle。 好处:欧盟 VAT、美国销售税、GST 都由他们替你代收代缴,你只挂一个价格, Paddle 帮你做地区加价和申报。代价:每笔大约 5% + 50¢(Stripe 是 2.9% + 30¢), 注册流程也稍微繁琐一点。 如果你卖软件给全球用户,又不想花两个月去注册公司、申请税号、每月报税, 就选 Paddle。

前置条件

  • 一个 Paddle 账号(paddle.com)。审核要几个工作日, 会问公司信息和网站 URL。
  • 一个 sandbox 账号(sandbox-vendors.paddle.com) —— 正式审核没下来时可以先开发。
  • Postgres 跑起来,schema 已 push。

1. 选定当前 Provider

src/config/site.ts
payment: {
  provider: 'paddle' as 'stripe' | 'paddle' | 'lemonsqueezy' | 'creem',
  currency: 'usd',
},

2. 在 Paddle 建商品和价格

Paddle dashboard(或 sandbox dashboard):Catalog → Products → New product。 一个商品下可以挂多个价格(一次性 / 订阅、不同币种等)。复制每个会用到的价格的 pri_… ID。 vibestrap 本体需要 promostandard 两个价格(都是一次性)。

3. 拿到 API key、client token、webhook 密钥

  • API key:Developer Tools → Authentication → API keys,用 server-side 的(secret)。
  • Client token:Developer Tools → Authentication → Client-side tokens, 公开,浏览器里 Paddle.js 用。
  • Webhook 密钥:Developer Tools → Notifications → New destination, 对应那个 destination 的 Notification key。它不是 API key —— 见下面 常见坑。

4. 配环境变量

.env.local(变量名严格对齐 src/env.ts):
PADDLE_API_KEY=pdl_...
PADDLE_WEBHOOK_SECRET=pdl_ntfset_...
NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=test_...
NEXT_PUBLIC_PADDLE_ENV=sandbox            # 或 'production'

# vibestrap 本体的价格 ID
PADDLE_PRICE_VIBESTRAP_PROMO=pri_...
PADDLE_PRICE_VIBESTRAP_STANDARD=pri_...
NEXT_PUBLIC_PADDLE_ENV 同时决定服务端 SDK 的环境(Environment.sandbox 还是 Environment.production)和客户端 checkout 的接入点,二者必须保持一致。

5. 配置 Notifications(webhook destination)

Paddle 把 webhook destination 叫 Notification。新建一个:
  • URLhttps://your-domain.com/api/webhooks/paddle
  • Events(至少订阅):transaction.completedsubscription.activatedsubscription.updatedsubscription.canceled
  • 复制该 destination 的 Notification key —— 就是你的 PADDLE_WEBHOOK_SECRET
本地联调的话,用 ngrok 或 cloudflared 把 localhost:3000 暴露到公网,URL 填那个。

验证

  1. NEXT_PUBLIC_PADDLE_ENV=sandbox,用 sandbox 的 key。
  2. pnpm dev,打开 /pricing,点结账 —— Paddle 的托管收银台应该打开。
  3. 用 Paddle 的测试卡(sandbox 环境下 4242 4242 4242 4242,过期日填未来, CVC 填 100)支付。
  4. 看终端,webhook 路由应该打印一条 transaction.completed 处理日志。
  5. 查数据库:
    select id, provider, scene, status, amount from payment
    where provider = 'paddle'
    order by created_at desc limit 1;
    

常见坑

  • Notification key 和 API key 搞混 —— 这俩是完全不同的凭证。API key 给 服务端调用用;Notification key 给 webhook 签名用。混了的话签名永远校验不过。
  • Sandbox / production 环境不一致 —— 如果 NEXT_PUBLIC_PADDLE_ENV=production 但 API key 是 sandbox 的(或反过来),SDK 会默默调到错的接入点,建 checkout 时报一个语焉不详的认证错误。
  • Webhook unmarshal 是 async 的 —— webhooks.unmarshal 是异步的(底层做 常时间 HMAC 比对)。不要在同步回调里 await,会丢事件。
  • 价格 ID 跟环境绑定 —— sandbox 的 pri_… 在 production 下不存在。两套 环境得各自配一份 env,或者搞个映射表。
  • Portal 不接受 returnUrl —— paddle.customerPortalSessions.create 不收 returnUrl,用户得通过你 app 的导航条回去。

官方文档