PHP项目小程序支付如何后端处理

wen PHP项目 26

本文目录导读:

PHP项目小程序支付如何后端处理

  1. 支付流程总览
  2. 核心处理步骤
  3. 辅助工具方法
  4. 安全与注意事项
  5. 完整调用示例(简化版)

在PHP项目中处理小程序支付,后端主要承担统一下单、签名生成、回调处理三大核心职责,下面是一个标准流程的详细实现说明,包含关键代码示例。


支付流程总览

小程序前端           PHP后端              微信支付服务器
   |                   |                      |
   |-- 1.请求下单 ----->|                      |
   |                   |-- 2.统一下单 --------->|
   |                   |<-- 3.返回prepay_id ---|
   |<-- 4.返回支付参数--|                      |
   |-- 5.调起支付 ----->|                      |
   |                   |                      |
   |-- 6.支付结果通知 ->|                      |
   |                   |-- 7.回调验证签名 ----->|
   |                   |<-- 8.查询订单状态 ----|
   |                   |                      |

核心处理步骤

步骤1:统一下单(后端接收前端请求)

小程序前端将 openid、商品信息等发给后端,后端向微信支付服务器发起统一下单请求。

// 统一下单接口
public function unifiedOrder($openid, $total_fee, $out_trade_no, $body)
{
    $appid = 'your_appid';
    $mch_id = 'your_mch_id';
    $key = 'your_api_key';
    $params = [
        'appid'            => $appid,
        'mch_id'           => $mch_id,
        'nonce_str'        => md5(uniqid(mt_rand(), true)),
        'body'             => $body,
        'out_trade_no'     => $out_trade_no,
        'total_fee'        => intval($total_fee * 100), // 单位:分
        'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
        'notify_url'       => 'https://yourdomain.com/api/wxpay/notify',
        'trade_type'       => 'JSAPI',
        'openid'           => $openid,
    ];
    // 生成签名
    $params['sign'] = $this->generateSign($params, $key);
    // 发送请求到微信支付
    $xml = $this->arrayToXml($params);
    $response = $this->postXmlCurl('https://api.mch.weixin.qq.com/pay/unifiedorder', $xml);
    $result = $this->xmlToArray($response);
    if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
        // 获取prepay_id
        $prepay_id = $result['prepay_id'];
        // 生成小程序所需支付参数
        $payParams = $this->getPayParams($appid, $prepay_id, $key);
        return [
            'code' => 0,
            'msg'  => 'success',
            'data' => $payParams
        ];
    } else {
        // 错误处理
        return [
            'code' => -1,
            'msg'  => $result['err_code_des'] ?? '下单失败'
        ];
    }
}

步骤2:生成小程序调起支付参数

private function getPayParams($appid, $prepay_id, $key)
{
    $params = [
        'appId'     => $appid,
        'timeStamp' => (string)time(),
        'nonceStr'  => md5(uniqid(mt_rand(), true)),
        'package'   => 'prepay_id=' . $prepay_id,
        'signType'  => 'MD5',
    ];
    // 计算paySign
    $params['paySign'] = $this->generateSign($params, $key);
    return $params;
}

步骤3:签名生成方法

private function generateSign($params, $key)
{
    // 按键名ASCII排序
    ksort($params);
    $string = '';
    foreach ($params as $k => $v) {
        if ($k != 'sign' && $v != '' && !is_array($v)) {
            $string .= $k . '=' . $v . '&';
        }
    }
    $string .= 'key=' . $key;
    return strtoupper(md5($string));
}

步骤4:支付结果回调处理

微信支付成功后会异步通知 notify_url,后端需验证签名并更新订单状态。

