# 财务接口

---

> **双钱包版本待发布**：本文余额与打码任务说明对应当前代码；当前 schema 尚未正式部署，发布前需完成配套数据库迁移，不代表已经上线。接口及规则见 [打码任务](201_wager_tasks.md)。

## 通用枚举 {#enums}

**充值订单状态 `Recharge.status`**

值 | 说明
---|---
0 | 等待处理
1 | 处理中
2 | 充值成功
3 | 已拒绝

**提现订单状态 `Withdraw.status`**

值 | 说明
---|---
0 | 等待处理
1 | 处理中
2 | 取款成功
3 | 退回账号
4 | 代付中
5 | 异常用户
6 | 强制成功（线下已出款，仍标记成功）
7 | 没收余额（订单关闭，申请时扣除的金额不退回）

**PIX 账户类型 `account_type`**：`CPF` / `EMAIL` / `PHONE`

**提款方式 `method`**（提款方式与商户币种一一绑定，玩家只会看到本商户币种下的方式）

method | 名称 | 币种 | 账户类型 `account_types`
---|---|---|---
`pix` | PIX | `BRL` | `["CPF", "PHONE", "EMAIL"]`
`bank_clabe` | CLABE | `MXN` | `["CLABE"]`
`card` | Card | `USD` | `["CARD"]`
`cashapp` | Cash App | `USD` | `["CASHTAG"]`
`paypal` | PayPal | `USD` | `["PAYPAL"]`

