OpenAPI
开放平台接入文档接入总览

开放平台接入文档

接入总览

商户通过 OpenAPI 创建入金、出金订单,并查询汇率、余额与订单状态。生产请求必须同时通过商户状态、OpenAPI 权限、IP 白名单、RSA 请求加密、HMAC-SHA256 签名、5 分钟时间窗和一次性 nonce 校验。

6个 POST 接口5 min签名时间窗120/min每商户与来源 IP8 KB加密数据解码上限
与当前服务端协议同步

上线前检查

  1. 确认商户状态正常且 OpenAPI 权限已由管理员启用。
  2. 在商户后台复制 UID;业务 JSON 和外层 envelope 的 merchantNo 必须都使用该 UID。
  3. 配置所有调用服务器的固定出口 IPv4 或 IPv4 CIDR,禁止使用全网通配地址。
  4. 配置可公网解析的回调 URL,并确保接收端不会重定向请求。
  5. 安全保存请求加密公钥和仅显示一次的 HMAC signing secret;同步系统时间。
  6. 回调接收端准备 eventId 唯一约束、验签逻辑,以及可选的回调解密私钥。

请求加密与签名

先把包含 merchantNo 的业务 JSON 按 UTF-8 编码,并使用平台请求加密公钥执行 RSA PKCS#1 v1.5 分段加密,Base64 后得到 encryptedData;再用商户 HMAC signing secret 对六行 canonical string 签名,输出无填充 Base64URL。

Business JSONUTF-8 JSON

内外层 merchantNo 必须一致。

encryptedDataRSA/ECB/PKCS1Padding

按 modulusBytes - 11 分段,拼接密文后使用标准 Base64。

signatureHMAC-SHA256

对精确六行字符串签名,结果使用无填充 Base64URL。

请求 canonical string 按以下顺序组成,使用 LF 换行且末尾没有换行。method 必须是大写 POST,path 只包含 URL pathname,不包含域名、query 或 fragment。

merchantNo=MCH-DEMO-1001
method=POST
path=/api/open/order/createBuyOrder
timestamp=1783737600000
nonce=dGVzdC1ub25jZS0wMDAx
encryptedData=BASE64_RSA_CIPHERTEXT

timestamp 必须是 13 位 Unix 毫秒;nonce 必须是 16-128 位 Base64URL/安全字符且每次请求唯一。签名计算必须覆盖最终发送的 encryptedData,任何空格、路径或密文变化都会使签名失效。

密钥管理

请求加密公钥

由平台生成,可复制到调用服务。平台私钥加密存储且不对商户展示。轮换后旧公钥立即不能用于新请求。

HMAC 签名密钥

由平台生成,只在注册成功或轮换时显示一次,同时用于请求签名和回调验签。只能保存在服务端密钥管理系统。

回调解密私钥

由商户浏览器本地生成且永不上传;平台只保存公钥。轮换时保留旧私钥,直到旧回调任务全部结束。

回调验签与解密

每个回调 Body 都是带 signature 的外层 envelope。必须先用同一 HMAC signing secret 验签并原子占用 eventId,再判断 payload 是否为 RSA-OAEP-SHA256 + AES-256-GCM 加密对象并解密,最后校验内外层商户号和订单号一致。

签名覆盖收到的 payload 原始对象语义:对象 key 递归按字典序排序、忽略值为 undefined 的对象字段、数组保持原顺序,并输出紧凑 JSON。必须先验签,再解密 payload。

{
  "eventId": "cb_1783737600000_abcd1234",
  "merchantNo": "MCH-DEMO-1001",
  "orderNo": "PI1783737600000ABC123",
  "timestamp": "1783737600000",
  "payload": {
    "encryption": "RSA_OAEP_SHA256_AES_256_GCM",
    "merchantNo": "MCH-DEMO-1001",
    "orderNo": "PI1783737600000ABC123",
    "encryptedKey": "BASE64_RSA_OAEP_SHA256_KEY",
    "iv": "BASE64_12_BYTE_IV",
    "tag": "BASE64_GCM_TAG",
    "encryptedData": "BASE64_AES_GCM_CIPHERTEXT",
    "encryptedAt": "2026-07-12T02:00:00.000Z"
  },
  "signature": "UNPADDED_BASE64URL_HMAC_SHA256"
}

回调 canonical string 同样使用 LF 且无末尾换行:

eventId=cb_1783737600000_abcd1234
merchantNo=MCH-DEMO-1001
orderNo=PI1783737600000ABC123
timestamp=1783737600000
payloadSha256=BASE64URL_SHA256_OF_STABLE_PAYLOAD_JSON
  1. 1

    校验 eventId、merchantNo、orderNo、timestamp、payload、signature,并拒绝超过 5 分钟的回调。

  2. 2

    计算 stable JSON payload SHA-256 和五行 canonical string,常量时间校验 HMAC signature。

  3. 3

    在数据库事务中用唯一约束原子占用 eventId;重复 eventId 不得再次更新订单或资金。

  4. 4

    payload.encryption 为 RSA_OAEP_SHA256_AES_256_GCM 时,用本地私钥解开 AES key,再用 AES-256-GCM 解密。

  5. 5

    核对解密后 MerchantNo/OrderNo 与已认证外层字段一致,幂等推进本地业务状态。

  6. 6

    成功后返回 HTTP 2xx,且 Body 为空、{"code":200}、{"success":true} 或纯文本 success;其他响应会进入重试。

幂等、重试与限流

merchantOrderNo

创建入金和出金时必填,并在同一商户内唯一。超时重试保持原值;收到 40007 后调用 queryOrder 回查。

nonce + signature

