PHP项目微信支付如何回调处理

wen PHP项目 27

本文目录导读:

PHP项目微信支付如何回调处理

  1. 接收回调数据
  2. 验证签名
  3. 完整的回调处理代码
  4. 关键注意事项
  5. 使用框架(ThinkPHP/Laravel)简化
  6. 测试建议

在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. 使用微信沙箱环境测试
  2. 设置1元测试支付
  3. 在回调URL后添加临时参数方便调试
  4. 使用ngrok等工具让外网访问本地服务

建议使用成熟的微信支付SDK(如wechatpay/wechatpay)简化开发流程。

抱歉,评论功能暂时关闭!