openapi: 3.1.0
info:
  title: One23 Pay 平台接口
  version: 1.0.0
  summary: >-
    六个签名 JSON 接口与两个 Webhook，面向在 Northgate Digital 接入收款与付款的平台团队。
  description: |
    平台对接参考。所有调用都是 HTTPS 上的 JSON 请求，由四个请求头完成鉴权，并以
    HMAC-SHA256 签名与请求体绑定。接口一共六个，Webhook 两个；没有别的，也没有可选项。

    > **动手写代码之前，先读完签名这一节。** 卡住的接入几乎都卡在规范字符串上，
    > 而原因通常是签名之后又把请求体重新序列化了一遍。签名覆盖的是字节，不是对象。

    ## 环境与地址

    | 用途 | 地址 |
    |---|---|
    | 平台接口 | 上方 `servers` 中的地址 |
    | 你的 Webhook 接收端 | 每笔订单由你通过 `webhook_url` 提供 |
    | 付款人回跳地址 | 每笔订单由你通过 `return_to` 提供 |

    没有独立的沙箱地址。测试凭证签发在同一个域名下，绑定在一个测试商户账号上，
    需要时向你的 Northgate Digital 对接人申请。

    ## 凭证

    每个商户账号发两个值：

    * **API Key** —— `pk_` 加 25 个字符，是公开标识，放在 `X-Api-Key` 里，
      用于告诉平台是谁在调用。
    * **API Secret** —— 48 个字符，是 HMAC 的密钥，永远不出现在请求里。
      一旦外泄就立即轮换；旧密钥被吊销的那一刻，用它算出的签名全部失效。

    ## 请求签名

    每个请求都带四个头：

    | 请求头 | 内容 |
    |---|---|
    | `X-Api-Key` | 你的 API Key。 |
    | `X-Timestamp` | Unix 秒。与平台时间相差 300 秒以内才接受。 |
    | `X-Nonce` | 16–64 个字符，同一商户 10 分钟内只能用一次。 |
    | `X-Signature` | 下面这串规范字符串的 HMAC-SHA256，小写十六进制。 |

    先拼出规范字符串，再用 API Secret 做 HMAC：

    ```
    "v2:" + METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + sha256hex(body or "")
    ```

    能不能验过，取决于四条：

    1. `METHOD` 大写 —— 是 `POST`，不是 `post`。
    2. `PATH` 只要路径，带前导斜杠，不带查询串也不带域名。查单时它里面含订单号：
       `/one23/collections/NGD-2026-08-000731`。
    3. `sha256hex` 算的是**你即将发出去的那串字节**。GET 没有请求体，因此算空串。
    4. 连接符是真正的换行，不是 `\` 和 `n` 这两个字符。

    ### 可复现的样例

    下面这组值是可复现的；本站的签名调试台内置同一组，并在浏览器里重新算一遍。

    | 输入 | 值 |
    |---|---|
    | Key | `pk_ngd0342live20260800000731` |
    | Secret | `northgatesecretnorthgatesecretnorthgatesecret342` |
    | `X-Timestamp` | `1787184000` |
    | `X-Nonce` | `9d4c17e6b0a35f28` |

    用这组值给 `POST /one23/collections` 签名，请求体为

    ```json
    {"rail":"bank","value":"36000.00","client_ref":"NGD-2026-08-000731","webhook_url":"https://ops.northgate-digital.io/payments/webhook","return_to":"https://ops.northgate-digital.io/checkout/return"}
    ```

    得到 `X-Signature: 7837fb4d997b9385162105fae3074afc2efe3b790c6fd186863cff5463db3b94`。

    同一组凭证、空请求体、`GET /one23/treasury`，得到
    `X-Signature: c1a40172b904f4847d6982be9e5fbc96372123882deb328d251331fc39a99da6`。

    两个都能复现，实现就是对的。两个都复现不出来，先逐字节比对你的规范字符串，
    别急着查别的。

    ## 响应信封

    所有接口都返回同一个四键对象。HTTP 200 本身不代表业务成功 —— 要看 `code`。

    ```json
    {
      "success": 1,
      "outcome": "SUCCESS",
      "detail": null,
      "result": { },
      "trace_id": "0f1b7d2a-4c85-4e19-9a63-77c0e5b41d8a"
    }
    ```

    * `status` 成功为 `1`，失败为 `0`。
    * `code` 是机器可读的结果码，完整取值见 `ErrorCode`。
    * `message` 在有校验文案时给出，否则为 `null`。它写给你的工程师看，
      不是写给付款人看的 —— 不要直接展示给客户。
    * `data` 失败时是 `[]`，不是 `{}`。客户端的类型定义要照这个来。
    * `trace_id` 是这次调用在平台日志里的编号。**记下来。**
      技术支持第一句话就会问它。

    ## 资金以 Webhook 为准

    创建成功只代表订单被受理，不代表钱已经到账。终态由 `webhook_url` 推送，
    这次推送才是对账的依据。因此有两条：

    * Webhook 处理必须**幂等** —— 同一笔订单可能被推送多次，包括运营手动重推；
    * 查单接口是漏推之后的补救手段，不是主链路。

    ## 限制

    * 每商户每分钟 300 次；无法解析到商户的 Key，按来源 IP 每分钟 20 次。
    * 金额一律是十进制字符串，不是浮点数。写 `"36000.00"`，
      不要写 `36000.0`，也不要写 `36000`。
    * `client_ref` 由你生成，在你的账号内必须唯一，比对时不区分大小写。
  contact:
    name: Northgate Digital 平台支持
  x-profile-digest: sha256:c96a2a4b3c595c84439a0c5d69b99281a61961f7ce287059ea84e71ac4626fb1
servers:
  - url: https://apihk.one23payz.com
    description: 平台域名，接口第 2 版
tags:
  - name: 资金流入
    description: 向付款人收款，订单终态由 Webhook 结算。
  - name: 资金流出
    description: 向收款人付款，订单终态由 Webhook 结算。
  - name: 平台数据
    description: 账户状态，以及上面两条流程需要的参考数据。

security:
  - ApiKey: []
    Timestamp: []
    Nonce: []
    Signature: []

paths:
  /one23/collections:
    post:
      tags: [资金流入]
      operationId: createDeposit
      summary: 开一笔收款订单
      description: |
        登记一笔收款意图，返回手续费拆分，以及 `data.deposit_instructions` ——
        要摆在付款人面前的收款账户信息。

        ## 要展示什么

        ```json
        "deposit_instructions": {
          "bank_name": "Pacific Commercial Bank",
          "credit_name": "NORTHGATE DIGITAL LIMITED",
          "credit_account": "883014772506",
          "allocated_amount": "36000.00",
          "return_to": "https://pay.one23payz.com/checkout/DP1787184000NG02XA41QP88"
        }
        ```

        要么展示四个账户字段让付款人手动转账，要么把他送到 `return_to` 走
        托管收银台。同一次会话里不要两个都做。

        ## 动手前该知道的规则

        * `allocated_amount` 才是付款人要转的数字，它可能与你提交的金额差一分 ——
          平台会分配一个可区分的金额，好让入账能被匹配上。展示
          `allocated_amount`，永远不要展示你自己请求里的值。
        * 复用 `client_ref` 会返回 `DUPLICATE_TRANSACTION`。它既不会再建一笔，
          也不会把第一笔返回给你。
        * `value` 会按你账号上配置的收款上下限校验，最多两位小数。
        * 通道档位由金额决定，你的账号必须订阅了算出来的那一档，
          否则返回 `PAYMENT_METHOD_NOT_SUBSCRIBE`。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepositCreateRequest'
            examples:
              goldenVector:
                summary: 上文那组样例，逐字节一致
                description: |
                  用 Key `pk_ngd0342live20260800000731`、Secret
                  `northgatesecretnorthgatesecretnorthgatesecret342`、
                  `X-Timestamp: 1787184000`、`X-Nonce: 9d4c17e6b0a35f28` 签名，
                  这个请求体得到
                  `X-Signature: 7837fb4d997b9385162105fae3074afc2efe3b790c6fd186863cff5463db3b94`。
                value:
                  rail: bank
                  value: "36000.00"
                  client_ref: NGD-2026-08-000731
                  webhook_url: https://ops.northgate-digital.io/payments/webhook
                  return_to: https://ops.northgate-digital.io/checkout/return
              namedPayer:
                summary: 事先带上付款人姓名
                value:
                  rail: bank
                  value: "36000.00"
                  client_ref: NGD-2026-08-000732
                  webhook_url: https://ops.northgate-digital.io/payments/webhook
                  return_to: https://ops.northgate-digital.io/checkout/return
                  sender_name: LEE KA WING
      responses:
        '200':
          description: 订单已受理，收款信息随之返回。
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      result:
                        $ref: '#/components/schemas/DepositCreateData'
              examples:
                accepted:
                  value:
                    success: 1
                    outcome: SUCCESS
                    detail: null
                    result:
                      settlement_id: DP1787184000NG02XA41QP88
                      client_ref: NGD-2026-08-000731
                      billed_amount: "36000.00"
                      commission: "360.00"
                      cleared_amount: "35640.00"
                      sender_name: ""
                      deposit_instructions:
                        bank_name: Pacific Commercial Bank
                        credit_name: NORTHGATE DIGITAL LIMITED
                        credit_account: "883014772506"
                        allocated_amount: "36000.00"
                        return_to: https://pay.one23payz.com/checkout/DP1787184000NG02XA41QP88
                    trace_id: 0f1b7d2a-4c85-4e19-9a63-77c0e5b41d8a
        '403': { $ref: '#/components/responses/AuthError' }
        '422': { $ref: '#/components/responses/ValidationError' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/ServerError' }

  /one23/collections/{client_ref}:
    get:
      tags: [资金流入]
      operationId: getDeposit
      summary: 查一笔收款订单
      description: |
        用你给订单起的 `client_ref` 查询，范围限定在你自己的账号内，比对不区分大小写。

        订单号是签名 `PATH` 的一部分，所以要签
        `/one23/collections/NGD-2026-08-000731`，而不是 `/one23/collections`。
        查单接口报 `INVALID_SIGNATURE`，绝大多数就是这一条。

        把它当成漏收 Webhook 时的补救手段，不要拿它替代接收 Webhook。
      parameters:
        - $ref: '#/components/parameters/ClientRef'
      responses:
        '200':
          description: 平台当前持有的订单状态。
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      result:
                        $ref: '#/components/schemas/DepositInquiryData'
              examples:
                settled:
                  value:
                    success: 1
                    outcome: SUCCESS
                    detail: null
                    result:
                      settlement_id: DP1787184000NG02XA41QP88
                      client_ref: NGD-2026-08-000731
                      sender_name: "-"
                      phase: COMPLETE
                      billed_amount: "36000.00"
                      commission: "360.00"
                      cleared_amount: "35640.00"
                    trace_id: 4a90c3f1-2b76-4d0e-8c55-1e3fa6b9d247
                unknownReference:
                  value:
                    success: 0
                    outcome: TRANSACTION_NOT_FOUND
                    detail: null
                    result: []
                    trace_id: 4a90c3f1-2b76-4d0e-8c55-1e3fa6b9d247
        '403': { $ref: '#/components/responses/AuthError' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/ServerError' }

  /one23/remittances:
    post:
      tags: [资金流出]
      operationId: createWithdrawal
      summary: 开一笔付款订单
      description: |
        指示平台向收款人付款。选一个通道，只发这个通道要的字段 ——
        下面两个请求示例就是各自的完整字段集。

        | 通道 | 需要发送 |
        |---|---|
        | `bank` | `rail`、`value`、`client_ref`、`webhook_url`、`institution_code`、`credit_account`、`credit_name` |
        | `fps` | `rail`、`value`、`client_ref`、`webhook_url`、`credit_mobile` |

        ## 手续费怎么算

        付款的手续费是**外加**而不是内扣：你的余额减少的是
        `cleared_amount = value + commission`。核对可用资金时要把这一项算进去 ——
        余额恰好等于 `value` 的账号付不出这笔款，会返回
        `MERCHANT_INSUFFICIENT_BALANCE`。

        这里的 `value` 必须正好两位小数。`42000` 和 `42000.5` 都会被拒，
        `42000.00` 才行。这比收款接口严，是刻意的 —— 付款不可撤销。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawCreateRequest'
            examples:
              toBankAccount:
                summary: 银行通道
                value:
                  rail: bank
                  value: "42000.00"
                  client_ref: NGD-P-2026-000217
                  webhook_url: https://ops.northgate-digital.io/payments/payout-webhook
                  credit_account: "744820159337"
                  credit_name: WONG SIU FUNG
                  institution_code: "021"
              toFpsIdentifier:
                summary: FPS 通道
                value:
                  rail: fps
                  value: "42000.00"
                  client_ref: NGD-P-2026-000218
                  webhook_url: https://ops.northgate-digital.io/payments/payout-webhook
                  credit_mobile: "69820431"
      responses:
        '200':
          description: 付款已受理，等待放款。
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      result:
                        $ref: '#/components/schemas/WithdrawCreateData'
              examples:
                queued:
                  value:
                    success: 1
                    outcome: SUCCESS
                    detail: null
                    result:
                      settlement_id: WT1787184000NGP0217X
                      client_ref: NGD-P-2026-000217
                      billed_amount: "42000.00"
                      commission: "147.00"
                      cleared_amount: "42147.00"
                    trace_id: 8c25e714-9fd3-4a60-b1e8-53d90a7c6f2b
        '403': { $ref: '#/components/responses/AuthError' }
        '422': { $ref: '#/components/responses/ValidationError' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/ServerError' }

  /one23/remittances/{client_ref}:
    get:
      tags: [资金流出]
      operationId: getWithdrawal
      summary: 查一笔付款订单
      description: |
        用你自己的订单号查询付款单，范围限定在你的账号内，不区分大小写。
        与收款查单一样，订单号在签名 `PATH` 里面。
      parameters:
        - $ref: '#/components/parameters/ClientRef'
      responses:
        '200':
          description: 平台当前持有的付款单状态。
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      result:
                        $ref: '#/components/schemas/WithdrawInquiryData'
              examples:
                released:
                  value:
                    success: 1
                    outcome: SUCCESS
                    detail: null
                    result:
                      settlement_id: WT1787184000NGP0217X
                      client_ref: NGD-P-2026-000217
                      phase: COMPLETE
                      billed_amount: "42000.00"
                      commission: "147.00"
                      cleared_amount: "42147.00"
                    trace_id: b7e0421d-6a33-49cf-90d5-c81f2e63a5b4
        '403': { $ref: '#/components/responses/AuthError' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/ServerError' }

  /one23/institutions:
    get:
      tags: [平台数据]
      operationId: listBanks
      summary: 取收款银行列表
      description: |
        当前可用于付款的银行。从这里取 `institution_code`，发在 `rail: bank` 的
        付款请求里；这些编码不足以硬编码在代码中，请定期刷新，
        不要把它固化进你的部署。
      responses:
        '200':
          description: 当前全部可用银行。
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      result:
                        type: array
                        items:
                          $ref: '#/components/schemas/Bank'
              examples:
                reachable:
                  value:
                    success: 1
                    outcome: SUCCESS
                    detail: null
                    result:
                      - { institution_id: 1, institution_name: Pacific Commercial Bank, institution_code: "021" }
                      - { institution_id: 2, institution_name: Kowloon Union Bank, institution_code: "038" }
                    trace_id: 5d61af08-3c2e-4b97-8ad4-96e07b1c2f30
        '403': { $ref: '#/components/responses/AuthError' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/ServerError' }

  /one23/treasury:
    get:
      tags: [平台数据]
      operationId: getBalance
      summary: 读取账户余额
      description: |
        你的商户账号当前可用的资金，保留两位小数。付款从这个数字里扣，
        并且记住一笔付款消耗的是 `value + commission`。

        它也是验证新接入最省事的一个调用，因为它没有请求体：用 Key
        `pk_ngd0342live20260800000731`、Secret
        `northgatesecretnorthgatesecretnorthgatesecret342`、
        `X-Timestamp: 1787184000`、`X-Nonce: 9d4c17e6b0a35f28`，
        `GET /one23/treasury` 必须签出
        `X-Signature: c1a40172b904f4847d6982be9e5fbc96372123882deb328d251331fc39a99da6`。
      responses:
        '200':
          description: 账户当前可用资金。
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Envelope'
                  - type: object
                    properties:
                      result:
                        type: object
                        properties:
                          available_funds:
                            type: number
                            description: 可用资金，保留两位小数。
                            examples: [1284630.55]
              examples:
                available:
                  value:
                    success: 1
                    outcome: SUCCESS
                    detail: null
                    result: { available_funds: 1284630.55 }
                    trace_id: e3427c95-08b1-4f6a-bd23-70a5c9e14806
        '403': { $ref: '#/components/responses/AuthError' }
        '429': { $ref: '#/components/responses/RateLimited' }
        '500': { $ref: '#/components/responses/ServerError' }

webhooks:
  depositCallback:
    post:
      tags: [资金流入]
      operationId: depositCallback
      summary: 收款结算 Webhook
      description: |
        订单进入终态时推送到该订单的 `webhook_url`，运营手动重推时会再推一次。
        请求体是裸 JSON —— **不套**接口用的那层响应信封，所以不要去找 `data`。

        应该拿来记账的是这次推送，不是你的创建调用。

        ## 如何验签

        随推送带三个头：

        | 请求头 | 内容 |
        |---|---|
        | `X-Timestamp` | 签名那一刻的 Unix 秒。 |
        | `X-Nonce` | 每次推送重新生成；重复出现即视为重放。 |
        | `X-Signature` | 下面这串的 HMAC-SHA256，小写十六进制，用你的 API Secret 做密钥。 |

        回调的规范字符串里没有方法也没有路径，因为在你的处理程序看到它之前，
        你自己的代理可能已经改写过 URL。绑定只落在请求体的字节上：

        ```
        "v2-callback:" + TIMESTAMP + "\n" + NONCE + "\n" + sha256hex(rawJsonBody)
        ```

        请对**收到的原始字节**重新计算。把 JSON 解析完再序列化回去，
        键顺序或空格就变了，一次完全合法的推送也会比对失败。
        请用常量时间比较，对 `X-Timestamp` 施加你自己的时效窗口，
        并拒绝已经见过的 nonce。

        ## 可复现的推送

        时间戳 `1787184000`、nonce `9d4c17e6b0a35f28`、请求体

        ```json
        {"settlement_id":"DP1787184000NG02XA41QP88","client_ref":"NGD-2026-08-000731","sender_name":"-","phase":"COMPLETE","billed_amount":"36000.00","commission":"360.00","cleared_amount":"35640.00"}
        ```

        其哈希为 `c65c98bdcdba576e94bf223eb229beb521feb9514ec8de15f4ae862f13a041df`，
        签名为
        `X-Signature: 14a99e38b63b5d71253a38ad4285b891b14d5bb02e9d5ad66f9cab194039b17f`。
      parameters:
        - $ref: '#/components/parameters/CallbackTimestamp'
        - $ref: '#/components/parameters/CallbackNonce'
        - $ref: '#/components/parameters/CallbackSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepositCallbackBody'
            examples:
              settlement:
                summary: 上文那次可复现的推送
                value:
                  settlement_id: DP1787184000NG02XA41QP88
                  client_ref: NGD-2026-08-000731
                  sender_name: "-"
                  phase: COMPLETE
                  billed_amount: "36000.00"
                  commission: "360.00"
                  cleared_amount: "35640.00"
      responses:
        '200':
          description: |
            返回 200，别的什么都不用。其他状态码一律视为推送失败，
            平台运营可能会重推。

  withdrawCallback:
    post:
      tags: [资金流出]
      operationId: withdrawCallback
      summary: 付款结算 Webhook
      description: |
        付款单结算时推送到该付款单的 `webhook_url`。同样是裸 JSON，
        字段与收款 Webhook 相同，只少一个 `sender_name` ——
        那个字段只在有付款人的场景下存在。

        验签方式完全一致：`X-Timestamp`、`X-Nonce`、`X-Signature`，算的是

        ```
        "v2-callback:" + TIMESTAMP + "\n" + NONCE + "\n" + sha256hex(rawJsonBody)
        ```

        密钥是你的 API Secret。完整步骤见上面的收款 Webhook；一个处理程序
        可以同时服务两者。幂等在这里同样适用，而且更要紧：
        一笔付款在你的账上记了两次，就是一个看起来像资金短少的对账缺口。
      parameters:
        - $ref: '#/components/parameters/CallbackTimestamp'
        - $ref: '#/components/parameters/CallbackNonce'
        - $ref: '#/components/parameters/CallbackSignature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WithdrawCallbackBody'
            examples:
              release:
                summary: 一笔已结算的付款
                value:
                  settlement_id: WT1787184000NGP0217X
                  client_ref: NGD-P-2026-000217
                  phase: COMPLETE
                  billed_amount: "42000.00"
                  commission: "147.00"
                  cleared_amount: "42147.00"
      responses:
        '200':
          description: |
            返回 200 表示已接收。其他任何状态码都记为推送失败，可能被重推。

components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: 商户标识，`pk_` 加 25 个字符。公开值 —— 它不是 HMAC 的密钥。
    Timestamp:
      type: apiKey
      in: header
      name: X-Timestamp
      description: Unix 秒，纯数字。与平台时间前后相差超过 300 秒即拒绝。
    Nonce:
      type: apiKey
      in: header
      name: X-Nonce
      description: 16 到 64 个字符，同一商户在 10 分钟窗口内只能用一次。
    Signature:
      type: apiKey
      in: header
      name: X-Signature
      description: |
        以你的 API Secret 为密钥，对下面这串做 HMAC-SHA256，输出小写十六进制：
        `"v2:" + METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + sha256hex(body or "")`

  parameters:
    ClientRef:
      name: client_ref
      in: path
      required: true
      description: 你给订单起的号，6 到 200 个字符，比对不区分大小写。
      schema:
        type: string
        minLength: 6
        maxLength: 200
      example: NGD-2026-08-000731
    CallbackTimestamp:
      name: X-Timestamp
      in: header
      required: true
      description: 本次推送签名时的 Unix 秒。
      schema: { type: string }
      example: "1787184000"
    CallbackNonce:
      name: X-Nonce
      in: header
      required: true
      description: 一次性值，每次推送重新生成。
      schema: { type: string }
      example: 9d4c17e6b0a35f28
    CallbackSignature:
      name: X-Signature
      in: header
      required: true
      description: 回调规范字符串的 HMAC-SHA256 十六进制值。
      schema: { type: string }
      example: 14a99e38b63b5d71253a38ad4285b891b14d5bb02e9d5ad66f9cab194039b17f

  responses:
    AuthError:
      description: |
        这次调用没有被认定为可信。具体看 `code`：`INVALID_MERCHANT`、
        `MERCHANT_BLOCKED`、`SIGNATURE_EXPIRED`、`INVALID_SIGNATURE`、
        `IP_RESTRICTION`，或者服务在你的账号上被关闭时的
        `SERVICE_NOT_AVAILABLE`。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
          examples:
            signatureRejected:
              value:
                success: 0
                outcome: INVALID_SIGNATURE
                detail: null
                result: []
                trace_id: 2f8a05be-71c4-4d3e-9016-ba4e7d0c31f5
    ValidationError:
      description: 请求没有通过校验；`message` 给出发现的第一个问题。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
          examples:
            badChannel:
              value:
                success: 0
                outcome: INVALID_PARAMS
                detail: "rail must be one of: bank, fps"
                result: []
                trace_id: 2f8a05be-71c4-4d3e-9016-ba4e7d0c31f5
    RateLimited:
      description: 触发限流 —— 每商户每分钟 300 次，无法解析到商户的 Key 按 IP 每分钟 20 次。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
    ServerError:
      description: 平台内部未处理的故障。一律报为 `FAIL`，不给细节。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Envelope'
          examples:
            masked:
              value:
                success: 0
                outcome: FAIL
                detail: null
                result: []
                trace_id: 2f8a05be-71c4-4d3e-9016-ba4e7d0c31f5

  schemas:
    Envelope:
      type: object
      description: 所有接口共用的四键外层结构。
      required: [success, outcome, result, trace_id]
      properties:
        success:
          type: integer
          enum: [0, 1]
          description: 成功为 1，失败为 0。
        outcome:
          $ref: '#/components/schemas/ErrorCode'
        detail:
          type: [string, 'null']
          description: 有校验文案时给出，否则为 null。给日志看的，不是给付款人看的。
        result:
          description: 成功时的载荷。失败时是空数组，不是空对象。
        trace_id:
          type: string
          description: 本次调用在平台日志中的关联编号。请记录下来。
    ErrorCode:
      type: string
      description: 平台可能在 `code` 中给出的全部结果。
      enum:
        - SUCCESS
        - FAIL
        - INVALID_MERCHANT
        - MERCHANT_BLOCKED
        - API_V2_NOT_ENABLED
        - SIGNATURE_EXPIRED
        - INVALID_SIGNATURE
        - IP_RESTRICTION
        - INVALID_PARAMS
        - DUPLICATE_TRANSACTION
        - SERVICE_NOT_AVAILABLE
        - PAYMENT_METHOD_NOT_SUBSCRIBE
        - MERCHANT_INSUFFICIENT_BALANCE
        - TRANSACTION_NOT_FOUND
        - NO_SERVICE_PROVIDED
        - SERVICE_UNDER_MAINTENANCE
        - PAYMENT_GATEWAY_MAINTENENCE
        - TOO_MANY_REQUEST
        - PATH_NOT_FOUND
    DepositRail:
      type: string
      description: 开这笔收款订单所走的通道。
      enum: [bank, fps]
    WithdrawRail:
      type: string
      description: 放这笔款所走的通道。
      enum: [bank, fps]
    OrderPhase:
      type: string
      description: |
        订单当前所处的位置。`PENDING` 不是终态；另外三个是终态，之后不再变化。
      enum: [PENDING, COMPLETE, REJECT, OVERTIME]
    DepositCreateRequest:
      type: object
      description: 开一笔收款订单要发送的内容。
      required: [rail, value, client_ref, webhook_url, return_to, sender_name]
      properties:
        rail:
          $ref: '#/components/schemas/DepositRail'
        value:
          type: string
          description: 十进制字符串，最多两位小数。按你账号上配置的限额校验。
        client_ref:
          type: string
          description: 你给这笔订单起的号，在你的账号内唯一。
        webhook_url:
          type: string
          description: 这笔订单的结算 Webhook 推送到哪里。
        return_to:
          type: string
          description: 托管收银台处理完之后，把付款人送回哪里。
        sender_name:
          type: string
          description: 已知付款人姓名时填写，便于运营匹配入账。
    DepositCreateData:
      type: object
      description: 收款订单开出来之后返回的内容。
      properties:
        settlement_id:
          type: string
          description: 平台自己给这笔订单的编号。
        client_ref:
          type: string
          description: 你在请求里给出的订单号，原封不动带回。
        billed_amount:
          type: string
          description: 这笔订单开出的金额。
        commission:
          type: string
          description: 手续费；收款场景下从收到的钱里内扣。
        cleared_amount:
          type: string
          description: 付款人付款之后，实际进入你余额的金额。
        sender_name:
          type: string
          description: 已记录的付款人姓名，尚不知晓时为空。
        deposit_instructions:
          type: object
          description: 要摆在付款人面前的收款账户信息。
    DepositInquiryData:
      type: object
      description: 平台当前持有的一笔收款订单。
      properties:
        settlement_id:
          type: string
          description: 平台给这笔订单的编号。
        client_ref:
          type: string
          description: 你自己的订单号。
        sender_name:
          type: string
          description: 已记录的付款人姓名，未采集到时是一个短横。
        phase:
          $ref: '#/components/schemas/OrderPhase'
        billed_amount:
          type: string
          description: 订单开出的金额。
        commission:
          type: string
          description: 已收取的手续费。
        cleared_amount:
          type: string
          description: 结算时入账的净额。
    WithdrawCreateRequest:
      type: object
      description: 开一笔付款订单要发送的内容。
      required: [rail, value, client_ref, webhook_url]
      properties:
        rail:
          $ref: '#/components/schemas/WithdrawRail'
        value:
          type: string
          description: 十进制字符串，必须正好两位小数。这里比收款严格。
        client_ref:
          type: string
          description: 你给这笔付款起的号，在你的账号内唯一。
        webhook_url:
          type: string
          description: 这笔付款的结算 Webhook 推送到哪里。
        institution_code:
          type: string
          description: 仅银行通道。取自银行列表接口的编码。
        credit_account:
          type: string
          description: 仅银行通道。收款人账号，纯数字。
        credit_name:
          type: string
          description: 仅银行通道。收款银行处登记的收款人姓名。
        credit_mobile:
          type: string
          description: 仅 FPS 通道。收款人登记的 FPS 标识。
        return_to:
          type: string
          description: 付款场景下会被接受但不使用；没有付款人需要送回。
    WithdrawCreateData:
      type: object
      description: 付款被受理之后返回的内容。
      properties:
        settlement_id:
          type: string
          description: 平台给这笔付款的编号。
        client_ref:
          type: string
          description: 你的订单号，原样回传。
        billed_amount:
          type: string
          description: 要付给收款人的金额。
        commission:
          type: string
          description: 手续费；付款场景下外加。
        cleared_amount:
          type: string
          description: 从你余额里扣掉的总额 —— 金额加手续费。
    WithdrawInquiryData:
      type: object
      description: 平台当前持有的一笔付款订单。
      properties:
        settlement_id:
          type: string
          description: 平台的编号。
        client_ref:
          type: string
          description: 你的订单号。
        phase:
          $ref: '#/components/schemas/OrderPhase'
        billed_amount:
          type: string
          description: 已付给收款人的金额。
        commission:
          type: string
          description: 本次放款收取的手续费。
        cleared_amount:
          type: string
          description: 从余额中扣除的总额。
    DepositCallbackBody:
      type: object
      description: 推送到收款 `webhook_url` 的裸 JSON。
      required: [settlement_id, client_ref, phase, billed_amount, commission, cleared_amount]
      properties:
        settlement_id:
          type: string
          description: 平台给这笔已结算订单的编号。
        client_ref:
          type: string
          description: 你的订单号 —— 用它来对上你的账。
        sender_name:
          type: string
          description: 平台采集到的付款人。
        phase:
          $ref: '#/components/schemas/OrderPhase'
        billed_amount:
          type: string
          description: 订单开出的金额。
        commission:
          type: string
          description: 已扣除的手续费。
        cleared_amount:
          type: string
          description: 入你余额的净额。
    WithdrawCallbackBody:
      type: object
      description: 推送到付款 `webhook_url` 的裸 JSON。
      required: [settlement_id, client_ref, phase, billed_amount, commission, cleared_amount]
      properties:
        settlement_id:
          type: string
          description: 平台给这笔已结算付款的编号。
        client_ref:
          type: string
          description: 你的订单号 —— 用它来对上你的账。
        phase:
          $ref: '#/components/schemas/OrderPhase'
        billed_amount:
          type: string
          description: 已发出的金额。
        commission:
          type: string
          description: 外加的手续费。
        cleared_amount:
          type: string
          description: 扣掉的总额。
    Bank:
      type: object
      description: 一家可用的收款银行。
      properties:
        institution_code:
          type: string
          description: 付款时作为 `institution_code` 发送的值。
        institution_name:
          type: string
          description: 银行的展示名称。
        institution_id:
          type: integer
          description: 平台自己的行标识。不是编码 —— 不要发它。
