接口文档
本文档提供统一下单、订单上传和代付三套相互独立的接口。所有示例均使用脱敏占位数据,金额单位为人民币元,字符编码统一为 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()错误码与重试
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 综合收银台,由付款人选择支付宝或微信。支付入口有效期为3分钟;入口关闭后系统继续查询1分30秒,第4分30秒仍未确认付款才标记为支付超时并释放核销任务。| 参数 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
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://m.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,目标账号填写需要处理的抖音号;旧版请求未传订单编码时仍兼容,新接入必须显式传递。
| 参数 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
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://m.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 通知上传方。修改全局默认值只影响之后新建的订单。上传订单查询
查询当前上传账户自己的订单。可按外部订单号、目标账号或两者同时查询;同时提交时必须匹配同一笔订单。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
pid | Integer | 是 | 订单上传账户号 |
order_code | String | 是 | 订单编码,固定为 dy;该字段参与签名 |
out_trade_no | String | 条件必填 | 外部订单号;与目标账号至少填写一项 |
target_account | 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"
}
返回字段
| 字段 | 说明 |
|---|---|
order_code | 订单编码,固定为 dy |
trade_no | 平台任务订单号 |
out_trade_no | 外部订单号 |
target_account | 订单目标账号 |
money | 订单金额 |
status | 处理状态码 |
status_name | 处理状态名称 |
failure_reason | 失败原因,仅失败状态有值 |
created_at | 创建时间 |
expires_at | 订单最终有效期 |
finished_at | 成功或失败完成时间 |
状态说明
标准成功响应
{
"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"
}
}
处理结果回调
上传订单变为处理成功或处理失败时,平台以 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 |
标准回调示例
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=2更新为处理成功;status=3更新为处理失败并记录原因。- 处理完成后返回 HTTP 2xx,响应正文必须且只能为小写
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://m.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://m.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://m.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://m.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://m.jctapay.com/PayoutApi/create", data=params, timeout=15)
print(response.json())完整接入示例
以下四套示例包含统一签名、统一下单、支付订单查询、订单上传、上传订单查询和回调验签。示例账户、密钥、订单号、目标账号和地址均为脱敏占位值。
<?php
$baseUrl = 'https://m.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. 订单上传。
$uploadJson = postForm($baseUrl . '/OrderApi/upload', [
'pid' => $uploadPid,
'order_code' => 'dy',
'out_trade_no' => 'UPLOAD_' . date('YmdHis'),
'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' => 'UPLOAD_20260721_000001',
], $uploadKey);
// 5. 回调验签:支付回调使用 merchantKey,上传订单回调使用 uploadKey。
function verifyCallback(array $query, string $key): bool {
return isset($query['sign']) && hash_equals(makeSign($query, $key), $query['sign']);
}
if (verifyCallback($_GET, $merchantKey)) {
// 校验订单号和金额,并幂等更新本地订单。
echo 'success';
}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://m.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://m.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://m.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)
# 验签、核对订单号与金额、幂等更新成功后:return "success"