EasyStarter logoEasyStarter
支付

Waffo 支付

配置 Waffo Pancake 托管结账、消费者门户和 Webhook

Waffo Pancake 支付集成

EasyStarter 的 Web 端内置了 Waffo Pancake 支付支持,仅适用于 Web 端,包含:

  • 订阅制(月付 / 年付)托管结账
  • 一次性买断(Lifetime)托管结账
  • Waffo 产品试用期
  • Waffo 消费者门户
  • 到期取消订阅
  • Webhook 事件处理(订单、订阅、续费与退款同步)

支付配置分为两部分:

  1. 环境变量:Merchant ID、私钥与运行环境,填入服务端 .dev.vars / .env.production
  2. 定价计划:在 packages/app-config/src/app-config.ts 中配置 Waffo Product ID 与价格信息

Waffo SDK 只能在服务端使用。切勿把 WAFFO_PRIVATE_KEY 写入 Web 环境变量、前端代码或提交到 Git。

所需环境变量

WAFFO_MERCHANT_ID=
WAFFO_PRIVATE_KEY=
WAFFO_ENVIRONMENT=test
变量说明
WAFFO_MERCHANT_ID商户 ID,格式为 MER_xxx;不要填写 Store ID
WAFFO_PRIVATE_KEY当前环境的 RSA 私钥,仅供服务端 SDK 签名请求
WAFFO_ENVIRONMENTWebhook 验签环境:开发填 test,生产填 prod

