Creem 支付
配置 Creem Checkout、账单管理门户和 Webhook
Creem 支付集成
EasyStarter 的 Web 端内置了完整的 Creem 支付支持,仅适用于 Web 端,开箱即用,包含:
- 订阅制(月付 / 年付)结账
- 一次性买断(Lifetime)结账
- 免费试用期(Trial)
- 客户账单管理门户(用户自助管理订阅)
- 计划升级(支持按比例折算)
- Webhook 事件处理(订阅状态同步、退款、争议等)
支付配置分为两部分:
- 环境变量:API 密钥与 Webhook 密钥,填入服务端
.dev.vars/.env.production - 定价计划:在
packages/app-config/src/app-config.ts中配置 Creem Product ID 与价格信息
所需环境变量
CREEM_API_KEY=
CREEM_WEBHOOK_SECRET=注册 Creem 并获取 API 密钥
- 前往 creem.io 注册账号
- 登录后进入控制台的 API Keys 页面
- 复制 API 密钥(以
creem_test_开头为测试密钥,creem_live_为生产密钥)
初始阶段使用测试密钥即可。上线前切换为生产密钥。
将复制的密钥填入:
CREEM_API_KEY=creem_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCREEM_API_KEY=creem_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEasyStarter 会根据密钥前缀自动路由 API 请求:
creem_test_→https://test-api.creem.io/v1creem_live_→https://api.creem.io/v1
在 Creem 创建产品
EasyStarter 默认包含三个计划:Free、Pro(月付 + 年付)、Lifetime(一次性买断)。
Free 计划无需在 Creem 创建,仅在本地配置中用作占位。以下为 Pro 和 Lifetime 的创建步骤:
- 在 Creem 控制台进入 Products 页面,点击 Create product
- 填写产品名称,例如
Pro Monthly - 订阅计划(月付 / 年付):计费类型选 Recurring,分别设置月付周期和年付周期,每个周期各建一个产品
- 买断计划(Lifetime):计费类型选 One time,建一个产品
- 每个产品保存后,复制其 Product ID(格式为
prod_xxxxxxx),下一步将用到
配置定价计划
将上一步获取的 Product ID 填入 packages/app-config/src/app-config.ts 的 web.payments.plans 字段:
web: {
payments: {
provider: "creem",
// test/prod 分别配置不同环境的 Creem Product ID。
// creem_test_ 使用测试产品 ID,creem_live_ 使用生产产品 ID。
plans: [
{
id: "free", // 免费计划,无需 Product ID
},
{
id: "pro",
prices: [
{
id: "monthly",
provider: "creem",
test: {
providerPriceId: "prod_xxxxxxxxxxxxxxxx", // 测试环境 Creem 月付 Product ID
},
prod: {
providerPriceId: "prod_xxxxxxxxxxxxxxxx", // 生产环境 Creem 月付 Product ID
},
currency: "usd",
amountCents: 1000, // $10.00
priceType: "subscription",
interval: "month",
trialDays: 7, // 必须与 Creem 产品中设置的试用天数一致
status: "active",
},
{
id: "yearly",
provider: "creem",
test: {
providerPriceId: "prod_xxxxxxxxxxxxxxxx", // 测试环境 Creem 年付 Product ID
},
prod: {
providerPriceId: "prod_xxxxxxxxxxxxxxxx", // 生产环境 Creem 年付 Product ID
},
currency: "usd",
amountCents: 10000, // $100.00
priceType: "subscription",
interval: "year",
trialDays: 7, // 必须与 Creem 产品中设置的试用天数一致
status: "active",
},
],
},
{
id: "lifetime",
prices: [
{
id: "lifetime",
provider: "creem",
test: {
providerPriceId: "prod_xxxxxxxxxxxxxxxx", // 测试环境 Creem 一次性 Product ID
},
prod: {
providerPriceId: "prod_xxxxxxxxxxxxxxxx", // 生产环境 Creem 一次性 Product ID
},
currency: "usd",
amountCents: 20000, // $200.00
priceType: "lifetime",
status: "active",
},
],
},
],
},
},字段说明:
| 字段 | 说明 |
|---|---|
test.providerPriceId | Creem 测试环境的 Product ID,格式 prod_xxx |
prod.providerPriceId | Creem 生产环境的 Product ID,格式 prod_xxx |
amountCents | 价格(分),1000 = $10.00 |
priceType | "subscription" 订阅 / "lifetime" 一次性 |
interval | 订阅周期:"month" / "year"(lifetime 不填) |
trialDays | 必须与 Creem 产品中设置的试用天数保持一致 —— Creem 不支持通过 API 设置试用期,此字段仅用于前端展示,实际试用天数以 Creem 控制台产品配置为准 |
status | "active" 启用 / "archived" 归档(不显示在定价页) |
配置 Creem Webhook
Webhook 用于接收 Creem 推送的事件(如支付成功、订阅变更、退款等),是支付状态同步的核心机制。
-
在 Creem 控制台进入 Webhooks 页面
-
点击 Add endpoint
-
填写 Endpoint URL:
- 本地开发:
https://your-ngrok-url/api/webhooks/creem(需使用 ngrok 等隧道工具) - 生产环境:
https://your-server.workers.dev/api/webhooks/creem
- 本地开发:
-
在 Events to send 中选择以下事件(EasyStarter 已处理):
事件 说明 checkout.completed结账完成(订阅或一次性) subscription.active订阅激活 subscription.trialing试用开始 subscription.paid续费成功 subscription.scheduled_cancel到期取消已安排 subscription.past_due付款逾期 subscription.update订阅变更 subscription.expired订阅到期 subscription.canceled订阅取消 subscription.paused订阅暂停 refund.created退款处理 dispute.created争议 / 拒付创建 -
保存后,复制 Webhook Secret 填入:
CREEM_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCREEM_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCreem 使用 HMAC-SHA256 签名 Webhook 负载,EasyStarter 会自动从
creem-signature请求头验证签名。
填入环境变量并启动
确认两个变量已在开发环境中填写完整:
CREEM_API_KEY=creem_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CREEM_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx然后启动服务:
pnpm dev:server由于 Creem 没有像 Stripe CLI 那样的本地转发工具,本地测试 Webhook 需要使用隧道服务(如 ngrok):
ngrok http 3001然后将 ngrok URL 设置为 Creem 控制台中的 Webhook 端点。
定价路由配置
支付成功 / 取消后的跳转页面在 packages/app-config/src/app-config.ts 的 web.routes 中配置:
web: {
routes: {
billingSuccess: "/billing/success", // 支付成功后跳转
billingCancel: "/billing/cancel", // 取消支付后跳转
billingReturn: "/settings/billing", // 账单管理门户返回后跳转
},
},客户账单管理门户
EasyStarter 已内置 Creem 客户账单管理门户集成。用户可在 /settings/billing 页面点击管理订阅,系统会自动通过 Creem API 创建管理会话并跳转至 Creem 提供的管理界面。
订阅状态映射
Creem 的订阅状态比 Stripe 更细粒度。以下是 Creem 状态到 EasyStarter 内部状态的映射关系:
| Creem 状态 | 内部状态 | 有权限 | 说明 |
|---|---|---|---|
active | active | 是 | 正常活跃订阅 |
trialing | trialing | 是 | 免费试用期 |
scheduled_cancel | active (cancelAtPeriodEnd) | 是 | 到期前仍可使用 |
paused | paused | 否 | 订阅已暂停 |
past_due | unpaid | 否 | 付款逾期 |
unpaid | unpaid | 否 | 未付款 |
incomplete | unpaid | 否 | 设置未完成 |
failed | unpaid | 否 | 支付失败 |
canceled | canceled | 否 | 订阅已取消 |
expired | canceled | 否 | 订阅已过期 |
扩展其他支付服务商
EasyStarter 的支付层基于 PaymentProvider 接口设计,内置 Stripe 和 Creem 实现。如需替换为其他服务商(如 Paddle、LemonSqueezy 等),只需按以下六步操作,无需改动业务逻辑。
第一步:注册服务商 Key
在 packages/app-config/src/types.ts 中,将新服务商 key 追加到 SUPPORTED_WEB_PAYMENT_PROVIDERS:
export const SUPPORTED_WEB_PAYMENT_PROVIDERS = ["stripe", "creem", "paddle"] as const;这会自动同步更新 WebPaymentProviderKey 和 ServerPaymentProviderKey 类型。
第二步:实现 PaymentProvider 接口
在 apps/server/src/payments/providers/ 下新建目录,实现 PaymentProvider 接口:
import type {
CreateCheckoutInput,
CreatePortalInput,
ParsedWebhookEvent,
PaymentProvider,
WebhookInput,
} from "../../public/types";
export function createPaddlePaymentProvider(): PaymentProvider {
return {
key: "paddle",
async createCheckoutSession(input: CreateCheckoutInput) {
// 调用 Paddle SDK 创建结账会话
// 返回 { providerSessionId, url, expiresAt }
},
async createPortalSession(input: CreatePortalInput) {
// 返回 Paddle 订阅管理页面的 URL
// 返回 { providerSessionId, url }
},
async parseWebhookEvent(input: WebhookInput): Promise<ParsedWebhookEvent> {
// 验证签名,解析 payload
// 返回 { providerEventId, type, createdAt, payload }
},
async setSubscriptionCancelAtPeriodEnd(input) {
// 调用 Paddle API 设置到期取消
},
async updateSubscriptionPrice(input) {
// 调用 Paddle API 升级订阅价格
},
};
}接口方法说明:
| 方法 | 说明 |
|---|---|
createCheckoutSession | 创建结账会话,返回跳转 URL |
createPortalSession | 创建订阅管理门户会话,返回跳转 URL |
parseWebhookEvent | 验证 Webhook 签名并解析事件 |
setSubscriptionCancelAtPeriodEnd | 设置订阅到期时取消 |
updateSubscriptionPrice | 变更订阅价格(用于计划升级) |
第三步:实现 Webhook 事件处理器
在 apps/server/src/payments/providers/paddle/webhook/ 下创建事件处理逻辑,将 Paddle 事件映射到数据库操作:
import type { Database } from "@/db";
export async function handlePaddleEvent(db: Database, payload: unknown) {
const event = payload as { event_type: string; data: unknown };
switch (event.event_type) {
case "subscription.created":
case "subscription.updated":
case "subscription.canceled": {
// 同步订阅状态到 billing_subscription 表
break;
}
case "transaction.completed": {
// 处理一次性购买,写入 billing_purchase 表
break;
}
// 按需处理其他事件...
default:
break;
}
}参考
apps/server/src/payments/providers/stripe/webhook/目录的结构,将复杂事件拆分到独立文件中。
第四步:注册到 Provider 工厂
在 apps/server/src/payments/providers/index.ts 中,将新 Provider 注册进工厂:
import { createPaddlePaymentProvider } from "./paddle/provider";
const providers: Record<ServerPaymentProviderKey, () => PaymentProvider> = {
stripe: createStripePaymentProvider,
creem: createCreemPaymentProvider,
paddle: createPaddlePaymentProvider, // 新增
};第五步:添加 Webhook 路由
在 apps/server/src/index.ts 中,为新服务商添加一个专属 Webhook 路由:
app.post("/api/webhooks/paddle", async (c) => {
const context = await createContext({ context: c });
const rawBody = await c.req.text();
const signature = c.req.header("paddle-signature");
await context.payments.handleWebhookEvent({
provider: "paddle",
rawBody,
signature,
});
return c.json({ received: true });
});服务端的 handleWebhookEvent 会自动将事件路由到对应的 handlePaddleEvent 处理器。
第六步:配置定价计划并切换 Provider
在 packages/app-config/src/app-config.ts 中,将 web.payments.provider 改为新服务商,并填入对应的 Price ID:
web: {
payments: {
provider: "paddle", // 切换到新服务商
plans: [
{
id: "pro",
prices: [
{
id: "monthly",
provider: "paddle",
providerPriceId: "pri_xxxxxxxxxxxxxxxx", // Paddle Price ID
currency: "usd",
amountCents: 1000,
priceType: "subscription",
interval: "month",
status: "active",
},
],
},
],
},
},完成后,所有结账、升级、门户入口都会自动走新服务商,无需改动任何业务代码。
生产上线前检查
| 项目 | 检查点 |
|---|---|
| API 密钥 | 切换为生产密钥 creem_live_ |
| Webhook Secret | 生产 Webhook 端点的签名密钥 |
| Product ID | 使用生产模式下创建的 Product ID |
| Webhook 端点 | 生产 URL 已在 Creem 控制台配置 |
| 环境变量推送 | 通过 pnpm run secrets:bulk:production 推送到 Cloudflare Workers |