138 lines
4.8 KiB
Markdown
138 lines
4.8 KiB
Markdown
# Stripe Pricing Architecture
|
||
|
||
## 设计决策
|
||
|
||
Stripe 是 Flux 充值定价的**单一真相源**。服务端不再在 Redis 维护 `FLUX_PACKAGES`,所有 package 信息直接从 Stripe API 获取。
|
||
|
||
### 为什么不用 Redis 维护 packages
|
||
|
||
之前的设计在 Redis ConfigKV 中维护 `FLUX_PACKAGES`(含 amount、label、price 等),导致:
|
||
|
||
- 价格信息在 Stripe 和 Redis 之间重复维护
|
||
- currency 硬编码为 USD,无法支持微信支付(需要 CNY/GBP)
|
||
- 新增/修改 package 需要同时改 Stripe 和 Redis
|
||
|
||
现在只需在 Stripe Dashboard 操作 Product/Price,服务端自动同步。
|
||
|
||
## 数据模型
|
||
|
||
### Stripe 侧
|
||
|
||
- **Product** — 代表 "Flux 充值" 这个商品(一个即可)
|
||
- **Price** — 代表一个具体的价格方案,每个 Price 包含:
|
||
- `unit_amount` + `currency`(如 300 USD = $3)
|
||
- `currency_options`(可选)— 支持多币种展示,如 `cny: { unit_amount: 2200 }`
|
||
- `metadata.fluxAmount` — 购买此 Price 获得的 Flux 数量
|
||
- `metadata.recommended` — (可选)设为 `'true'` 时前端会高亮展示为推荐套餐
|
||
|
||
Product ID 存储在 ConfigKV `STRIPE_FLUX_PRODUCT_ID` 中(运营配置,非环境变量)。
|
||
|
||
### 多币种支持
|
||
|
||
通过 Stripe Price 的 `currency_options` 实现。一个 USD Price 可以同时支持 CNY 结算:
|
||
|
||
- 前端展示所有可用货币(从 `currency_options` 自动提取),用户通过 SelectTab 切换
|
||
- 前端 checkout 时传 `{ stripePriceId, currency }` 给服务端
|
||
- 服务端在 Checkout Session 上设 `currency` 参数,Stripe 自动用对应 `currency_options` 的金额
|
||
- Stripe Checkout 页面根据货币自动展示兼容的支付方式(如 CNY → 微信支付)
|
||
|
||
**创建带多币种的 Price:**
|
||
|
||
```bash
|
||
curl https://api.stripe.com/v1/prices \
|
||
-u "$STRIPE_API_KEY:" \
|
||
-d "product=prod_xxx" \
|
||
-d "unit_amount=300" \
|
||
-d "currency=usd" \
|
||
-d "metadata[fluxAmount]=500" \
|
||
-d "currency_options[cny][unit_amount]=2200"
|
||
```
|
||
|
||
> 注意:Stripe CLI 的 `prices create` 对嵌套参数支持不好,`currency_options` 需要用 `curl` 直接调 API。
|
||
|
||
### 支付方式
|
||
|
||
`STRIPE_PAYMENT_METHODS` 在 ConfigKV 中为可选配置:
|
||
|
||
- **未设置(推荐)**:不传 `payment_method_types`,Stripe 根据 Dashboard 设置和货币自动决定
|
||
- **已设置**:覆盖 Stripe 自动选择,如 `["card", "wechat_pay", "alipay"]`
|
||
|
||
如果手动指定了 `wechat_pay`,还需设 `STRIPE_PAYMENT_METHOD_OPTIONS` 为 `{"wechat_pay":{"client":"web"}}`。
|
||
|
||
## 缓存
|
||
|
||
Stripe Price 列表通过 Redis 缓存(key: `cache:stripe:prices`,TTL 5 分钟),所有实例共享。
|
||
|
||
- 命中:直接返回缓存的 Price 列表
|
||
- 未命中:调 Stripe API `prices.list` (含 `expand: ['data.currency_options']`),按 `unit_amount` 升序排列后写入缓存
|
||
- Checkout 时如果 priceId 不在缓存中,fallback 到 `prices.retrieve` 并 invalidate 缓存
|
||
|
||
## API 流程
|
||
|
||
### GET /api/v1/stripe/packages
|
||
|
||
1. 从 ConfigKV 读取 `STRIPE_FLUX_PRODUCT_ID`
|
||
2. 从 Redis 缓存或 Stripe API 获取 active prices
|
||
3. 返回每个 price 的所有可用货币价格:
|
||
```json
|
||
{
|
||
"stripePriceId": "price_xxx",
|
||
"label": "500 Flux",
|
||
"defaultCurrency": "usd",
|
||
"currencies": { "usd": "$3.00", "cny": "¥22.00" },
|
||
"recommended": false
|
||
}
|
||
```
|
||
|
||
### POST /api/v1/stripe/checkout
|
||
|
||
1. 前端发送 `{ stripePriceId, currency? }`
|
||
2. 服务端验证 price 归属和 `fluxAmount` metadata
|
||
3. 创建 Checkout Session:
|
||
- `currency` 参数(如有)让 Stripe 用 `currency_options` 中的金额
|
||
- `payment_method_types` 根据 ConfigKV 是否配置决定传或不传
|
||
4. Webhook 收到 `checkout.session.completed` 后从 metadata 读取 fluxAmount 充值
|
||
|
||
## 运营操作
|
||
|
||
### 新增价格
|
||
|
||
用 curl 创建带多币种的 Price(Stripe CLI 不支持嵌套参数):
|
||
|
||
```bash
|
||
curl https://api.stripe.com/v1/prices \
|
||
-u "$STRIPE_API_KEY:" \
|
||
-d "product=prod_xxx" \
|
||
-d "unit_amount=1200" \
|
||
-d "currency=usd" \
|
||
-d "metadata[fluxAmount]=2000" \
|
||
-d "metadata[recommended]=true" \
|
||
-d "currency_options[cny][unit_amount]=8800"
|
||
```
|
||
|
||
无需修改代码或 Redis,`/packages` 端点在缓存过期后自动返回新 Price。
|
||
|
||
### 下架价格
|
||
|
||
在 Stripe Dashboard 将 Price 设为 inactive,缓存过期后 `/packages` 自动不再返回。
|
||
|
||
### 修改 Product ID
|
||
|
||
```bash
|
||
redis-cli SET "config:STRIPE_FLUX_PRODUCT_ID" '"prod_new_id"'
|
||
```
|
||
|
||
### 手动清缓存(立即生效)
|
||
|
||
```bash
|
||
redis-cli DEL "cache:stripe:prices"
|
||
```
|
||
|
||
## ConfigKV 配置清单
|
||
|
||
| Key | 类型 | 默认 | 说明 |
|
||
|-----|------|------|------|
|
||
| `STRIPE_FLUX_PRODUCT_ID` | `string?` | 无 | Stripe Product ID,未设置时 top-up 不可用 |
|
||
| `STRIPE_PAYMENT_METHODS` | `string[]?` | 无 | 不设则 Stripe 自动决定;设了则覆盖 |
|
||
| `STRIPE_PAYMENT_METHOD_OPTIONS` | `Record?` | `{}` | 支付方式选项,如 `{"wechat_pay":{"client":"web"}}` |
|