feat: add recharge create and status APIs
This commit is contained in:
+39
-2
@@ -25,6 +25,8 @@ T-303 已实现余额查询基线:`GET /api/v1/balance` 已接入 API Key 鉴
|
||||
|
||||
T-304 已实现充值回调基线:`POST /api/v1/recharge/callback/wechat` 与 `/alipay` 已 `@csrf_exempt`,回调先验签(开发/测试可用明确 HMAC mock,生产 `PAYMENT_CALLBACK_MODE=sdk` 走支付 SDK),再按 `order_no` 锁定 `RechargeOrder` 幂等入账;金额或支付方式不一致不加点,重复回调不重复写充值流水。
|
||||
|
||||
T-305 已实现扫码充值下单与轮询基线:`POST /api/v1/recharge/create` 与 `GET /api/v1/recharge/status` 走用户端 `SessionAuthentication + CSRF`,不接受 API Key;下单创建 pending 订单并锁定汇率/点数,再返回微信 `code_url` 或支付宝 `qr_code`;状态查询只允许订单所属用户访问,并在 pending 时尝试主动查单补入账,查单不可用时保持 pending 等回调。
|
||||
|
||||
通用错误响应:
|
||||
|
||||
```json
|
||||
@@ -49,6 +51,9 @@ T-304 已实现充值回调基线:`POST /api/v1/recharge/callback/wechat` 与
|
||||
| `upstream_error` | 上游 AI 失败(已退点) | 502 |
|
||||
| `signature_invalid` | 支付回调验签失败 | 400 |
|
||||
| `amount_mismatch` | 支付回调金额与本地订单金额不一致 | 400 |
|
||||
| `no_exchange_rate` | 未配置当前币种汇率,无法创建充值订单 | 400 |
|
||||
| `payment_order_create_failed` | 支付平台下单失败 | 502 |
|
||||
| `order_not_found` | 充值订单不存在或不属于当前用户 | 404 |
|
||||
|
||||
## 对外接口
|
||||
|
||||
@@ -132,6 +137,7 @@ T-202 后,计费计算已有独立模块;T-203 后,扣点/退点也收敛
|
||||
calculate_points_cost(operation_type: str, alias: str, resolution: str | None = None) -> int
|
||||
quote_recharge_points(amount, currency: str = "CNY", at=None) -> RechargeQuote
|
||||
calculate_points_granted(amount, currency: str = "CNY", at=None) -> int
|
||||
create_recharge_order(..., user, amount, pay_method: str, currency: str = "CNY") -> RechargeOrder
|
||||
precharge_call(..., user, points_cost: int, operation_type: str, alias: str, ...) -> CallCharge
|
||||
mark_call_success(call_record: CallRecord, ...) -> CallRecord
|
||||
refund_call_points(call_record: CallRecord, ...) -> RefundResult
|
||||
@@ -146,6 +152,7 @@ query_and_apply_recharge_payment(order_no: str, query_func) -> RechargeResult
|
||||
- 缺计费规则抛 `NoPricingRuleError(code="no_pricing_rule")`,API 层应翻译为上方同名错误码。
|
||||
- `ExchangeRate` 按 `currency + effective_from` 取当前 active 汇率;充值下单时应锁定当时的 `exchange_rate` / `points_granted` 到订单,回调入账不得按新汇率重算。
|
||||
- 金额换点数采用 `floor(amount * points_per_unit)`,点数为整数。
|
||||
- `create_recharge_order()` 创建 `RechargeOrder(status=pending)` 并绑定发起用户;写入订单创建时的金额、币种、汇率、预计点数,再调用支付网关下单取二维码票据并回填 `code_url` / `expires_at`;支付平台下单失败时订单标记 `failed`。
|
||||
- `precharge_call()` 使用事务 + `select_for_update()` 锁 `UserWallet` 行;余额不足抛 `InsufficientPointsError(code="insufficient_points")`,不创建 `CallRecord`、不写 `PointsLedger`、不调上游。
|
||||
- 预扣成功后写 `CallRecord(status=pending)` 与 `PointsLedger(change_type=consume, points_delta=-N)`;上游成功只更新调用记录,余额不再变化。
|
||||
- 上游失败调用 `refund_call_points()`:同一 `CallRecord` 只写一条 `refund` 流水,重复调用不会重复加点;成功调用不能走失败退点。
|
||||
@@ -197,11 +204,15 @@ query_and_apply_recharge_payment(order_no: str, query_func) -> RechargeResult
|
||||
{
|
||||
"order_no": "T202606291230001234",
|
||||
"amount": "100.00",
|
||||
"exchange_rate": "10.00",
|
||||
"currency": "CNY",
|
||||
"exchange_rate": "10.0000",
|
||||
"points_granted": 1000,
|
||||
"pay_method": "weixin",
|
||||
"status": "pending",
|
||||
"code_url": "weixin://wxpay/bizpayurl?pr=abc123", // 微信 native 返回;支付宝为 qr_code(https://qr.alipay.com/...)
|
||||
"expires_at": "2026-06-29T12:10:00Z" // 二维码有效期
|
||||
"expires_at": "2026-06-29T12:10:00Z", // 二维码有效期
|
||||
"paid_at": null,
|
||||
"is_expired": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -209,6 +220,32 @@ query_and_apply_recharge_payment(order_no: str, query_func) -> RechargeResult
|
||||
- `exchange_rate` 与 `points_granted` 以订单创建时的配置为准;回调入账使用订单值,不因后台后续改汇率而变化。
|
||||
- 微信走 `pay/transactions/native` 取 `code_url`;支付宝走 `trade.precreate` 取 `qr_code`;前端用 qrcode.js 渲染。
|
||||
- 走 **Web session** 鉴权(用户端流程),不同于对外 API Key;订单绑定发起用户,防充错账户。
|
||||
- 开发 / 测试 `PAYMENT_CALLBACK_MODE=mock` 时返回 mock 二维码票据;生产应使用 `sdk` 模式与真实商户配置。
|
||||
|
||||
### `GET /api/v1/recharge/status?order_no=...`
|
||||
|
||||
由**已登录用户**轮询充值订单状态。只返回当前登录用户自己的订单;不属于当前用户的订单按不存在处理。
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"order_no": "T202606291230001234",
|
||||
"amount": "100.00",
|
||||
"currency": "CNY",
|
||||
"exchange_rate": "10.0000",
|
||||
"points_granted": 1000,
|
||||
"pay_method": "weixin",
|
||||
"status": "paid",
|
||||
"code_url": "weixin://wxpay/bizpayurl?pr=abc123",
|
||||
"expires_at": "2026-06-29T12:10:00Z",
|
||||
"paid_at": "2026-06-29T12:05:00Z",
|
||||
"is_expired": false
|
||||
}
|
||||
```
|
||||
|
||||
- 若订单仍为 `pending`,服务端会尝试主动查单并复用 `query_and_apply_recharge_payment()` 补入账;查单不可用或尚未支付时仍返回当前本地状态。
|
||||
- 到账以服务端回调或主动查单后的本地订单状态为准;`is_expired` 仅是二维码本地有效期提示,不自动阻断延迟到达的真实支付回调。
|
||||
|
||||
## AI 调用模块合约(`apps/ai`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user