本文目录导读:

在PHP项目中对接聚合支付接口(如PayJS、虎皮椒、甚至一些开源聚合支付网关),通常遵循标准的第三方HTTP API对接流程(构建参数 -> 签名 -> 发送请求 -> 接收回调)。
以下是一个通用的、标准化的对接步骤和代码示例,以通用的聚合支付网关(假设支持 支付宝、微信支付、QQ钱包 等)为例。
核心步骤(五步走)
- 获取配置:在聚合支付平台注册账号,获得
商户ID和商户密钥(AppSecret/API Key)。 - 封装请求参数:根据官方文档,组装订单号、金额、支付方式、异步通知地址等。
- 生成签名:这是最关键的步骤,防止数据被篡改(通常使用 MD5 或 HMAC-SHA256)。
- 发起HTTP请求:使用
cURL或Guzzle发送 POST 请求到支付网关。 - 处理异步通知:接收支付平台 POST 过来的回调数据,验证签名并更新订单状态。
代码实战(PHP + cURL)
准备配置类 (config.php)
<?php
// 聚合支付配置
return [
'merchant_id' => '10086', // 商户ID
'merchant_key' => 'xxxxxxxxxxxxxxx', // 商户密钥(API Key)
'gateway_url' => 'https://api.juhepay.com/gateway', // 支付网关地址
'notify_url' => 'https://yourdomain.com/notify.php', // 异步通知地址
'return_url' => 'https://yourdomain.com/return.php', // 同步跳转地址
];
?>
发起支付请求 (pay.php)
<?php
require_once 'config.php';
// 1. 接收前端参数
$pay_type = $_POST['pay_type'] ?? 'alipay'; // alipay, wechat, qq
$amount = $_POST['amount'] ?? 0.01;
$order_id = 'ORD_' . time() . rand(1000, 9999); // 生成商户订单号
// 2. 构建请求参数(具体字段参考聚合支付文档)
$params = [
'merchant_id' => $config['merchant_id'],
'order_id' => $order_id,
'amount' => $amount * 100, // 通常单位为分
'pay_type' => $pay_type,
'notify_url' => $config['notify_url'],
'return_url' => $config['return_url'],
'timestamp' => time(),
];
// 3. 生成签名
$params['sign'] = generateSign($params, $config['merchant_key']);
// 4. 通过cURL发送请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $config['gateway_url']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); // 或 json_encode
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 生产环境建议开启验证
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
curl_close($ch);
// 5. 处理返回结果(通常是JSON)
$result = json_decode($response, true);
if ($result['code'] == 'SUCCESS') {
// 跳转到支付页面(URL在 result['pay_url'] 或 result['qrcode'])
header('Location: ' . $result['pay_url']);
exit;
} else {
echo '支付请求失败:' . $result['msg'];
}
// 签名函数
function generateSign($data, $key) {
// 1. 去除 sign 字段
unset($data['sign']);
// 2. 按ASCII排序
ksort($data);
// 3. 拼接成字符串
$str = '';
foreach ($data as $k => $v) {
if ($v !== '' && $v !== null) {
$str .= $k . '=' . $v . '&';
}
}
$str = trim($str, '&');
// 4. 拼接密钥
$str .= '&key=' . $key;
// 5. 生成签名(大部分使用MD5)
return strtoupper(md5($str));
}
?>
处理异步通知 (notify.php)
注意:这是最关键的部分,必须验证签名,且只处理 POST 请求。
<?php
require_once 'config.php';
// 只处理POST请求
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
exit('Request method error');
}
// 1. 获取回调数据
$data = $_POST; // 或 file_get_contents('php://input') 如果是JSON
// 2. 验证签名(同生成签名逻辑)
$expected_sign = generateSign($data, $config['merchant_key']);
if ($data['sign'] !== $expected_sign) {
// 签名验证失败,可能是伪造请求
exit('sign error');
}
// 3. 验证订单金额(可选)和订单状态
if ($data['status'] !== 'SUCCESS') {
// 支付失败,记录日志
exit('fail');
}
// 4. 业务处理:更新订单状态
$order_id = $data['order_id'];
$pay_amount = $data['pay_amount']; // 实际支付金额
// 伪代码:查询本地订单,更新状态
// $order = $db->get('orders', ['order_id' => $order_id]);
// if ($order && $order['status'] == 0) {
// $db->update('orders', ['status' => 1, 'pay_time' => time()], ['order_id' => $order_id]);
// }
// 5. 通知支付平台处理成功(必须返回 success,否则平台会重复通知)
echo 'success';
exit;
?>
同步跳转处理 (return.php)
<?php
// 用户支付完成后跳转的页面
// 注意:此页面不可靠,仅作为用户提示,不要在这里更新订单状态
echo '支付成功,正在跳转...';
// 等待3秒后跳转到订单详情页
header('refresh:3;url=/order_detail.php?order_id='.$_GET['order_id']);
?>
重要提醒(坑点汇总)
| 类别 | 常见问题 | 正确做法 |
|---|---|---|
| 签名算法 | 拼接顺序错乱、未按文档处理空值 | 严格按照官方文档: 去除空值 -> 按ASCII排序 -> 拼接key -> MD5 |
| 金额单位 | 直接传元(如 00) |
大部分接口以 分 为单位,需 * 100 |
| 通知处理 | 直接在 return.php 更新订单 |
只在 notify.php 更新订单(服务端互信) |
| 幂等性 | 未判重,导致重复发货 | 在 notify.php 先查本地订单状态,已处理则不再处理 |
| 编码 | 中文乱码导致签名失败 | 全部使用 UTF-8 编码 |
| 调试 | 签名校验失败找不到问题 | 在签名前后 var_dump 拼接字符串,与官方文档示例对比 |
推荐使用的工具/库
- Guzzle(推荐)
composer require guzzlehttp/guzzle
- 直接 cURL(原生无依赖,如上代码所示)
- 开源聚合支付 SDK(如:
yansongda/pay,Pay.js 官方 SDK)
对接成功后的测试清单
- [ ] 生成一个1分钱的订单,扫码支付后确认用户余额减少。
- [ ] 查看本地数据库,
notify.php更新了订单状态。 - [ ] 模拟伪造的
notify.php请求(修改金额),确认签名验证拦截。 - [ ] 断网后重发通知,确认幂等性处理。
如果你能提供你具体对接的聚合支付平台名称(如 PayJS、易支付、USDT支付等),我可以给你提供更针对性的代码示例。