// 回调处理
public function notify()
{
    // 获取微信通知的XML数据
    $xml = file_get_contents('php://input');
    $data = $this->xmlToArray($xml);
    // 验证签名
    $sign = $data['sign'];
    unset($data['sign']);
    $mySign = $this->generateSign($data, $this->key);
    if ($sign !== $mySign) {
        // 签名验证失败
        return $this->responseXml('FAIL', '签名失败');
    }
    // 验证返回码
    if ($data['return_code'] !== 'SUCCESS' || $data['result_code'] !== 'SUCCESS') {
        return $this->responseXml('FAIL', '支付失败');
    }
    // 验证订单
    $out_trade_no = $data['out_trade_no'];
    $transaction_id = $data['transaction_id'];
    $total_fee = $data['total_fee']; // 单位:分
    // 查询本地订单并验证金额
    $order = $this->getOrder($out_trade_no);
    if (!$order) {
        return $this->responseXml('FAIL', '订单不存在');
    }
    if ($order['total_fee'] != $total_fee) {
        return $this->responseXml('FAIL', '金额不匹配');
    }
    if ($order['status'] != 'unpaid') {
        // 防止重复处理
        return $this->responseXml('SUCCESS', 'OK');
    }
    // 更新订单状态
    $this->updateOrderStatus($out_trade_no, 'paid', $transaction_id);
    // 执行后续业务逻辑(比如加积分、发货等)
    // 返回成功给微信
    return $this->responseXml('SUCCESS', 'OK');
}
// 返回给微信的XML格式
private function responseXml($return_code, $return_msg)
{
    return "<xml>
              <return_code><![CDATA[{$return_code}]]></return_code>
              <return_msg><![CDATA[{$return_msg}]]></return_msg>
            </xml>";
}

步骤5:订单查询(可选 - 用于兜底)

public function queryOrder($out_trade_no)
{
    $params = [
        'appid'        => $this->appid,
        'mch_id'       => $this->mch_id,
        'out_trade_no' => $out_trade_no,
        'nonce_str'    => md5(uniqid(mt_rand(), true)),
    ];
    $params['sign'] = $this->generateSign($params, $this->key);
    $xml = $this->arrayToXml($params);
    $response = $this->postXmlCurl('https://api.mch.weixin.qq.com/pay/orderquery', $xml);
    $result = $this->xmlToArray($response);
    if ($result['trade_state'] === 'SUCCESS') {
        // 支付成功
    }
    return $result;
}

辅助工具方法

// XML转数组
private function xmlToArray($xml)
{
    libxml_disable_entity_loader(true);
    return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
// 数组转XML
private function arrayToXml($arr)
{
    $xml = "<xml>";
    foreach ($arr as $key => $val) {
        if (is_numeric($val)) {
            $xml .= "<{$key}>{$val}</{$key}>";
        } else {
            $xml .= "<{$key}><![CDATA[{$val}]]></{$key}>";
        }
    }
    $xml .= "</xml>";
    return $xml;
}
// POST XML数据
private function postXmlCurl($url, $xml)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    $response = curl_exec($ch);
    curl_close($ch);
    return $response;
}

安全与注意事项

1 签名验证

  • 所有与微信交互的数据都必须进行签名验证(包括请求和响应)
  • 签名算法必须正确,密钥需严格保密

2 回调幂等性

  • 微信可能重复发送回调通知
  • 必须通过订单状态(如 status = 'unpaid')确保只处理一次

3 金额校验

  • 回调中必须验证 total_fee 与数据库订单金额一致
  • 防止中间人篡改或伪造通知

4 密钥安全

  • API密钥、商户证书等敏感信息应存储在环境变量或配置文件中
  • 切勿硬编码在代码中或上传到公开仓库

5 日志记录

  • 记录完整的请求/响应日志(XML格式),用于排查问题
  • 记录关键时间点:下单请求、回调接收、订单处理完成

完整调用示例(简化版)

// 小程序端 - 用户点击支付
// 1. 后端接收前端请求,获得 openid 和订单信息
$openid = $_POST['openid'];
$order = [
    'out_trade_no' => '20231001' . rand(1000, 9999),
    'total_fee'    => 0.01, // 测试金额
    'body'         => '测试商品',
];
// 2. 统一下单
$result = $this->unifiedOrder($openid, $order['total_fee'], $order['out_trade_no'], $order['body']);
if ($result['code'] == 0) {
    // 3. 返回支付参数给小程序端
    echo json_encode($result['data']);
} else {
    echo json_encode(['error' => $result['msg']]);
}
// 小程序端收到后,调用 wx.requestPayment 发起支付

PHP后端处理小程序支付的核心流程:

  1. 统一下单:向微信服务器获取 prepay_id
  2. 生成支付参数appId, timeStamp, nonceStr, package, signType, paySign
  3. 异步回调处理:验证签名、金额、防止重复处理、更新订单状态

注意:一定要严格验证签名和金额,处理好回调的幂等性,以上代码为便于理解做了简化,实际生产环境建议使用微信官方 SDK(如 wechatpay-php)或成熟的第三方库。

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