diff --git a/docs/api/README.md b/docs/api/README.md index 89556a2..5ae09fd 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -22,9 +22,36 @@ | 9 | `POST /api/v1/meituan/coupons` | 无 | [详情](./meituan-coupons.md) | | 10 | `POST /api/v1/meituan/feed` | 无 | [详情](./meituan-feed.md) | | 11 | `POST /api/v1/meituan/referral-link` | 无 | [详情](./meituan-referral-link.md) | +| **钱包 / 我的资产**(前缀 `/api/v1/wallet`) ||| +| 12 | `GET /api/v1/wallet/account` | Bearer | [详情](./wallet-account.md) | +| 13 | `GET /api/v1/wallet/coin-transactions` | Bearer | [详情](./wallet-coin-transactions.md) | +| 14 | `GET /api/v1/wallet/cash-transactions` | Bearer | [详情](./wallet-cash-transactions.md) | +| 15 | `GET /api/v1/wallet/exchange-info` | 无 | [详情](./wallet-exchange-info.md) | +| 16 | `POST /api/v1/wallet/exchange` | Bearer | [详情](./wallet-exchange.md) | +| 17 | `POST /api/v1/wallet/bind-wechat` | Bearer | [详情](./wallet-bind-wechat.md) | +| 18 | `POST /api/v1/wallet/unbind-wechat` | Bearer | [详情](./wallet-unbind-wechat.md) | +| 19 | `GET /api/v1/wallet/withdraw-info` | Bearer | [详情](./wallet-withdraw-info.md) | +| 20 | `POST /api/v1/wallet/withdraw` | Bearer | [详情](./wallet-withdraw.md) | +| 21 | `GET /api/v1/wallet/withdraw/status` | Bearer | [详情](./wallet-withdraw-status.md) | +| 22 | `GET /api/v1/wallet/withdraw-orders` | Bearer | [详情](./wallet-withdraw-orders.md) | +| **签到**(前缀 `/api/v1/signin`) ||| +| 23 | `GET /api/v1/signin/status` | Bearer | [详情](./signin-status.md) | +| 24 | `POST /api/v1/signin` | Bearer | [详情](./signin-do.md) | +| **任务**(前缀 `/api/v1/tasks`) ||| +| 25 | `GET /api/v1/tasks` | Bearer | [详情](./tasks-list.md) | +| 26 | `POST /api/v1/tasks/{task_key}/claim` | Bearer | [详情](./tasks-claim.md) | +| **省钱**(前缀 `/api/v1/savings`) ||| +| 27 | `GET /api/v1/savings/summary` | Bearer | [详情](./savings-summary.md) | +| 28 | `GET /api/v1/savings/battle` | Bearer | [详情](./savings-battle.md) | +| 29 | `GET /api/v1/savings/records` | Bearer | [详情](./savings-records.md) | +| **看广告发奖**(前缀 `/api/v1/ad`) ||| +| 30 | `GET /api/v1/ad/pangle-callback` | 验签 | [详情](./ad-pangle-callback.md) | +| 31 | `GET /api/v1/ad/reward-status` | Bearer | [详情](./ad-reward-status.md) | +| 32 | `POST /api/v1/ad/test-grant` | Bearer | [详情](./ad-test-grant.md) | > ⚠️ 美团三个接口当前**无鉴权**,且 `referral-link` 的 `sid` 允许客户端传值覆盖默认渠道——见各接口"备注"。 -> `coupon/step` 是后端**唯一需要 JWT 鉴权的业务接口**。 +> 福利相关业务接口(wallet/signin/tasks/savings、`ad/reward-status`)均需 **Bearer**;`wallet/exchange-info` 是静态规则无鉴权;`ad/pangle-callback` 不走 JWT、靠穿山甲**验签**;`ad/test-grant` **仅本地联调**(开关控制,生产 404)。 +> 金额字段一律以**分**为单位(`*_cents`)。 --- @@ -40,6 +67,13 @@ - `502` 上游调用失败(极光验证/解密失败、美团接口失败) - **接口文档(交互式)**:`APP_ENV` 非 prod 时开放 `GET /docs`(Swagger UI)、`GET /redoc`;生产环境关闭。 +### 游标分页约定 +钱包流水 / 提现单 / 省钱明细等列表接口统一用**游标分页**: + +- 请求 query:`limit`(每页条数,1–100,默认 20)、`cursor`(上一页返回的 `next_cursor`,首页不传)。 +- 响应:`{ "items": [...], "next_cursor": }`。`next_cursor` 为**末条记录的 id**;为 `null` 表示已到底,无更多数据。 +- `items` 按 `id` 倒序(最新在前)。 + --- ## 复用数据结构 diff --git a/docs/api/ad-pangle-callback.md b/docs/api/ad-pangle-callback.md new file mode 100644 index 0000000..8e4362a --- /dev/null +++ b/docs/api/ad-pangle-callback.md @@ -0,0 +1,28 @@ +# GET /api/v1/ad/pangle-callback — 穿山甲激励视频发奖回调(S2S) + +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:**无 JWT,靠验签**(穿山甲服务器调用) | 限流:同 IP ≤300 次/分 | [← 返回 API 索引](./README.md) + +## 入参(query,由穿山甲拼装) +穿山甲在激励视频播完后回调,带一组 query 参数,关键: + +| 字段 | 类型 | 说明 | +|---|---|---| +| `user_id` | string | 客户端起播时传入的用户标识(须为数字 = 本系统 user.id) | +| `trans_id` | string | 穿山甲交易号(**幂等键**) | +| `sign` | string | 穿山甲签名(用 `PANGLE_REWARD_SECRET` 验) | +| `reward_name` | string | 奖励名(可选,入库备注) | +| … | | 其余参与签名的参数 | + +## 出参 +响应 `200`:`PangleCallbackOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `is_valid` | bool | `true`=已受理(发奖成功 或 当日达上限 或 user 非法,均不让穿山甲重试) | + +## 错误码 +- `403` 验签失败(`bad sign`,留给真请求重试) +- `503` 回调未配置(`pangle_callback_configured=false`) + +## 说明 +**发奖唯一可信入口**:验签 → 取 `user_id`/`trans_id` → 幂等发金币(按 `trans_id` 去重 + 每日上限)。客户端不参与发奖,被破解也刷不到钱。验签过但参数缺/坏或 user 不存在 → 受理但不发(`is_valid` 仍据情况返回,避免无意义重试)。 diff --git a/docs/api/ad-reward-status.md b/docs/api/ad-reward-status.md new file mode 100644 index 0000000..7cd7e2d --- /dev/null +++ b/docs/api/ad-reward-status.md @@ -0,0 +1,19 @@ +# GET /api/v1/ad/reward-status — 今日看广告发奖进度 + +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`AdRewardStatusOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `used_today` | int | 今日已成功发奖次数 | +| `daily_limit` | int | 每日发奖次数上限 | +| `remaining` | int | 今日剩余可领次数 | +| `coin_per_ad` | int | 看完一个激励视频发的金币 | + +## 说明 +福利页「看视频赚金币」用:展示「今日还能看 N 次」「看一次得 M 金币」。真正发奖走 [ad-pangle-callback](./ad-pangle-callback.md)(穿山甲 S2S)。 diff --git a/docs/api/ad-test-grant.md b/docs/api/ad-test-grant.md new file mode 100644 index 0000000..8c45b53 --- /dev/null +++ b/docs/api/ad-test-grant.md @@ -0,0 +1,27 @@ +# POST /api/v1/ad/test-grant — [仅本地联调]模拟穿山甲回调发奖 + +> 所属:Ad 组(前缀 `/api/v1/ad`) | 鉴权:Bearer | 限流:同 IP ≤60 次/分 | [← 返回 API 索引](./README.md) +> +> ⚠️ **仅本地联调**,受 `AD_REWARD_TEST_GRANT_ENABLED` 开关控制,**生产必须关闭**(默认 False → 一律 404)。 + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`TestGrantOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `granted` | bool | 本次是否真发了金币(达每日上限则 false) | +| `status` | string | `granted` / `capped`(达上限) | +| `coin` | int | 本次发放金币(capped 时 0) | +| `used_today` | int | 今日已成功发奖次数 | +| `daily_limit` | int | 每日发奖次数上限 | +| `remaining` | int | 今日剩余可领次数 | +| `coin_per_ad` | int | 看完一个激励视频发的金币 | + +## 错误码 +- `404` 开关未开(伪装不存在) / 用户不存在 + +## 说明 +没公网、穿山甲 S2S 回调打不到本地时,debug 客户端看完广告后调它,直接走与 [ad-pangle-callback](./ad-pangle-callback.md) 相同的发奖逻辑(每次新 `trans_id`,幂等 + 每日上限)。它让已登录客户端能自助发奖 = 绕过反作弊,**严禁在生产开启**。 diff --git a/docs/api/savings-battle.md b/docs/api/savings-battle.md new file mode 100644 index 0000000..7bd982e --- /dev/null +++ b/docs/api/savings-battle.md @@ -0,0 +1,18 @@ +# GET /api/v1/savings/battle — 省钱战绩 + +> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`SavingsBattleOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `week_saved_cents` | int | 本周已省(分) | +| `beat_percent` | int | 超过百分之多少用户 | +| `streak_days` | int | 连续省钱天数 | + +## 说明 +「我的」页「省钱战绩」卡数据源。接口已通但无真实业务写入,实际多为 0。 diff --git a/docs/api/savings-records.md b/docs/api/savings-records.md new file mode 100644 index 0000000..31fcf6c --- /dev/null +++ b/docs/api/savings-records.md @@ -0,0 +1,29 @@ +# GET /api/v1/savings/records — 省钱明细(游标分页) + +> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参(query) + +| 字段 | 类型 | 必填 | 默认 | 说明 | +|---|---|---|---|---| +| `limit` | int | ❌ | 20 | 1–100 | +| `cursor` | int | ❌ | null | 上一页末条 `id`,首页不传 | + +## 出参 +响应 `200`:`{ items: SavingsRecordOut[], next_cursor: int|null }`(分页见 [索引#游标分页约定](./README.md#游标分页约定)) + +**SavingsRecordOut** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | int | 记录 id(也是游标) | +| `order_amount_cents` | int | 订单金额(分) | +| `saved_amount_cents` | int | 本单省下(分) | +| `platform` | string \| null | 平台(美团/饿了么等) | +| `title` | string \| null | 标题 | +| `shop_name` | string \| null | 店铺名 | +| `dishes` | string[] | 菜品列表 | +| `created_at` | datetime | 时间 | + +## 说明 +订单/省钱明细页(`RECORDS`)数据源。 diff --git a/docs/api/savings-summary.md b/docs/api/savings-summary.md new file mode 100644 index 0000000..3082a28 --- /dev/null +++ b/docs/api/savings-summary.md @@ -0,0 +1,18 @@ +# GET /api/v1/savings/summary — 累计帮你省了 + +> 所属:Savings 组(前缀 `/api/v1/savings`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`SavingsSummaryOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `total_saved_cents` | int | 累计省下(分) | +| `order_count` | int | 累计省钱订单数 | +| `avg_saved_cents` | int | 平均每单省(分) | + +## 说明 +「我的」页「累计帮你省了」卡数据源。当前比价上报未接入,实际多为 0。 diff --git a/docs/api/signin-do.md b/docs/api/signin-do.md new file mode 100644 index 0000000..513ba60 --- /dev/null +++ b/docs/api/signin-do.md @@ -0,0 +1,22 @@ +# POST /api/v1/signin — 执行今日签到 + +> 所属:Signin 组(前缀 `/api/v1/signin`,本接口 POST 到前缀本身) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`SigninResultOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `coin_awarded` | int | 本次签到发放金币 | +| `cycle_day` | int | 本次签到落在循环第几天 | +| `streak` | int | 签到后的连续天数 | +| `coin_balance` | int | 签到后金币余额 | + +## 错误码 +- `409` 今日已签(`already signed today`) + +## 说明 +发金币已计入 [wallet-account](./wallet-account.md) 的余额(客户端就地刷新即可,不必另叠)。 diff --git a/docs/api/signin-status.md b/docs/api/signin-status.md new file mode 100644 index 0000000..8e09a54 --- /dev/null +++ b/docs/api/signin-status.md @@ -0,0 +1,29 @@ +# GET /api/v1/signin/status — 今日签到状态 + 7 天档位 + +> 所属:Signin 组(前缀 `/api/v1/signin`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`SigninStatusOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `today_signed` | bool | 今日是否已签 | +| `consecutive_days` | int | 当前连续签到天数 | +| `today_cycle_day` | int | 今日处于循环内第几天(1..7) | +| `today_coin` | int | 今日签到可得金币 | +| `can_claim` | bool | 今日是否可领(= 未签) | +| `steps` | SigninStepOut[] | 7 天档位 | + +**SigninStepOut** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `day` | int | 循环内第几天 1..7 | +| `coin` | int | 该档金币 | +| `status` | string | `claimed`(已领) / `today`(今日待领) / `locked`(未到) | + +## 说明 +福利页签到行 + 签到弹窗 7 档 timeline 数据源。 diff --git a/docs/api/tasks-claim.md b/docs/api/tasks-claim.md new file mode 100644 index 0000000..64b8a21 --- /dev/null +++ b/docs/api/tasks-claim.md @@ -0,0 +1,25 @@ +# POST /api/v1/tasks/{task_key}/claim — 领取任务奖励 + +> 所属:Tasks 组(前缀 `/api/v1/tasks`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参(path) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `task_key` | string | ✅ | 任务标识(见 [tasks-list](./tasks-list.md) 的 `task_key`) | + +## 出参 +响应 `200`:`TaskClaimResultOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `task_key` | string | 任务标识 | +| `coin_awarded` | int | 本次发放金币 | +| `coin_balance` | int | 领奖后金币余额 | + +## 错误码 +- `404` 未知任务(`unknown task`) +- `409` 任务已领取(`task already claimed`) + +## 说明 +发奖端 gate 由后端把控(如「打开消息提醒」须客户端确认权限已开后才调)。金币已计入余额。 diff --git a/docs/api/tasks-list.md b/docs/api/tasks-list.md new file mode 100644 index 0000000..63cc73d --- /dev/null +++ b/docs/api/tasks-list.md @@ -0,0 +1,20 @@ +# GET /api/v1/tasks — 任务及领取状态 + +> 所属:Tasks 组(前缀 `/api/v1/tasks`,本接口 GET 前缀本身) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`TaskListOut` = `{ items: TaskOut[] }` + +**TaskOut** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `task_key` | string | 任务标识,如 `enable_notification`(打开消息提醒) | +| `coin` | int | 完成可得金币 | +| `claimed` | bool | 是否已领取 | + +## 说明 +福利页一次性任务行(如「打开消息提醒」)的状态源。领取走 [tasks-claim](./tasks-claim.md)。 diff --git a/docs/api/wallet-account.md b/docs/api/wallet-account.md new file mode 100644 index 0000000..c059f4c --- /dev/null +++ b/docs/api/wallet-account.md @@ -0,0 +1,18 @@ +# GET /api/v1/wallet/account — 金币 + 现金余额(我的资产) + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`CoinAccountOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `coin_balance` | int | 当前金币余额 | +| `cash_balance_cents` | int | 当前现金余额(分) | +| `total_coin_earned` | int | 累计赚取金币 | + +## 说明 +账户不存在时自动创建(零余额)。福利页「我的资产」卡的数据源。 diff --git a/docs/api/wallet-bind-wechat.md b/docs/api/wallet-bind-wechat.md new file mode 100644 index 0000000..a532d53 --- /dev/null +++ b/docs/api/wallet-bind-wechat.md @@ -0,0 +1,26 @@ +# POST /api/v1/wallet/bind-wechat — 微信授权 code 换 openid 并绑定 + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | 限流:同 IP ≤10 次/分 | [← 返回 API 索引](./README.md) + +## 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `code` | string | ✅ | 微信授权返回的 code(客户端 OAuth 拿到) | + +## 出参 +响应 `200`:`BindWechatResultOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `bound` | bool | 固定 `true` | +| `wechat_nickname` | string \| null | 微信昵称 | +| `wechat_avatar_url` | string \| null | 微信头像 URL | + +## 错误码 +- `400` code 无效 / 换 openid 失败 +- `409` 该微信已绑定其他账号 +- `503` 微信支付未配置(`wxpay_configured=false`) + +## 说明 +后端用 `code` 调微信 `jscode2session`(或 OAuth)换 `openid` 并写入 user。提现前置:必须先绑定。`openid` 全局唯一(DB 唯一约束),一个微信只能绑一个账号。 diff --git a/docs/api/wallet-cash-transactions.md b/docs/api/wallet-cash-transactions.md new file mode 100644 index 0000000..80463e9 --- /dev/null +++ b/docs/api/wallet-cash-transactions.md @@ -0,0 +1,28 @@ +# GET /api/v1/wallet/cash-transactions — 现金流水(游标分页) + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参(query) + +| 字段 | 类型 | 必填 | 默认 | 说明 | +|---|---|---|---|---| +| `limit` | int | ❌ | 20 | 1–100 | +| `cursor` | int | ❌ | null | 上一页末条 `id`,首页不传 | + +## 出参 +响应 `200`:`{ items: CashTransactionOut[], next_cursor: int|null }`(分页见 [索引#游标分页约定](./README.md#游标分页约定)) + +**CashTransactionOut** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | int | 流水 id(也是游标) | +| `amount_cents` | int | 现金变动(分),正=兑入 负=提现 | +| `balance_after_cents` | int | 变动后现金余额(分) | +| `biz_type` | string | 业务类型(兑换/提现等) | +| `ref_id` | string \| null | 关联业务 id(如提现单号) | +| `remark` | string \| null | 备注 | +| `created_at` | datetime | 时间 | + +## 说明 +现金历史页(`CASH_HISTORY`)数据源。 diff --git a/docs/api/wallet-coin-transactions.md b/docs/api/wallet-coin-transactions.md new file mode 100644 index 0000000..70e0b69 --- /dev/null +++ b/docs/api/wallet-coin-transactions.md @@ -0,0 +1,28 @@ +# GET /api/v1/wallet/coin-transactions — 金币流水(游标分页) + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参(query) + +| 字段 | 类型 | 必填 | 默认 | 说明 | +|---|---|---|---|---| +| `limit` | int | ❌ | 20 | 1–100 | +| `cursor` | int | ❌ | null | 上一页末条 `id`,首页不传 | + +## 出参 +响应 `200`:`{ items: CoinTransactionOut[], next_cursor: int|null }`(分页见 [索引#游标分页约定](./README.md#游标分页约定)) + +**CoinTransactionOut** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | int | 流水 id(也是游标) | +| `amount` | int | 金币变动,正=入账 负=出账 | +| `balance_after` | int | 变动后余额 | +| `biz_type` | string | 业务类型(签到/任务/兑换等) | +| `ref_id` | string \| null | 关联业务 id | +| `remark` | string \| null | 备注 | +| `created_at` | datetime | 时间 | + +## 说明 +金币历史页(`COIN_HISTORY`)数据源。 diff --git a/docs/api/wallet-exchange-info.md b/docs/api/wallet-exchange-info.md new file mode 100644 index 0000000..23c4b04 --- /dev/null +++ b/docs/api/wallet-exchange-info.md @@ -0,0 +1,18 @@ +# GET /api/v1/wallet/exchange-info — 金币兑现金 汇率/规则 + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:**无**(返回静态配置,不读用户) | [← 返回 API 索引](./README.md) + +## 入参 +无。 + +## 出参 +响应 `200`:`ExchangeInfoOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `coin_per_yuan` | int | 多少金币兑 1 元 | +| `min_coin` | int | 单次兑换最少金币 | +| `step_coin` | int | 兑换金币需为该值的整数倍(对齐整分) | + +## 说明 +给兑换页展示汇率与规则用。值来自 `app/core/rewards.py`(`COIN_PER_YUAN` / `MIN_EXCHANGE_COIN` / `COIN_PER_CENT`)。 diff --git a/docs/api/wallet-exchange.md b/docs/api/wallet-exchange.md new file mode 100644 index 0000000..9eaefae --- /dev/null +++ b/docs/api/wallet-exchange.md @@ -0,0 +1,26 @@ +# POST /api/v1/wallet/exchange — 金币兑现金 + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `coin_amount` | int | ✅(>0) | 要兑换的金币数,须 ≥ `min_coin` 且为 `step_coin` 整数倍 | + +## 出参 +响应 `200`:`ExchangeResultOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `coin_amount` | int | 本次扣除的金币 | +| `cash_added_cents` | int | 本次兑入的现金(分) | +| `coin_balance` | int | 兑换后金币余额 | +| `cash_balance_cents` | int | 兑换后现金余额(分) | + +## 错误码 +- `400` `coin_amount` 不满足 ≥ `min_coin` 且为 `step_coin` 整数倍 +- `409` 金币余额不足 + +## 说明 +兑换规则见 [wallet-exchange-info](./wallet-exchange-info.md)。金币与现金的扣加在同一事务内完成。 diff --git a/docs/api/wallet-unbind-wechat.md b/docs/api/wallet-unbind-wechat.md new file mode 100644 index 0000000..78e1392 --- /dev/null +++ b/docs/api/wallet-unbind-wechat.md @@ -0,0 +1,16 @@ +# POST /api/v1/wallet/unbind-wechat — 解绑微信(清空 openid) + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`UnbindWechatResultOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `bound` | bool | 固定 `false` | + +## 说明 +清空当前用户的 `wechat_openid` / `wechat_nickname` / `wechat_avatar_url`。幂等:未绑定时调用也返回 `bound=false`。解绑后该微信可被其他账号绑定。 diff --git a/docs/api/wallet-withdraw-info.md b/docs/api/wallet-withdraw-info.md new file mode 100644 index 0000000..d6e0d59 --- /dev/null +++ b/docs/api/wallet-withdraw-info.md @@ -0,0 +1,20 @@ +# GET /api/v1/wallet/withdraw-info — 提现额度 / 绑定状态 + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参 +无(用户由 token 确定)。 + +## 出参 +响应 `200`:`WithdrawInfoOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `min_cents` | int | 单次最低提现(分) | +| `max_cents` | int | 单次最高提现(分) | +| `wechat_bound` | bool | 是否已绑定微信 | +| `wechat_nickname` | string \| null | 微信昵称(可能脱敏/空) | +| `wechat_avatar_url` | string \| null | 微信头像 URL(可能空) | + +## 说明 +提现页进入时拉一次:决定展示额度区间、是否需先去绑定微信。额度来自 `rewards.py`(`WITHDRAW_MIN_CENTS` / `WITHDRAW_MAX_CENTS`)。 diff --git a/docs/api/wallet-withdraw-orders.md b/docs/api/wallet-withdraw-orders.md new file mode 100644 index 0000000..80b5105 --- /dev/null +++ b/docs/api/wallet-withdraw-orders.md @@ -0,0 +1,28 @@ +# GET /api/v1/wallet/withdraw-orders — 提现单列表(游标分页) + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参(query) + +| 字段 | 类型 | 必填 | 默认 | 说明 | +|---|---|---|---|---| +| `limit` | int | ❌ | 20 | 1–100 | +| `cursor` | int | ❌ | null | 上一页末条 `id`,首页不传 | + +## 出参 +响应 `200`:`{ items: WithdrawOrderOut[], next_cursor: int|null }`(分页见 [索引#游标分页约定](./README.md#游标分页约定)) + +**WithdrawOrderOut** + +| 字段 | 类型 | 说明 | +|---|---|---| +| `id` | int | 单 id(也是游标) | +| `out_bill_no` | string | 商户提现单号 | +| `amount_cents` | int | 提现额(分) | +| `status` | string | `pending` / `success` / `failed` | +| `wechat_state` | string \| null | 微信侧原始状态 | +| `fail_reason` | string \| null | 失败原因 | +| `created_at` | datetime | 发起时间 | + +## 说明 +提现记录页数据源。 diff --git a/docs/api/wallet-withdraw-status.md b/docs/api/wallet-withdraw-status.md new file mode 100644 index 0000000..4b60d87 --- /dev/null +++ b/docs/api/wallet-withdraw-status.md @@ -0,0 +1,25 @@ +# GET /api/v1/wallet/withdraw/status — 查提现单状态(轮询) + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | [← 返回 API 索引](./README.md) + +## 入参(query) + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `out_bill_no` | string | ✅ | 商户提现单号([withdraw](./wallet-withdraw.md) 返回的) | + +## 出参 +响应 `200`:`WithdrawStatusOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `out_bill_no` | string | 商户提现单号 | +| `status` | string | `pending` / `success` / `failed` | +| `wechat_state` | string \| null | 微信侧原始状态 | +| `amount_cents` | int | 提现额(分) | + +## 错误码 +- `404` 提现单不存在(或不属于当前用户) + +## 说明 +App 从微信确认页返回后调此接口刷新单状态。**副作用**:若查时仍是 `WAIT_USER_CONFIRM`(用户在确认页放弃),后端会**撤销该单并退回余额**(`cancel_if_unconfirmed=True`)。 diff --git a/docs/api/wallet-withdraw.md b/docs/api/wallet-withdraw.md new file mode 100644 index 0000000..ade947e --- /dev/null +++ b/docs/api/wallet-withdraw.md @@ -0,0 +1,34 @@ +# POST /api/v1/wallet/withdraw — 发起提现到微信零钱 + +> 所属:Wallet 组(前缀 `/api/v1/wallet`) | 鉴权:Bearer | 限流:同 IP ≤20 次/分 | [← 返回 API 索引](./README.md) + +## 入参 + +| 字段 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `amount_cents` | int | ✅(>0) | 提现金额(分),须落在 `[min_cents, max_cents]` | +| `user_name` | string | ❌ | 实名(达额时微信商家转账要求,可空) | +| `out_bill_no` | string | ❌ | **客户端幂等键(商户单号)**:同号重试不重复转账;不传则服务端生成 | + +## 出参 +响应 `200`:`WithdrawResultOut` + +| 字段 | 类型 | 说明 | +|---|---|---| +| `out_bill_no` | string | 商户提现单号(查单用) | +| `status` | string | `pending` / `success` / `failed` | +| `wechat_state` | string \| null | 微信侧原始状态 | +| `amount_cents` | int | 本次提现额(分) | +| `cash_balance_cents` | int | 提现后现金余额(分) | +| `package_info` | string \| null | 拉起微信确认页用(`WXOpenBusinessView` requestMerchantTransfer) | +| `mch_id` | string \| null | 商户号 | +| `app_id` | string \| null | 微信 AppID | + +## 错误码 +- `400` 金额越界 / 未绑定微信 +- `409` 现金余额不足 +- `502` 微信转账调用失败(余额已退回) +- `503` 微信支付未配置 + +## 说明 +走微信「商家转账到零钱」V3。先扣余额建单再调微信,转账失败则退回余额。`out_bill_no` 幂等防止网络重试重复打款。返回的 `package_info`/`mch_id`/`app_id` 供 App 拉起微信确认页;确认结果由 [wallet-withdraw-status](./wallet-withdraw-status.md) 轮询。