版本:v1.0(2026-09-30)| 适用对象:接入本平台的外部商户技术人员
本平台为多国聚合支付平台,采用「一国一收银台」模型:商户只需对接一次平台统一 API,即可使用各国收银台的代收 / 代付能力。上游通道的切换、权重、启停由平台侧完成,商户无感。
接入流程(5 步):
API Key 与 API Secret(见 §2)。notify_url(或在商户后台配置默认通知地址,以联调时确认为准),确保能接收并正确处理异步通知(见 §6)。| 名称 | 说明 | 在哪拿 |
|---|---|---|
| 商户号 | 平台分配的商户标识,如 M10001 |
开户时由运营提供 |
| API Key | 接口身份标识(公开,不参与签名) | 商户后台 → API 密钥管理 |
| API Secret | 接口签名密钥(绝密,参与签名与通知验签) | 商户后台 → API 密钥管理 → 「重置密钥」获取明文,只显示一次,请立即保存 |
⚠️ API Secret 明文平台不保存、不回显。遗失请重新重置,重置后旧密钥立即失效。
网关地址:
| 环境 | 网关地址 |
|---|---|
| 沙箱 / 测试 | http://45.76.155.11 |
| 生产 | 以开户时提供的为准 |
以下接口路径均为相对路径,如代收下单完整地址为 http://45.76.155.11/api/v1/pay/mch/payin/create。
支持的国家收银台(沙箱):
| 国家 | 国家代码 | 币种 | 支付方式编码 |
|---|---|---|---|
| 巴基斯坦 | PK |
PKR |
EP、JZ |
| 孟加拉 | BD |
BDT |
NG、BK、EP |
| 泰国 | TH |
THB |
THAI_QR |
| 印度 | IN |
INR |
UPI |
下单时
currency必须与收银台币种一致,pay_method必须在该国支持列表内,否则返回参数/业务错误。
所有商户统一 API 请求都必须签名。鉴权方式为请求头三件套:
| 请求头 | 说明 |
|---|---|
X-Api-Key |
你的 API Key(公开标识,不参与签名) |
X-Timestamp |
Unix 秒级时间戳,服务端允许 ±300 秒漂移,超时拒绝 |
X-Sign |
按下述算法计算的签名字符串 |
签名算法(7 步):
sign 字段(精确匹配小写 sign);null 或 ""(空字符串)时跳过;k1=v1&k2=v2&...&kn=vn 形式。值先字符串化:str(),即布尔为 True/False,注意不是 true/false);, 和 :、不转义 Unicode),即 json.dumps(v, sort_keys=True, separators=(",", ":"), ensure_ascii=False);&key={api_secret},api_secret 为你的接口密钥明文;X-Sign 的值。示例(api_secret = "testsecret123",body 为 {"country_code":"PK","pay_method":"EP","amount_minor":10000,"currency":"PKR","mch_order_no":"ORDER20260930001","notify_url":"https://merchant.example/notify"}):
待签串:
amount_minor=10000&country_code=PK¤cy=PKR&mch_order_no=ORDER20260930001¬ify_url=https://merchant.example/notify&pay_method=EP&key=testsecret123
X-Sign = MD5(待签串).upper()
注意:
mode、return_url、goods_desc、client_ip若传空字符串则不参与签名;若传了非空值则参与签名,body 与签名必须一致。
验签失败一律返回 HTTP 401,code=40100(签名验证失败 / 时间戳过期 / 缺少鉴权参数 / 商户不存在或被禁用)。
POST /api/v1/pay/mch/payin/create | Content-Type: application/json
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| country_code | string | 是 | 国家二字码,如 PK(大小写不敏感) |
| pay_method | string | 是 | 支付方式编码,如 EP(大小写不敏感) |
| mode | string | 否 | cashier=收银台 H5(默认),direct=直连 |
| amount_minor | int | 是 | 金额(最小货币单位,如 PKR 100.00 元传 10000),必须 > 0 |
| currency | string | 是 | 币种三字码,如 PKR,必须与收银台币种一致 |
| mch_order_no | string | 是 | 商户订单号,1–64 字符,同一商户下必须唯一,重复返回 40900 |
| notify_url | string | 是 | 异步通知地址(支付终态时平台 POST 通知,见 §6) |
| return_url | string | 否 | 同步跳转地址(收银台支付完成后用户跳回地址),默认空 |
| goods_desc | string | 否 | 商品描述,最长 128 字符,默认空 |
| client_ip | string | 否 | 用户 IP,默认空(为空时取请求来源 IP) |
响应(code=0):
{
"code": 0,
"msg": "下单成功",
"data": {
"platform_order_no": "PI20260930010101001",
"mch_order_no": "ORDER20260930001",
"pay_url": "http://45.76.155.11/api/v1/pay/cashier/PI20260930010101001",
"qr_data": "",
"status": "PENDING"
}
}
| 字段 | 说明 |
|---|---|
| platform_order_no | 平台订单号,代收前缀 PI + UTC 时间 yyyymmddHHMMSS + 6 位随机数 |
| pay_url | 收款链接:将用户引导至该地址完成支付(cashier 模式为平台收银台 H5) |
| qr_data | 二维码内容(有则展示,无则为空字符串) |
| status | 下单时状态,一般为 PENDING(待支付) |
POST /api/v1/pay/mch/payin/query | Content-Type: application/json
请求字段(二选一,至少传一个):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| mch_order_no | string | 条件 | 商户订单号 |
| platform_order_no | string | 条件 | 平台订单号 |
响应(code=0,data 为订单详情):
{
"code": 0,
"msg": "成功",
"data": {
"platform_order_no": "PI20260930010101001",
"mch_order_no": "ORDER20260930001",
"order_type": "payin",
"country_code": "PK",
"country_name": "巴基斯坦",
"pay_method": "EP",
"mode": "cashier",
"amount_minor": 10000,
"currency": "PKR",
"fee": 30,
"status": "SUCCESS",
"upstream_order_no": "UP202609300001",
"pay_url": "http://45.76.155.11/api/v1/pay/cashier/PI20260930010101001",
"qr_data": "",
"notified": true,
"paid_at": "2026-09-30T10:05:00+00:00",
"created_at": "2026-09-30T10:01:00+00:00"
}
}
订单状态说明:
| 状态 | 含义 |
|---|---|
| PENDING | 待支付 |
| PROCESSING | 处理中 |
| SUCCESS | 成功(终态) |
| FAILED | 失败(终态) |
| CANCELLED | 已取消(终态) |
| REVERSED | 已冲正(终态) |
POST /api/v1/pay/mch/payout/create | Content-Type: application/json
请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| country_code | string | 是 | 国家二字码,如 PK |
| pay_method | string | 是 | 支付方式编码,如 JZ |
| amount_minor | int | 是 | 金额(最小货币单位),必须 > 0 |
| currency | string | 是 | 币种三字码,必须与收银台币种一致 |
| mch_order_no | string | 是 | 商户订单号,1–64 字符,同一商户下必须唯一 |
| account_no | string | 是 | 收款账号 |
| account_name | string | 是 | 收款人姓名 |
| bank_code | string | 否 | 银行编码,默认空 |
| extra | object | 否 | 扩展字段(JSON 对象),默认 {} |
| notify_url | string | 否 | 异步通知地址,默认空(为空则不发送通知) |
| client_ip | string | 否 | 用户 IP,默认空 |
响应(code=0):
{
"code": 0,
"msg": "代付下单成功",
"data": {
"platform_order_no": "PO20260930010101002",
"mch_order_no": "PAYOUT20260930001",
"status": "PROCESSING"
}
}
平台订单号代付前缀为
PO。代付为资金出账操作,请确保商户余额充足。
POST /api/v1/pay/mch/payout/query | Content-Type: application/json
请求字段与响应格式同 §4.2(order_type 为 payout)。
订单进入终态(SUCCESS / FAILED / CANCELLED / REVERSED)且下单时传入了 notify_url,平台将向该地址发送异步通知。
POST,Content-Type: application/json,超时 10 秒;| 字段 | 类型 | 说明 |
|---|---|---|
| platform_order_no | string | 平台订单号 |
| mch_order_no | string | 商户订单号 |
| order_type | string | payin=代收,payout=代付 |
| status | string | 终态:SUCCESS / FAILED / CANCELLED / REVERSED |
| amount_minor | int | 金额(最小货币单位) |
| paid_amount | int | 实付金额(最小货币单位),无差异时等于 amount_minor |
| currency | string | 币种 |
| fee | int | 手续费(最小货币单位) |
| paid_at | string | 支付成功时间(ISO8601),失败时为空字符串 |
| timestamp | int | 通知发送时的 Unix 秒级时间戳 |
| sign | string | 签名(MD5 大写,算法同 §3,用你的 api_secret 计算;验签时排除 sign 本身与空值) |
验签:收到通知后,用 §3 算法对除 sign 外的字段重新计算签名,与 sign 比对(转大写后比对)。验签不通过的通知不得作为入账依据。
商户收到通知后应返回什么:HTTP 状态码 < 500,且响应体包含 SUCCESS 或 success(如直接返回纯文本 SUCCESS),平台即视为送达成功。
重试机制:首次发送失败后平台自动重试,最多 8 次,退避间隔为 1 分钟 → 5 分钟 → 30 分钟 → 2 小时 → 12 小时 → 12 小时 → 12 小时;8 次仍失败标记为不再重试。请做好幂等处理(同一订单可能重复通知,以 platform_order_no + status 去重)。
建议:不要完全依赖通知,重要订单主动调用查询接口对账。
统一响应体为 {"code": ..., "msg": "...", "data": ...}。code=0 表示成功。
| code | 说明 | 常见场景 |
|---|---|---|
| 0 | 成功 | — |
| 40000 | 参数错误 | 缺字段、字段格式非法、mode 非法、支付方式不在该国列表 |
| 40100 | 未登录或登录已过期(HTTP 401) | 缺少 X-Api-Key/X-Timestamp/X-Sign、签名验证失败、时间戳过期、商户不存在或被禁用、密钥未配置 |
| 40300 | 无权限 | — |
| 40400 | 资源不存在 | 查询的订单不存在 |
| 40900 | 资源已存在 | mch_order_no 重复("商户订单号已存在") |
| 42200 | 业务校验失败 | 币种与收银台不一致、商户被禁用等 |
| 42900 | 请求过于频繁 | 触发限流,请稍后重试 |
| 50000 | 系统内部错误 | 服务端异常 |
| 50300 | 服务暂不可用 | — |
http://45.76.155.11(HTTP,沙箱阶段可用;生产为 HTTPS,以开户提供为准)。PK + EP + 10000(PKR 100.00)下一笔,拿到 pay_url 浏览器打开,查看收银台 H5;mch_order_no 查到订单,核对字段;notify_url 填你方可公网访问的地址,下单后观察收到的 POST 与 sign 验签;notify_url 可选。GET /api/v1/pay/cashier/{platform_order_no}(无需鉴权),即 pay_url 指向的页面,展示国家 / 金额 / 支付方式 / 二维码。import hashlib
import hmac
import json
import time
import urllib.request
API_BASE = "http://45.76.155.11"
API_KEY = "你的API Key"
API_SECRET = "你的API Secret"
def _stringify(v):
if isinstance(v, bool):
return str(v) # True/False,与服务端一致
if isinstance(v, (dict, list)):
return json.dumps(v, sort_keys=True, separators=(",", ":"),
ensure_ascii=False)
return str(v)
def build_sign(params: dict, api_secret: str) -> str:
items = [(k, _stringify(v)) for k, v in params.items()
if k != "sign" and v is not None and v != ""]
items.sort(key=lambda kv: kv[0]) # ASCII 升序
canonical = "&".join(f"{k}={v}" for k, v in items)
canonical = f"{canonical}&key={api_secret}" if canonical else f"key={api_secret}"
return hashlib.md5(canonical.encode("utf-8")).hexdigest().upper()
def api_post(path: str, body: dict) -> dict:
ts = str(int(time.time()))
headers = {
"Content-Type": "application/json",
"X-Api-Key": API_KEY,
"X-Timestamp": ts,
"X-Sign": build_sign(body, API_SECRET),
}
req = urllib.request.Request(API_BASE + path,
data=json.dumps(body).encode("utf-8"),
headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=15) as resp:
return json.loads(resp.read().decode("utf-8"))
# ---- 代收下单 ----
order = api_post("/api/v1/pay/mch/payin/create", {
"country_code": "PK",
"pay_method": "EP",
"mode": "cashier",
"amount_minor": 10000,
"currency": "PKR",
"mch_order_no": "TEST" + str(int(time.time())),
"notify_url": "https://merchant.example/notify",
"return_url": "https://merchant.example/return",
"goods_desc": "测试商品",
})
print(json.dumps(order, ensure_ascii=False, indent=2))
# ---- 通知验签(在你的 notify_url 处理函数中使用) ----
def verify_notify(payload: dict) -> bool:
sign = str(payload.get("sign", "")).upper()
return hmac.compare_digest(build_sign(payload, API_SECRET), sign)
<?php
function mch_ksort_recursive($arr) {
if (!is_array($arr)) return $arr;
if (array_keys($arr) !== range(0, count($arr) - 1)) {
ksort($arr, SORT_STRING); // 关联数组按 key ASCII 排序
}
foreach ($arr as $k => $v) $arr[$k] = mch_ksort_recursive($v);
return $arr;
}
function mch_stringify($v) {
if (is_bool($v)) return $v ? 'True' : 'False'; // 对齐服务端 Python str()
if ($v instanceof stdClass) return '{}'; // 空对象
if (is_array($v)) {
return json_encode(mch_ksort_recursive($v),
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
return (string)$v;
}
function mch_sign(array $params, string $apiSecret): string {
$items = [];
foreach ($params as $k => $v) {
if ($k === 'sign' || $v === null || $v === '') continue; // 排除 sign 与空值
$items[$k] = mch_stringify($v);
}
ksort($items, SORT_STRING);
$pairs = [];
foreach ($items as $k => $v) $pairs[] = $k . '=' . $v;
$canonical = implode('&', $pairs);
$canonical = $canonical !== '' ? $canonical . '&key=' . $apiSecret
: 'key=' . $apiSecret;
return strtoupper(md5($canonical));
}
// 使用示例
$body = [
'country_code' => 'PK',
'pay_method' => 'EP',
'mode' => 'cashier',
'amount_minor' => 10000,
'currency' => 'PKR',
'mch_order_no' => 'TEST' . time(),
'notify_url' => 'https://merchant.example/notify',
];
$headers = [
'Content-Type: application/json',
'X-Api-Key: 你的API Key',
'X-Timestamp: ' . time(),
'X-Sign: ' . mch_sign($body, '你的API Secret'),
];
// 之后用 curl POST JSON $body 到网关,略
PHP 注意:空对象请用
(object)[]/new stdClass(json_encode输出{}),不要用[](会输出[]导致签名不一致);extra等可选字段不传则不参与签名。
TS=$(date +%s)
# X-Sign 需按 §3 算法预先算好,此处以 PLACEHOLDER 代替
curl -X POST http://45.76.155.11/api/v1/pay/mch/payin/create \
-H 'Content-Type: application/json' \
-H 'X-Api-Key: 你的API Key' \
-H "X-Timestamp: $TS" \
-H 'X-Sign: 预先算好的大写MD5签名' \
-d '{
"country_code": "PK",
"pay_method": "EP",
"mode": "cashier",
"amount_minor": 10000,
"currency": "PKR",
"mch_order_no": "TEST1699999999",
"notify_url": "https://merchant.example/notify",
"goods_desc": "测试商品"
}'
文档外问题请联系平台运营 / 技术对接人。生产网关地址与密钥以开户资料为准。