获取 Merchant ID 与测试私钥

  1. 登录 Waffo Pancake 商户后台
  2. 进入 集成 页面,先选择 测试模式
  3. 复制页面顶部的 商户 ID(格式为 MER_xxx
  4. 创建 API 密钥 区域创建测试密钥,复制私钥或直接使用 复制 .env 配置
  5. 将凭证写入本地服务端环境文件:
apps/server/.dev.vars
WAFFO_MERCHANT_ID=MER_xxxxxxxxxxxxxxxxxxxxxxxx
WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
WAFFO_ENVIRONMENT=test

Waffo 的测试密钥与生产密钥相互独立。切换后台环境时,必须同步更换私钥与 WAFFO_ENVIRONMENT,否则 API 调用或 Webhook 验签会失败。

私钥可以保留后台复制出的 PEM 格式。若 .env 使用单行值,请保留引号与 \n 转义换行;Waffo SDK 会自动规范化 PEM 内容。

在 Waffo 创建产品

EasyStarter 默认包含三个计划:FreePro(月付 + 年付)与 Lifetime(一次性买断)。Free 计划无需创建 Waffo 产品。

在商户后台进入 产品 页面,创建以下三个产品:

EasyStarter 价格Waffo 产品类型Waffo 计费周期
Pro MonthlySubscriptionMonthly
Pro YearlySubscriptionYearly
LifetimeOne-time不适用

保存后复制每个产品的 Product ID(格式为 PROD_xxx)。EasyStarter 将 Waffo Product ID 存放在通用的 providerPriceId 字段中。

如需试用期,请在 Waffo 订阅产品或产品组中设置试用天数,并让下一步的 trialDays 与后台配置保持一致。

配置定价计划

将 Waffo Product ID 填入 packages/app-config/src/app-config.tsweb.payments.plans

packages/app-config/src/app-config.ts
web: {
  payments: {
    provider: "waffo",
    plans: [
      {
        id: "free",
      },
      {
        id: "pro",
        prices: [
          {
            id: "monthly",
            provider: "waffo",
            test: {
              providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Test monthly Product ID
            },
            prod: {
              providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Production monthly Product ID
            },
            currency: "usd",
            amountCents: 1000,
            priceType: "subscription",
            interval: "month",
            trialDays: 7,
            status: "active",
          },
          {
            id: "yearly",
            provider: "waffo",
            test: {
              providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Test yearly Product ID
            },
            prod: {
              providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Production yearly Product ID
            },
            currency: "usd",
            amountCents: 10000,
            priceType: "subscription",
            interval: "year",
            trialDays: 7,
            status: "active",
          },
        ],
      },
      {
        id: "lifetime",
        prices: [
          {
            id: "lifetime",
            provider: "waffo",
            test: {
              providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Test one-time Product ID
            },
            prod: {
              providerPriceId: "PROD_xxxxxxxxxxxxxxxx", // Production one-time Product ID
            },
            currency: "usd",
            amountCents: 20000,
            priceType: "lifetime",
            status: "active",
          },
        ],
      },
    ],
  },
},

字段说明:

字段说明
providerWeb 默认支付服务商与每个价格的服务商都设为 "waffo"
test.providerPriceId测试环境可用的 Waffo Product ID,格式为 PROD_xxx
prod.providerPriceId生产环境已发布的 Waffo Product ID,格式为 PROD_xxx
amountCentsEasyStarter 前端展示的金额(分),必须与 Waffo 产品价格一致
priceType"subscription" 订阅 / "lifetime" 一次性买断
interval订阅周期:"month" / "year",Lifetime 不填
trialDays非空时结账会请求启用试用;具体天数以 Waffo 产品或产品组配置为准
status"active" 启用 / "archived" 归档

Waffo SDK 创建产品时使用 "10.00" 这样的展示金额;EasyStarter 的本地配置仍使用分,因此 $10.00 应填写 amountCents: 1000

配置 Waffo Webhook

Webhook 是订单完成后授予权限以及同步订阅状态的依据,必须配置。

  1. 在 Waffo 商户后台进入 设置 → Webhooks

  2. 添加 HTTP Webhook,并选择与当前凭证一致的测试或生产环境

  3. 填写 Endpoint URL:

    • 本地开发:https://your-ngrok-url/api/webhooks/waffo
    • 生产环境:https://your-server.workers.dev/api/webhooks/waffo
  4. 订阅 EasyStarter 已处理的事件:

    事件EasyStarter 行为
    order.completed完成一次性购买或积分包订单
    subscription.activated激活订阅
    subscription.payment_succeeded同步续费成功
    subscription.canceling标记到期取消,当前周期内保留权限
    subscription.uncanceled恢复已安排取消的订阅状态
    subscription.updated同步订阅变更
    subscription.canceled标记订阅已终止
    subscription.past_due标记续费逾期
    refund.succeeded撤销对应买断或积分权益
    refund.failed记录事件,不改变权益状态

Waffo 使用 RSA-SHA256 为原始请求体签名,并通过 x-waffo-signature 请求头发送签名。EasyStarter 会读取原始文本并使用 Waffo SDK 自动验签,不需要单独配置 Webhook Secret。

本地调试请使用 ngrok 并转发 Server 的 3001 端口。不要使用会移除自定义请求头的隧道,否则 x-waffo-signature 丢失后将无法验签。

ngrok http 3001

启动并完成沙盒结账

启动 Web 与 Server:

pnpm dev:web+server

打开定价页完成一次测试结账。Waffo 结账会在新标签页打开,以保留 EasyStarter 当前页面状态。

场景测试卡号
支付成功4576 7500 0000 0110
支付失败4576 7500 0000 0220

有效期可填写任意未来日期,CVC 可填写任意值。支付后确认 Server 收到 POST /api/webhooks/waffo 且返回 200,再到 /settings/billing 检查订阅或买断权益。

生产环境上线

上线前完成以下检查:

  1. 在 Waffo 后台切换到 生产模式,创建并复制独立的生产私钥
  2. 将所需产品发布到生产环境,并确认 prod.providerPriceId 对应可用的生产 Product ID
  3. 在生产环境注册 https://your-server.workers.dev/api/webhooks/waffo
  4. 写入生产服务端环境变量:
apps/server/.env.production
WAFFO_MERCHANT_ID=MER_xxxxxxxxxxxxxxxxxxxxxxxx
WAFFO_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
WAFFO_ENVIRONMENT=prod
  1. 部署 Server文档上传生产 Secrets 并部署

不要在生产环境复用测试私钥,也不要在产品尚未发布时填写生产 Product ID。

消费者门户与当前限制

用户可从 /settings/billing 前往 Waffo 消费者门户管理订阅。当前 EasyStarter 集成的能力边界如下:

  • 支持取消订阅,并在当前计费周期结束时失效
  • 取消后的恢复操作需由买家在 Waffo 门户完成
  • Waffo 的更换订阅产品接口目前尚未实现,调用固定返回 501 Not Implemented,因此 EasyStarter 暂时无法在应用内提供订阅升级或降级;这是 Waffo 平台当前的能力限制
  • Waffo Webhook 仍会同步买家在门户完成的恢复和订阅变更

更多平台细节可参考 Waffo 官方 SDK 集成指南Webhook 指南