{
  "openapi": "3.1.0",
  "info": {
    "title": "CNIXPay Merchant API",
    "version": "2.0.0",
    "description": "CNIXPay 商户接口 V2。金额一律为十进制字符串（USD 2 位小数，原生币 6 位），时间一律为 Unix 秒。响应体固定为 { code, message, data, requestId }，HTTP 状态码跟随错误类型（错误码前三位即 HTTP 状态码）。message 按 Accept-Language 返回中文或英文。"
  },
  "servers": [{ "url": "https://api.cnixpay.com/api/v2" }],
  "security": [{ "ApiKey": [] }],
  "paths": {
    "/orders": {
      "post": {
        "operationId": "createOrder",
        "summary": "创建订单",
        "description": "商户订单号重复返回 409 / 40901，data.orderNo 为已有订单的系统订单号：下单超时、不确定是否成功时用它查询确认，不要换订单号重下。",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateOrderRequest" } } } },
        "responses": {
          "200": { "$ref": "#/components/responses/OrderResponse" },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "409": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      },
      "get": {
        "operationId": "listOrders",
        "summary": "订单列表 / 按订单号批量查",
        "description": "按 createTime 倒序，用 page / pageSize 翻页，列表项是完整的 Order（含 transactions）。可按 merchantOrderNo 批量查询本地未完结的订单，作为回调的兜底。",
        "parameters": [
          { "name": "merchantOrderNo", "in": "query", "description": "按商户订单号查（精确），可重复传，最多 100 个", "schema": { "type": "array", "items": { "type": "string" }, "maxItems": 100 }, "style": "form", "explode": true },
          { "name": "orderNo", "in": "query", "description": "按系统订单号查，可重复传，最多 100 个", "schema": { "type": "array", "items": { "type": "string" }, "maxItems": 100 }, "style": "form", "explode": true },
          { "name": "status", "in": "query", "description": "订单状态，可重复传", "schema": { "type": "array", "items": { "$ref": "#/components/schemas/OrderStatus" } }, "style": "form", "explode": true },
          { "name": "createTimeFrom", "in": "query", "description": "创建时间下限（Unix 秒，含）", "schema": { "type": "integer", "format": "int64" } },
          { "name": "createTimeTo", "in": "query", "description": "创建时间上限（Unix 秒，不含）", "schema": { "type": "integer", "format": "int64" } },
          { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } },
          { "name": "pageSize", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } }
        ],
        "responses": {
          "200": {
            "description": "成功",
            "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } },
            "content": { "application/json": { "schema": { "allOf": [
              { "$ref": "#/components/schemas/Envelope" },
              { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/OrderPage" } } }
            ] } } }
          },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/orders/{orderNo}": {
      "get": {
        "operationId": "getOrder",
        "summary": "查询订单",
        "parameters": [{ "$ref": "#/components/parameters/OrderNo" }],
        "responses": {
          "200": { "$ref": "#/components/responses/OrderResponse" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/orders/{orderNo}/payment-options": {
      "post": {
        "operationId": "addPaymentOptions",
        "summary": "追加收款方式",
        "description": "仅 pending、未收到付款且未过 expireTime 的订单可追加，否则 409 / 40902。已存在的收款方式幂等；原生币订单不能追加（409 / 40902）。合计不超过 10 条。",
        "parameters": [{ "$ref": "#/components/parameters/OrderNo" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": {
          "type": "object", "required": ["paymentOptions"],
          "properties": { "paymentOptions": { "type": "array", "minItems": 1, "maxItems": 10, "items": { "$ref": "#/components/schemas/PaymentOptionInput" } } }
        } } } },
        "responses": {
          "200": { "$ref": "#/components/responses/OrderResponse" },
          "400": { "$ref": "#/components/responses/Error" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "409": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/orders/{orderNo}/close": {
      "post": {
        "operationId": "closeOrder",
        "summary": "关闭订单",
        "description": "仅未收到付款的 pending 订单可关闭（含已过 expireTime、仍在宽限期内的）；已收到付款，或有链上确认中的到账（transactions 里 status 为 pending），返回 409 / 40902。关闭后 status = closed、closeReason = merchant，之后再到账不会自动入账。已由商户关闭的订单重复关闭视为成功。",
        "parameters": [{ "$ref": "#/components/parameters/OrderNo" }],
        "responses": {
          "200": { "$ref": "#/components/responses/OrderResponse" },
          "401": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "409": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/TooManyRequests" },
          "500": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "webhooks": {
    "orderEvent": {
      "post": {
        "summary": "订单回调事件",
        "description": "POST 到 callbackUrl。请求头：X-Webhook-Id（事件 ID，重试不变，用于去重）、X-Webhook-Timestamp（本次发送时间，每次重试重新生成）、X-Webhook-Signature（v2= + 小写 hex(HMAC-SHA256(API Secret, timestamp + \".\" + 原始请求体))）。商户应对原始请求体验签，并校验时间戳与当前时间相差 5 分钟内。返回 HTTP 2xx 视为成功；单次请求超时 5 秒，不跟随重定向（3xx 算失败）；失败按时间表重试约 22 小时。订单没有回调地址（下单未传、应用也未配置）时不推送。回调里的订单是事件发生那一刻的快照，可能乱序送达，以 version 大的为准。",
        "parameters": [
          { "name": "X-Webhook-Id", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "X-Webhook-Timestamp", "in": "header", "required": true, "schema": { "type": "string" } },
          { "name": "X-Webhook-Signature", "in": "header", "required": true, "schema": { "type": "string", "pattern": "^v2=[0-9a-f]{64}$" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEvent" } } } },
        "responses": { "2XX": { "description": "已收到" } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": { "type": "http", "scheme": "bearer", "description": "请求头 Authorization: Bearer <API Key>，使用应用的正式 API Key（测试 Key 返回 401 / 40101）" }
    },
    "parameters": {
      "OrderNo": { "name": "orderNo", "in": "path", "required": true, "description": "系统订单号", "schema": { "type": "string" } }
    },
    "headers": {
      "RequestId": { "description": "请求编号，与响应体 requestId 相同；反馈问题时提供", "schema": { "type": "string", "examples": ["req_1c9e4b2a7f3d8e6c5a0b9f12"] } }
    },
    "responses": {
      "OrderResponse": {
        "description": "成功，返回订单对象",
        "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } },
        "content": { "application/json": { "schema": { "allOf": [
          { "$ref": "#/components/schemas/Envelope" },
          { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Order" } } }
        ] } } }
      },
      "Error": {
        "description": "失败。code 见 ErrorCode；参数类错误 data.field 为出错的字段名，40901 时 data.orderNo 为已有订单",
        "headers": { "X-Request-Id": { "$ref": "#/components/headers/RequestId" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
      },
      "TooManyRequests": {
        "description": "请求过于频繁（42901），按 Retry-After 秒后重试。限流按应用计：创建 5 次/秒，查询与列表共 20 次/秒，追加收款方式 10 次/秒，关闭 5 次/秒",
        "headers": { "Retry-After": { "schema": { "type": "integer" } }, "X-Request-Id": { "$ref": "#/components/headers/RequestId" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
      }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "required": ["code", "message", "data", "requestId"],
        "properties": {
          "code": { "type": "integer", "description": "0 成功，非 0 见 ErrorCode" },
          "message": { "type": "string" },
          "data": {},
          "requestId": { "type": "string", "description": "请求编号，同响应头 X-Request-Id；反馈问题时提供" }
        }
      },
      "ErrorResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/Envelope" },
          { "type": "object", "properties": {
            "code": { "$ref": "#/components/schemas/ErrorCode" },
            "data": { "oneOf": [
              { "type": "null" },
              { "type": "object", "properties": { "field": { "type": "string" }, "orderNo": { "type": "string" } } }
            ] }
          } }
        ]
      },
      "ErrorCode": {
        "type": "integer",
        "enum": [40001, 40002, 40003, 40004, 40005, 40101, 40301, 40302, 40401, 40901, 40902, 42901, 50001],
        "description": "40001 参数缺失或格式错误；40002 金额格式错误（不是字符串、不大于 0 或超出小数位）；40003 金额超出单笔上限；40004 链或币种不支持 / 未开放；40005 收款方式数量超限；40101 API Key 缺失或无效（含测试 Key）；40301 请求 IP 不在应用白名单；40302 应用未上线、已停用，或商户账户已禁用；40401 订单不存在；40901 商户订单号已存在；40902 订单状态不允许此操作；42901 请求过于频繁；50001 系统繁忙"
      },
      "OrderStatus": {
        "type": "string",
        "enum": ["pending", "paid", "underpaid", "closed"],
        "description": "pending 还没有结果（待付款、已收到部分款、确认中、过期后的宽限期内）→ 等待；paid 已足额支付（多付也算）→ 发货；underpaid 宽限期结束只收到部分款，已按实收入账 → 人工处理；closed 没收到钱已关闭 → 作废。除 pending 外正常情况下都是最终结果；唯一例外是平台人工补单：closed 可能变为 paid 或 underpaid，underpaid 可能变为 paid，并推送对应事件。paid 不会再变"
      },
      "Chain": { "type": "string", "enum": ["tron", "bsc", "eth", "polygon", "solana"] },
      "CreateOrderRequest": {
        "type": "object",
        "required": ["merchantOrderNo", "amount"],
        "properties": {
          "merchantOrderNo": { "type": "string", "maxLength": 64, "description": "商户订单号，应用内唯一" },
          "amount": { "type": "string", "pattern": "^\\d+(\\.\\d+)?$", "description": "应收金额，十进制字符串，大于 0。USD 最多 2 位小数，原生币最多 6 位。传 JSON 数字返回 40002", "examples": ["13.89"] },
          "currency": { "type": "string", "enum": ["USD", "TRX", "BNB", "ETH", "POL", "SOL"], "default": "USD", "description": "报价币种。USD 订单按 USDT / USDC 1:1 收款；原生币订单只能用该币支付" },
          "paymentOptions": { "type": "array", "minItems": 1, "maxItems": 10, "items": { "$ref": "#/components/schemas/PaymentOptionInput" }, "description": "指定收款方式；不传时 USD 订单由收银台让付款人选择，原生币订单自动用该币所在的链" },
          "expireMinutes": { "type": "integer", "minimum": 5, "maximum": 1440, "description": "订单有效期（分钟），默认 30" },
          "callbackUrl": { "type": "string", "format": "uri", "maxLength": 500, "description": "回调地址，只允许 https，不能是本地或内网地址；不传用应用配置（也须为 https）" },
          "returnUrl": { "type": "string", "format": "uri", "maxLength": 500, "description": "收银台支付完成后跳转，只允许 https" },
          "description": { "type": "string", "maxLength": 200, "description": "订单摘要，收银台展示" },
          "extra": { "type": "string", "maxLength": 500, "description": "透传，查询和回调原样返回" }
        }
      },
      "PaymentOptionInput": {
        "type": "object",
        "required": ["chain", "token"],
        "properties": {
          "chain": { "$ref": "#/components/schemas/Chain" },
          "token": { "type": "string", "description": "USDT / USDC / 原生币符号", "examples": ["USDT"] }
        }
      },
      "Order": {
        "type": "object",
        "description": "订单对象：查询、列表、创建 / 追加 / 关闭的返回和回调都是它，字段齐全",
        "required": ["orderNo", "merchantOrderNo", "status", "closeReason", "version", "amount", "currency", "paidAmount", "feeAmount", "netAmount", "paymentOptions", "transactions", "txCount", "cashierUrl", "description", "extra", "createTime", "expireTime", "paidTime", "updateTime"],
        "properties": {
          "orderNo": { "type": "string", "description": "系统订单号" },
          "merchantOrderNo": { "type": "string" },
          "status": { "$ref": "#/components/schemas/OrderStatus" },
          "closeReason": { "type": "string", "enum": ["", "timeout", "merchant"], "description": "status 为 closed 时：timeout 超时未付款，merchant 商户关闭；其他为空" },
          "version": { "type": "integer", "description": "订单版本号，创建时为 1，对外可见字段每变化一次加 1；回调乱序时以 version 大的为准" },
          "amount": { "type": "string", "description": "应收金额（currency 计价）" },
          "currency": { "type": "string" },
          "paidAmount": { "type": "string", "description": "累计已到账（只算已确认的交易）；少付 / 多付由 amount 与 paidAmount 相减（按十进制计算）" },
          "feeAmount": { "type": "string", "description": "平台手续费；钱在 paid / underpaid 时入账，pending 时为 0。USD 订单按分计（四舍五入到分）" },
          "netAmount": { "type": "string", "description": "商户实收 = 入账金额 − 手续费，即实际进入商户余额的金额。USD 订单入账金额取整到分，不足一分的零头不入账" },
          "paymentOptions": { "type": "array", "items": { "$ref": "#/components/schemas/PaymentOption" } },
          "transactions": { "type": "array", "maxItems": 50, "items": { "$ref": "#/components/schemas/Transaction" }, "description": "链上到账明细，按到账顺序，最多最近 50 条；付款人实际用哪条链、哪个币付到哪个地址以这里为准。付错币的不列出" },
          "txCount": { "type": "integer", "description": "到账明细笔数" },
          "cashierUrl": { "type": "string", "description": "平台收银台链接" },
          "description": { "type": "string" },
          "extra": { "type": "string" },
          "createTime": { "type": "integer", "format": "int64" },
          "expireTime": { "type": "integer", "format": "int64" },
          "paidTime": { "type": "integer", "format": "int64", "description": "付足时间；未付足为 0" },
          "updateTime": { "type": "integer", "format": "int64", "description": "最近一次变化时间，与 version 同时更新" }
        }
      },
      "OrderPage": {
        "type": "object",
        "required": ["items", "total", "page", "pageSize"],
        "properties": {
          "items": { "type": "array", "items": { "$ref": "#/components/schemas/Order" } },
          "total": { "type": "integer" },
          "page": { "type": "integer" },
          "pageSize": { "type": "integer" }
        }
      },
      "PaymentOption": {
        "type": "object",
        "required": ["chain", "token", "address", "amount"],
        "properties": {
          "chain": { "$ref": "#/components/schemas/Chain" },
          "token": { "type": "string" },
          "address": { "type": "string", "description": "收款地址" },
          "amount": { "type": "string", "description": "该收款方式应付的链上资产数量" }
        }
      },
      "Transaction": {
        "type": "object",
        "required": ["chain", "token", "txHash", "fromAddress", "toAddress", "amount", "status", "blockTime"],
        "properties": {
          "chain": { "$ref": "#/components/schemas/Chain" },
          "token": { "type": "string", "description": "实际到账的币（USD 订单可能与下单时选的不同，如 USDT 地址收到 USDC）" },
          "txHash": { "type": "string" },
          "fromAddress": { "type": "string" },
          "toAddress": { "type": "string", "description": "收款地址" },
          "amount": { "type": "string", "description": "链上精度" },
          "status": { "type": "string", "enum": ["pending", "confirmed"], "description": "pending 确认中，confirmed 已确认（只有 confirmed 计入 paidAmount）" },
          "blockTime": { "type": "integer", "format": "int64" }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "required": ["id", "event", "createTime", "data"],
        "properties": {
          "id": { "type": "string", "description": "事件 ID，与请求头 X-Webhook-Id 相同" },
          "event": { "type": "string", "enum": ["order.partially_paid", "order.paid", "order.underpaid", "order.closed"], "description": "order.partially_paid 收到一笔付款但还不够（pending）；order.paid 足额到账（发货）；order.underpaid 宽限期结束只收到部分款（人工处理）；order.closed 宽限期结束没收到钱（作废）。商户主动关闭不推送；平台人工补单改变结果时会再推送 order.paid 或 order.underpaid" },
          "createTime": { "type": "integer", "format": "int64", "description": "事件发生时间，重试不变" },
          "data": { "$ref": "#/components/schemas/Order" }
        }
      }
    }
  }
}
