feat(payment): 优化套餐配置和支付服务
- 简化套餐配置结构,删除冗余的 default.yaml 和 us.yaml - 优化 Apple IAP 服务和验证逻辑 - 更新套餐数据模型和协议文档 - 添加支付相关测试用例
This commit is contained in:
@@ -230,6 +230,49 @@ Managed by `python -m core.runtime.cli sync-notifications [flags]`:
|
||||
|
||||
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.
|
||||
|
||||
## Points Ledger API
|
||||
|
||||
### GET /api/v1/points/ledger
|
||||
|
||||
Returns the authenticated user's points ledger in reverse chronological order.
|
||||
|
||||
**Request:**
|
||||
- Auth: Required (JWT)
|
||||
- Query:
|
||||
- `limit`: integer, `1..100`, default `20`
|
||||
- `cursor`: optional ISO 8601 datetime returned by the previous response `nextCursor`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "9cfd5d1d-0dd8-4b30-88ce-6e4a63d22d76",
|
||||
"direction": 1,
|
||||
"amount": 60,
|
||||
"balanceAfter": 160,
|
||||
"changeType": "purchase",
|
||||
"createdAt": "2026-04-28T08:30:00+00:00"
|
||||
}
|
||||
],
|
||||
"nextCursor": "2026-04-28T08:30:00+00:00",
|
||||
"hasMore": true
|
||||
}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
- `items`: ledger rows ordered by `createdAt desc`
|
||||
- `direction`: `1` for income, `-1` for spending/deduction
|
||||
- `amount`: positive points delta
|
||||
- `balanceAfter`: account balance after the ledger event
|
||||
- `changeType`: one of `register`, `purchase`, `consume`, `adjust`, `refund`
|
||||
- `createdAt`: ISO 8601 datetime for display and pagination
|
||||
- `nextCursor`: last returned row `createdAt` when `hasMore=true`; otherwise `null`
|
||||
- `hasMore`: whether another page is available
|
||||
|
||||
**Errors:**
|
||||
- `POINTS_INVALID_CURSOR` (`422`): `cursor` is not a valid ISO 8601 datetime
|
||||
|
||||
## Packages API
|
||||
|
||||
### GET /api/v1/points/packages
|
||||
@@ -243,25 +286,21 @@ Returns available purchase packages for the current user's region, including sta
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"region": "US",
|
||||
"currency": "USD",
|
||||
"packages": [
|
||||
{
|
||||
"productCode": "new_user_pack",
|
||||
"appStoreProductId": "com.meeyao.qianwen.new_user_pack",
|
||||
"type": "starter",
|
||||
"price": "0.99",
|
||||
"credits": 60,
|
||||
"badge": null,
|
||||
"isStarter": true,
|
||||
"starterEligible": true,
|
||||
"sortOrder": 0
|
||||
},
|
||||
{
|
||||
"productCode": "basic_pack",
|
||||
"productCode": "starter_pack",
|
||||
"appStoreProductId": "com.meeyao.qianwen.starter_pack",
|
||||
"type": "regular",
|
||||
"price": "4.99",
|
||||
"credits": 100,
|
||||
"badge": null,
|
||||
"isStarter": false,
|
||||
"starterEligible": false,
|
||||
"sortOrder": 10
|
||||
@@ -271,51 +310,38 @@ Returns available purchase packages for the current user's region, including sta
|
||||
```
|
||||
|
||||
**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 (e.g., `new_user_pack`, `basic_pack`, `popular_pack`, `premium_pack`)
|
||||
- `type`: "starter" (new user pack) or "regular"
|
||||
- `price`: Price in the response currency (decimal string, for display reference only; actual payment uses StoreKit price)
|
||||
- `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)
|
||||
- `productCode`: Unique product identifier (e.g., `new_user_pack`, `starter_pack`, `popular_pack`, `premium_pack`)
|
||||
- `appStoreProductId`: Apple App Store product identifier used for StoreKit purchase
|
||||
- `type`: "starter" (new user pack) or "regular"
|
||||
- `credits`: Number of credits
|
||||
- `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`
|
||||
1. Load package mapping from `backend/src/core/config/static/packages/mapping.yaml`
|
||||
2. 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`
|
||||
- Example: `mapping.yaml`
|
||||
|
||||
```yaml
|
||||
region: US
|
||||
currency: USD
|
||||
packages:
|
||||
- product_code: new_user_pack
|
||||
type: starter
|
||||
price: "0.99"
|
||||
product_mappings:
|
||||
new_user_pack:
|
||||
app_store_product_id: com.meeyao.qianwen.new_user_pack
|
||||
credits: 60
|
||||
badge: null
|
||||
type: starter
|
||||
sort_order: 0
|
||||
enabled: true
|
||||
- product_code: basic_pack
|
||||
type: regular
|
||||
price: "4.99"
|
||||
starter_pack:
|
||||
app_store_product_id: com.meeyao.qianwen.starter_pack
|
||||
credits: 100
|
||||
badge: null
|
||||
type: regular
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user