接口文档
本文档提供统一下单、订单上传和代付三套相互独立的接口。所有示例均使用脱敏占位数据,金额单位为人民币元,字符编码统一为 UTF-8,生产接入必须使用 HTTPS。
签名规则
统一下单、订单上传与代付接口使用同一套签名规则。账户号和密钥由平台管理端分配。
- 移除
sign、sign_type,并忽略值为空字符串的参数。 - 按参数名 ASCII 升序排列,以
key=value形式使用&连接。 - 在拼接结果末尾直接追加商户密钥,不添加
&key=。 - 对完整字符串计算 MD5,输出 32 位小写签名。
sign 和 sign_type,再按同样规则计算。四语言签名示例
function makeSign(array $params, string $merchantKey): string
{
unset($params['sign'], $params['sign_type']);
$params = array_filter($params, static fn($value) => $value !== '');
ksort($params);
$pairs = [];
foreach ($params as $key => $value) {
$pairs[] = $key . '=' . $value;
}
return md5(implode('&', $pairs) . $merchantKey);
}static String makeSign(Map<String, String> input, String merchantKey) throws Exception {
TreeMap<String, String> sorted = new TreeMap<>(input);
sorted.remove("sign");
sorted.remove("sign_type");
String source = sorted.entrySet().stream()
.filter(item -> item.getValue() != null && !item.getValue().isEmpty())
.map(item -> item.getKey() + "=" + item.getValue())
.collect(Collectors.joining("&")) + merchantKey;
byte[] digest = MessageDigest.getInstance("MD5")
.digest(source.getBytes(StandardCharsets.UTF_8));
StringBuilder result = new StringBuilder();
for (byte value : digest) result.append(String.format("%02x", value & 0xff));
return result.toString();
}static string MakeSign(IDictionary<string, string> input, string merchantKey)
{
var source = string.Join("&", input
.Where(item => item.Key != "sign" && item.Key != "sign_type" && item.Value != "")
.OrderBy(item => item.Key, StringComparer.Ordinal)
.Select(item => item.Key + "=" + item.Value)) + merchantKey;
return Convert.ToHexString(
MD5.HashData(Encoding.UTF8.GetBytes(source))
).ToLowerInvariant();
}import hashlib
def make_sign(params: dict, merchant_key: str) -> str:
pairs = [
f"{name}={value}"
for name, value in sorted(params.items())
if name not in ("sign", "sign_type") and value != ""
]
source = "&".join(pairs) + merchant_key
return hashlib.md5(source.encode("utf-8")).hexdigest()错误码与重试
code=200、msg=查询成功,只表示查到了订单;必须继续读取 data.status。订单失败时仍可返回 code=200,此时 data.status=3。| 场景 / 字段 | 含义 |
|---|---|
| HTTP 200 | HTTP 请求正常返回,不能据此判定订单结果 |
/OrderApi/query 顶层 code | 200:接口调用成功;201:接口调用失败 |
/OrderApi/upload、/OrderApi/query 的 data.status | 0:待处理;1:处理中;2:处理成功;3:处理失败 |
上传订单 GET 回调参数 status | 2:处理成功;3:处理失败 |
回调接收方回复 success | 已接收并处理这次通知;成功通知和失败通知都需要确认 |
支付商户 /Api/findorder 的 status | 1 表示已支付。此接口与上传订单的状态定义不同,不能共用状态映射 |
JSON 接口的接口调用失败(如验签失败、参数错误、订单不存在)使用 code=201,并增加稳定的 error_code 和 retryable。请依据错误码处理,不要依赖中文提示文本。
{
"code": 201,
"error_code": "AMOUNT_INVENTORY_EMPTY",
"msg": "当前暂无匹配金额的可用订单,请稍后重试",
"retryable": true,
"data": null
}
| error_code | 含义 | 可重试 | 处理建议 |
|---|---|---|---|
PID_REQUIRED | 账户号缺失 | 否 | 补齐账户号后重新签名 |
SIGN_INVALID | 签名验证失败 | 否 | 检查参数排序、空值处理和账户密钥 |
ACCOUNT_UNAVAILABLE | 账户不存在或已停用 | 否 | 联系平台检查账户状态 |
ACCOUNT_ROLE_DENIED | 账户角色不能发起支付 | 否 | 支付下单必须使用商户账户 |
UPLOAD_PERMISSION_DENIED | 账户无订单上传权限 | 否 | 订单上传必须使用上传账户 |
ORDER_NO_REQUIRED | 商户或外部订单号缺失 | 否 | 补齐订单号后重新签名 |
ORDER_CODE_INVALID | 订单上传编码无效 | 否 | 使用平台指定的订单编码 |
TARGET_ACCOUNT_INVALID | 目标账号格式无效 | 否 | 检查长度、空格和控制字符 |
PAYMENT_CODE_REQUIRED | 支付编码缺失 | 否 | 提交后台授权的支付编码 |
PAYMENT_CODE_UNAVAILABLE | 支付编码无效或已停用 | 否 | 检查编码与启用状态 |
PAYMENT_PERMISSION_DENIED | 商户未授权该支付编码 | 否 | 联系平台配置收款权限 |
AMOUNT_INVALID | 金额格式或额度规则不符 | 否 | 按支付编码金额规则修正请求 |
BALANCE_INSUFFICIENT | 商户余额不足 | 否 | 补充余额后再提交 |
NOTIFY_URL_INVALID | 异步回调地址缺失或无效 | 否 | 提交有效的 HTTP/HTTPS 地址 |
ACCOUNT_EXCLUSIVE_REQUIRED | 银行卡号与支付宝号未按二选一提交 | 否 | 只保留一种收款账户参数后重新签名 |
BANK_CARD_INVALID | 银行卡号格式无效 | 否 | 提交10至30位数字银行卡号 |
ALIPAY_ACCOUNT_INVALID | 支付宝号格式无效 | 否 | 移除空格并检查长度 |
PAYEE_NAME_INVALID | 收款人姓名缺失或无效 | 否 | 提交正确收款人姓名 |
RETURN_URL_INVALID | 同步跳转地址缺失或无效 | 否 | 提交有效的 HTTP/HTTPS 地址 |
DUPLICATE_ORDER | 外部或商户订单号重复 | 否 | 先查询原订单,不要生成重复业务单 |
AMOUNT_INVENTORY_EMPTY | 暂时没有匹配金额的可用订单 | 是 | 稍后使用原业务订单号重试 |
CHANNEL_UNAVAILABLE | 当前没有可用支付通道 | 是 | 建议 5 秒后重试 |
CHANNEL_BUSY | 支付通道暂时繁忙 | 是 | 建议 5 至 10 秒后重试 |
ORDER_NOT_FOUND | 未找到当前账户的订单 | 否 | 检查订单号类型及账户号 |
SYSTEM_BUSY | 系统暂时繁忙 | 是 | 退避重试并保留请求日志 |
retryable=true 才建议自动重试。网络超时或未拿到明确响应时,应先调用查询接口确认订单是否已创建,再决定是否重试;不要直接更换业务订单号。code=201;请求方法错误返回 HTTP 405。调用方应同时检查 HTTP 状态和响应体。统一下单接口
创建支付订单并返回 JSON 结果。调用方必须使用表单格式向 /mapi.php 提交参数,成功后将响应中的 payurl 交给付款用户打开。
dyzf。系统先打开平台支付页,再跳转到抖音 PC 综合收银台,由付款人选择支付宝或微信。当前支持实时下单模式和前置下单模式,以平台订单设置为准。valid_minutes 是核销订单自身有效期,两者分别计算。| 参数 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
pid | Integer | 是 | 商户号 | 100*** |
type | String | 是 | 后台可用的一级支付模式编码或已授权的二级支付编码,支持纯数字、字母或组合 | PAY_MODE_OR_CODE |
out_trade_no | String | 是 | 商户订单号;同一商户下必须唯一 | ORDER_20260721_****** |
notify_url | String | 是 | 服务器异步回调地址,必须为 HTTP/HTTPS | https://merchant.example/callback |
return_url | String | 是 | 支付完成后的页面跳转地址 | https://merchant.example/result |
name | String | 是 | 订单名称,不得包含等号或后台屏蔽词 | 业务订单 |
money | Decimal | 是 | 订单金额;同时受全局额度和支付编码额度规则限制 | ***.00 |
sitename | String | 否 | 商户站点或业务名称 | 示例业务 |
sign | String | 是 | 按统一签名规则生成 | 32位小写MD5 |
sign_type | String | 否 | 固定为 MD5 | MD5 |
请求示例
curl -X POST 'https://k.jctapay.com/mapi.php' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'pid=YOUR_MERCHANT_ID' \
--data-urlencode 'type=YOUR_PAY_CODE' \
--data-urlencode 'out_trade_no=ORDER_20260721_000001' \
--data-urlencode 'notify_url=https://merchant.example/callback' \
--data-urlencode 'return_url=https://merchant.example/result' \
--data-urlencode 'name=业务订单' \
--data-urlencode 'money=100.00' \
--data-urlencode 'sign=请替换为计算后的签名' \
--data-urlencode 'sign_type=MD5'
标准请求参数
pid=YOUR_MERCHANT_ID&type=YOUR_PAY_CODE&out_trade_no=ORDER_20260721_000001¬ify_url=https%3A%2F%2Fmerchant.example%2Fcallback&return_url=https%3A%2F%2Fmerchant.example%2Fresult&name=%E4%B8%9A%E5%8A%A1%E8%AE%A2%E5%8D%95&money=100.00&sign=SIGN_VALUE&sign_type=MD5
下单成功响应
{
"code": 1,
"msg": "获取成功!",
"trade_no": "PLATFORM_20260721_********",
"type": "PAY_MODE_OR_CODE",
"route_type": "ACTUAL_PAY_CODE",
"qrcode": "PAYMENT_CONTENT",
"payurl": "https://pay.example/checkout/******",
"collection_mode": "h5",
"collection_mode_name": "H5支付",
"cashier_mode": "h5"
}
type 始终返回商户下单时提交的编码。仅当提交一级编码并发生聚合路由时返回 route_type,表示本次实际使用的二级支付编码;直接使用二级编码下单时不返回该字段。/mapi.php 当前为兼容既有客户端,下单成功使用 code=1;失败使用 code=201,并返回 error_code、retryable 和 data=null。订单查询
按平台订单号或商户订单号查询当前商户自己的订单,其他商户的订单不会返回。
| 参数 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
pid | Integer | 是 | 商户号 | 100*** |
order_no | String | 是 | 需要查询的订单号 | ORDER_20260721_****** |
type | Integer | 是 | 1 平台订单号;2 商户订单号 | 2 |
sign | String | 是 | 按统一签名规则生成 | 32位小写MD5 |
sign_type | String | 否 | 固定为 MD5 | MD5 |
标准请求
POST /Api/findorder
Content-Type: application/x-www-form-urlencoded
pid=YOUR_MERCHANT_ID&order_no=ORDER_20260721_000001&type=2&sign=SIGN_VALUE&sign_type=MD5
成功响应
{
"code": 200,
"msg": "获取成功!",
"data": {
"id": "******",
"type": "PAY_CODE",
"route_type": "ACTUAL_PAY_CODE",
"trade_no": "PLATFORM_20260721_********",
"out_trade_no": "ORDER_20260721_000001",
"name": "业务订单",
"money": "100.00",
"status": 1,
"status_name": "已支付",
"failure_reason": ""
}
}
type 为原始请求编码;聚合下单时额外返回 route_type。老订单或直接二级编码下单不会返回 route_type。失败响应
{
"code": 201,
"error_code": "ORDER_NOT_FOUND",
"msg": "未找到该商户订单",
"retryable": false,
"data": null
}
0 未支付,1 已支付,2 支付超时,3 支付错误,4 订单风控。status_name 返回对应中文状态;支付错误或订单风控时,failure_reason 返回可公开的失败原因。支付回调
订单支付成功后,平台以 GET 查询参数请求下单时提交的 notify_url。商户应先验签,再依据商户订单号幂等更新业务状态。
| 参数 | 类型 | 说明 |
|---|---|---|
pid | Integer | 商户号 |
trade_no | String | 平台订单号 |
out_trade_no | String | 商户订单号 |
type | String | 商户下单时提交的一级或二级编码 |
route_type | String | 聚合下单实际使用的二级支付编码;直接二级编码下单时不发送 |
name | String | 订单名称;后台启用隐藏名称时不发送 |
money | Decimal | 订单金额 |
trade_status | String | 支付成功固定为 TRADE_SUCCESS |
sign | String | 回调签名 |
sign_type | String | 固定为 MD5 |
标准回调示例
GET /callback?pid=YOUR_MERCHANT_ID&trade_no=PLATFORM_20260721_********&out_trade_no=ORDER_20260721_000001&type=PAY_MODE&route_type=ACTUAL_PAY_CODE&name=%E4%B8%9A%E5%8A%A1%E8%AE%A2%E5%8D%95&money=100.00&trade_status=TRADE_SUCCESS&sign=SIGN_VALUE&sign_type=MD5
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
success
处理要求
- 验签通过,并确认
pid、out_trade_no、money与本地订单一致。 - 仅当
trade_status=TRADE_SUCCESS时更新订单,重复通知必须幂等返回成功。 - 处理完成后返回 HTTP 2xx,响应正文必须且只能为小写
success。
route_type 参与签名。请对实际收到的全部非空业务字段排序验签,不要使用写死字段列表。success 均视为失败。订单上传接口
授权账户可上传一笔待处理订单。当前订单编码固定为 dy,目标账号填写需要处理的抖音号;所有请求都必须显式传递 order_code=dy,并将该字段计入签名。
| 参数 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
pid | Integer | 是 | 订单上传账户号 | 200*** |
order_code | String | 是 | 订单编码,固定为 dy;该字段参与签名 | dy |
out_trade_no | String | 是 | 外部订单号;同一账户下唯一,最长 100 字符且不能含空格 | UPLOAD_20260721_****** |
target_account | String | 是 | 需要处理的抖音号,最长 64 字符且不能含空格 | ACCOUNT_****** |
money | Integer | 是 | 订单金额,必须为大于 0 的整数 | *** |
valid_minutes | Integer | 否 | 订单有效期,范围 3–1440 分钟;不传时使用平台全局默认值,该字段传入后参与签名 | 60 |
notify_url | String | 否 | 处理成功或失败的结果回调地址,最长 500 字符 | https://uploader.example/callback |
sign | String | 是 | 使用订单上传账户密钥签名 | 32位小写MD5 |
sign_type | String | 否 | 固定为 MD5 | MD5 |
请求示例
curl -X POST 'https://k.jctapay.com/OrderApi/upload' \
-H 'Content-Type: application/json' \
-d '{
"pid": "YOUR_UPLOAD_ACCOUNT_ID",
"order_code": "dy",
"out_trade_no": "UPLOAD_20260721_000001",
"target_account": "ACCOUNT_000001",
"money": 100,
"valid_minutes": 60,
"notify_url": "https://uploader.example/callback",
"sign": "请替换为计算后的签名",
"sign_type": "MD5"
}'
成功响应
{
"code": 200,
"msg": "订单上传成功,等待处理",
"data": {
"order_code": "dy",
"trade_no": "TASK_20260721_********",
"out_trade_no": "UPLOAD_20260721_000001",
"target_account": "ACCOUNT_000001",
"money": "100.00",
"status": 0,
"status_name": "待处理",
"failure_reason": "",
"created_at": "2026-07-21 10:00:00",
"expires_at": "2026-07-21 11:00:00",
"finished_at": ""
}
}
失败响应
{
"code": 201,
"error_code": "DUPLICATE_ORDER",
"msg": "外部订单号已存在,请勿重复上传",
"retryable": false,
"data": null
}
valid_minutes 可按订单传入 3–1440 分钟;不传时使用平台全局默认有效期(初始值 1440 分钟)。到期仍未处理成功的订单会转为处理失败,并按 notify_url 通知上传方。修改全局默认值只影响之后新建的订单。上传订单查询
查询当前上传账户自己的订单。逐笔查单、对账和更新本地订单时,必须使用上传时的外部订单号 out_trade_no,并核对响应里的 data.out_trade_no。
out_trade_no 查询,并按该订单号处理返回结果。此处 out_trade_no 是上传方提交的外部订单号;平台返回的任务号 trade_no 不是本接口的查询参数。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
pid | Integer | 是 | 订单上传账户号 |
order_code | String | 是 | 订单编码,固定为 dy;该字段参与签名 |
out_trade_no | String | 是 | 上传时提交的外部订单号;逐笔对账使用此字段 |
sign | String | 是 | 使用订单上传账户密钥签名 |
sign_type | String | 否 | 固定为 MD5 |
标准请求
POST /OrderApi/query
Content-Type: application/json
{
"pid": "YOUR_UPLOAD_ACCOUNT_ID",
"order_code": "dy",
"out_trade_no": "UPLOAD_20260721_000001",
"sign": "SIGN_VALUE",
"sign_type": "MD5"
}
返回字段
| 字段 | 说明 |
|---|---|
code | 接口调用结果;200 不代表订单成功 |
msg | 接口提示文本;不能用于判定订单成功 |
data.order_code | 订单编码,固定为 dy |
data.trade_no | 平台任务订单号 |
data.out_trade_no | 外部订单号 |
data.target_account | 订单目标账号 |
data.money | 订单金额 |
data.status | 处理状态码 |
data.status_name | 处理状态名称 |
data.failure_reason | 失败原因,仅失败状态有值 |
data.created_at | 创建时间 |
data.expires_at | 订单最终有效期 |
data.finished_at | 成功或失败完成时间 |
data.status 状态说明
查询成功,且订单处理成功
{
"code": 200,
"msg": "查询成功",
"data": {
"order_code": "dy",
"trade_no": "TASK_20260721_********",
"out_trade_no": "UPLOAD_20260721_000001",
"target_account": "ACCOUNT_000001",
"money": "100.00",
"status": 2,
"status_name": "处理成功",
"failure_reason": "",
"created_at": "2026-07-21 10:00:00",
"expires_at": "2026-07-21 10:30:00",
"finished_at": "2026-07-21 10:06:18"
}
}
查询成功,但订单处理失败
{
"code": 200,
"msg": "查询成功",
"data": {
"order_code": "dy",
"trade_no": "TASK_EXAMPLE_000002",
"out_trade_no": "UPLOAD_EXAMPLE_000002",
"target_account": "ACCOUNT_000001",
"money": "100.00",
"status": 3,
"status_name": "处理失败",
"failure_reason": "订单已过期",
"created_at": "2026-09-13 10:00:00",
"expires_at": "2026-09-13 11:00:00",
"finished_at": "2026-09-13 11:00:00"
}
}查询成功,订单仍在处理中
{
"code": 200,
"msg": "查询成功",
"data": {
"order_code": "dy",
"trade_no": "TASK_EXAMPLE_000002",
"out_trade_no": "UPLOAD_EXAMPLE_000002",
"target_account": "ACCOUNT_000001",
"money": "100.00",
"status": 1,
"status_name": "处理中",
"failure_reason": "",
"created_at": "2026-09-13 10:00:00",
"expires_at": "2026-09-13 11:00:00",
"finished_at": ""
}
}只有 data.status=2 才能将对应上传订单记为成功;0 和 1 应继续等待,3 记为失败并保存 failure_reason。接口调用失败、网络异常或未知状态不能当作订单成功,也不能直接当作订单处理失败。
处理结果回调
上传订单变为处理成功或处理失败时,平台以 GET 查询参数请求上传时提交的 notify_url。未传回调地址时不发送。
| 参数 | 类型 | 说明 |
|---|---|---|
pid | Integer | 订单上传账户号 |
order_code | String | 订单编码,固定为 dy |
trade_no | String | 平台任务订单号 |
out_trade_no | String | 外部订单号 |
target_account | String | 订单目标账号 |
money | Decimal | 订单金额 |
status | Integer | 2 处理成功;3 处理失败 |
status_name | String | 处理成功或处理失败 |
failure_reason | String | 失败原因,成功时为空 |
finish_time | String | 完成时间,格式 YYYY-MM-DD HH:mm:ss |
sign | String | 使用订单上传账户密钥生成的签名 |
sign_type | String | 固定为 MD5 |
处理成功通知示例(status=2)
GET /callback?pid=YOUR_UPLOAD_ACCOUNT_ID&order_code=dy&trade_no=TASK_20260721_********&out_trade_no=UPLOAD_20260721_000001&target_account=ACCOUNT_000001&money=100.00&status=2&status_name=%E5%A4%84%E7%90%86%E6%88%90%E5%8A%9F&failure_reason=&finish_time=2026-07-21%2010%3A06%3A18&sign=SIGN_VALUE&sign_type=MD5
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
success
处理失败通知示例(status=3)
GET /callback?pid=YOUR_UPLOAD_ACCOUNT_ID&order_code=dy&trade_no=TASK_EXAMPLE_000002&out_trade_no=UPLOAD_EXAMPLE_000002&target_account=ACCOUNT_000001&money=100.00&status=3&status_name=%E5%A4%84%E7%90%86%E5%A4%B1%E8%B4%A5&failure_reason=%E8%AE%A2%E5%8D%95%E5%B7%B2%E8%BF%87%E6%9C%9F&finish_time=2026-09-13%2011%3A00%3A00&sign=SIGN_VALUE&sign_type=MD5
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
successsuccess,仅确认通知已处理。不得据此将订单记为成功。示例中的 SIGN_VALUE 是占位符,实际签名按收到并 URL 解码后的全部非空业务参数计算。处理要求
- 使用订单上传账户密钥验签;按当前账户及
out_trade_no定位本地订单,核对pid、order_code=dy、目标账号、金额及已保存的平台任务号。不要只按抖音号更新订单。 - 依据 GET 参数
status分支:2更新为处理成功;3更新为处理失败并记录原因。不要依据status_name文本或验签成功判断订单成功。 - 业务结果须先事务落库并完成幂等处理,再返回 HTTP 2xx,响应正文必须且只能为小写
success;相同通知重复到达时不能重复结算。 - 验签失败、订单信息不符、未知状态或本地保存失败时,不返回
success。如本地已有相冲突的终态,应按外部订单号核对并告警,不直接覆盖。
代付下单接口
商户请求平台向指定银行卡或支付宝账户付款。代付使用现有商户号和商户密钥,但订单、状态和统计与收款订单完全独立。订单创建后先进入待发布状态,由平台发布到码商任务大厅;默认有效期为30分钟,过期未完成将按代付失败通知商户。
bank_card 与 alipay_account 必须且只能填写一个;payee_name、money 和 notify_url 均为必填。不要在日志中记录完整收款账户或商户密钥。| 参数 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
pid | Integer | 是 | 现有商户号 | 100*** |
out_trade_no | String | 是 | 商户代付订单号,同一商户下唯一 | PAYOUT_20260729_000001 |
bank_card | String | 二选一 | 银行卡号,允许输入空格或短横线,签名使用原始请求值 | 6222********1234 |
alipay_account | String | 二选一 | 支付宝账号 | user***@example.com |
payee_name | String | 是 | 收款人姓名,最长100字符 | 张* |
money | Decimal | 是 | 代付金额,最多两位小数 | 100.00 |
notify_url | String | 是 | 代付结果异步通知地址 | https://merchant.example/payout-notify |
sign | String | 是 | 按统一签名规则生成 | 32位小写MD5 |
sign_type | String | 否 | 固定为MD5 | MD5 |
请求示例
curl -X POST 'https://k.jctapay.com/PayoutApi/create' \
--data-urlencode 'pid=YOUR_MERCHANT_ID' \
--data-urlencode 'out_trade_no=PAYOUT_20260729_000001' \
--data-urlencode 'alipay_account=user@example.com' \
--data-urlencode 'payee_name=张三' \
--data-urlencode 'money=100.00' \
--data-urlencode 'notify_url=https://merchant.example/payout-notify' \
--data-urlencode 'sign=SIGN_VALUE' \
--data-urlencode 'sign_type=MD5'成功响应
{
"code": 200,
"msg": "代付订单提交成功,等待平台发布",
"data": {
"payout_no": "DF20260729************",
"out_trade_no": "PAYOUT_20260729_000001",
"account_type": "alipay",
"account_masked": "use********mple.com",
"payee_name": "张三",
"money": "100.00",
"status": 0,
"status_name": "待处理",
"failure_reason": "",
"created_at": "2026-07-29 10:00:00",
"expires_at": "2026-07-29 10:30:00",
"finished_at": ""
}
}out_trade_no 和相同业务内容时返回原订单;若金额、姓名或收款账户不同,则返回 DUPLICATE_ORDER。代付查询接口
使用平台代付订单号或商户代付订单号查询当前商户自己的代付订单,至少填写一个订单号。
| 参数 | 必填 | 说明 |
|---|---|---|
pid | 是 | 商户号 |
payout_no | 二选一 | 平台代付订单号 |
out_trade_no | 二选一 | 商户代付订单号 |
sign | 是 | 按统一签名规则生成 |
sign_type | 否 | 固定为MD5 |
POST /PayoutApi/query
Content-Type: application/x-www-form-urlencoded
pid=YOUR_MERCHANT_ID&out_trade_no=PAYOUT_20260729_000001&sign=SIGN_VALUE&sign_type=MD50 待处理,1 处理中,2 代付成功,3 代付失败。失败时读取 failure_reason;调用方应按商户代付订单号幂等更新业务。代付结果回调
码商完成付款并上传凭证后,平台通过 GET 请求创建订单时提交的 notify_url。平台也会对后台明确失败的订单发送失败通知。
| 参数 | 说明 |
|---|---|
pid | 商户号 |
payout_no | 平台代付订单号 |
out_trade_no | 商户代付订单号 |
money | 代付金额 |
status | 2成功,3失败 |
status_name | 代付成功或代付失败 |
failure_reason | 失败原因;成功时为空且不参与签名 |
finish_time | 平台完成时间 |
sign | 回调签名 |
sign_type | 固定为MD5 |
GET /payout-notify?pid=YOUR_MERCHANT_ID&payout_no=DF20260729XXXXXXXXXXXX&out_trade_no=PAYOUT_20260729_000001&money=100.00&status=2&status_name=%E4%BB%A3%E4%BB%98%E6%88%90%E5%8A%9F&finish_time=2026-07-29%2010%3A10%3A00&sign=SIGN_VALUE&sign_type=MD5success,不要返回JSON或HTML。代付下单代码示例
以下示例使用支付宝账户。改用银行卡时,删除 alipay_account 并设置 bank_card。签名函数直接复用本文“签名规则”章节。
$params = [
'pid' => 'YOUR_MERCHANT_ID',
'out_trade_no' => 'PAYOUT_' . date('YmdHis'),
'alipay_account' => 'user@example.com',
'payee_name' => '张三',
'money' => '100.00',
'notify_url' => 'https://merchant.example/payout-notify',
];
$params['sign'] = makeSign($params, 'YOUR_MERCHANT_KEY');
$params['sign_type'] = 'MD5';
$ch = curl_init('https://k.jctapay.com/PayoutApi/create');
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $params, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15]);
$response = curl_exec($ch);Map<String, String> params = new HashMap<>();
params.put("pid", "YOUR_MERCHANT_ID");
params.put("out_trade_no", "PAYOUT_20260729_000001");
params.put("alipay_account", "user@example.com");
params.put("payee_name", "张三");
params.put("money", "100.00");
params.put("notify_url", "https://merchant.example/payout-notify");
params.put("sign", makeSign(params, "YOUR_MERCHANT_KEY"));
params.put("sign_type", "MD5");
HttpRequest request = HttpRequest.newBuilder(URI.create("https://k.jctapay.com/PayoutApi/create"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(formEncode(params))).build();
String response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()).body();var fields = new Dictionary<string, string> {
["pid"] = "YOUR_MERCHANT_ID", ["out_trade_no"] = "PAYOUT_20260729_000001",
["alipay_account"] = "user@example.com", ["payee_name"] = "张三",
["money"] = "100.00", ["notify_url"] = "https://merchant.example/payout-notify"
};
fields["sign"] = MakeSign(fields, "YOUR_MERCHANT_KEY");
fields["sign_type"] = "MD5";
using var client = new HttpClient();
var response = await client.PostAsync("https://k.jctapay.com/PayoutApi/create", new FormUrlEncodedContent(fields));
var body = await response.Content.ReadAsStringAsync();import requests
params = {
"pid": "YOUR_MERCHANT_ID", "out_trade_no": "PAYOUT_20260729_000001",
"alipay_account": "user@example.com", "payee_name": "张三",
"money": "100.00", "notify_url": "https://merchant.example/payout-notify",
}
params["sign"] = make_sign(params, "YOUR_MERCHANT_KEY")
params["sign_type"] = "MD5"
response = requests.post("https://k.jctapay.com/PayoutApi/create", data=params, timeout=15)
print(response.json())请求、签名与回调处理示例
以下四套示例展示统一签名和接口请求;PHP 另提供上传订单查询判定及回调处理函数。数据库读写、事务与幂等逻辑需由接入方实现。示例账户、密钥、订单号、目标账号和地址均为占位值。上传回调使用 uploadKey,支付商户回调使用 merchantKey,两类接口的状态映射分别处理。
<?php
$baseUrl = 'https://k.jctapay.com';
$merchantPid = 'YOUR_MERCHANT_ID';
$merchantKey = 'YOUR_MERCHANT_KEY';
$uploadPid = 'YOUR_UPLOAD_ACCOUNT_ID';
$uploadKey = 'YOUR_UPLOAD_ACCOUNT_KEY';
function makeSign(array $params, string $key): string {
unset($params['sign'], $params['sign_type']);
$params = array_filter($params, static fn($value) => $value !== '');
ksort($params);
$pairs = [];
foreach ($params as $name => $value) $pairs[] = $name . '=' . $value;
return md5(implode('&', $pairs) . $key);
}
function postForm(string $url, array $params, string $key): string {
$params['sign'] = makeSign($params, $key);
$params['sign_type'] = 'MD5';
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$body = curl_exec($ch);
if ($body === false) throw new RuntimeException(curl_error($ch));
curl_close($ch);
return $body;
}
// 1. 统一下单:服务端接入使用统一下单地址。
$payJson = postForm($baseUrl . '/mapi.php', [
'pid' => $merchantPid, 'type' => 'YOUR_PAY_CODE',
'out_trade_no' => 'ORDER_' . date('YmdHis'),
'notify_url' => 'https://merchant.example/callback',
'return_url' => 'https://merchant.example/result',
'name' => '业务订单', 'money' => '100.00',
], $merchantKey);
// 2. 商户订单查询。
$queryJson = postForm($baseUrl . '/Api/findorder', [
'pid' => $merchantPid, 'order_no' => 'ORDER_20260721_000001', 'type' => '2',
], $merchantKey);
$query = json_decode($queryJson, true, 512, JSON_THROW_ON_ERROR);
// 3. 订单上传:使用本地已保存的唯一业务单号,重试时保持不变。
$uploadOutTradeNo = 'UPLOAD_20260721_000001';
$uploadJson = postForm($baseUrl . '/OrderApi/upload', [
'pid' => $uploadPid,
'order_code' => 'dy',
'out_trade_no' => $uploadOutTradeNo,
'target_account' => 'ACCOUNT_000001', 'money' => '100',
'notify_url' => 'https://uploader.example/callback',
], $uploadKey);
// 4. 上传订单查询。
$uploadQueryJson = postForm($baseUrl . '/OrderApi/query', [
'pid' => $uploadPid,
'order_code' => 'dy',
'out_trade_no' => $uploadOutTradeNo,
], $uploadKey);
// 5. 查询结果:先检查接口结果,再读取 data.status 并核对外部订单号。
$result = json_decode($uploadQueryJson, true, 512, JSON_THROW_ON_ERROR);
if (($result['code'] ?? null) !== 200) {
throw new RuntimeException('查单接口未成功,不更新订单终态');
}
$data = $result['data'] ?? [];
if (($data['out_trade_no'] ?? '') !== $uploadOutTradeNo) {
throw new RuntimeException('查询返回的订单号不一致');
}
if (!isset($data['status']) || !is_int($data['status']) || !in_array($data['status'], [0,1,2,3], true)) {
throw new RuntimeException('未知订单状态,需核对');
}
switch ($data['status'] ?? null) {
case 0: case 1: /* 仍在等待/处理中,保持未完成 */ break;
case 2: /* 幂等保存这笔上传订单的成功结果 */ break;
case 3: /* 幂等保存失败结果及 failure_reason,不记为成功 */ break;
default: throw new RuntimeException('未知订单状态,需核对');
}
// 6. 上传回调处理函数:放到独立回调路由中,不要在该路由执行上面的下单示例。
// localOrder:按上传账户 + out_trade_no 读取的本地订单,money 保存为两位小数字符串。
// commitResult:由接入方实现事务与幂等,保存明确的 succeeded/failed 结果后才返回 true。
// 重复且一致的通知可返回 true;数据库失败、终态冲突必须抛异常或返回 false。
// 路由成功调用本函数后,才将返回值作为 HTTP 200 的纯文本正文;异常时不确认通知。
function handleUploadCallback(array $query, array $localOrder, string $uploadPid,
string $uploadKey, callable $commitResult): string {
foreach ($query as $value) {
if (!is_string($value)) throw new RuntimeException('回调参数须为 GET 字符串');
}
foreach (['pid','order_code','trade_no','out_trade_no','target_account','money','status','sign'] as $field) {
if (!isset($query[$field]) || $query[$field] === '') throw new RuntimeException('回调缺少参数');
}
if (!hash_equals(makeSign($query, $uploadKey), $query['sign'])) {
throw new RuntimeException('上传回调验签失败');
}
if ($query['pid'] !== $uploadPid || $query['order_code'] !== 'dy'
|| (string)($localOrder['pid'] ?? '') !== $uploadPid
|| $query['out_trade_no'] !== ($localOrder['out_trade_no'] ?? '')
|| $query['target_account'] !== ($localOrder['target_account'] ?? '')
|| $query['money'] !== ($localOrder['money'] ?? '')
|| (!empty($localOrder['trade_no']) && $query['trade_no'] !== $localOrder['trade_no'])) {
throw new RuntimeException('回调与本地订单不匹配');
}
if ($query['status'] === '2') {
$outcome = 'succeeded';
$reason = '';
} elseif ($query['status'] === '3') {
$outcome = 'failed';
$reason = $query['failure_reason'] ?? '';
} else {
throw new RuntimeException('未知回调状态');
}
if ($commitResult($localOrder, $outcome, $reason, $query) !== true) {
throw new RuntimeException('业务结果尚未保存');
}
return 'success'; // 仅为通知接收确认,订单结果由 outcome 决定。
}import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public final class YPayDemo {
static final String BASE_URL = "https://k.jctapay.com";
static final String MERCHANT_PID = "YOUR_MERCHANT_ID";
static final String MERCHANT_KEY = "YOUR_MERCHANT_KEY";
static final String UPLOAD_PID = "YOUR_UPLOAD_ACCOUNT_ID";
static final String UPLOAD_KEY = "YOUR_UPLOAD_ACCOUNT_KEY";
static final HttpClient HTTP = HttpClient.newHttpClient();
static String sign(Map<String, String> input, String key) throws Exception {
TreeMap<String, String> sorted = new TreeMap<>(input);
sorted.remove("sign"); sorted.remove("sign_type");
String source = sorted.entrySet().stream()
.filter(e -> e.getValue() != null && !e.getValue().isEmpty())
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining("&")) + key;
byte[] digest = MessageDigest.getInstance("MD5")
.digest(source.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : digest) hex.append(String.format("%02x", b & 0xff));
return hex.toString();
}
static String post(String path, Map<String, String> input, String key) throws Exception {
Map<String, String> params = new TreeMap<>(input);
params.put("sign", sign(params, key));
params.put("sign_type", "MD5");
String form = params.entrySet().stream()
.map(e -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8) + "="
+ URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpRequest request = HttpRequest.newBuilder(URI.create(BASE_URL + path))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form)).build();
return HTTP.send(request, HttpResponse.BodyHandlers.ofString()).body();
}
public static void main(String[] args) throws Exception {
// 商户下单,返回 JSON。
String payJson = post("/mapi.php", Map.of(
"pid", MERCHANT_PID, "type", "YOUR_PAY_CODE",
"out_trade_no", "ORDER_20260721_000001",
"notify_url", "https://merchant.example/callback",
"return_url", "https://merchant.example/result",
"name", "业务订单", "money", "100.00"), MERCHANT_KEY);
String orderJson = post("/Api/findorder", Map.of(
"pid", MERCHANT_PID, "order_no", "ORDER_20260721_000001", "type", "2"),
MERCHANT_KEY);
String uploadJson = post("/OrderApi/upload", Map.of(
"pid", UPLOAD_PID,
"order_code", "dy",
"out_trade_no", "UPLOAD_20260721_000001", "target_account", "ACCOUNT_000001",
"money", "100", "notify_url", "https://uploader.example/callback"),
UPLOAD_KEY);
String uploadOrderJson = post("/OrderApi/query", Map.of(
"pid", UPLOAD_PID,
"order_code", "dy",
"out_trade_no", "UPLOAD_20260721_000001"), UPLOAD_KEY);
}
static boolean verifyCallback(Map<String, String> query, String key) throws Exception {
String received = query.get("sign");
return received != null && MessageDigest.isEqual(
sign(query, key).getBytes(StandardCharsets.US_ASCII),
received.getBytes(StandardCharsets.US_ASCII));
}
}using System.Security.Cryptography;
using System.Text;
public static class YPayDemo
{
const string BaseUrl = "https://k.jctapay.com";
const string MerchantPid = "YOUR_MERCHANT_ID";
const string MerchantKey = "YOUR_MERCHANT_KEY";
const string UploadPid = "YOUR_UPLOAD_ACCOUNT_ID";
const string UploadKey = "YOUR_UPLOAD_ACCOUNT_KEY";
static readonly HttpClient Http = new();
static string MakeSign(IDictionary<string, string> input, string key)
{
var source = string.Join("&", input
.Where(p => p.Key != "sign" && p.Key != "sign_type" && p.Value != "")
.OrderBy(p => p.Key, StringComparer.Ordinal)
.Select(p => p.Key + "=" + p.Value)) + key;
return Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(source))).ToLowerInvariant();
}
static async Task<string> PostAsync(
string path, IDictionary<string, string> input, string key)
{
var data = new Dictionary<string, string>(input) {
["sign"] = MakeSign(input, key), ["sign_type"] = "MD5"
};
using var response = await Http.PostAsync(BaseUrl + path, new FormUrlEncodedContent(data));
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
public static async Task RunAsync()
{
// 商户下单,返回 JSON。
var payJson = await PostAsync("/mapi.php", new Dictionary<string, string> {
["pid"] = MerchantPid, ["type"] = "YOUR_PAY_CODE",
["out_trade_no"] = "ORDER_20260721_000001",
["notify_url"] = "https://merchant.example/callback",
["return_url"] = "https://merchant.example/result",
["name"] = "业务订单", ["money"] = "100.00"
}, MerchantKey);
var orderJson = await PostAsync("/Api/findorder", new Dictionary<string, string> {
["pid"] = MerchantPid, ["order_no"] = "ORDER_20260721_000001", ["type"] = "2"
}, MerchantKey);
var uploadJson = await PostAsync("/OrderApi/upload", new Dictionary<string, string> {
["pid"] = UploadPid,
["order_code"] = "dy",
["out_trade_no"] = "UPLOAD_20260721_000001", ["target_account"] = "ACCOUNT_000001",
["money"] = "100", ["notify_url"] = "https://uploader.example/callback"
}, UploadKey);
var uploadOrderJson = await PostAsync("/OrderApi/query", new Dictionary<string, string> {
["pid"] = UploadPid,
["order_code"] = "dy",
["out_trade_no"] = "UPLOAD_20260721_000001"
}, UploadKey);
}
// ASP.NET 回调处理中将 Request.Query 转为字典后验签。
static bool VerifyCallback(IDictionary<string, string> query, string key)
{
return query.TryGetValue("sign", out var received)
&& CryptographicOperations.FixedTimeEquals(
Encoding.ASCII.GetBytes(MakeSign(query, key)),
Encoding.ASCII.GetBytes(received));
}
}import hashlib
import hmac
import requests
BASE_URL = "https://k.jctapay.com"
MERCHANT_PID = "YOUR_MERCHANT_ID"
MERCHANT_KEY = "YOUR_MERCHANT_KEY"
UPLOAD_PID = "YOUR_UPLOAD_ACCOUNT_ID"
UPLOAD_KEY = "YOUR_UPLOAD_ACCOUNT_KEY"
def make_sign(params: dict, key: str) -> str:
pairs = [
f"{name}={value}"
for name, value in sorted(params.items())
if name not in ("sign", "sign_type") and value != ""
]
return hashlib.md5(("&".join(pairs) + key).encode("utf-8")).hexdigest()
def post(path: str, params: dict, key: str) -> str:
payload = dict(params)
payload["sign"] = make_sign(payload, key)
payload["sign_type"] = "MD5"
response = requests.post(BASE_URL + path, data=payload, timeout=15)
response.raise_for_status()
return response.text
# 1. 商户下单,返回 JSON。
pay_json = post("/mapi.php", {
"pid": MERCHANT_PID, "type": "YOUR_PAY_CODE",
"out_trade_no": "ORDER_20260721_000001",
"notify_url": "https://merchant.example/callback",
"return_url": "https://merchant.example/result",
"name": "业务订单", "money": "100.00",
}, MERCHANT_KEY)
# 2. 商户订单查询。
order_json = post("/Api/findorder", {
"pid": MERCHANT_PID, "order_no": "ORDER_20260721_000001", "type": "2",
}, MERCHANT_KEY)
# 3. 订单上传。
upload_json = post("/OrderApi/upload", {
"pid": UPLOAD_PID,
"order_code": "dy",
"out_trade_no": "UPLOAD_20260721_000001", "target_account": "ACCOUNT_000001",
"money": "100", "notify_url": "https://uploader.example/callback",
}, UPLOAD_KEY)
# 4. 上传订单查询。
upload_order_json = post("/OrderApi/query", {
"pid": UPLOAD_PID,
"order_code": "dy",
"out_trade_no": "UPLOAD_20260721_000001",
}, UPLOAD_KEY)
# 5. Flask/Django/FastAPI 回调处理中传入查询参数验签。
def verify_callback(query: dict, key: str) -> bool:
received = query.get("sign", "")
return bool(received) and hmac.compare_digest(make_sign(query, key), received)
# 上传回调使用 UPLOAD_KEY;按账户和 out_trade_no 定位订单并核对目标、金额。
# status=2 保存成功;status=3 保存失败。事务落库并幂等处理后才 return "success"。
# 验签失败、未知状态、保存失败或终态冲突时不确认通知;不要只凭验签成功回复 success。