本文目录导读:

在PHP项目中处理微信支付回调,核心流程如下:
接收回调数据
// 获取微信服务器POST的原始数据
$xmlData = file_get_contents('php://input');
// 将XML转换为数组
$data = simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA);
$data = json_decode(json_encode($data), true);
验证签名
function verifySign($data, $key) {
// 1. 过滤空值并排序
ksort($data);
// 2. 拼接字符串
$string = '';
foreach ($data as $k => $v) {
if ($k != 'sign' && $v != '') {
$string .= $k . '=' . $v . '&';
}
}
// 3. 拼接商户密钥
$string = $string . 'key=' . $key;
// 4. MD5加密并转大写
$sign = strtoupper(md5($string));
// 5. 对比签名
return $sign === $data['sign'];
}
完整的回调处理代码
<?php
// notify.php
// 微信支付回调处理
class WechatPayNotify {
private $appid = '你的APPID';
private $mchid = '你的商户号';
private $key = '你的商户密钥';
public function handle() {
try {
// 1. 获取回调数据
$xmlData = file_get_contents('php://input');
Log::write('微信回调数据:' . $xmlData);
// 2. 解析XML
$data = $this->xmlToArray($xmlData);
// 3. 验证签名
if (!$this->verifySign($data)) {
throw new \Exception('签名验证失败');
}
// 4. 验证业务结果
if ($data['return_code'] != 'SUCCESS' || $data['result_code'] != 'SUCCESS') {
throw new \Exception('支付失败:' . $data['err_code_des']);
}
// 5. 处理订单
$orderNo = $data['out_trade_no'];
$transactionId = $data['transaction_id'];
$totalFee = $data['total_fee']; // 单位:分
// 查询订单状态,避免重复处理
if ($this->isOrderProcessed($transactionId)) {
return $this->responseSuccess();
}
// 执行订单更新逻辑
$this->updateOrderStatus($orderNo, $transactionId, $totalFee);
// 6. 返回成功响应
return $this->responseSuccess();
} catch (\Exception $e) {
Log::error('回调处理失败:' . $e->getMessage());
return $this->responseFail();
}
}
// XML转数组
private function xmlToArray($xml) {
$result = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
return json_decode(json_encode($result), true);
}
// 验证签名
private function verifySign($data) {
if (!isset($data['sign'])) {
return false;
}
// 过滤空值并排序
$signData = array_filter($data, function($value) {
return $value !== '';
});
unset($signData['sign']);
ksort($signData);
// 拼接字符串
$string = '';
foreach ($signData as $k => $v) {
$string .= $k . '=' . $v . '&';
}
$string .= 'key=' . $this->key;
// 生成签名
$sign = strtoupper(md5($string));
return $sign === $data['sign'];
}
// 检查订单是否已处理(幂等性处理)
private function isOrderProcessed($transactionId) {
// 查询数据库是否已存在该交易记录
// return Db::table('orders')->where('transaction_id', $transactionId)->find();
return false;
}
// 更新订单状态
private function updateOrderStatus($orderNo, $transactionId, $totalFee) {
// 1. 开启事务
Db::beginTransaction();
try {
// 2. 更新订单状态
Db::table('orders')
->where('order_no', $orderNo)
->update([
'status' => 1, // 已支付
'transaction_id' => $transactionId,
'pay_time' => date('Y-m-d H:i:s'),
'total_fee' => $totalFee
]);
// 3. 记录支付日志
Db::table('pay_logs')->insert([
'order_no' => $orderNo,
'transaction_id' => $transactionId,
'total_fee' => $totalFee,
'create_time' => date('Y-m-d H:i:s')
]);
// 4. 提交事务
Db::commit();
// 5. 其他业务处理(发送通知等)
// $this->afterPay($orderNo);
} catch (\Exception $e) {
Db::rollback();
throw $e;
}
}
// 返回成功响应
private function responseSuccess() {
$result = '<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>';
echo $result;
exit;
}
// 返回失败响应
private function responseFail() {
$result = '<xml>
<return_code><![CDATA[FAIL]]></return_code>
<return_msg><![CDATA[处理失败]]></return_msg>
</xml>';
echo $result;
exit;
}
}
// 使用示例
$notify = new WechatPayNotify();
$notify->handle();
?>
关键注意事项
1 幂等性处理
- 使用 transaction_id 作为唯一标识,防止重复处理
- 在更新订单前检查是否已处理
2 日志记录
// 记录所有回调数据
file_put_contents('/tmp/wechat_notify.log', date('Y-m-d H:i:s') . " " . $xmlData . "\n", FILE_APPEND);
3 返回格式
微信要求返回XML格式:
<!-- 成功 --> <xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg> </xml> <!-- 失败 --> <xml> <return_code><![CDATA[FAIL]]></return_code> <return_msg><![CDATA[参数格式校验错误]]></return_msg> </xml>
4 安全性检查
// 1. 验证商户号
if ($data['mch_id'] != $this->mchid) {
throw new \Exception('商户号不匹配');
}
// 2. 验证APPID
if ($data['appid'] != $this->appid) {
throw new \Exception('APPID不匹配');
}
// 3. 验证金额(防止篡改)
$order = Db::table('orders')->where('order_no', $data['out_trade_no'])->find();
if ($order && $order['total_fee'] != $data['total_fee']) {
throw new \Exception('金额不匹配');
}
使用框架(ThinkPHP/Laravel)简化
// ThinkPHP 6 示例
use think\facade\Log;
use think\facade\Db;
class Notify
{
public function handle()
{
$xmlData = file_get_contents('php://input');
$data = json_decode(json_encode(simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
Log::info('微信回调', $data);
// 验证签名...
// 处理订单...
return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}
}
测试建议
- 使用微信沙箱环境测试
- 设置1元测试支付
- 在回调URL后添加临时参数方便调试
- 使用ngrok等工具让外网访问本地服务
建议使用成熟的微信支付SDK(如wechatpay/wechatpay)简化开发流程。