代付回调通知接口
说明: 根据代付下单接口中回调地址(noticeUrl),进行异步通知。
请求方式: POST
Content-Type: application/json
一、回调通知参数
| 参数名称 | 参数变量名 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| 签名 | sign | String | 是 | 参数加密后算法生成,签名规则见附录 |
| 友商唯一订单号 | tradeNo | String(32) | 是 | 友商系统中的唯一订单号 |
| 订单总金额 | totalAmount | Number | 是 | 订单总金额以元为单位,精确到小数点后两位 |
| 平台订单号 | outTradeNo | String(32) | 是 | 平台方支付平台里唯一的订单号 |
| 订单交易时间 | tradeTime | String(20) | 是 | 格式为 yyyy-MM-dd HH:mm:ss,例如:2015-01-01 12:45:52 |
| 交易状态 | tradeStatus | Int | 是 | 0 = 审核中,1 = 审核成功,2 = 审核失败,3 = 代付中,4 = 代付已转账,1 和 4 都为成功状态 |
| 附加字段 | attach | String(128) | 是 | attach 参数列表中参数转换 json 串,base64 编码后的数据 |
attach 参数说明
attach 字段是一个 JSON 字符串的 Base64 编码,解码后包含以下参数:
| 参数名称 | 参数变量名 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| 姓名 | bankAccount | String | 是 | 户主名称,如:张三 |
| 银行卡号 | bankCard | String | 是 | 银行卡号 |
| 银行代码 | bankMark | String | 否 | 银行的简码,如:CCB |
| 银行名称 | bankName | String | 是 | 银行的名称,如:建设银行 |
| 省份 | bankProv | String | 否 | 如:台湾省 |
| 城市 | bankCity | String | 否 | 如:台北市 |
| 开户支行 | bankBranch | String | 否 | 如:建设银行台北XX支行 |
交易状态说明
| 状态值 | 说明 |
|---|---|
| 0 | 审核中 |
| 1 | 审核成功 |
| 2 | 审核失败 |
| 3 | 代付中 |
| 4 | 代付已转账 |
重要: 1(审核成功)和 4(代付已转账)都代表成功状态。
二、回调请求示例
json
{
"sign": "3E541783111209F709ACDA0F4BFD17EA",
"tradeNo": "ORDER20250115001",
"totalAmount": "100.00",
"outTradeNo": "PO20250115001001",
"tradeTime": "2025-01-15 12:30:45",
"tradeStatus": 4,
"attach": "eyJiYW5rQWNjb3VudCI6IuWwj+eOi..."
}三、商户响应要求
当通知方式为服务器后台异步通知时,友商系统在收到通知并处理完成后,必须打印输出包含 success 这个字符串。
如果响应内容不包含 success,支付平台系统会认为通知失败,并在后续进行重试。
正确响应示例
方式一:直接返回文字
text
success方式二:返回 JSON
json
{
"code": 0,
"message": "success"
}四、PHP 回调处理示例
php
<?php
// 获取回调参数(POST 请求,JSON 格式)
$json = file_get_contents('php://input');
$params = json_decode($json, true);
if (!$params) {
echo 'fail';
exit;
}
// 保存 sign 后用于验证
$receivedSign = $params['sign'];
unset($params['sign']);
// 按参数名 ASCII 字典序排序
ksort($params);
// 拼接字符串
$stringA = '';
foreach ($params as $k => $v) {
if ($v !== '' && $v !== null) {
$stringA .= $k . '=' . $v . '&';
}
}
$stringA = rtrim($stringA, '&');
// 拼接密钥并生成签名
$key = 'your_api_key_here';
$signStr = $stringA . '&key=' . $key;
$calculatedSign = strtoupper(md5($signStr));
// 验证签名
if ($receivedSign === $calculatedSign) {
// 签名验证通过
$tradeStatus = $params['tradeStatus'];
$tradeNo = $params['tradeNo'];
$totalAmount = $params['totalAmount'];
// 判断交易状态
if ($tradeStatus == 1 || $tradeStatus == 4) {
// 交易成功,处理订单业务逻辑
// 例如:更新订单状态为成功,给用户账户加款等
echo 'success';
} elseif ($tradeStatus == 0 || $tradeStatus == 3) {
// 处理中状态,暂不处理,等待后续状态通知
echo 'success';
} else {
// 交易失败,处理订单业务逻辑
echo 'success';
}
} else {
// 签名验证失败,不处理业务
echo 'fail';
}五、Java 回调处理示例
java
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.util.*;
import java.security.MessageDigest;
@RestController
@RequestMapping("/payout")
public class PayoutNotifyController {
@PostMapping("/notify")
public String notify(HttpServletRequest request) {
try {
// 读取请求体(JSON 格式)
BufferedReader reader = request.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String jsonBody = sb.toString();
// 解析 JSON 参数
Map<String, Object> paramMap = new com.fasterxml.jackson.databind.ObjectMapper()
.readValue(jsonBody, Map.class);
// 提取并移除 sign
String receivedSign = (String) paramMap.remove("sign");
// 按参数名 ASCII 字典序排序(使用 TreeMap)
Map<String, Object> sortedParams = new TreeMap<>(paramMap);
// 拼接字符串
StringBuilder signStr = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
if (entry.getValue() != null && !entry.getValue().toString().isEmpty()) {
signStr.append(entry.getKey()).append("=")
.append(entry.getValue()).append("&");
}
}
if (signStr.length() > 0) {
signStr.deleteCharAt(signStr.length() - 1);
}
// 拼接密钥并生成签名
String key = "your_api_key_here";
String calculatedSign = md5(signStr.toString() + "&key=" + key).toUpperCase();
// 验证签名
if (receivedSign != null && receivedSign.equals(calculatedSign)) {
// 签名验证通过,处理业务逻辑
return "success";
} else {
return "fail";
}
} catch (Exception e) {
e.printStackTrace();
return "fail";
}
}
private static String md5(String input) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes("UTF-8"));
StringBuilder hexString = new StringBuilder();
for (byte b : digest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
}六、注意事项
- 幂等性:建议友商系统对同一订单号的回调通知做幂等处理,避免重复处理
- 签名验证:必须对所有回调通知进行签名验证,防止伪造请求
- 成功状态判断:代付成功状态为
1(审核成功)和4(代付已转账) - 返回要求:无论业务处理成功与否,都应返回
success字符串防止重复通知 - 回调格式:代付回调为 JSON 格式,与代收回调的 form-data 格式不同
- 异步处理:建议将回调通知放入异步队列处理,避免超时
- attach 解码:如需使用 attach 中的银行信息,请先进行 Base64 解码
