概述
PatMall支付系统为商户提供统一的支付接口,支持微信支付、支付宝等多种支付方式。 本文档将帮助您快速集成PatMall支付服务。
一套API支持所有支付方式
MD5签名机制保障安全
5分钟完成集成
快速开始
获取商户凭证
登录商户后台,在"API配置"中获取以下信息:
商户号(merchantNo): MCH17625364836767533(字符串,创建订单时使用) API密钥(apiSecret): 9557b487d93c9281a754039852907c33(32位MD5值,用于签名) 通道编码(channelCode): wechat_native(必填,如wechat_native、alipay_h5等) 接口地址(apiUrl): https://api.yourdomain.com(系统自动生成,不要以/结尾)
说明:
• merchantNo:商户号(字符串,如:MCH17625364836767533,创建订单时使用)
• apiSecret:API密钥(32位MD5值,用于API请求和回调通知的MD5签名)
• channelCode:通道编码(必填,如wechat_native、alipay_h5等,不传则无法创建订单)
• apiUrl:接口地址(系统自动生成,从商户后台API配置中获取真实的后端地址,不要以/结尾)
• 回调通知使用apiSecret进行MD5签名(与API请求签名方式完全相同)
理解签名机制
所有API请求都需要MD5签名验证,使用apiSecret进行签名
• 排除sign字段和空值
• 签名参数按ASCII码升序排列
• 拼接成 key=value&key=value 格式
• 追加 &key=apiSecret
• MD5加密后转大写
调用API创建订单
发送POST请求到创建订单接口
POST http://127.0.0.1:8001/open/pay/create
Content-Type: application/json
{
"merchantNo": "MCH17625364836767533",
"outTradeNo": "MCH20231201001",
"amount": 1.00,
"subject": "测试商品",
"channelCode": "wechat_native",
"timestamp": 1234567890,
"sign": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}引导用户支付
使用返回的paymentUrl引导用户跳转到支付页面
接收支付通知
配置回调地址,接收异步支付通知
签名机制
签名生成步骤
步骤1: 准备参数
merchantNo: MCH17625364836767533 amount: 1.00 subject: 测试商品 timestamp: 1234567890
步骤2: 过滤并排序
排除sign字段,按ASCII码升序排列
amount, merchantNo, subject, timestamp
步骤3: 拼接字符串
amount=1.00&merchantNo=MCH17625364836767533&subject=测试商品×tamp=1234567890
步骤4: 追加密钥
amount=1.00&merchantNo=MCH17625364836767533&subject=%E6%B5%8B%E8%AF%95%E5%95%86%E5%93%81×tamp=1234567890&key=YOUR_API_SECRET
步骤5: MD5加密并转大写
sign = MD5(stringSignTemp).toUpperCase()
在线签名计算器
必填,如:wechat_native、alipay_h5等
用于API请求和回调通知签名,在商户后台"API配置"中获取(32位MD5值)
API文档
基础信息
创建支付订单
创建支付订单并获取支付链接
http://127.0.0.1:8001/open/pay/create请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchantNo | String | 是 | 商户号(字符串,如:MCH17625364836767533,在商户后台API配置中获取) |
| outTradeNo | String | 是 | 商户订单号(必填,用于异步通知时识别订单) |
| amount | Number | 是 | 订单金额(元,最多2位小数) |
| subject | String | 是 | 订单标题 |
| channelCode | String | 是 | 通道编码(必填,如wechat_native、alipay_h5等,不传则无法创建订单) |
| timestamp | Number | 是 | 时间戳(秒) |
| sign | String | 是 | MD5签名 |
| body | String | 否 | 商品描述 |
| notifyUrl | String | 否 | 异步通知地址(用于接收支付结果通知,支持占位符。如果不传,则使用商户后台配置的默认notifyUrl) |
| returnUrl | String | 否 | 同步跳转地址(如果不传,则使用商户后台配置的默认returnUrl) |
| requestIp | String | 否 | 发送订单IP(可以是商户服务器IP或用户真实IP,由商户决定传什么) |
| attach | String | 否 | 附加数据(原样返回,用于商户自定义参数) |
返回参数
{
"code": 1000,
"message": "success",
"data": {
"orderNo": "P20231201120000001",
"mchOrderNo": "MCH20231201001",
"amount": 1.00,
"status": 1,
"paymentUrl": "https://pay.yourdomain.com/payment/wechat?orderNo=P20231201120000001",
"expireTime": "2023-12-01 12:30:00"
}
}请求示例
const crypto = require('crypto');
// 1. 准备参数
const params = {
merchantNo: 'MCH17625364836767533',
outTradeNo: 'MCH20231201001', // 必填,商户订单号
amount: 1.00,
subject: '测试商品',
channelCode: 'wechat_native', // 必填,通道编码
timestamp: Math.floor(Date.now() / 1000)
};
// 2. 生成签名(使用apiSecret,32位MD5值)
const apiSecret = 'YOUR_API_SECRET'; // 商户后台API配置中的apiSecret(32位MD5值)
// 过滤空值和sign字段
const filteredParams = {};
Object.keys(params).forEach(key => {
if (key !== 'sign' && params[key] !== undefined && params[key] !== null && params[key] !== '') {
filteredParams[key] = params[key];
}
});
// 按ASCII码升序排序
const sortedKeys = Object.keys(filteredParams).sort();
const stringA = sortedKeys.map(k => `${k}=${filteredParams[k]}`).join('&');
const stringSignTemp = `${stringA}&key=${apiSecret}`;
const sign = crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();
// 3. 发送请求
const requestData = { ...params, sign };
const response = await fetch('http://127.0.0.1:8001/open/pay/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
});
const result = await response.json();
console.log(result);
// 4. 处理返回结果
if (result.code === 1000) {
// 创建成功,引导用户跳转到支付页面
window.location.href = result.data.paymentUrl;
} else {
// 创建失败
console.error('创建订单失败:', result.message);
}查询订单
查询订单支付状态和详情
http://127.0.0.1:8001/open/pay/query请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| orderNo | String | 二选一 | 系统订单号 |
| outTradeNo | String | 二选一 | 商户订单号 |
返回参数
{
"code": 1000,
"message": "success",
"data": {
"orderNo": "P20231201120000001",
"mchOrderNo": "MCH20231201001",
"amount": 1.00,
"status": 2,
"payTime": "2023-12-01 12:05:30",
"channelType": "wxpay",
"payMethod": "native",
"subject": "测试商品"
}
}关闭订单
关闭未支付的订单
http://127.0.0.1:8001/open/pay/close请求参数
与查询订单接口相同,提供orderNo或outTradeNo即可
返回参数
{
"code": 1000,
"message": "订单关闭成功",
"data": {
"orderNo": "P20231201120000001",
"status": 3
}
}申请退款
对已支付订单申请退款
http://127.0.0.1:8001/open/pay/refund请求参数
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| orderNo | String | 是 | 系统订单号 |
| refundAmount | Number | 是 | 退款金额(元,最多2位小数) |
| refundReason | String | 否 | 退款原因 |
返回参数
{
"code": 1000,
"message": "退款申请成功",
"data": {
"refundNo": "R20231201120000001",
"orderNo": "P20231201120000001",
"refundAmount": 1.00,
"status": 1
}
}回调通知
支付成功后,系统会向商户配置的notifyUrl发送异步通知。
通知参数
| 参数名 | 类型 | 说明 |
|---|---|---|
| orderNo | String | 系统订单号(用于商户查询订单状态) |
| mchOrderNo | String | 商户订单号 ✅ 核心字段 |
| amount | String | 订单金额(元,字符串格式,保留2位小数) |
| status | Number | 订单状态(1=待支付 2=已支付 3=已关闭 4=已退款) |
| payTime | Number | 支付时间(Unix时间戳,秒) |
| timestamp | Number | 请求时间戳(Unix时间戳,秒,用于防重放攻击) |
| nonce_str | String | 随机字符串(32位,用于防重放攻击) |
| channelType | String | 通道类型(wxpay/alipay等) |
| timestamp | Number | 通知时间戳(秒) |
| nonce_str | String | 随机字符串 |
| isTest | Number | 是否测试回调(0=否, 1=是) |
| sign | String | MD5签名(使用apiSecret签名,与API请求签名方式完全相同) |
⚠️ 重要说明
- • 回调通知使用 apiSecret 进行MD5签名(与API请求签名方式完全相同)
- • 请务必验证签名以确保通知来自平台
- • 收到通知后,应返回纯文本
SUCCESS或JSON格式{"code": 1000}或{"code": "SUCCESS"} - • 未返回SUCCESS,系统将重试通知(最多3次)
- • 回调通知为POST请求,Content-Type为application/json
通知示例
{
"orderNo": "P20231201120000001",
"mchOrderNo": "MCH20231201001",
"amount": "1.00",
"status": 2,
"payTime": 1701406530,
"timestamp": 1701406531,
"nonce_str": "abc123xyz",
"sign": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}验证签名示例
const crypto = require('crypto');
// 1. 接收通知数据(POST请求,Content-Type: application/json)
const notifyData = req.body;
// 2. 提取签名
const receivedSign = notifyData.sign;
// 3. 生成签名(使用apiSecret,32位MD5值,与API请求签名方式完全相同)
const apiSecret = 'YOUR_API_SECRET'; // 商户后台API配置中的apiSecret(32位MD5值)
const params = { ...notifyData };
delete params.sign; // 排除sign字段
// 过滤空值
const filteredParams = {};
Object.keys(params).forEach(key => {
if (params[key] !== undefined && params[key] !== null && params[key] !== '') {
filteredParams[key] = params[key];
}
});
// 按ASCII码升序排序
const sortedKeys = Object.keys(filteredParams).sort();
const stringA = sortedKeys.map(k => `${k}=${filteredParams[k]}`).join('&');
const stringSignTemp = `${stringA}&key=${apiSecret}`;
const calculatedSign = crypto.createHash('md5')
.update(stringSignTemp)
.digest('hex')
.toUpperCase();
// 4. 验证签名
if (receivedSign === calculatedSign) {
// 签名验证通过,处理业务逻辑
console.log('支付成功:', notifyData.orderNo);
// 5. 返回SUCCESS(支持多种格式)
res.send('SUCCESS');
// 或返回JSON格式
// res.json({ code: 1000 });
} else {
// 签名验证失败
console.error('签名验证失败');
res.status(400).send('FAIL');
}状态码说明
响应状态码
| 状态码 | 说明 |
|---|---|
| 1000 | 成功 |
| 1001 | 参数错误 |
| 1002 | 签名验证失败 |
| 1003 | 商户不存在或已禁用 |
| 1004 | 订单不存在 |
| 1005 | 支付通道不可用 |
订单状态
| 状态值 | 说明 |
|---|---|
| 1 | 待支付 |
| 2 | 已支付 |
| 3 | 已关闭 |
| 4 | 已退款 |
代码示例
Node.js
const crypto = require('crypto');
const axios = require('axios');
class PatMallPayClient {
constructor(merchantNo, apiSecret) {
this.merchantNo = merchantNo; // 商户号(字符串,如:MCH17625364836767533)
this.apiSecret = apiSecret; // API密钥Secret(32位MD5值,用于API请求和回调通知签名)
this.baseUrl = 'http://127.0.0.1:8001';
}
// 生成签名
generateSign(params) {
// 过滤空值和sign字段
const filteredParams = {};
Object.keys(params).forEach(key => {
if (key !== 'sign' && params[key] !== undefined && params[key] !== null && params[key] !== '') {
filteredParams[key] = params[key];
}
});
// 按ASCII码升序排序
const sortedKeys = Object.keys(filteredParams).sort();
const stringA = sortedKeys.map(k => `${k}=${filteredParams[k]}`).join('&');
const stringSignTemp = `${stringA}&key=${this.apiSecret}`;
return crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase();
}
// 创建订单
async createOrder(orderData) {
const params = {
merchantNo: this.merchantNo,
outTradeNo: orderData.outTradeNo, // 必填,商户订单号
amount: orderData.amount,
subject: orderData.subject,
channelCode: orderData.channelCode, // 必填,通道编码
timestamp: Math.floor(Date.now() / 1000)
};
// 可选参数(不为空时才添加)
if (orderData.body) params.body = orderData.body;
if (orderData.notifyUrl) params.notifyUrl = orderData.notifyUrl;
if (orderData.returnUrl) params.returnUrl = orderData.returnUrl;
params.sign = this.generateSign(params);
const response = await axios.post(`${this.baseUrl}/open/pay/create`, params);
return response.data;
}
// 查询订单
async queryOrder(orderNo) {
const response = await axios.post(`${this.baseUrl}/open/pay/query`, { orderNo });
return response.data;
}
}
// 使用示例
const client = new PatMallPayClient('MCH17625364836767533', 'YOUR_API_SECRET');
// 创建订单
const result = await client.createOrder({
outTradeNo: 'MCH20231201001', // 必填,商户订单号
amount: 1.00,
subject: '测试商品',
channelCode: 'wechat_native', // 必填,通道编码
body: '商品详情',
notifyUrl: 'https://your-domain.com/notify',
returnUrl: 'https://your-domain.com/return'
});
console.log('支付链接:', result.data.paymentUrl);PHP
<?php
class PatMallPayClient {
private $merchantNo;
private $apiSecret;
private $baseUrl;
public function __construct($merchantNo, $apiSecret) {
$this->merchantNo = $merchantNo; // 商户号(字符串,如:MCH17625364836767533)
$this->apiSecret = $apiSecret; // API密钥Secret(32位MD5值)
$this->baseUrl = 'http://127.0.0.1:8001';
}
// 生成签名
private function generateSign($params) {
unset($params['sign']);
// 过滤空值
$params = array_filter($params, function($value) {
return $value !== null && $value !== '';
});
ksort($params);
$stringA = http_build_query($params);
$stringSignTemp = $stringA . '&key=' . $this->apiSecret;
return strtoupper(md5($stringSignTemp));
}
// 创建订单
public function createOrder($orderData) {
$params = [
'merchantNo' => $this->merchantNo,
'outTradeNo' => $orderData['outTradeNo'], // 必填,商户订单号
'amount' => $orderData['amount'],
'subject' => $orderData['subject'],
'channelCode' => $orderData['channelCode'], // 必填,通道编码
'timestamp' => time()
];
// 可选参数(不为空时才添加)
if (!empty($orderData['body'])) $params['body'] = $orderData['body'];
if (!empty($orderData['notifyUrl'])) $params['notifyUrl'] = $orderData['notifyUrl'];
if (!empty($orderData['returnUrl'])) $params['returnUrl'] = $orderData['returnUrl'];
$params['sign'] = $this->generateSign($params);
$ch = curl_init($this->baseUrl . '/open/pay/create');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// 验证回调签名(使用apiSecret,与API请求签名方式相同)
public function verifyNotify($notifyData, $apiSecret) {
$sign = $notifyData['sign'];
unset($notifyData['sign']);
// 过滤空值
$notifyData = array_filter($notifyData, function($value) {
return $value !== null && $value !== '';
});
ksort($notifyData);
$stringA = http_build_query($notifyData);
$stringSignTemp = $stringA . '&key=' . $apiSecret;
$calculatedSign = strtoupper(md5($stringSignTemp));
return $sign === $calculatedSign;
}
}
// 使用示例
$client = new PatMallPayClient('MCH17625364836767533', 'YOUR_API_SECRET');
$result = $client->createOrder([
'outTradeNo' => 'MCH20231201001', // 必填,商户订单号
'amount' => 1.00,
'subject' => '测试商品',
'channelCode' => 'wechat_native', // 必填,通道编码
'body' => '商品详情',
'notifyUrl' => 'https://your-domain.com/notify.php',
'returnUrl' => 'https://your-domain.com/return.php'
]);
echo '支付链接: ' . $result['data']['paymentUrl'];
?>Python
import hashlib
import requests
import time
class PatMallPayClient:
def __init__(self, merchant_no, api_secret):
self.merchant_no = merchant_no # 商户号(字符串,如:MCH17625364836767533)
self.api_secret = api_secret # API密钥Secret(32位MD5值)
self.base_url = 'http://127.0.0.1:8001'
def generate_sign(self, params):
"""生成签名"""
# 过滤sign字段和空值
filtered = {k: v for k, v in params.items() if k != 'sign' and v is not None and v != ''}
# 按key排序
sorted_keys = sorted(filtered.keys())
# 拼接字符串
string_a = '&'.join([f'{k}={filtered[k]}' for k in sorted_keys])
string_sign_temp = f'{string_a}&key={self.api_secret}'
# MD5加密并转大写
return hashlib.md5(string_sign_temp.encode('utf-8')).hexdigest().upper()
def create_order(self, order_data):
"""创建订单"""
params = {
'merchantNo': self.merchant_no,
'outTradeNo': order_data['outTradeNo'], # 必填,商户订单号
'amount': order_data['amount'],
'subject': order_data['subject'],
'channelCode': order_data['channelCode'], # 必填,通道编码
'timestamp': int(time.time())
}
# 可选参数(不为空时才添加)
if order_data.get('body'):
params['body'] = order_data['body']
if order_data.get('notifyUrl'):
params['notifyUrl'] = order_data['notifyUrl']
if order_data.get('returnUrl'):
params['returnUrl'] = order_data['returnUrl']
params['sign'] = self.generate_sign(params)
response = requests.post(f'{self.base_url}/open/pay/create', json=params)
return response.json()
def query_order(self, order_no):
"""查询订单"""
response = requests.post(f'{self.base_url}/open/pay/query', json={'orderNo': order_no})
return response.json()
def verify_notify(self, notify_data, api_secret):
"""验证回调签名(使用apiSecret,与API请求签名方式相同)"""
sign = notify_data.get('sign')
if not sign:
return False
# 生成签名(使用apiSecret)
params = {k: v for k, v in notify_data.items() if k != 'sign'}
calculated_sign = self.generate_sign_with_secret(params, api_secret)
return sign == calculated_sign
def generate_sign_with_secret(self, params, secret):
"""使用指定密钥生成签名"""
# 过滤空值
filtered = {k: v for k, v in params.items() if v is not None and v != ''}
sorted_keys = sorted(filtered.keys())
# 拼接字符串
string_a = '&'.join([f'{k}={filtered[k]}' for k in sorted_keys])
string_sign_temp = f'{string_a}&key={secret}'
return hashlib.md5(string_sign_temp.encode('utf-8')).hexdigest().upper()
# 使用示例
client = PatMallPayClient('MCH17625364836767533', 'YOUR_API_SECRET')
result = client.create_order({
'outTradeNo': 'MCH20231201001', # 必填,商户订单号
'amount': 1.00,
'subject': '测试商品',
'channelCode': 'wechat_native', # 必填,通道编码
'body': '商品详情',
'notifyUrl': 'https://your-domain.com/notify',
'returnUrl': 'https://your-domain.com/return'
})
print('支付链接:', result['data']['paymentUrl'])常见问题
Q: 签名验证失败怎么办?
A: 请检查以下几点:
- 确认使用的是正确的API密钥(apiSecret)
- 确认参数按ASCII码升序排列
- 确认MD5结果已转为大写
- 确认没有将sign字段参与签名计算
Q: 回调通知如何验签?
A: 回调通知使用apiSecret进行MD5签名,与API请求签名方式完全相同。验签方法与请求签名相同,使用相同的apiSecret(32位MD5值)。
Q: 对接需要填写哪些信息?
A: 在商户后台"API配置"中可以获取以下信息:
- merchantNo: 商户号(字符串,如:MCH17625364836767533,创建订单时使用)
- apiSecret: API密钥(32位MD5值,用于API请求和回调通知的MD5签名)
- apiUrl: 接口地址(系统自动生成,从商户后台API配置中获取真实的后端地址)
- channelCode: 通道编码(必填,如wechat_native、alipay_h5等,不传则无法创建订单)
- notifyUrl: 默认异步通知地址(可选,也可在创建订单时传入)
- returnUrl: 默认同步跳转地址(可选,也可在创建订单时传入)
Q: 订单金额单位是什么?
A: 所有金额单位为元,最多支持2位小数。例如:1.00、99.99、0.01。
Q: 如何选择支付方式?
A: 创建订单时可以指定channelCode(如wechat_native、alipay_h5等),如不指定则由系统根据用户环境自动选择最佳支付方式。
Q: 回调通知会重试吗?
A: 如果您的服务器未返回"SUCCESS",系统会进行重试,最多重试3次。