上线前,商户管理员在商户系统的“应用管理”中创建应用。每个商户最多创建 20 个应用,建议按环境和业务系统隔离,例如“商城生产环境”“ERP 测试环境”。
appId:由本系统自动生成并保存在本系统,用于标识调用应用。apiSecret:创建应用时自动生成的请求签名密钥。recipientId:余额分账申请使用的、已完成系统审核并生效的分账接收方标识。订单分账申请不使用该字段,改用实际分账接收商户号。完整密钥只在创建或重置成功后展示一次。调用方必须立即保存到服务端密钥管理系统,不得写入浏览器前端、小程序、APP、日志或代码仓库。
应用状态说明:
appId 发起新的开放接口请求。密钥轮换说明:
appId + apiSecret 用于证明请求来自本系统已启用的合法应用。apiSecret 不作为 JSON 字段传输;调用方使用它计算 X-Signature。merchantNo,系统校验该资金商户号已绑定当前应用所属商户账号。receiveMerchantNo 表示实际分账接收商户号,不是原支付订单的收款商户号,也不要求绑定当前开放应用。系统根据 outTradeNo 自动取得原订单收款商户号并校验该原订单及其资金商户属于当前应用。notifyUrl 是业务方最终接收通知的地址,必须命中应用回调白名单。{
"code": "00000",
"msg": "成功",
"data": {}
}
| code | 含义 | 建议 |
|---|---|---|
00000 | 请求成功 | 继续判断 data.status,不能把 HTTP 成功当作业务终态 |
A0001 | 参数错误 | 修正参数后再提交 |
A0002 | 鉴权失败、签名错误或请求重放 | 检查签名、时间戳、nonce 和密钥 |
A0003 | 无权限 | 检查调用方授权范围 |
B0001 | 业务处理失败 | 按原业务单号查询状态,避免直接换单号重试 |
C0001 | 系统暂不可用 | 稍后按原业务单号重试或查询 |
开放 API 不提供登录接口,也不接收商户后台 JWT、Authorization: Bearer 或登录 Cookie。调用方直接使用在“应用管理”中取得的 appId 和 apiSecret 对每个请求独立签名;验签通过即代表该应用身份合法。
每个请求必须携带:
| Header | 必填 | 说明 |
|---|---|---|
X-App-Id | 是 | 调用方标识 |
X-Timestamp | 是 | 10 位 Unix 秒时间戳 |
X-Nonce | 是 | 16~128 位随机字符串;同一 appId 下不可重复 |
X-Signature | 是 | 64 位小写十六进制 HMAC-SHA256 |
Content-Type | POST 是 | application/json; charset=UTF-8 |
除上述请求头外无需传登录令牌。缺少签名、签名错误、时间戳过期、nonce 重复、应用停用或应用不存在时,系统返回 HTTP 401;开放接口未启用或防重放服务不可用时返回 HTTP 503。
待签名串由六行组成,末尾不追加换行:
HTTP_METHOD
REQUEST_PATH
RAW_QUERY_STRING
X_TIMESTAMP
X_NONCE
SHA256_HEX(RAW_BODY_BYTES)
计算方式:
X-Signature = hex_lower(
HMAC_SHA256(apiSecret, canonicalString)
)
注意:
REQUEST_PATH 只包含路径,例如 /openapi/v1/payments。RAW_QUERY_STRING 必须与实际发送内容完全一致;无查询参数时为空行。Python 签名工具类(以下所有接口示例均基于此工具类):
import hashlib
import hmac
import json
import secrets
import time
import urllib.request
class OpenApiClient:
def __init__(self, base_url: str, app_id: str, api_secret: str):
self.base_url = base_url
self.app_id = app_id
self.api_secret = api_secret
def post(self, path: str, payload: dict) -> str:
body = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
return self._do_request("POST", path, "", body)
def get(self, path: str, raw_query: str = "") -> str:
return self._do_request("GET", path, raw_query, b"")
def _do_request(self, method: str, path: str, raw_query: str, body: bytes) -> str:
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(body).hexdigest()
canonical = "\n".join([method, path, raw_query, timestamp, nonce, body_hash])
signature = hmac.new(
self.api_secret.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
url = self.base_url + path + (("?" + raw_query) if raw_query else "")
req = urllib.request.Request(url, data=body if body else None, method=method)
req.add_header("X-App-Id", self.app_id)
req.add_header("X-Timestamp", timestamp)
req.add_header("X-Nonce", nonce)
req.add_header("X-Signature", signature)
if method == "POST":
req.add_header("Content-Type", "application/json; charset=UTF-8")
with urllib.request.urlopen(req) as resp:
return resp.read().decode("utf-8")
Java 签名工具类(以下所有接口示例均基于此工具类):
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.net.URI;
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.security.SecureRandom;
import java.time.Instant;
public class OpenApiClient {
private static final SecureRandom RANDOM = new SecureRandom();
private final HttpClient http = HttpClient.newHttpClient();
private final String baseUrl;
private final String appId;
private final String apiSecret;
public OpenApiClient(String baseUrl, String appId, String apiSecret) {
this.baseUrl = baseUrl;
this.appId = appId;
this.apiSecret = apiSecret;
}
/** POST 请求 */
public String post(String path, String jsonBody) throws Exception {
return doRequest("POST", path, "", jsonBody);
}
/** GET 请求(无请求体) */
public String get(String path, String rawQuery) throws Exception {
return doRequest("GET", path, rawQuery, "");
}
private String doRequest(String method, String path, String rawQuery, String jsonBody) throws Exception {
String timestamp = String.valueOf(Instant.now().getEpochSecond());
String nonce = randomNonce();
byte[] bodyBytes = jsonBody.getBytes(StandardCharsets.UTF_8);
String bodyHash = sha256Hex(bodyBytes);
String canonical = String.join("\n", method, path, rawQuery, timestamp, nonce, bodyHash);
String signature = hmacSha256Hex(apiSecret, canonical);
URI uri = rawQuery.isEmpty()
? URI.create(baseUrl + path)
: URI.create(baseUrl + path + "?" + rawQuery);
HttpRequest.Builder builder = HttpRequest.newBuilder(uri)
.header("X-App-Id", appId)
.header("X-Timestamp", timestamp)
.header("X-Nonce", nonce)
.header("X-Signature", signature);
if ("POST".equals(method)) {
builder.header("Content-Type", "application/json; charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofByteArray(bodyBytes));
} else {
builder.GET();
}
HttpResponse<String> resp = http.send(builder.build(), HttpResponse.BodyHandlers.ofString());
return resp.body();
}
private static String sha256Hex(byte[] data) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(data);
return new BigInteger(1, hash).toString(16);
}
private static String hmacSha256Hex(String secret, String data) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return new BigInteger(1, hash).toString(16);
}
private static String randomNonce() {
byte[] buf = new byte[16];
RANDOM.nextBytes(buf);
return new BigInteger(1, buf).toString(16);
}
}
outTradeNo 幂等。refundRequestNo 幂等。businessNo 幂等。POST /payments
请求字段:
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
merchantNo | 是 | string(64) | 业务方在本系统已审核绑定的资金商户号 |
outTradeNo | 是 | string(64) | 调用方唯一订单号;重试必须保持不变 |
amount | 是 | decimal | 订单金额,单位:元,必须大于 0 |
paymentMethod | 是 | string | 支付产品:WECHAT、ALIPAY、UNIONPAY |
paymentMode | 是 | string | 支付交互方式,见下表 |
businessScene | 否 | string(32) | 业务场景,默认 OFFLINE |
authorizationDirectory | 否 | string(512) | 支付授权目录,完整 HTTP/HTTPS 地址 |
goodsName | 是 | string(128) | 商品名称 |
userId | 条件必填 | string(128) | 支付用户标识;小程序、公众号、生活号、JS 支付场景必填 |
userIp | 是 | string(64) | 用户真实公网 IP,不能使用中台服务器 IP |
notifyUrl | 是 | string(512) | 调用方接收支付结果通知的 HTTPS 地址 |
settlementNotifyUrl | 条件必填 | string(512) | 延迟结算结果回调地址;为空时沿用 notifyUrl |
redirectUrl | 否 | string(512) | 支付完成后的页面跳转地址,仅作展示,不能作为支付终态 |
memo | 否 | string(85) | 调用方对账备注 |
paymentOptions | 否 | string(2048) | 系统已约定的支付扩展信息 JSON |
terminalId | 条件必填 | string(64) | 线下面对面交易终端标识 |
terminalInfo | 条件必填 | string(2048) | 商家终端场景信息 JSON |
settlementMode | 否 | string(32) | 结算模式:IMMEDIATE(即时)或 DELAYED(延迟,后续可分账),默认 IMMEDIATE |
expireTime | 否 | datetime | 订单过期时间,ISO-8601 本地时间,例如 2026-07-30T18:30:00 |
paymentMode 支持:
| 值 | 场景 | 额外要求 |
|---|---|---|
USER_SCAN | 用户扫描二维码 | 通常返回可展示的支付数据 |
MINI_PROGRAM | 小程序支付 | userId |
WECHAT_OFFIACCOUNT | 公众号支付 | userId |
ALIPAY_LIFE | 生活号支付 | userId |
JS_PAY | JS 支付 | userId |
SDK_PAY | APP SDK 支付 | — |
H5_PAY | 浏览器 H5 支付 | 通常填写 redirectUrl |
UNCONSCIOUS_PAY | 免密支付 | 须提前开通对应能力 |
DIRECT_PAY | 直接支付 | 须提前开通对应能力 |
Java 示例:
OpenApiClient client = new OpenApiClient("https://{api-host}/openapi/v1", appId, apiSecret);
String body = """
{
"merchantNo": "10080000001",
"outTradeNo": "ORDER202607300001",
"amount": 1.00,
"paymentMethod": "WECHAT",
"paymentMode": "USER_SCAN",
"goodsName": "测试商品",
"userIp": "203.0.113.10",
"notifyUrl": "https://merchant.example.com/callbacks/payment",
"settlementMode": "IMMEDIATE"
}
""";
String result = client.post("/payments", body);
System.out.println(result);
Python 示例:
client = OpenApiClient("https://{api-host}/openapi/v1", app_id, api_secret)
result = client.post("/payments", {
"merchantNo": "10080000001",
"outTradeNo": "ORDER202607300001",
"amount": 1.00,
"paymentMethod": "WECHAT",
"paymentMode": "USER_SCAN",
"goodsName": "测试商品",
"userIp": "203.0.113.10",
"notifyUrl": "https://merchant.example.com/callbacks/payment",
"settlementMode": "IMMEDIATE"
})
print(result)
响应 data 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
outTradeNo | string | 调用方订单号 |
paymentNo | string | 系统支付单号 |
amount | decimal | 订单金额,单位:元 |
currency | string | 币种,默认 CNY |
status | string | 支付状态,见下表 |
settlementStatus | string | 清算状态;即时结算为 null |
settlementAmount | decimal | 清算金额,单位:元;未清算为 null |
unSplitAmount | decimal | 可分账余额,单位:元;非延迟结算为 null |
settlementTime | datetime | 清算时间 |
paymentData | string | 支付唤起凭证,可能是 URL 或 JSON 字符串 |
paidAmount | decimal | 实付金额,单位:元;未支付为 null |
refundAmount | decimal | 累计已退款金额,单位:元 |
paidTime | datetime | 支付成功时间 |
expireTime | datetime | 订单过期时间 |
errorCode | string | 错误码;正常为 null |
errorMessage | string | 错误描述;正常为 null |
syncedAt | datetime | 最近一次与渠道同步状态的时间 |
paymentData 是支付唤起凭证,可能是 URL 或 JSON 字符串。调用方应按已开通的 paymentMode 处理,不应解析或依赖未在系统接入说明中约定的扩展字段。
支付状态:
| 状态 | 终态 | 说明 |
|---|---|---|
PENDING | 否 | 已创建,尚未受理支付 |
PAYING | 否 | 支付处理中 |
SUCCESS | 是 | 支付成功 |
FAILED | 是 | 支付失败 |
CLOSED | 是 | 已关闭或已过期 |
REFUNDED | 是 | 已全部退款 |
GET /payments/{outTradeNo}?merchantNo={merchantNo}&refresh=true
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
outTradeNo | path | 是 | 调用方订单号 |
merchantNo | query | 是 | 资金商户号 |
refresh | query | 否 | true(默认)同步渠道状态后返回;false 只读本地缓存 |
Java 示例:
String result = client.get(
"/payments/ORDER202607300001",
"merchantNo=10080000001&refresh=true");
System.out.println(result);
Python 示例:
result = client.get(
"/payments/ORDER202607300001",
"merchantNo=10080000001&refresh=true")
print(result)
响应 data 字段同 5.1。
POST /refunds
请求字段:
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
merchantNo | 是 | string(64) | 必须与原支付订单所属资金商户号一致 |
refundRequestNo | 是 | string(64) | 调用方退款请求号;全局唯一,只能包含字母、数字和下划线,重试必须保持不变 |
outTradeNo | 是 | string(64) | 原支付接口使用的调用方订单号 |
amount | 是 | decimal | 退款金额,单位:元;累计不得超过原订单实付金额 |
reason | 否 | string(128) | 退款原因 |
notifyUrl | 是 | string(512) | 调用方接收退款状态通知的 HTTPS 地址 |
Java 示例:
String body = """
{
"merchantNo": "10080000001",
"refundRequestNo": "REFUND_20260730_0001",
"outTradeNo": "ORDER202607300001",
"amount": 1.00,
"reason": "用户取消订单",
"notifyUrl": "https://merchant.example.com/callbacks/refund"
}
""";
String result = client.post("/refunds", body);
System.out.println(result);
Python 示例:
result = client.post("/refunds", {
"merchantNo": "10080000001",
"refundRequestNo": "REFUND_20260730_0001",
"outTradeNo": "ORDER202607300001",
"amount": 1.00,
"reason": "用户取消订单",
"notifyUrl": "https://merchant.example.com/callbacks/refund"
})
print(result)
响应 data 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
refundRequestNo | string | 调用方退款请求号 |
outTradeNo | string | 原支付订单号 |
refundAmount | decimal | 申请退款金额,单位:元 |
actualRefundAmount | decimal | 实际退款金额,单位:元;未完成退款为 null |
userReceivedAmount | decimal | 用户实际到账金额,单位:元 |
returnedFee | decimal | 退回手续费,单位:元 |
reason | string | 退款原因 |
status | string | 退款状态,见下表 |
acceptanceStatus | string | 受理状态:ACCEPTED、REJECTED 或 UNKNOWN;最终结果以 status 为准 |
errorCode | string | 错误码;正常为 null |
errorMessage | string | 错误描述;正常为 null |
retryable | boolean | 当前状态是否允许使用原退款请求号重试 |
nextAction | string | 建议的后续操作,见下方说明 |
requestedAt | datetime | 退款申请时间 |
succeededAt | datetime | 退款成功时间;未成功为 null |
syncedAt | datetime | 最近一次与渠道同步状态的时间 |
createdAt | datetime | 记录创建时间 |
updatedAt | datetime | 记录更新时间 |
退款状态:
| 状态 | 终态 | 说明 |
|---|---|---|
CREATED | 否 | 已创建 |
PROCESSING | 否 | 处理中 |
UNKNOWN | 否 | 受理结果不确定,必须查询 |
ACTION_REQUIRED | 否 | 需要人工处理 |
SUCCESS | 是 | 退款成功 |
FAILED | 是 | 退款失败 |
CLOSED | 是 | 已关闭 |
nextAction 可能为:
| 值 | 说明 |
|---|---|
WAIT_CALLBACK | 等待回调,也可主动查询 |
QUERY | 使用原退款请求号查询 |
RETRY_SAME_ID | 仅在 retryable=true 时使用原请求号重试 |
MANUAL_PROCESS | 联系系统人工处理 |
NONE | 无需后续动作 |
GET /refunds/{refundRequestNo}?merchantNo={merchantNo}&refresh=true
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
refundRequestNo | path | 是 | 调用方退款请求号 |
merchantNo | query | 是 | 资金商户号 |
refresh | query | 否 | true(默认)同步渠道状态并校准结果;false 读取本地缓存 |
Java 示例:
String result = client.get(
"/refunds/REFUND_20260730_0001",
"merchantNo=10080000001&refresh=true");
System.out.println(result);
Python 示例:
result = client.get(
"/refunds/REFUND_20260730_0001",
"merchantNo=10080000001&refresh=true")
print(result)
响应 data 字段同 6.1。
发起订单分账前必须满足:
SUCCESS,且未发生全额退款。支付机构状态仍显示支付成功,但系统订单状态已变为 REFUNDED 时不能分账。settlementMode=DELAYED。paymentMode=USER_SCAN 时,系统收银台会在实际发起支付时继承该结算模式;创建订单后不能再切换。receiveMerchantNo 对应的实际接收商户已具备有效的订单分账接收能力。字段封装边界:
businessNo 是调用方幂等键,中台据此生成并保存内部请求标识;调用方不得改用支付机构请求号重试或查询。outTradeNo 必须是本系统创建支付时使用的调用方订单号。中台校验订单归属后,从订单记录取得底层支付单信息。receiveMerchantNo 是资金实际转入的商户号;原支付订单的收款商户号由 outTradeNo 自动解析,两者不得混用。merchantNo 或 recipientId,当前一次申请只支持一个实际接收商户和一条分账明细。notifyUrl 只用于系统向调用方发送统一结果通知,不作为底层渠道字段透传。POST /profit-sharing/orders
请求字段:
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
receiveMerchantNo | 是 | string(32 bytes) | 实际分账接收商户号;不是原订单收款商户号,不要求绑定当前开放应用 |
businessNo | 是 | string(64) | 业务幂等号;同一业务重试必须保持不变 |
outTradeNo | 是 | string(64) | 本系统支付接口使用的调用方订单号 |
notifyUrl | 是 | string(512) | 调用方接收订单分账结果通知的 HTTPS 地址 |
allocationMode | 否 | string(16) | 分账方式:AMOUNT(按金额,默认)或 PERCENTAGE(按比例) |
allocationBaseAmount | 条件必填 | decimal(14,2) | 按比例分账时的计算基准金额,单位:元;仅 PERCENTAGE 模式填写,必须大于 0 且不超过订单剩余可分账金额 |
releaseRemainingAmount | 否 | boolean | 是否在本次分账后释放订单剩余可分账金额,默认 false;仅 AMOUNT 模式可设为 true |
details | 是 | array | 实际接收商户的分账明细,固定 1 条 |
details[].amount | 条件必填 | decimal(14,2) | 按金额分账时填写的分账金额,单位:元,必须大于 0;AMOUNT 模式必填且不得填写 percentage |
details[].percentage | 条件必填 | decimal(3,2) | 按比例分账时填写的比例,范围 (0,100];PERCENTAGE 模式必填且所有明细合计必须等于 100 |
details[].description | 否 | string(128 bytes) | 分账明细描述,UTF-8 编码不超过 128 字节 |
Java 示例:
String body = """
{
"receiveMerchantNo": "10080000002",
"businessNo": "ALLOC_ORDER_20260730_0001",
"outTradeNo": "ORDER202607300001",
"notifyUrl": "https://merchant.example.com/callbacks/profit-sharing",
"allocationMode": "AMOUNT",
"releaseRemainingAmount": false,
"details": [
{
"amount": 0.80,
"description": "服务费分账"
}
]
}
""";
String result = client.post("/profit-sharing/orders", body);
System.out.println(result);
Python 示例:
result = client.post("/profit-sharing/orders", {
"receiveMerchantNo": "10080000002",
"businessNo": "ALLOC_ORDER_20260730_0001",
"outTradeNo": "ORDER202607300001",
"notifyUrl": "https://merchant.example.com/callbacks/profit-sharing",
"allocationMode": "AMOUNT",
"releaseRemainingAmount": False,
"details": [
{
"amount": 0.80,
"description": "服务费分账"
}
]
})
print(result)
按比例时:
allocationMode=PERCENTAGE;按金额时可省略 allocationMode,默认 AMOUNT。allocationBaseAmount,单位:元。percentage=100,最多两位小数;allocationBaseAmount 是本次按比例计算的基准金额。amount 不填写。releaseRemainingAmount 只能为 false 或不传。按金额时:
amount,最多两位小数,不限制为 100 元以内;明细中的 percentage 不填写。allocationBaseAmount。releaseRemainingAmount=true 表示本次处理后不再继续对该订单剩余金额分账,请仅在业务确认分账结束时使用。recipientId;其实际接收方就是请求顶层的 receiveMerchantNo。GET /profit-sharing/orders/{businessNo}?receiveMerchantNo={receiveMerchantNo}
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
businessNo | path | 是 | 分账业务幂等号 |
receiveMerchantNo | query | 是 | 原申请中的实际分账接收商户号 |
查询接口只接收上述两个业务字段。中台先按当前应用和 businessNo 定位原申请,再校验 receiveMerchantNo 与原申请的实际接收商户一致,并自动取得查询所需的原订单信息;不接收原订单收款商户号、支付机构订单号或内部请求标识。若申请结果不确定,必须使用原 businessNo 和原 receiveMerchantNo 查询,不能换号。
Java 示例:
String result = client.get(
"/profit-sharing/orders/ALLOC_ORDER_20260730_0001",
"receiveMerchantNo=10080000002");
System.out.println(result);
Python 示例:
result = client.get(
"/profit-sharing/orders/ALLOC_ORDER_20260730_0001",
"receiveMerchantNo=10080000002")
print(result)
响应 data 字段见 8.3。
POST /profit-sharing/order-returns
该接口用于将一笔已成功的订单分账金额归还至原支付订单。一次订单分账申请当前只支持一条分账明细,因此归还时系统根据原支付订单自动定位已成功的分账明细;调用方不传接收商户号、系统请求号或支付机构的订单和分账明细标识。
前置条件:原支付订单属于当前开放应用、原订单分账已成功,且本次归还金额不超过该原分账明细的可归还金额。
请求字段:
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
outTradeNo | 是 | string(64) | 原支付订单的调用方订单号 |
businessNo | 是 | string(64) | 本次资金归还的业务幂等号;重试必须保持不变 |
amount | 是 | decimal(14,2) | 本次归还金额,单位:元,必须大于 0 |
reason | 否 | string(128) | 归还原因 |
Java 示例:
String body = """
{
"outTradeNo": "ORDER202607300001",
"businessNo": "RETURN_ORDER_20260730_0001",
"amount": 0.80,
"reason": "售后归还"
}
""";
String result = client.post("/profit-sharing/order-returns", body);
System.out.println(result);
Python 示例:
result = client.post("/profit-sharing/order-returns", {
"outTradeNo": "ORDER202607300001",
"businessNo": "RETURN_ORDER_20260730_0001",
"amount": 0.80,
"reason": "售后归还"
})
print(result)
响应 data 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
businessNo | string | 本次资金归还业务幂等号 |
outTradeNo | string | 原支付订单的调用方订单号 |
status | string | CREATED、PROCESSING、UNKNOWN、SUCCESS 或 FAILED |
amount | decimal | 本次归还金额,单位:元 |
reason | string | 归还原因 |
createdAt | datetime | 归还申请创建时间 |
completedAt | datetime | 归还完成时间;未完成时为 null |
errorCode | string | 仅失败时返回系统统一错误码 |
errorMessage | string | 仅失败时返回系统统一错误描述 |
nextAction | string | QUERY、REVIEW_AND_RETRY 或 NONE |
不会返回也不会接收原支付收款商户号、实际接收商户号、系统内部请求号、底层支付订单号、底层分账请求号或分账明细号。
GET /profit-sharing/order-returns/{businessNo}?outTradeNo={outTradeNo}
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
businessNo | path | 是 | 资金归还业务幂等号 |
outTradeNo | query | 是 | 原支付订单的调用方订单号 |
系统先校验订单归属,再用内部保存的关联信息查询最新状态;查询响应字段与 7.3 相同。状态不确定时必须使用原 businessNo 查询,不能更换新的业务单号。
Java 示例:
String result = client.get(
"/profit-sharing/order-returns/RETURN_ORDER_20260730_0001",
"outTradeNo=ORDER202607300001");
System.out.println(result);
Python 示例:
result = client.get(
"/profit-sharing/order-returns/RETURN_ORDER_20260730_0001",
"outTradeNo=ORDER202607300001")
print(result)
余额分账不依赖原支付订单,只允许向系统已审核生效的 recipientId 分账。
POST /profit-sharing/balances
请求字段:
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
merchantNo | 是 | string(64) | 资金商户号 |
businessNo | 是 | string(64) | 业务幂等号;同一业务重试必须保持不变 |
notifyUrl | 是 | string(512) | 调用方接收余额分账结果通知的 HTTPS 地址 |
details | 是 | array | 分账接收方明细列表,最多 30 条 |
details[].recipientId | 是 | string(64) | 系统分配的分账接收方标识 |
details[].amount | 是 | decimal | 分账金额,单位:元,必须大于 0 |
details[].description | 否 | string(32) | 分账明细描述 |
details[].feeMode | 否 | string(16) | 手续费收取方式:DEDUCT_FROM_AMOUNT(从分账金额中扣除,默认)或 CHARGE_SEPARATELY(另行收取) |
details[].purpose | 否 | string(40) | 分账用途 |
Java 示例:
String body = """
{
"merchantNo": "10080000001",
"businessNo": "ALLOC_BALANCE_20260730_0001",
"notifyUrl": "https://merchant.example.com/callbacks/profit-sharing",
"details": [
{
"recipientId": "RECIPIENT_0001",
"amount": 10.00,
"description": "业务分润",
"feeMode": "DEDUCT_FROM_AMOUNT",
"purpose": "服务费"
}
]
}
""";
String result = client.post("/profit-sharing/balances", body);
System.out.println(result);
Python 示例:
result = client.post("/profit-sharing/balances", {
"merchantNo": "10080000001",
"businessNo": "ALLOC_BALANCE_20260730_0001",
"notifyUrl": "https://merchant.example.com/callbacks/profit-sharing",
"details": [
{
"recipientId": "RECIPIENT_0001",
"amount": 10.00,
"description": "业务分润",
"feeMode": "DEDUCT_FROM_AMOUNT",
"purpose": "服务费"
}
]
})
print(result)
GET /profit-sharing/balances/{businessNo}?merchantNo={merchantNo}
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
businessNo | path | 是 | 分账业务幂等号 |
merchantNo | query | 是 | 资金商户号 |
Java 示例:
String result = client.get(
"/profit-sharing/balances/ALLOC_BALANCE_20260730_0001",
"merchantNo=10080000001");
System.out.println(result);
Python 示例:
result = client.get(
"/profit-sharing/balances/ALLOC_BALANCE_20260730_0001",
"merchantNo=10080000001")
print(result)
响应 data 字段见 8.3。
订单分账和余额分账使用统一响应。
响应 data 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
businessNo | string | 分账业务幂等号 |
outTradeNo | string | 关联的支付订单号;余额分账为 null |
status | string | 分账整体状态,见下表 |
divideAmount | decimal | 分账总金额,单位:元 |
divideRule | string | 分账规则:AMOUNT(按金额)或 PERCENTAGE(按比例) |
createdAt | datetime | 分账创建时间 |
completedAt | datetime | 分账完成时间;未完成为 null |
errorCode | string | 错误码;正常为 null |
errorMessage | string | 错误描述;正常为 null |
nextAction | string | 建议的后续操作 |
details | array | 分账明细结果列表 |
details[].recipientId | string | 分账接收方标识 |
details[].amount | decimal | 分账金额,单位:元 |
details[].percentage | decimal | 分账比例;按金额模式为 null |
details[].receivedAmount | decimal | 接收方实际到账金额,单位:元 |
details[].fee | decimal | 手续费,单位:元 |
details[].status | string | 该明细的分账状态 |
details[].errorMessage | string | 该明细的错误描述 |
分账状态:
| 状态 | 说明 |
|---|---|
CREATED | 已创建 |
PROCESSING | 处理中 |
UNKNOWN | 结果不确定,应主动查询 |
SUCCESS | 分账成功 |
FAILED | 分账失败 |
余额分账在成功后仍可能因后续退回而更新为失败。调用方不得用“成功后永不变化”的状态机处理余额分账,应以最新事件为准。
事件类型:
| eventType | businessNo 对应字段 |
|---|---|
payment.status.changed | outTradeNo |
payment.settlement.changed | outTradeNo |
refund.status.changed | refundRequestNo |
profit-sharing.order.status.changed | 分账 businessNo |
profit-sharing.balance.status.changed | 分账 businessNo |
回调请求头:
| Header | 说明 |
|---|---|
X-Callback-App-Id | 调用方标识 |
X-Callback-Timestamp | Unix 秒时间戳 |
X-Callback-Event-Id | 全局唯一事件 ID |
X-Callback-Event-Type | 事件类型 |
回调体字段:
| 字段 | 类型 | 说明 |
|---|---|---|
eventId | string | 全局唯一事件 ID,用于幂等去重 |
eventType | string | 事件类型 |
occurredAt | datetime | 事件发生时间(UTC) |
appId | string | 调用方应用标识 |
merchantNo | string | 资金商户号;订单分账事件中为原支付订单收款商户号,不是 receiveMerchantNo |
businessNo | string | 关联的业务单号 |
status | string | 最新状态 |
data | object | 事件附加数据 |
回调体示例:
{
"eventId": "7edb6c9a-1f7d-4aa7-a147-16c8c9ef73d4",
"eventType": "refund.status.changed",
"occurredAt": "2026-07-30T09:40:00Z",
"appId": "app_0123456789abcdef01234567",
"merchantNo": "10080000001",
"businessNo": "REFUND_20260730_0001",
"status": "SUCCESS",
"data": {}
}
接收要求:
appId 和 merchantNo 与本应用一致。eventId 做幂等去重。eventType + businessNo 更新业务状态,并允许合法的后续状态修正。SUCCESS。SUCCESS 会触发重试;调用方不得依赖固定重试次数或间隔。