vercel付费方案
Vercel 轻量付费服务方案
架构图
┌──────────┐ ① 输入邮箱 ┌─────────────────┐
│ 前端 │ ───────────────→ │ Vercel Function │
│ (Vercel) │ │ /api/auth/send │
│ │ ←─────────────── │ │
│ │ 验证码已发送 └────────┬────────┘
│ │ │ ② 存 KV
│ │ ③ 输入验证码 ┌────────▼────────┐
│ │ ────────────────→ │ Vercel Function │
│ │ │ /api/auth/verify │
│ │ ←─────────────── │ │
│ │ JWT + isPaid └────────┬────────┘
│ │ │ ④ 查 Stripe
│ │ ⑤ 请求带 JWT ┌────────▼────────┐
│ │ ────────────────→ │ Vercel Function │
│ │ │ /api/protected │
│ │ ←─────────────── │ │
│ │ 付费/免费内容 └─────────────────┘
└──────────┘
核心概念
Vercel
前端云平台,托管静态文件(HTML/JS/CSS)+ Serverless Function。
- 前端文件 → CDN → 浏览器执行,代码公开,不能放 secret key
- Vercel Function(API Routes) → Vercel 服务器执行,不发给浏览器,可安全存放 secret key(
STRIPE_SECRET_KEY、JWT_SECRET等)
Vercel Function(BFF)
服务端无服务器函数,作为前端和后端服务之间的代理层:
前端 → Vercel Function (BFF) → Stripe / KV / Resend
- 无服务器,不需要管 nginx/进程管理
- 按请求付费,没人调用时零成本
- 自动扩缩容
Vercel KV
基于 Upstash Redis 的 key-value 存储:
- 宕机不会丢数据(AOF + 快照持久化)
- 即使 KV 数据丢失,Stripe 永远是源数据源,可从 Stripe 恢复
- 适合存验证码、OAuth 映射、付费状态缓存
技术选型
| 需求 | 方案 | 成本 |
|------|------|------|
| 认证 | 邮箱验证码(无密码、无注册、无 DB) | 零 |
| 会话 | JWT,存 localStorage | 零 |
| 支付 | Stripe | 2.9% + $0.30/笔 |
| 验证码存储 | Vercel KV (Redis),TTL 10min | 免费层 30 万 reads/月 |
| 发邮件 | Resend | 免费 100 封/天 |
| 实时付费状态 | Stripe Webhook → KV 更新(可选) | 零 |
关于 Resend 发邮件
Resend 是"邮件即服务",你不需要自建邮件服务器。只需:
1. 有一个域名
2. 在 Resend 控制台添加两条 DNS TXT 记录(验证域名 + DKIM)
3. 之后即可用 API 发送邮件
Resend 背后托管了 SMTP 服务器、发信 IP 池、退信处理等一切基础设施。
为什么不需要数据库
- 无密码 → 不需要存用户凭证
- 无注册 → 不需要用户表
- 用户身份 = 邮箱(验证码证明拥有权)
- 邮箱 → Stripe customer ID 可存在 KV,或直接通过 Stripe API 按邮箱查询
最简路径(不用 KV、不用 Webhook,每次实时查 Stripe):
请求 → JWT 鉴权(含邮箱)→ 实时查 Stripe subscriptions → 返回结果
代价是每次多 200-500ms 延迟,适合低频场景。
JWT 鉴权流程
1. 用户输入邮箱 → 后端生成 6 位验证码 → Resend 发送邮件 → KV 存储验证码(TTL 10 分钟)
2. 用户输入验证码 → 后端比对 KV 中的验证码 → 调 Stripe 查询该邮箱是否有 active subscription
3. 签发 JWT:{ email, isPaid, stripeCustomerId, iat, exp },有效期 24 小时
4. 后续请求带 JWT → 中间件验证签名和过期时间 → 根据 isPaid 放行或拒绝
JWT vs 每次都查 Stripe
| 方案 | 延迟 | Stripe API 调用 | 场景 |
|------|------|----------------|------|
| JWT(本地方验证) | 零 | 仅在签发时 | 高频操作 |
| 每次都查 Stripe | 200-500ms | 每次请求 | 低频操作 |
Stripe Webhook(可选)
如果要求实时付费状态变化立即生效,监听 Stripe 事件:
| Stripe 事件 | KV 操作 |
|-------------|---------|
| checkout.session.completed | 写入 stripe:status:{email} = true |
| customer.subscription.updated | 更新状态 |
| customer.subscription.deleted | 写入 = false |
中间件在验证 JWT 后额外查 KV 覆盖,实现实时性。
如果不做 webhook,接受 JWT 签发时的快照(最长 24h 延迟),可以完全砍掉此部分。
Vercel 项目结构
my-app/
├── app/
│ ├── api/
│ │ ├── auth/
│ │ │ ├── send/route.ts # 发验证码
│ │ │ └── verify/route.ts # 验证+签发 JWT
│ │ ├── webhooks/
│ │ │ └── stripe/route.ts # Stripe webhook(可选)
│ │ └── protected/
│ │ └── route.ts # 付费用户 API
│ ├── page.tsx # 首页(输入邮箱)
│ └── dashboard/
│ └── page.tsx # 付费仪表盘
├── lib/
│ ├── stripe.ts # Stripe 初始化
│ ├── kv.ts # Vercel KV 初始化
│ └── jwt.ts # JWT 签名/验证
├── middleware.ts # 鉴权中间件
├── .env.local
└── package.json
环境变量
STRIPE_SECRET_KEY=sk_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
JWT_SECRET=your-random-256bit-secret
KV_URL=...
KV_REST_TOKEN=...
RESEND_API_KEY=re_xxx
成本估算
| 服务 | 月费 | 免费层限制 |
|------|------|-----------|
| Vercel Hobby | $0 | 10 万次函数调用/天 |
| Vercel KV | $0 | 30 万 reads + 3 万 writes/月 |
| Resend | $0 | 100 封/天 |
| Stripe | 按交易 | 2.9% + $0.30/笔 |
| 域名 | ~$10-15/年 | 用于邮件 + 网站 |
月成本 ≈ $0(不超免费额度)