> 该表仅供对照，**客户端不要写死**：实际可用方式一律以【[提款配置](#withdraw_config)】下发的 `method_list` 为准。

---

## 金额与币种 {#amount_currency}

**金额字段一律为字符串形式的十进制数**（如 `amount` / `received_amount` / `fee` / `min_amount` / `max_amount` / `user_money` 等），避免 JS 浮点精度丢失；服务端存储精度为小数点后 2 位。

**金额单位是货币面额，不是最小单位「分」**。例如 `"100.00"` 表示 100 巴西雷亚尔、100 墨西哥比索或 100 美元。客户端传参与展示均使用面额。

**币种由商户配置决定**，取值 `BRL`（巴西雷亚尔）/ `MXN`（墨西哥比索）/ `USD`（美元），通过【[提款配置](#withdraw_config)】的 `data.currency` 下发。**客户端不要写死货币符号**（如 `R$`），应按 `currency` 动态渲染。

---

## 获取充值配置 {#recharge_config}

返回启用中的代收支付通道（按 `channel_code` 聚合）和充值套餐。

!!! warning "一个通道可能有多个支付项"
    响应中 `data.channel` 是**通道分组**列表，每个分组的 `channel` 字段又是一个**数组**，里面是该通道下所有启用的支付项。**同一通道下可能存在多条支付项**（区别在 `product_code`），玩家端必须遍历 `channel[].channel[]` 渲染全部可选项，**不要只取第一条**。

    例：美区（`USD`）商户在同一个通道下会返回 3 条支付项（Apple Pay / Google Pay / Cash App）；巴西（`BRL`）、墨西哥（`MXN`）商户通常每个通道只有 1 条。

**URL:** `GET` /finance/recharge/config

**输出:**

字段 | 类型 | 说明
---|---|---
data.channel | list<object\> | 代收通道分组列表
data.packages | list<object\> | 启用的充值套餐列表
data.bet_multiple | int | 本金打码配置倍数（作用于实际到账本金，不含赠送；入账时生效倍数至少为 `1`）
data.fixed_amount | bool | 是否固定金额。`true` 时玩家仅可从 `packages[].amount` 中选择面额，**不得提供自定义金额输入框**（如 conapay 美国代收上游只接受固定面额）；`false` 保持原有行为

`channel` 单项字段：

字段 | 类型 | 说明
---|---|---
id | string | 通道 code（同 `channel.code`）
validate_cpf | bool | 该通道是否要求填写 CPF
currency | string | 该通道的结算币种，`BRL` / `MXN` / `USD`
channel | list<object\> | 该通道下的支付项（**数组，可能多条**）

`channel.channel` 单项字段：

字段 | 类型 | 说明
---|---|---
name | string | 支付项名称（商户自定义展示名，直接展示给玩家）
code | string | 通道代码（下单时作为 `action_type` 传回）。**同一分组内各项的 `code` 相同**，不能用它区分支付项
currency | string | 结算币种，`BRL` / `MXN` / `USD`
product_code | string | **代收产品编码**，同一通道内区分支付项的唯一依据；下单时作为 `product_code` 传回，并决定充值结果页的渲染方式，详见【[创建充值订单](#recharge_create)】
min | string | 单笔最小金额
max | string | 单笔最大金额

`product_code` 当前取值：

product_code | 含义 | 币种
---|---|---
`PIX` | PIX 扫码 / 复制粘贴码 | `BRL`
`SPEI` | SPEI 银行转账（收款账号信息文本） | `MXN`
`CASHIER` | SPEI 收银台支付页（`egpaymx` 通道） | `MXN`
`USDApplePay` | Apple Pay | `USD`
`USDGooglePay` | Google Pay | `USD`
`USDCashApp` | Cash App | `USD`

> `product_code` 是**开放集**，后续还会新增。玩家端**不得把未知值当作错误过滤掉**，应按兼容口径照常展示（名称用 `name`）。

**输出示例（美区 USD 商户，同一通道下 3 条支付项）:**

```json
{
  "data": {
    "channel": [{
      "id": "conapay",
      "validate_cpf": false,
      "currency": "USD",
      "channel": [
        {"name": "Apple Pay",  "code": "conapay", "currency": "USD", "product_code": "USDApplePay",  "min": "1", "max": "10000"},
        {"name": "Google Pay", "code": "conapay", "currency": "USD", "product_code": "USDGooglePay", "min": "1", "max": "10000"},
        {"name": "Cash App",   "code": "conapay", "currency": "USD", "product_code": "USDCashApp",   "min": "1", "max": "10000"}
      ]
    }],
    "packages": [],
    "bet_multiple": 1,
    "fixed_amount": false
  }
}
```

`packages` 单项（充值套餐配置项）：

字段 | 类型 | 说明
---|---|---
id | string | 套餐序号（数组下标，从 "0" 起；仅用于前端列表 key，创建订单用 amount 而非此 id）
amount | string | 充值面额
give_amount | string | 赠送金额
bet_multiple | int | 赠送金额的打码倍数（作用于该档 give_amount；本档命中时才生效）
is_default | bool | 是否默认套餐（玩家端可据此预选）

本金计入现金余额 `user_money` 并建立不可放弃的打码任务。套餐赠送有打码要求时计入奖金余额 `bonus_money` 并建立独立奖励任务，无打码要求时计入 `user_money`；奖励任务的放弃属性快照见 [任务列表](201_wager_tasks.md#list)，当前整体放弃资格以 [余额接口](#account_balance) 的 `can_abandon_all` 为准。不要将本金和赠送相加后作为可提现金额。

---

## 创建充值订单 {#recharge_create}

**URL:** `POST` /finance/recharge/create

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
action_type | string | 否 | 支付通道代码（对应 `recharge/config` 中的 `channel.code`）。**不传时使用当前启用代收通道中 `sort_order` 最小的一条；若无可用通道，接口返回错误。**
amount | string | 是 | 充值金额，必须 > 0
product_code | string | 否 | **代收产品编码**，取自 `recharge/config` 中同通道 `channel[]` 数组各项的 `product_code`。同一通道下有多条支付项时用它精确选中玩家点击的那一条；详见下方说明
source | string | 否 | 充值来源。缺省 `normal`。详见下方枚举。

!!! info "`product_code` 是向后兼容的新增可选参数"
    - **不传**（或传空串）= 保持原有行为：服务端取该通道下按 `sort_order` 排序的**第一条启用支付项**。**老客户端无需任何改动**。
    - **传了** = 精确选中该通道下 `product_code` 相等的支付项（影响限额校验、手续费与实际走的产品）。
    - 传了但在该通道下**找不到匹配的启用支付项**时，返回业务错误 `支付方式不可用`（**不会**静默回退到第一条）。
    - ❗ **当一个通道下有多条支付项时（如美区 USD 的 Apple Pay / Google Pay / Cash App），必须传 `product_code`**，否则无论玩家点哪个图标都会走同一条（第一条）。单产品通道（`BRL` / `MXN`）可不传。

`source` 枚举：

- `normal`（默认）：普通充值，不触发活动加赠；**充值套餐赠送只在本来源下生效**
- `first_recharge_bonus`：参加「[首充奖励](413_promotion_first_recharge_bonus.md)」活动。**只要玩家未领取过首充奖励即可参加，与历史充值金额无关**；支付成功后按到账金额命中的最高档位自动发放，每人仅一次
- `recharge_bonanza`：触发「充值大酬宾」奖励
- `daily_bankrupt_pack`：触发「[每日幸运补偿](417_promotion_daily_bankrupt_pack.md)」加赠，**需在礼包倒计时内下单**（无有效礼包或已过期时不加赠）
- `lucky_compensation`：领取「[幸运补偿](419_promotion_lucky_compensation.md#boost)」的幸运加赠（补偿 × 倍数的固定金额），**需已开箱且在加赠有效期内**，金额 ≥ `boost_min_deposit`；未开箱 / 已过期 / 未达门槛时不加赠（未达门槛不消耗资格）
- `win_back`：参加「[捞回活动](420_promotion_win_back.md)」；须在存款页主动选择，建单时校验已领取宝箱、有效期、充值门槛及订单资格。符合条件的充值单绑定该宝箱；有效期内创建的订单即使在宝箱到期后到账，仍按绑定规则结算加倍奖金

**source 值必须与后端枚举严格一致；传错或不传将不会触发对应促销奖励。**

!!! warning "两点务必注意"
    1. **活动来源与充值套餐赠送互斥**：只要 `source` 不是 `normal`，本单就不再发放充值套餐的赠送金额。玩家端在让玩家勾选参加活动时应提示清楚，避免玩家误以为可以叠加。
    2. **活动资格校验因来源而异**：`win_back` 在建单时强校验活动资格；无有效宝箱、金额未达门槛、已有捞回充值单处理中或其他资格不满足时，接口返回错误，不会静默降级为普通充值。其他来源仍按各自活动文档处理；其中部分活动不校验资格，订单可能正常创建但不发活动加赠。
    3. **捞回充值不与其他充值活动叠加**：选择 `win_back` 后，本笔充值只绑定捞回宝箱；不发放充值套餐赠送，也不触发其他充值活动。普通充值或选择其他来源均不消耗捞回宝箱的翻倍资格。

**输出:**

字段 | 类型 | 说明
---|---|---
id | string | 充值订单 ID
order_sn | string | 平台订单号
pay_info | string | 三方返回的支付信息，**内容按商户币种不同，见下方说明**。通道未接三方或走人工时不返回
third_order_sn | string | 三方平台订单号
raw | string | 三方原始响应报文（调试用，客户端一般不需要解析）

> **`pay_info` 渲染必须按产品编码分支：**
>
> 判定依据是【[获取充值配置](#recharge_config)】中本次下单所选支付项的 `product_code`（**不是通道代码 `code`**，`code` 同一通道下全部相同，无法区分）：
>
> - `product_code = PIX`（巴西，`pay4z` / `egpaybr` / `egpayglobal` / `nanapay` 四通道一致；`egpayglobal` 仅接受**整数金额**，非整数下单会被拒绝）：`pay_info` 是**收银台支付页 URL**（页面内展示 PIX 二维码与复制粘贴码），应**跳转 / 内嵌打开**该地址，**不要**直接当复制粘贴码或二维码内容渲染。
> - `product_code = SPEI`（墨西哥）：`pay_info` 是 SPEI 银行转账的收款账号信息文本，**不是二维码内容，不可当二维码渲染**，应渲染为**账号信息卡片 + 一键复制**。
> - `product_code = CASHIER`（墨西哥，`egpaymx` 通道）：`pay_info` 是**收银台支付页 URL**（页面内展示 SPEI 收款 CLABE 等信息），应**跳转 / 内嵌打开**该地址，**不要当文本展示、不要当二维码渲染**。注意：玩家可能保存该 CLABE 后多次转账，每笔到账都会按实付金额生成一笔新的充值订单并入账（在【[我的充值订单](#recharge_my)】中可见，`amount` 为实付金额）；实付金额与下单金额不一致时，原订单 `amount` 也会被改为实付金额。
> - `product_code = USDApplePay` / `USDGooglePay` / `USDCashApp`（美区）：`pay_info` 是**收银台支付页 URL**，应**跳转 / 内嵌打开**该地址让玩家完成支付，**不要当文本展示、不要当二维码渲染**。支付完成后以服务端回调入账为准（可轮询【[我的充值订单](#recharge_my)】或实时推送），**不要依赖返回页判定成功**。
>
> 若后续新增产品编码，玩家端应做兵底处理（未知 `product_code` 按纯文本 + 复制展示；内容以 `http(s)://` 开头时按链接打开），不要直接当二维码。

---

## 我的充值订单 {#recharge_my}

**URL:** `GET` /finance/recharge/my

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
status | string | 否 | 按 `Recharge.status` 过滤，支持逗号分隔多值，如 `0,1`
start_time | string | 否 | 创建时间下限（含），`YYYY-MM-DD` 或 `YYYY-MM-DD HH:mm:ss`，按商户时区解析
end_time | string | 否 | 创建时间上限（含），格式同上；传纯日期时包含当天整日
page | number | 否 | 页码
size | number | 否 | 每页条数

**输出:**

字段 | 类型 | 说明
---|---|---
list | list<Recharge\> | 充值订单列表（按 `id DESC`）
total | number | 总数

`Recharge` 字段：

字段 | 类型 | 说明
---|---|---
id | string | 订单 ID
member_id | string | 会员 ID
action_type | string | 支付项代码
order_sn | string | 订单号
amount | string | 申请金额
received_amount | string | 实际到账金额
give_amount | string | 赠送金额（审核通过后写入）
bet_multiple | number | 赠送对应的打码倍数
package_id | string | 命中的套餐 ID
channel | string | 通道代码
remark | string | 备注
status | number | 订单状态
created_at | string | 创建时间
updated_at | string | 更新时间

---

## 提款配置 {#withdraw_config}

**提现页唯一配置入口**，也是下方【[统一提现接口](#withdraw_unified)】组的配置来源：一次返回渲染整个提现页所需的全部静态配置（商户币种、可用提款方式、每种方式的表单字段与可用代付通道及限额）。

**URL:** `GET` /finance/withdraw/config

**输出:**

字段 | 类型 | 说明
---|---|---
data.currency | string | 商户币种：`BRL` / `MXN` / `USD`（未配置时默认 `BRL`）
data.methods | list<string\> | 可用提款方式代码，见【[提款方式枚举](#enums)】；顺序与 `method_list` 一致
data.method_list | list<object\> | 提款方式详情列表（推荐使用；`methods` 为其代码集合，保留兼容）。**只包含至少有 1 个可用 `channels` 的方式**，为空数组表示当前无可用提款方式

**方式的下发与排序规则：**

- **只下发有可用通道的方式**：商户后台未配置 / 未启用对应代付项的提款方式**不会出现**在 `method_list` / `methods` 中（此前会以空 `channels` 下发，现已不再下发）。客户端**不要**假定 USD 商户必然返回 3 条、BRL 商户必然返回 `pix`，一律以实际响应为准；`method_list` 为空表示当前无可用提款方式，应展示对应空态。
- **顺序按商户后台代付项配置排序**（`sort_order`），不再是固定的注册表顺序；**首项即默认选中的提款方式**。`methods` 与 `method_list` 顺序一致。

**每个商户只会看到本币种下的方式**，各币种**最多可能下发**的方式如下（实际条目以响应为准，可能少于此表）：

商户币种 | 最多可能下发的 `methods`
---|---
`BRL` | `["pix"]`
`MXN` | `["bank_clabe"]`
`USD` | `["card", "cashapp", "paypal"]`（顺序以响应为准）

`method_list` 单项字段：

字段 | 类型 | 说明
---|---|---
method | string | 提款方式代码：`pix` / `bank_clabe` / `card` / `cashapp` / `paypal`
name | string | 提款方式展示名
currency | string | 该方式对应币种：`BRL` / `MXN` / `USD`
account_types | list<string\> | 账户类型可选值，见【[提款方式枚举](#enums)】
extra_fields | list<string\> | **该提款方式的收款资料表单字段 key 列表**，玩家端据此动态渲染表单；与【[获取收款资料](#withdraw_account_get)】响应中 `fields` 对象的 key **完全对称**
channels | list<object\> | **已按该提款方式过滤**的可用代付通道（含限额），玩家端直接使用，无需再调 `/withdraw-*/channels`

各方式的 `extra_fields`（服务端当前下发值，**以实际响应为准，不要写死**）：

method | extra_fields
---|---
`pix` | `["account_type", "account", "cpf", "email", "phone", "name"]`
`bank_clabe` | `["account", "account_name"]`
`card` | `["account_name", "account_no", "card_expire_year", "card_expire_month"]`
`cashapp` | `["account_no"]`
`paypal` | `["account_no"]`

`channels` 单项字段：

字段 | 类型 | 说明
---|---|---
channel | string | 通道代码（拼写正确；旧端点 `/withdraw-*/channels` 中该字段拼作 `channle`）
name | string | 通道展示名（商户后台代付项名称，同一 `channel` 在不同 `method` 下可能不同）
min_amount | string | 单笔最小金额（已合并方式级限制，直接可用于前端校验）
max_amount | string | 单笔最大金额（同上）
fee_rate | string | 手续费费率（小数，如 `0.02` 表示 2%；`0` 表示无比例手续费）
fee_fixed | string | 每笔固定手续费（`0` 表示无固定手续费）

**手续费口径**：服务端按 `fee = round(amount × fee_rate + fee_fixed, 2)` 计算（四舍五入保留 2 位小数，结果为负按 `0`、超过 `amount` 按 `amount` 夹紧），提现订单中的 `fee` 字段即为该值。客户端可据此在提现页做提示：

- **门槛提示**：当可用金额 `< min_amount + (min_amount × fee_rate + fee_fixed)` 时，提示玩家余额不足以覆盖该渠道最低提现金额及手续费。
- **预估手续费**：输入金额后可按同一公式展示预估手续费与预计到账金额，最终以订单返回的 `fee` / `received_amount` 为准。

> **注意：同一通道在不同提款方式下的名称、限额与手续费都可能不同。**
> 商户后台代付项按提款方式拆条配置（如 conapay USD 的 `card` / `cashapp` / `paypal` 各一条），
> 因此同一个 `channel` 在不同 `method_list[]` 项下的 `name` / `min_amount` / `max_amount` /
> `fee_rate` / `fee_fixed` 都可能不同；其中 `min_amount` / `max_amount` 是**商户代付项限额与提款方式上游安全区间的交集**
> （安全区间很宽，如 USD 三种方式均为 `1–50000`，实际生效值基本由商户配置决定）。
> 请**按 method + channel 组合**取值，不要按 channel 缓存一份全局的名称 / 限额 / 手续费。

**输出示例（墨西哥商户）:**

```json
{
  "code": 0,
  "data": {
    "currency": "MXN",
    "methods": ["bank_clabe"],
    "method_list": [{
      "method": "bank_clabe",
      "name": "CLABE",
      "currency": "MXN",
      "account_types": ["CLABE"],
      "extra_fields": ["account", "account_name"],
      "channels": [{
        "channel": "pay4z",
        "name": "Pay4Z",
        "min_amount": "10",
        "max_amount": "19999",
        "fee_rate": "0",
        "fee_fixed": "0"
      }]
    }]
  }
}
```

**输出示例（美区 USD 商户，3 种提款方式均已配置代付项；顺序为商户后台 `sort_order`，首项 `cashapp` 为默认选中）:**

```json
{
  "code": 0,
  "data": {
    "currency": "USD",
    "methods": ["cashapp", "card", "paypal"],
    "method_list": [{
      "method": "cashapp",
      "name": "Cash App",
      "currency": "USD",
      "account_types": ["CASHTAG"],
      "extra_fields": ["account_no"],
      "channels": [{"channel": "conapay", "name": "Cash App", "min_amount": "10", "max_amount": "10000", "fee_rate": "0.02", "fee_fixed": "0"}]
    }, {
      "method": "card",
      "name": "Card",
      "currency": "USD",
      "account_types": ["CARD"],
      "extra_fields": ["account_name", "account_no", "card_expire_year", "card_expire_month"],
      "channels": [{"channel": "conapay", "name": "Card", "min_amount": "10", "max_amount": "2000", "fee_rate": "0.03", "fee_fixed": "1"}]
    }, {
      "method": "paypal",
      "name": "PayPal",
      "currency": "USD",
      "account_types": ["PAYPAL"],
      "extra_fields": ["account_no"],
      "channels": [{"channel": "conapay", "name": "PayPal", "min_amount": "10", "max_amount": "2000", "fee_rate": "0.02", "fee_fixed": "0.5"}]
    }]
  }
}
```

**输出示例（美区 USD 商户，仅配置了 `cashapp` 代付项）:**

```json
{
  "code": 0,
  "data": {
    "currency": "USD",
    "methods": ["cashapp"],
    "method_list": [{
      "method": "cashapp",
      "name": "Cash App",
      "currency": "USD",
      "account_types": ["CASHTAG"],
      "extra_fields": ["account_no"],
      "channels": [{"channel": "conapay", "name": "Cash App", "min_amount": "10", "max_amount": "10000", "fee_rate": "0.02", "fee_fixed": "0"}]
    }]
  }
}
```

> `channels` 的 `name` / 限额 / `fee_rate` / `fee_fixed` 由商户后台代付项配置，上例仅为示意；**同一通道在不同方式下的名称、限额与手续费可能各不相同**，下单前请按选中 `method` 对应的 `channels[]` 做前置校验与手续费提示。

---

## 统一提现接口（推荐）——迁移指引 {#withdraw_unified}

以 `method` 为参数的一套通用提现接口，覆盖全部提款方式（当前 `pix` / `bank_clabe` / `card` / `cashapp` / `paypal`，后续新增国家/方式不再新增端点）。

- **`bank_clabe`（墨西哥 CLABE）与美区的 `card` / `cashapp` / `paypal` 只能走本组接口**，无独立旧端点。
- `pix` 的 7 个旧端点**仍然可用且行为不变**，但已标记废弃，建议玩家端逐步迁移。

**推荐调用顺序：**

`GET /finance/withdraw/config` → 按 `method_list` 渲染方式选择与表单（用 `extra_fields` 决定表单字段）→ `GET /finance/withdraw/account` 回填已保存资料 → `POST /finance/withdraw/account` 保存 → `POST /finance/withdraw/create` 下单 → `GET /finance/withdraw/my` 查询订单列表。

!!! success "新增美区（USD）方式对既有接入方**无破坏性变更**"
    - 【[获取收款资料](#withdraw_account_get)】【[保存收款资料](#withdraw_account_save)】【[删除收款资料](#withdraw_account_remove)】三个端点的**请求与响应结构逐字段不变**，`fields` 仍是与 `extra_fields` 对称的 kv 或 `null`。
    - **`BRL`（PIX）/ `MXN`（CLABE）商户的行为零变化**：可用方式、表单字段、校验规则、错误文案与响应内容全部与此前一致。
    - 新增的 `card` / `cashapp` / `paypal` **只会下发给 `USD` 商户**。已按 `extra_fields` 动态渲染表单的客户端**无需任何改动**即可支持；写死了 `pix` / `bank_clabe` 分支的客户端才需要改为按 `method_list` 驱动。

**旧端点 → 新端点映射：**

旧端点 | 新端点
---|---
`GET /finance/pix/config` + `GET /finance/withdraw-pix/channels` | `GET` /finance/withdraw/config（[提款配置](#withdraw_config)）
`GET /finance/pix/index` | `GET` /finance/withdraw/account（[获取收款资料](#withdraw_account_get)）
`POST /finance/pix/create` | `POST` /finance/withdraw/account（[保存收款资料](#withdraw_account_save)）
`POST /finance/pix/remove` | `POST` /finance/withdraw/account/remove（[删除收款资料](#withdraw_account_remove)）
`POST /finance/withdraw-pix/create` | `POST` /finance/withdraw/create（[创建提现订单](#withdraw_create)）
`GET /finance/withdraw-pix/my` | `GET` /finance/withdraw/my（[我的提现订单](#withdraw_my)）

---

## 获取收款资料（统一） {#withdraw_account_get}

按提款方式查询玩家已保存的收款资料。`fields` 的 key 严格等于【[提款配置](#withdraw_config)】中该 `method` 的 `extra_fields`，可直接用于表单回填。

**URL:** `GET` /finance/withdraw/account

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
method | string | 是 | 提款方式：`pix` / `bank_clabe` / `card` / `cashapp` / `paypal`（必须属于当前商户币种）

**输出:**

字段 | 类型 | 说明
---|---|---
data.method | string | 提款方式代码
data.fields | object 或 null | 收款资料键值对；尚未保存过时为 `null`

**输出示例（PIX）:**

```json
{
  "data": {
    "method": "pix",
    "fields": {
      "account_type": "CPF",
      "account": "12345678909",
      "cpf": "12345678909",
      "email": "",
      "phone": "",
      "name": "Joao"
    }
  }
}
```

**输出示例（CLABE）:**

```json
{
  "data": {
    "method": "bank_clabe",
    "fields": {
      "account": "032180000118359719",
      "account_name": "Juan"
    }
  }
}
```

**输出示例（Card，美区）:**

```json
{
  "data": {
    "method": "card",
    "fields": {
      "account_name": "John Doe",
      "account_no": "4111111111111111",
      "card_expire_year": "2030",
      "card_expire_month": "08"
    }
  }
}
```

**输出示例（Cash App / PayPal）:**

```json
{
  "data": {
    "method": "cashapp",
    "fields": {
      "account_no": "$johndoe"
    }
  }
}
```

> `method` 非法或不属于当前商户币种时，返回业务错误 `提现方式不可用`。

---

## 保存收款资料（统一） {#withdraw_account_save}

按 (merchant_id, member_id, method) upsert：玩家该方式无记录则创建，已存在则覆盖。

**URL:** `POST` /finance/withdraw/account

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
method | string | 是 | 提款方式：`pix` / `bank_clabe` / `card` / `cashapp` / `paypal`（必须属于当前商户币种）
fields | object | 是 | 收款资料键值对，key 取自【[提款配置](#withdraw_config)】中该方式的 `extra_fields`

> `fields` 的值一律用 **string**；服务端会先 trim 首尾空格，再按该 `method` 的 `extra_fields` 做 **key 白名单裁剪**——**不在白名单内的 key 会被静默丢弃**（不报错），因此多传字段不会导致保存失败，但也不会被保存。

**输入示例（PIX）:**

```json
{
  "method": "pix",
  "fields": {
    "account_type": "CPF",
    "account": "12345678909",
    "name": "Joao"
  }
}
```

**输入示例（CLABE）:**

```json
{
  "method": "bank_clabe",
  "fields": {
    "account": "032180000118359719",
    "account_name": "Juan"
  }
}
```

**输入示例（Card，美区）:**

```json
{
  "method": "card",
  "fields": {
    "account_name": "John Doe",
    "account_no": "4111111111111111",
    "card_expire_year": "2030",
    "card_expire_month": "08"
  }
}
```

**输入示例（Cash App，美区）:**

```json
{
  "method": "cashapp",
  "fields": {
    "account_no": "$johndoe"
  }
}
```

**输入示例（PayPal，美区）:**

```json
{
  "method": "paypal",
  "fields": {
    "account_no": "john.doe@example.com"
  }
}
```

**输出:**

字段 | 类型 | 说明
---|---|---
data.id | string | 收款资料记录 ID

### 字段校验规则 {#withdraw_account_rules}

以下规则由服务端强制执行，玩家端应做**同口径的前置校验**以减少无效请求：

- `pix`：`account_type` 只能是 `CPF` / `EMAIL` / `PHONE`；
- `bank_clabe`：`account` 必须为 18 位数字且通过 mod-10 校验位，`account_name` 必填且长度不超过 80；
- `card`（美区银行卡）：

    字段 | 规则
    ---|---
    `account_no` | **纯数字，长度 12–19 位**（不能带空格或分隔符，前端需先剔除）
    `account_name` | 必填（持卡人姓名）
    `card_expire_year` | **4 位数字**（如 `2030`），且不早于当前年
    `card_expire_month` | **2 位数字 `01`–`12`**（个位月份必须补零，如 `08`）
    年 + 月组合 | **不得早于当前月**

- `cashapp`：`account_no` 为 Cashtag，须匹配 `^\$[A-Za-z][A-Za-z0-9_]{0,19}$`（`$` + 字母开头 + 字母/数字/下划线，总长度 ≤ 21）。**玩家未带 `$` 前缀时服务端会自动补上再校验并存储**，因此回读到的值一定带 `$`；
- `paypal`：`account_no` 为**邮箱或手机号**，长度 ≤ 64。手机号形态为可选 `+` 前缀 + 6–20 位数字（如 `+14155550123`）。

业务错误：`account_type 只能是 CPF/EMAIL/PHONE`、`CLABE 必须为 18 位`、`CLABE 校验位错误`、`请填写户名`、`卡号必须为 12-19 位数字`、`请填写持卡人姓名`、`有效期年份必须为 4 位数字`、`有效期月份必须为 01-12`、`银行卡有效期不能早于 2026-09`（年月随当前时间变化）、`请填写 Cashtag`、`Cashtag 格式不正确`、`请填写 PayPal 邮箱或手机号`、`PayPal 账号长度不能超过 64 位`、`PayPal 账号必须为邮箱或手机号`、`提现方式不可用`。

---

## 删除收款资料（统一） {#withdraw_account_remove}

**URL:** `POST` /finance/withdraw/account/remove

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
method | string | 是 | 提款方式：`pix` / `bank_clabe` / `card` / `cashapp` / `paypal`

**输出:** 空对象 `{"data": {}}`

> 删除后再调【[获取收款资料](#withdraw_account_get)】返回 `fields: null`；重新保存同一方式会直接恢复为有效记录（不会报「已存在」）。

---

## 创建提现订单（统一） {#withdraw_create}

按 `method` 下单，服务端取该方式已保存的收款资料快照到订单上。下单成功即从现金余额 `user_money` 扣除申请金额并写入订单，状态 `WithdrawWait(0)`；奖金余额 `bonus_money` 不可直接提现。

**URL:** `POST` /finance/withdraw/create

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
method | string | 是 | 提款方式：`pix` / `bank_clabe` / `card` / `cashapp` / `paypal`
amount | string | 是 | 金额，必须 > 0
channel | string | 是 | 通道代码，取自【[提款配置](#withdraw_config)】的 `method_list[].channels[].channel`
remark | string | 否 | 备注，长度不超过 255

**输入示例:**

```json
{
  "method": "pix",
  "amount": "100.00",
  "channel": "pay4z",
  "remark": ""
}
```

**输出:**

字段 | 类型 | 说明
---|---|---
data.id | string | 提现订单 ID
data.order_sn | string | 订单号

前置条件与校验与旧端点一致：

- 已通过【[保存收款资料](#withdraw_account_save)】保存该方式的收款资料；
- 会员 `auto_withdraw` 不为「禁止」；
- 该会员无未完成提现单（状态在 0/1/4 中，**跨提现方式全局判定**）；
- 旧剩余打码为 `0`，且所有打码任务均不再是 `pending`，即 [余额接口](#account_balance) 的 `can_withdraw=true`；仅 `bet_amount=0` 不够；
- `user_money` 足以支付申请金额，不能用 `total` 或 `bonus_money` 代替现金余额；
- 通道已启用，金额满足通道与提现配置的单笔上下限。

> **新增校验**：`method` 必须与商户币种匹配（`pix` ↔ `BRL`、`bank_clabe` ↔ `MXN`、`card` / `cashapp` / `paypal` ↔ `USD`），否则返回业务错误 `提现方式与商户币种不匹配`。此校验对新旧端点**同时生效**。

> **收款资料完整性**：`card` / `cashapp` / `paypal` 下单前会校验该方式 `extra_fields` 的**每一个 key 都非空**，否则拒绝建单：
>
> - 从未保存过（或已删除）该方式的资料 → `请先添加收款资料`；
> - 资料存在但缺字段（如早期保存时少传了 `card_expire_month`）→ `收款账号资料不完整，请重新填写收款方式后再提现`。

**业务错误:**

message | 触发条件
---|---
金额必须大于 0 | `amount` ≤ 0
您的账户已被禁止提现 | 会员 `auto_withdraw` 为「禁止」
游客账号不支持提现，请先绑定正式账号 | 当前会员为游客账号（`is_guest=true`，见【[游客注册](100_auth.md#guest_register)】）
提现通道不可用 | `method` 不是已注册的提款方式
提现方式与商户币种不匹配 | `method` 与商户币种不匹配
打码量未达标，还需 %s | 旧打码或待完成任务未清零；`%s` 为旧打码与任务剩余要求之和，保留两位小数
余额不足 | 现金余额不足以支付申请金额

---

## 我的提现订单（统一） {#withdraw_my}

返回统一形状的提现订单列表；旧表历史记录也已折叠为同一形状，无需再分别查询两个旧端点。

**URL:** `GET` /finance/withdraw/my

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
method | string | 否 | 按提款方式过滤：`pix` / `bank_clabe` / `card` / `cashapp` / `paypal`；**不传返回全部方式**
status | string | 否 | 按【[提现订单状态](#enums)】过滤，支持逗号分隔多值，如 `0,1`；含 `2` 时同时返回 `6`/`7`（对玩家均展示为取款成功）
start_time | string | 否 | 创建时间下限（含），`YYYY-MM-DD` 或 `YYYY-MM-DD HH:mm:ss`，按商户时区解析
end_time | string | 否 | 创建时间上限（含），格式同上；传纯日期时包含当天整日
page | number | 否 | 页码
size | number | 否 | 每页条数

**输出:**

字段 | 类型 | 说明
---|---|---
list | list<Withdraw\> | 提现订单列表
total | number | 总数

`Withdraw` 字段：

字段 | 类型 | 说明
---|---|---
id | string | 订单 ID
merchant_id | string | 商户 ID
member_id | string | 会员 ID
order_sn | string | 订单号
amount | string | 申请金额
fee | string | 手续费
received_amount | string | 实际到账金额
channel | string | 通道代码
method | string | 提款方式：`pix` / `bank_clabe` / `card` / `cashapp` / `paypal`
account_extra | object | 收款账户信息快照，key 即该 `method` 的 `extra_fields`
third_order_sn | string | 三方订单号
remark | string | 备注
status | number | 订单状态
audit_tags | list<string\> | 审核标签
audit_source | string | 审核来源，如 `manual`
created_at | string | 创建时间
updated_at | string | 更新时间

**输出示例:**

```json
{
  "list": [{
    "id": "19xxxx",
    "created_at": "2026-07-25T12:00:00Z",
    "updated_at": "...",
    "merchant_id": "1",
    "member_id": "2",
    "order_sn": "19yyyy",
    "amount": "100",
    "fee": "2",
    "received_amount": "98",
    "channel": "pay4z",
    "method": "pix",
    "account_extra": {
      "account_type": "CPF",
      "account": "12345678909",
      "cpf": "12345678909",
      "email": "",
      "phone": "",
      "name": "Joao"
    },
    "third_order_sn": "",
    "remark": "",
    "status": 0,
    "audit_tags": [],
    "audit_source": "manual"
  }],
  "total": 3
}
```

---

## PIX 提现通道列表 {#withdraw_pix_channels}

> **已废弃**：请改用【[提款配置](#withdraw_config)】。本端点仍可用且行为不变，但不再接收新特性；新接入的提款方式（新国家）不会出现在此端点。

返回启用中的 PIX 代付通道（含最小/最大额度），供玩家选择。

**URL:** `GET` /finance/withdraw-pix/channels

**输出:**

字段 | 类型 | 说明
---|---|---
data | list<object\> | 通道列表

通道单项字段：

字段 | 类型 | 说明
---|---|---
channle | string | 通道代码（注：原文拼写）
type | object | PIX 账户类型枚举映射 `{CPF, Phone, Email}`
name | string | 通道展示名
min_amount | string | 单笔最小金额
max_amount | string | 单笔最大金额

---

## 创建 PIX 提现 {#withdraw_pix_create}

> **已废弃**：请改用【[创建提现订单（统一）](#withdraw_create)】。本端点仍可用且行为不变，但不再接收新特性；新接入的提款方式（新国家）不会出现在此端点。

本端点同样执行 [统一提现的双钱包校验](#withdraw_create)：仅扣现金余额 `user_money`，旧打码与全部待完成任务均须清零；余额不足或打码未达标时返回相同业务错误。

下单前玩家必须先通过 [保存 PIX 收款资料](#pix_create) 保存一条 PIX 资料（CPF / EMAIL / PHONE 任选一种）；接口会用已保存的 `account_type / account / name` 以及该会员已填写的 `cpf / email / phone` 快照到订单上。

同时校验：
- 会员 `auto_withdraw` 不为「禁止」；
- 会员已保存 PIX 资料且姓名非空，`account_type` 是 `CPF / EMAIL / PHONE` 之一；
- 该会员无未完成提现单（状态在 0/1/4 中，跨提现方式全局判定）；
- 通道已启用，金额满足通道与提现配置的单笔上下限。

下单成功即冻结余额并写入订单，状态 `WithdrawWait(0)`。

**URL:** `POST` /finance/withdraw-pix/create

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
amount | string | 是 | 金额，必须 > 0
channel | string | 是 | 通道代码（取自 `withdraw-pix/channels`）
remark | string | 否 | 备注，长度不超过 255

> PIX 账号信息（account_type / account / cpf / email / phone / name）不再从接口输入，全部从已保存的 PIX 资料读取。如需修改，先调用 `/finance/pix/create` 覆盖。

**输出:**

字段 | 类型 | 说明
---|---|---
id | string | 提现订单 ID
order_sn | string | 订单号

**业务错误:**

message | 触发条件
---|---
金额必须大于 0 | `amount` ≤ 0
您的账户已被禁止提现 | 会员 `auto_withdraw` 为「禁止」
游客账号不支持提现，请先绑定正式账号 | 当前会员为游客账号（`is_guest=true`，见【[游客注册](100_auth.md#guest_register)】）

---

## 我的 PIX 提现订单 {#withdraw_pix_my}

> **已废弃**：请改用【[我的提现订单（统一）](#withdraw_my)】。本端点仍可用且行为不变，但不再接收新特性；新接入的提款方式（新国家）不会出现在此端点。

**URL:** `GET` /finance/withdraw-pix/my

**Query:**

字段 | 类型 | 必填 | 说明
---|---|---|---
status | string | 否 | 按 `Withdraw.status` 过滤，支持逗号分隔多值，如 `0,1`
page | number | 否 | 页码
size | number | 否 | 每页条数

**输出:**

字段 | 类型 | 说明
---|---|---
list | list<WithdrawPix\> | 提现订单列表（按 `id DESC`）
total | number | 总数

`WithdrawPix` 字段：

字段 | 类型 | 说明
---|---|---
id | string | 订单 ID
member_id | string | 会员 ID
order_sn | string | 订单号
amount | string | 申请金额
fee | string | 手续费
received_amount | string | 实际到账金额
channel | string | 通道代码
account_type | string | PIX 账户类型（下单时使用）
account | string | PIX 账号（下单时使用，与 cpf/email/phone 中之一对应）
cpf | string | 会员 PIX 资料中的 CPF 快照
email | string | 会员 PIX 资料中的邮箱快照
phone | string | 会员 PIX 资料中的手机号快照
name | string | 收款人姓名
remark | string | 备注
third_order_sn | string | 三方订单号
status | number | 订单状态
created_at | string | 创建时间
updated_at | string | 更新时间

---

## 获取我的 PIX 收款资料 {#pix_index}

> **已废弃**：请改用【[获取收款资料（统一）](#withdraw_account_get)】。本端点仍可用且行为不变，但不再接收新特性；新接入的提款方式（新国家）不会出现在此端点。

**URL:** `GET` /finance/pix/index

**输出:**

字段 | 类型 | 说明
---|---|---
data | object 或 null | PIX 收款资料；尚未保存返回 `null`

PIX 资料字段：

字段 | 类型 | 说明
---|---|---
id | string | PIX 记录 ID
member_id | string | 会员 ID
account_type | string | 账户类型，`CPF` / `EMAIL` / `PHONE`
account | string | 当前账号（与 cpf/email/phone 中之一对应）
cpf | string | CPF
email | string | 邮箱
phone | string | 手机号
name | string | 收款人姓名

---

## 获取 PIX 账户类型枚举 {#pix_config}

> **已废弃**：请改用【[提款配置](#withdraw_config)】。本端点仍可用且行为不变，但不再接收新特性；新接入的提款方式（新国家）不会出现在此端点。

**URL:** `GET` /finance/pix/config

**输出:**

字段 | 类型 | 说明
---|---|---
data.account_types | list<string\> | 可选账户类型，`["CPF", "PHONE", "EMAIL"]`

---

## 保存 PIX 收款资料 {#pix_create}

> **已废弃**：请改用【[保存收款资料（统一）](#withdraw_account_save)】。本端点仍可用且行为不变，但不再接收新特性；新接入的提款方式（新国家）不会出现在此端点。

按 (merchant_id, member_id) upsert：玩家无记录则创建，已存在则覆盖账户类型 / 账号 / 姓名。

**URL:** `POST` /finance/pix/create

**输入:**

字段 | 类型 | 必填 | 说明
---|---|---|---
account_type | string | 是 | `CPF` / `EMAIL` / `PHONE`
account | string | 是 | 账号，长度不超过 100
name | string | 否 | 收款人姓名，长度不超过 50

**输出:**

字段 | 类型 | 说明
---|---|---
id | string | PIX 记录 ID

---

## 删除 PIX 收款资料 {#pix_remove}

> **已废弃**：请改用【[删除收款资料（统一）](#withdraw_account_remove)】。本端点仍可用且行为不变，但不再接收新特性；新接入的提款方式（新国家）不会出现在此端点。

**URL:** `POST` /finance/pix/remove

**输入:** 无

**输出:** 无

---

## 获取账户余额 {#account_balance}

未创建账户时返回零值视图。

**URL:** `GET` /finance/account/balance

**输出:**

字段 | 类型 | 说明
---|---|---
data.user_money | string | 现金余额；不是总余额，也不等于当前可提现金额
data.bonus_money | string | 奖金余额；不可直接提现
data.total | string | 总余额，等于 `user_money + bonus_money`，不可作为可提现金额；**不包含** `free_money`
data.free_money | string | 免费账户余额（体验金，供【[免费账户启动游戏](300_game.md#launch_free)】使用，即【[免费注册奖金](418_promotion_free_register_bonus.md)】的进度分子）。与 `user_money` / `bonus_money` 完全隔离，不可提现、不可转入真实余额、不计打码量；首笔充值到账后为冻结值，累计充值达标拆笔后清零，去向见该活动 `detail.record`
data.free_granted_at | string | 免费额度领取时间，未领取为 `null`；发放一次后用完不补。到期时刻、达标金额等活动规则见【[免费注册奖金 detail](418_promotion_free_register_bonus.md#detail)】
data.bet_amount | string | 旧剩余打码量；不包含 `task_wager_remaining`，不能仅凭其为 `0` 判断可提现
data.task_wager_remaining | string | 所有 `pending` 任务的 `target_amount - progress_amount` 之和
data.pending_task_count | number | 待完成任务数
data.can_withdraw | bool | 仅表示打码门槛已满足：旧剩余打码量为 `0` 且没有 `pending` 任务；仍需满足余额、账号资格、通道限额等提现校验
data.can_abandon_all | bool | 商户允许主动放弃（默认关闭），且至少有一个 `pending` 任务，且全部 `pending` 任务（包括排队任务）的 `can_abandon` 为 `true`；不表示旧打码可放弃
data.block_reasons | list<string\> | 打码阻断原因：`legacy_wager`（旧打码未清零）、`pending_tasks`（存在待完成任务），可同时出现；无阻断时为 `[]`，不包含其他提现校验原因
data.total_bet_amount | string | 累计投注统计兼容字段，不是任务进度，也不能作为可提现判据
data.first_deposit_amount | string | 首充金额（首充当日商户时区累计充值）

`user_money` 与 `bonus_money` 是标准余额字段，不是兼容别名；响应不返回 `cash`、`bonus` 字段。

免费账户余额 `free_money` **不参与提现与打码**：不计入 `total`、不影响 `can_withdraw` / `bet_amount` / `task_wager_remaining`，仅用于游客体验金试玩。

旧剩余打码仅使用 `bet_amount`，响应不返回 `legacy_wager_remaining`、`required_bet_amount`。总剩余打码量按 `bet_amount + task_wager_remaining` 计算，不另设总剩余字段。

整体放弃只公开综合资格 `can_abandon_all`，不新增商户开关响应字段或玩家参数。商户关闭时，即使所有任务快照均可放弃，新放弃请求仍被拒绝；开启也不能覆盖不可放弃的充值任务。切换影响已有待完成任务的资格，不修改快照、旧 `bet_amount`、提现或系统耗尽清理规则。策略直接读配置、无本地缓存，关闭影响后续读取，不撤销已通过校验的在途事务，也不保证跨库瞬时原子生效。提交及已成功请求重试规则见 [整体放弃](201_wager_tasks.md#abandon_all)。

---

## 首充广告上报数据 {#recharge_first_report}

供玩家端（客户端）判断是否需要触发首充广告像素（Facebook Pixel / Kwai Pixel / TikTok Pixel 等）。

服务端逻辑：

- 玩家 `is_ad_reported=true`（历史已上报）→ 返回空数据
- 玩家无首充记录（`Account.first_deposit_at` 为空）→ 返回空数据
- 首充时间距今超过 24 小时 → 服务端**标记为已上报**后返回空数据
- 玩家有上级邀请人（`inviter_id > 0`，非一级用户）→ 返回空数据
- 无成功充值订单 → 返回空数据
- 玩家注册来源的推广链路（`PromoterLink`）不存在 → 返回空数据
- 推广链路的 `channel_type` **不含 `PIXEL`**（即服务端 API 上报或其它非像素类型）→ 服务端**标记为已上报**后返回空数据
- 满足以上过滤后，返回最近一笔成功充值的信息（`is_first_recharge=1`），前端读取到 `is_first_recharge=1` 后应立即触发对应像素上报

**URL:** `GET` /finance/recharge/first_report

**输出:**

字段 | 类型 | 说明
---|---|---
data.is_first_recharge | number | `1` 需要触发像素；`0` 无需触发（其它字段为空/零值）
data.order_no | string | 首充订单号（`is_first_recharge=1` 时有效）
data.pay_amount | string | 首充金额（`is_first_recharge=1` 时有效）
data.updated_at | number | 首充订单最后更新时间戳（秒；`is_first_recharge=1` 时有效）
