feat: 新人初始礼包购买追踪功能

- 数据库:添加 has_purchased_starter_pack 字段到 register_bonus_claims
- 后端:创建静态配置管理套餐信息,支持按国家/地区区分
- 后端:新增 GET /api/v1/points/packages API 返回可用套餐
- 后端:创建 utils/paths.py 统一路径管理
- 前端:动态获取套餐信息,移除硬编码
- 前端:添加 ProductCode 枚举约束,前后端类型安全
- 配置:Profile 默认国家改为 US(ISO 3166-1 alpha-2)
- 文档:更新协议文档说明新 API 和字段
This commit is contained in:
qzl
2026-04-16 16:11:09 +08:00
parent 443c0c80ae
commit ff40ff9dd8
38 changed files with 1434 additions and 2517 deletions
@@ -98,7 +98,7 @@ Protocol verification status:
### register_bonus_claims
- PK: `id`
- Core fields: `email_hash`, `user_email_snapshot`, `first_user_id_snapshot`, `balance_snapshot`, `grant_event_id`, `created_at`, `updated_at`
- Core fields: `email_hash`, `user_email_snapshot`, `first_user_id_snapshot`, `balance_snapshot`, `grant_event_id`, `has_purchased_starter_pack`, `created_at`, `updated_at`
- Constraints:
- `email_hash` unique
- `grant_event_id` unique
@@ -106,6 +106,7 @@ Protocol verification status:
- `email_hash` must be HMAC-SHA256 over normalized email (`trim + lower`)
- key source: backend config `points_policy.register_bonus_hmac_key`
- `balance_snapshot` stores the latest pre-delete account balance for same-email re-registration recovery
- `has_purchased_starter_pack` tracks whether user has purchased the starter pack ($0.99/60 credits)
#### points_ledger.metadata (schema_version=1)
@@ -206,3 +207,93 @@ Managed by `python -m core.runtime.cli sync-notifications [flags]`:
- `--source-key <key>` — sync only the notification with the matching `source_key`
Run after migrations on fresh environments or after adding new notification YAML definitions. Not included in `bootstrap` to keep bootstrap fast and free of unintended side effects.
## Packages API
### GET /api/v1/points/packages
Returns available purchase packages for the current user's region, including starter pack eligibility.
**Request:**
- Auth: Required (JWT)
- Headers: `Authorization: Bearer <token>`
**Response:**
```json
{
"region": "US",
"currency": "USD",
"packages": [
{
"productCode": "new_user_pack_099_60",
"type": "starter",
"priceUsd": "0.99",
"credits": 60,
"badge": null,
"isStarter": true,
"starterEligible": true,
"sortOrder": 0
},
{
"productCode": "basic_pack_499_100",
"type": "regular",
"priceUsd": "4.99",
"credits": 100,
"badge": null,
"isStarter": false,
"starterEligible": false,
"sortOrder": 10
}
]
}
```
**Fields:**
- `region`: ISO 3166-1 alpha-2 country code (e.g., "US", "CN")
- `currency`: ISO 4217 currency code (e.g., "USD")
- `packages`: List of available packages
- `productCode`: Unique product identifier
- `type`: "starter" (new user pack) or "regular"
- `priceUsd`: Price in USD (decimal string)
- `credits`: Number of credits
- `badge`: Optional badge text (e.g., "Popular")
- `isStarter`: Whether this is a starter pack
- `starterEligible`: Whether user is eligible to purchase starter pack
- `sortOrder`: Display order (ascending)
**Business Logic:**
1. Determine user's region from `profile.settings.preferences.country` (default: "US")
2. Load package configuration from `backend/src/core/config/static/packages/{country}.yaml` (fallback to `default.yaml`)
3. Check starter pack eligibility:
- If `register_bonus_claims.has_purchased_starter_pack = true`, exclude starter pack from response
- Otherwise, include starter pack with `starterEligible: true`
**Configuration Files:**
- Path: `backend/src/core/config/static/packages/`
- Format: YAML
- Example: `us.yaml`
```yaml
region: US
currency: USD
packages:
- product_code: new_user_pack_099_60
type: starter
price_usd: "0.99"
credits: 60
badge: null
sort_order: 0
enabled: true
- product_code: basic_pack_499_100
type: regular
price_usd: "4.99"
credits: 100
badge: null
sort_order: 10
enabled: true
```
**Country/Region Codes:**
- Uses ISO 3166-1 alpha-2 standard
- Default: `US` (United States)
- Examples: `CN` (China), `TW` (Taiwan), `HK` (Hong Kong), `JP` (Japan)