本文目录导读:

在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后端处理小程序支付的核心流程:
- 统一下单:向微信服务器获取
prepay_id - 生成支付参数:
appId, timeStamp, nonceStr, package, signType, paySign - 异步回调处理:验证签名、金额、防止重复处理、更新订单状态
注意:一定要严格验证签名和金额,处理好回调的幂等性,以上代码为便于理解做了简化,实际生产环境建议使用微信官方 SDK(如 wechatpay-php)或成熟的第三方库。