每一次发送和重试都生成新的 nonce、timestamp 和 signature。可复用同一业务 payload/encryptedData。

Retry policy

仅重试网络错误、408、429 和 5xx,并使用退避。普通 4xx 是确定性请求错误,应修正后再发。

Limits

每商户与来源 IP 默认每分钟 120 次;HTTP JSON 请求体上限 96 KB,encryptedData 解码后上限 8 KB。

全局错误码

错误码说明与处理建议
40000通用业务参数错误,也用于解密失败或订单不存在;请结合 message 检查具体原因。
40001商户号无效,merchantNo 不存在或填写错误。
40002商户账户已被暂停,当前不能继续创建订单或查询资金。
40003商户未开启 OpenAPI 权限,请在商户 API 安全配置中启用接口访问。
40004未配置回调地址,请在商户后台设置默认 callbackUrl,或在创建订单时传入合法回调地址。
40005请求来源 IP 不在商户白名单内,请将服务器出口 IP 加入白名单后重试。
40007商户订单号重复,同一商户下 merchantOrderNo 必须唯一;重复请求请先查询原订单状态。
40010暂不支持该法币币种,请使用平台已开启的 CNY、USD、HKD、THB、VND 等币种。
40011当前没有匹配的流动性池,平台暂时无法为该币种或金额创建订单。
40012没有可用广告,或订单金额不在广告支持的最小/最大额度范围内。
40013入金可能表示广告额度不足;出金表示商户可用 USDT 余额不足。
40014订单金额不是有效正数、精度或金额拆分无效,或超过业务限额。
40015该客户姓名已被风控名单拦截,请联系平台客服或管理员处理。
40016生产模式已禁用明文 JSON 请求,请使用加密且签名的 OpenAPI envelope。
40017回调地址不允许访问
40018IP 白名单包含不安全地址
40019HMAC-SHA256 请求签名缺失或无效,请检查签名密钥和 canonical string。
40020timestamp 无效或与平台时间相差超过 5 分钟。
40021nonce 已被使用;每次请求和每次重试都必须生成新 nonce。
40022创建入金或出金订单时 merchantOrderNo 不能为空。
40023HTTP 请求体超过接口允许大小。
40024HTTP 请求体不是有效 UTF-8 JSON。

SDK 与完整示例

Node.js 18+docs/openapi-sdk/node/client.js

仅使用内置 crypto 和 fetch。

Python 3.10+docs/openapi-sdk/python/client.py

pip install cryptography

PHP 8.1+docs/openapi-sdk/php/client.php

composer require phpseclib/phpseclib:^3.0

Java 17+docs/openapi-sdk/java/OpenApiExample.java

使用 JDK HttpClient 与 JCA。

const crypto = require("crypto");

const baseUrl = "https://pay.example.com";
const merchantNo = "MCH-DEMO-1001";
const signingSecret = process.env.CORRIDORPAY_SIGNING_SECRET;
const platformPublicKey = `-----BEGIN PUBLIC KEY-----
PASTE_PLATFORM_PUBLIC_KEY_HERE
-----END PUBLIC KEY-----`;

const payload = {
  merchantNo,
  amount: 1000,
  currencyType: "CNY",
  merchantOrderNo: "demo-001",
  customerName: "Alice",
  redirectUrl: "https://merchant.example.com/return",
  hideUsdt: false,
  paymentType: 1,
  callbackUrl: "https://merchant.example.com/callback",
  customerCountry: "CN",
  customerPhoneNumber: "13800000000",
  customerIdCardNumber: "110101199001010000"
};

function encryptPayload(data) {
  const key = crypto.createPublicKey(platformPublicKey);
  const blockSize = Math.floor((key.asymmetricKeyDetails?.modulusLength || 2048) / 8);
  const maxPayloadBlock = blockSize - 11;
  const input = Buffer.from(JSON.stringify(data), "utf8");
  const encryptedBlocks = [];

  for (let offset = 0; offset < input.length; offset += maxPayloadBlock) {
    encryptedBlocks.push(
      crypto.publicEncrypt(
        { key, padding: crypto.constants.RSA_PKCS1_PADDING },
        input.subarray(offset, offset + maxPayloadBlock)
      )
    );
  }

  return Buffer.concat(encryptedBlocks).toString("base64");
}

async function main() {
  const encryptedData = encryptPayload(payload);
  const timestamp = String(Date.now());
  const nonce = crypto.randomBytes(24).toString("base64url");
  const canonical = [
    `merchantNo=${merchantNo}`,
    "method=POST",
    "path=/api/open/order/createBuyOrder",
    `timestamp=${timestamp}`,
    `nonce=${nonce}`,
    `encryptedData=${encryptedData}`
  ].join("\n");
  const body = {
    merchantNo,
    timestamp,
    nonce,
    encryptedData,
    signature: crypto.createHmac("sha256", signingSecret).update(canonical, "utf8").digest("base64url")
  };

  const response = await fetch(baseUrl + "/api/open/order/createBuyOrder", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body)
  });

  console.log(response.status, await response.text());
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

全部接口

方法接口路径说明
POST/api/open/order/createBuyOrder创建商户入金订单,平台分配交易员收款方式并返回平台订单号和收银台地址。
POST/api/open/order/getBuyRate按商户号和法币币种查询当前入金汇率。
POST/api/open/order/getBalance查询商户 USDT 账面总余额和冻结余额。
POST/api/open/order/createSellOrder创建法币出金订单,平台冻结商户对应 USDT,等待后台分配和处理。
POST/api/open/order/getSellRate查询出金汇率,支持普通和加急策略。
POST/api/open/order/queryOrder按平台订单号或商户订单号查询入金/出金订单状态。
下一页创建入金订单