支付与积分
支付提供商集成、订阅管理与积分系统配置指南
概览
01MVP 内置了一套完整的支付和积分系统,支持国际和国内主流支付方式。核心逻辑封装在 packages/payment/(支付)和 packages/credits/(积分)两个包中。
- 支持单次购买、订阅(周期性付费)和终身购买三种模式
- 内置 webhook(支付平台在用户付款成功后主动通知你服务器的方式)处理,自动更新订单状态
- 积分系统独立于支付方式,可用任意渠道充值后消费
支持的支付方式
| 支付提供商 | 支付渠道 | 适用场景 |
|---|---|---|
| Stripe | Checkout Session、订阅、Customer Portal | 国际用户、信用卡/借记卡、订阅制产品 |
| 微信支付 | Native(扫码支付)、JSAPI(公众号/小程序内支付) | 中国大陆用户 |
| 支付宝 | PC 网页支付 | 中国大陆用户 |
| PayPal | Orders API v2 | 国际用户、PayPal 余额/银行卡 |
| Waffo/Pancake | SDK Checkout | 快速接入,适合东南亚市场 |
所有提供商都实现了统一的 PaymentProvider 接口,切换提供商只需修改环境变量,业务代码无需改动。
切换支付提供商
通过 PAYMENT_PROVIDER 环境变量选择使用哪个提供商:
# apps/01mvp-web/.env.local
PAYMENT_PROVIDER=stripe # 可选: stripe | waffo当前系统默认支持 stripe 和 waffo 两种模式切换。微信支付、支付宝、PayPal 需要在业务代码中直接实例化对应 Provider 使用。
Stripe 配置
Stripe 是国际用户首选的支付方案,支持一次性付款、订阅和客户自助管理门户。
获取 API 密钥:登录 Stripe Dashboard,在 Developers > API keys 中获取 Secret Key 和 Publishable Key
配置环境变量:
PAYMENT_PROVIDER=stripe
STRIPE_SECRET_KEY=sk_test_xxxx # 测试密钥
STRIPE_WEBHOOK_SECRET=whsec_xxxx # Webhook 签名密钥
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxx
# 价格 ID(在 Stripe Dashboard 的 Products 中创建)
NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY=price_xxxx
NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY=price_xxxx
NEXT_PUBLIC_STRIPE_PRICE_LIFETIME=price_xxxx配置 Webhook 端点:在 Stripe Dashboard > Developers > Webhooks 中添加端点:
https://your-domain.com/api/webhooks/stripe选择监听以下事件:
checkout.session.completed— 用户完成付款customer.subscription.updated— 订阅状态变更customer.subscription.deleted— 订阅取消invoice.payment_succeeded— 续费成功
使用 sk_test_ 和 pk_test_ 开头的密钥。Stripe 提供 测试卡号,如 4242 4242 4242 4242。可用 stripe listen --forward-to 命令将 webhook 转发到本地。
切换到 sk_live_ 和 pk_live_ 开头的密钥。确保 Webhook 端点使用 HTTPS,且 STRIPE_WEBHOOK_SECRET 与 Dashboard 中一致。
Stripe Secret Key 和 Webhook Secret 具有极高权限。绝不能提交到 Git 仓库或暴露在前端代码中。
微信支付配置
微信支付使用 V3 API,支持 Native(扫码)和 JSAPI(公众号/小程序内)两种渠道。
注册商户号:在 微信支付商户平台 注册并通过审核,获取商户号(mchId)
获取 API 密钥:在商户平台的 API 安全中生成 APIv3 密钥,并下载商户私钥证书
配置环境变量:
WECHAT_PAY_APP_ID=wx1234567890 # 公众号/小程序 AppID
WECHAT_PAY_MCH_ID=1234567890 # 商户号
WECHAT_PAY_SERIAL_NO=XXXXXXXXXX # 证书序列号
WECHAT_PAY_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..." # 商户私钥
WECHAT_PAY_API_V3_KEY=your-api-v3-key # API v3 密钥
WECHAT_PAY_NOTIFY_URL=https://your-domain.com/api/webhooks/wechat-pay支付渠道说明
Native 支付:生成二维码图片,用户扫码完成支付。适合 PC 网站场景。
JSAPI 支付:在微信公众号或小程序内直接调起支付。需要获取用户的 openid。适合微信生态内的 H5 页面。
微信支付私钥是敏感文件。建议通过环境变量注入,不要将 .pem 文件放在项目目录中。
支付宝配置
支付宝支持 PC 网页支付,通过统一收单接口完成。
注册开发者:在 支付宝开放平台 创建应用并完成签约
配置密钥:生成 RSA2 密钥对,将公钥上传到支付宝开放平台,私钥配置到项目中
配置环境变量:
ALIPAY_APP_ID=your-alipay-app-id
ALIPAY_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n..."
ALIPAY_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..." # 支付宝公钥
ALIPAY_NOTIFY_URL=https://your-domain.com/api/webhooks/alipay支付宝的 AlipayProvider 需要直接实例化传入配置,不支持通过 createPaymentProvider("alipay") 工厂方法创建。
PayPal 配置
PayPal 使用 Orders API v2,支持 OAuth 2.0 认证。
创建应用:登录 PayPal Developer,在 Dashboard 创建 REST API 应用
获取凭证:获取 Client ID 和 Client Secret
配置环境变量:
PAYPAL_CLIENT_ID=your-client-id
PAYPAL_CLIENT_SECRET=your-client-secret
PAYPAL_WEBHOOK_ID=your-webhook-id # Webhook ID(在应用设置中创建)
PAYPAL_MODE=sandbox # sandbox | live积分系统
积分系统独立于支付方式,适用于按次付费的场景(如 AI 生成次数、导出次数等)。
CreditService API
积分服务位于 packages/credits/,提供以下方法:
| 方法 | 说明 | 返回值 |
|---|---|---|
getBalance(userId) | 查询用户当前积分余额 | number |
addCredits(params) | 增加积分(购买、奖励等) | 交易记录 |
consumeCredits(params) | 消耗积分(使用功能时扣费) | ConsumeCreditsResult |
hasEnoughCredits(userId, amount) | 检查余额是否充足 | boolean |
getStatus(userId) | 获取余额及收支汇总 | { balance, totalPurchased, totalConsumed } |
getTransactions(userId, options) | 查询交易历史记录 | 交易记录数组 |
交易类型
积分交易支持以下类型:purchase(购买)、consumption(消费)、refund(退款)、bonus(赠送)、adjustment(手动调整)。
使用示例
在 API 路由中使用积分:
import { CreditService } from "@01mvp/credits";
const creditService = new CreditService(prisma);
// 检查余额
const hasEnough = await creditService.hasEnoughCredits(userId, 10);
if (!hasEnough) {
return Response.json({ error: "积分不足" }, { status: 402 });
}
// 扣除积分
const result = await creditService.consumeCredits({
userId,
amount: 10,
description: "AI 图片生成",
metadata: { feature: "image-generation" },
});
if (!result.success) {
return Response.json({ error: result.error }, { status: 402 });
}购买成功后添加积分:
// 在 Webhook 处理中
await creditService.addCredits({
userId,
amount: 100,
type: "purchase",
orderId: "order_xxx",
description: "购买 100 积分套餐",
});所有积分操作都在数据库事务中执行,保证余额计算的原子性和一致性。用户余额存储在 User.creditBalance 字段中。
订阅管理
订阅(Subscription)是一种周期性付费模式,用户按月或按年自动续费。
订阅生命周期
- 创建订阅 — 用户选择计划,跳转到支付页面完成首次付款
- 生效中(active) — 订阅有效,用户可正常使用付费功能
- 试用期(trialing) — 可选,设置试用天数,试用期内免费
- 续费 — 到期时自动扣款,
invoice.payment_succeeded事件触发 - 到期未续(past_due) — 续费失败,Stripe 会自动重试
- 取消 — 用户主动取消或到期不续,状态变为
canceled - 过期(expired) — 当前付费周期结束,功能降级
订阅工具函数
@01mvp/payment 提供了一组数据库无关的工具函数:
| 函数 | 说明 |
|---|---|
isSubscriptionValid(subscription) | 检查订阅是否有效(active 或 trialing 且未过期) |
isLifetimeSubscription(subscription) | 检查是否为终身订阅 |
isSubscriptionExpired(subscription) | 检查订阅是否已过期 |
getSubscriptionDisplayStatus(subscription) | 获取可读的状态文案 |
checkSubscription(subscription) | 综合检查,返回 { hasSubscription, isLifetime, subscription } |
路由保护
在页面或 API 路由中使用 createSubscriptionGuard 限制付费用户访问:
import { checkSubscription, createSubscriptionGuard } from "@01mvp/payment";
// 在 API 路由中
const subscription = await db.subscription.findFirst({
where: { userId: user.id },
});
const guard = createSubscriptionGuard(subscription, {
redirectUrl: "/pricing",
});
if (guard) {
return NextResponse.redirect(new URL(guard.redirectUrl, request.url));
}