Files
moeka-project/apps/server/docs/ai-context/stripe-pricing.md
T

138 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 创建带多币种的 PriceStripe 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"}}` |