通用规则
本文档描述了支付接口的通用规则,包括接口调用规范、签名算法、业务字典等内容。
接口规则
请求方式
所有接口必须使用 POST 方法进行请求。
签名算法
所有请求必须包含签名参数,签名算法如下:
签名步骤
- 将所有请求参数(除sign字段外)按照参数名的ASCII码升序排列
- 使用URL键值对的格式(即key1=value1&key2=value2...)拼接成字符串
- 使用HMAC-SHA256算法对拼接后的字符串进行签名
- 签名密钥为商户的merchantSecret
- 将签名结果作为sign参数的值
签名示例(Java)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
// 准备请求参数
Map<String, Object> params = new HashMap<>();
params.put("merchantNo", "M1001");
params.put("payMethod", "ALI_WAP");
params.put("outTradeNo", "20231229001");
params.put("amount", 100);
params.put("goodsName", "测试商品");
// 商户密钥
String merchantSecret = "商户密钥";
// 1. 按参数名ASCII升序排序
TreeMap<String, Object> sortedParams = new TreeMap<>(params);
// 2. 拼接成字符串
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
String data = sb.toString();
// 3. HMAC-SHA256签名
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(merchantSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKey);
byte[] signBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 4. 转换为十六进制字符串
StringBuilder hex = new StringBuilder();
for (byte b : signBytes) {
hex.append(String.format("%02x", b));
}
// 5. 将sign添加到请求参数中
params.put("sign", hex.toString());
签名验证
服务端会验证请求签名的正确性,签名验证失败的请求将被拒绝。
注意:签名密钥(merchantSecret)是商户的重要安全凭证,请妥善保管,不要泄露给第三方。
响应格式
所有接口统一返回JSON格式数据,响应结构如下:
{
"code": 1,
"msg": "成功",
"data": {},
"sign": "签名值"
}
响应参数说明
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | Integer | 是 | 响应码,1表示成功,其他表示失败 |
| msg | String | 是 | 响应消息 |
| data | Object | 否 | 响应数据 |
| sign | String | 是 | 响应签名,使用相同的签名算法生成 |
业务字典
支付方式
| 枚举值 | 说明 |
|---|---|
| WX_JSAPI | 微信内部浏览器支付 |
| WX_NATIVE | 微信扫码支付 |
| WX_APP | 微信APP支付 |
| WX_H5 | 微信H5支付 |
| WX_APPLET | 微信小程序支付 |
| ALI_APP | 支付宝APP支付 |
| ALI_WAP | 支付宝WAP支付 |
| ALI_PC | 支付宝PC支付 |
| ALI_JSAPI | 支付宝JSAPI支付 |
订单状态
| 状态码 | 状态说明 |
|---|---|
| 0 | 已创建 |
| 1 | 支付中 |
| 2 | 支付成功 |
| 3 | 支付失败 |
| 10 | 部分退款 |
| 11 | 全部退款 |
| 99 | 已关闭 |
支付接口
支付接口提供了统一的支付下单、订单查询、支付结果通知等功能。
统一支付下单
接口说明
统一支付下单接口用于发起支付请求,支持多种支付方式(微信、支付宝等)。
请求地址
POST /api/pay/order
请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchantNo | String | 是 | 商户号,格式:M1000001 |
| payMethod | String | 是 | 支付方式,详见 业务字典 |
| outTradeNo | String | 是 | 商户订单号,长度不超过32字符 |
| amount | Integer | 是 | 支付金额,单位:分 |
| goodsName | String | 是 | 商品名称,长度不超过128字符 |
| sign | String | 是 | 请求签名 |
| extraParams | String | 否 | 扩展参数,JSON格式 |
| expireSeconds | Integer | 否 | 订单过期时间,单位:秒 |
| goodsDesc | String | 否 | 商品描述,长度不超过128字符 |
| notifyUrl | String | 否 | 支付结果通知地址,长度不超过256字符 |
| returnUrl | String | 否 | 支付返回地址,长度不超过256字符 |
| channelParams | String | 否 | 渠道参数,JSON字符串 |
渠道参数(channelParams)说明
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| wxOpenId | String | 否 | 子商户微信openId,微信小程序和JSAPI时必传 |
| wxPayerClientIp | String | 否 | 微信支付者客户端IP,微信小程序、JSAPI、APP、H5时必传 |
请求示例
{
"merchantNo": "M1001",
"payMethod": "ALI_WAP",
"outTradeNo": "20231229001",
"amount": 100,
"goodsName": "测试商品",
"sign": "生成的签名值",
"notifyUrl": "https://example.com/notify",
"channelParams": {
"wxPayerClientIp": "127.0.0.1"
}
}
响应参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| tradeNo | String | 平台交易流水号 |
| payMethod | String | 支付方式 |
| payData | String | 支付数据,根据不同支付方式返回不同内容 |
响应示例
{
"code": 1,
"msg": "成功",
"data": {
"tradeNo": "2023122900000001",
"payMethod": "ALI_WAP",
"payData": "支付宝支付数据"
},
"sign": "响应签名值"
}
注意:
- 商户订单号(outTradeNo)在同一商户下必须唯一
- 支付金额单位为分,请确保金额正确
- 签名验证失败会直接返回错误
支付结果查询
接口说明
支付结果查询接口用于查询订单的支付状态和详细信息。商户可以通过商户订单号(outTradeNo)或平台交易流水号(tradeNo)查询订单状态。
请求地址
POST /api/pay/query
请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchantNo | String | 是 | 商户号,格式:M1000001 |
| outTradeNo | String | 否 | 商户订单号,与tradeNo至少填写一个 |
| tradeNo | String | 否 | 平台交易流水号,与outTradeNo至少填写一个 |
| sign | String | 是 | 请求签名 |
注意:
outTradeNo和tradeNo至少填写一个,如果同时填写,优先使用tradeNo- 商户订单号(outTradeNo)在同一商户下必须唯一
请求示例
{
"merchantNo": "M1001",
"outTradeNo": "20231229001",
"sign": "生成的签名值"
}
响应参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| merchantNo | String | 商户号 |
| outTradeNo | String | 商户订单号 |
| payMethod | String | 支付方式,详见 业务字典 |
| tradeNo | String | 平台交易流水号 |
| amount | Integer | 支付金额,单位:分 |
| goodsName | String | 商品名称 |
| extraParams | String | 扩展参数 |
| status | Integer | 订单状态,详见 业务字典 |
响应示例
{
"code": 1,
"msg": "成功",
"data": {
"merchantNo": "M1001",
"outTradeNo": "20231229001",
"payMethod": "ALI_WAP",
"tradeNo": "2023122900000001",
"amount": 100,
"goodsName": "测试商品",
"extraParams": "",
"status": 2
},
"sign": "响应签名值"
}
使用场景
- 用户支付完成后,主动查询订单状态
- 未收到支付结果通知时,主动查询订单状态
- 对账时查询订单详细信息
重要提示:
- 建议商户在支付完成后主动查询订单状态,以确保订单状态准确
- 查询接口不应频繁调用,建议间隔至少5秒
- 如果订单不存在,接口会返回错误信息
支付结果通知
接口说明
支付结果通知接口用于将支付结果通知到商户指定的回调地址。系统会定时扫描需要通知的订单,并通过POST方式将支付结果发送到商户的notifyUrl。
通知机制
- 系统每10秒扫描一次需要通知的订单
- 每次通知间隔至少30秒
- 最多通知6次
- 通知成功后不再重复通知
- 6次通知失败后,订单通知状态标记为失败
通知地址
商户在发起支付时通过notifyUrl参数指定通知地址。
通知方式
系统使用POST方式发送通知,Content-Type为application/json。
通知参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| merchantNo | String | 商户号 |
| outTradeNo | String | 商户订单号 |
| payMethod | String | 支付方式 |
| tradeNo | String | 平台交易流水号 |
| amount | Integer | 支付金额,单位:分 |
| goodsName | String | 商品名称 |
| extraParams | String | 扩展参数 |
| status | Integer | 订单状态,详见 业务字典 |
| sign | String | 通知签名 |
通知示例
{
"merchantNo": "M1001",
"outTradeNo": "20231229001",
"payMethod": "ALI_WAP",
"tradeNo": "2023122900000001",
"amount": 100,
"goodsName": "测试商品",
"extraParams": "",
"status": 2,
"sign": "通知签名值"
}
响应要求
商户在接收到通知后,需要验证签名,并返回固定字符串表示处理成功。
成功响应
success
失败响应
返回除"success"以外的任何内容,系统将认为通知失败,并在30秒后重试。
处理流程
- 接收通知请求
- 验证通知签名(使用相同的签名算法)
- 根据outTradeNo查询本地订单
- 检查订单状态,避免重复处理
- 更新本地订单状态
- 返回"success"表示处理成功
签名验证示例(Java)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
// 接收到的通知数据
Map<String, Object> notifyData = new HashMap<>();
notifyData.put("merchantNo", "M1001");
notifyData.put("outTradeNo", "20231229001");
notifyData.put("payMethod", "ALI_WAP");
notifyData.put("tradeNo", "2023122900000001");
notifyData.put("amount", 100);
notifyData.put("goodsName", "测试商品");
notifyData.put("status", 2);
String receivedSign = notifyData.remove("sign");
// 商户密钥(Base64编码)
String merchantSecret = "商户密钥Base64字符串";
// 1. 按参数名ASCII升序排序
TreeMap<String, Object> sortedParams = new TreeMap<>(notifyData);
// 2. 拼接成字符串
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> entry : sortedParams.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
String data = sb.toString();
// 3. HMAC-SHA256签名
byte[] secretBytes = Base64.getDecoder().decode(merchantSecret);
SecretKeySpec secretKey = new SecretKeySpec(secretBytes, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
byte[] signBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
String calculatedSign = Base64.getEncoder().encodeToString(signBytes);
// 4. 验证签名
boolean isValid = calculatedSign.equals(receivedSign);
if (isValid) {
// 签名验证成功,处理业务逻辑
// ...
return "success";
} else {
// 签名验证失败
return "签名验证失败";
}
重要提示:
- 必须验证通知签名,防止伪造通知
- 必须检查订单状态,避免重复处理
- 必须返回"success"字符串,不要返回JSON或其他格式
- 建议记录所有通知日志,便于问题排查
通知时机:
- 订单支付成功后立即触发通知
- 支付失败或关闭也会触发通知
- 通知包含最终的订单状态