PHP 微信扫码支付怎么接:从配置到回调一次讲清

编程狮(w3cschool.cn) 2026-09-04 16:59:57 浏览数 (68)
反馈

微信扫码支付(Native 模式)的接入可以拆成五步:商户配置、统一下单、生成二维码、异步回调验签、查单补单。本文把这条完整链路一次讲清,重点放在回调验签和幂等这两个最容易翻车的环节。跟着做,你就能在本地跑通一个可验证的扫码支付流程。下面每个环节都附带最容易踩的坑,建议边看边对照自己的商户后台。不管你是用 PHP 做网站还是做后台接口,只要涉及收款,这套流程都绕不开。把链路走顺,你的订单就能从"用户扫码"一路走到"后台确认收款",不用再为掉单、重复通知这类问题发愁。

PHP 微信扫码支付接入流程封面图

一、微信扫码支付整体链路:前后端各自负责什么

扫码支付在微信里的官方叫法是 Native 支付。它的核心思路很朴素:你先把一笔订单告诉微信,微信返回一个很短的支付链接 code_url;你把这个链接变成二维码贴在页面上,用户用微信扫一扫,手机上弹出付款页完成支付;付完之后微信主动通知你的服务器,你再更新订单状态。整个过程里,真正需要你写代码的就是三件事:下单、出码、收通知。

为什么新手容易一次跑通、却在后面卡住?因为前面三步都属于"你主动调用微信",出错会立刻报错,好排查;而最后一步是"微信反过来调你",请求来自外部,签名、超时、重复通知任何一个处理不好,订单就会卡在"已付款但状态没变"。所以这篇文章把重心压在回调上,前面几步只讲清楚怎么把路铺平。

如果你还没系统学过这门语言,可以先过一遍 PHP 教程,把数组、curl、字符串处理这几个基础打牢,再回来接支付会顺很多。下面我们从商户后台的配置开始,一步步往下走。

二、商户配置与统一下单:拿到那串支付链接

在写代码之前,得先确认商户平台里四样东西齐了:公众号或小程序绑定的 AppID、商户号 mchid、APIv3 密钥(用来给回调报文解密和做签名)、以及平台证书(用来验证微信回传的签名)。这四样缺一个,后面的请求都会被微信拒掉。密钥和证书都属于敏感信息,务必放在环境变量或配置文件中,不要硬编码进代码仓库。

统一下单是整条链路的起点。你向微信下单接口提交一笔订单的参数,比较关键的有:商户订单号 out_trade_no(你自己生成的唯一串,建议带上时间戳和随机串)、商品描述 body、金额 total(单位是分,不是元)、回调地址 notify_url、以及 Native 支付特有的商品 ID product_id。微信校验通过后会返回一个 code_url,这就是后面要变成二维码的那串链接。在外文文档和 SDK 示例里,商户订单号常被连写成 outtradeno,支付链接字段常被连写成 codeurl,看到这两种写法知道指的是同一回事就行。

下面是一段简化的 PHP 下单示意,真实项目里建议直接用官方 SDK,这里只是为了让你看清参数和签名的关系:

// 简化示意:构造统一下单参数并请求微信
$params = [
    'appid'        => $appid,
    'mchid'        => $mchid,
    'description'  => '编程狮会员月度订阅',
    'out_trade_no' => 'w3cschool_' . date('YmdHis') . rand(1000, 9999),
    'notify_url'   => 'https://www.w3cschool.cn/notify',
    'amount'       => ['total' => 9900, 'currency' => 'CNY'],
];
// 真实场景需按微信规则对 params 做签名后,再用 curl 发送 JSON 请求
$ch = curl_init('https://api.mch.weixin.qq.com/v3/pay/transactions/native');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Authorization: WECHATPAY2-SHA256-RSA2048 ...']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
$code_url = json_decode(curl_exec($ch), true)['code_url'] ?? '';

这段做了什么:把订单关键信息装进数组,按微信要求签名后用 curl 发出去,从返回里取出 code_url。注意金额单位是分,9900 表示 99 元,写错单位会直接下单失败。拿到 code_url 之后,链路就走到了出码这一步。

三、生成二维码:让用户用微信扫起来

code_url 本身是一串普通文本链接,用户没法直接"扫一串字",所以要把它渲染成二维码图片。PHP 侧可以用开源的二维码生成库(比如基于 QR Code 算法的 composer 包)在后端直接输出 PNG;也可以把这个链接丢给前端,让前端用 JS 库渲染。对初学者最省事的做法是后端生成图片、前端只负责展示。

这里要注意一个常见的认知偏差:code_url 不等于支付成功,它只是"这笔订单的付款入口"。二维码贴出去之后,订单在微信侧处于"待支付"状态,必须等用户真正付款、并且你收到回调,状态才会流转。所以出码环节只负责把入口做出来,不要在这里就提前把页面跳转到"支付成功"。

当你想先确认 code_url 能不能被正常识别时,可以借助 二维码生成器 把链接当场变成图片看效果。这一步纯属联调期的便利手段,等你和前端、微信侧联调通过,再换成项目里真正的生成组件即可,不必长期依赖外部页面。

实际出码时有一个细节值得留意:二维码对应的内容必须是微信返回的 code_url 原文,不要手抖拼接或截断,任何一个字符变动都会让扫码后提示"商户订单号不存在"或"二维码已失效",这种错最难现场复现。

另外,当你拿到一张别人生成好的二维码、又想确认它里面对应的就是这笔订单的 code_url 时,可以借助 二维码解码器 把图片内容还原出来做核对。尤其在对接多个商户号、多个环境的联调阶段,贴错链接导致用户付到别的订单上是最难排查的一类事故,解码核对能提前排雷。

四、异步回调验签与幂等:最容易翻车的两道坎

回调是整套流程里最关键、也最容易被轻视的一步。用户付款完成后,微信会向你在下单时填的 notify_url 发送一个 POST 请求,报文里带着支付结果,同时请求头里附带了签名相关的信息(平台证书序列号、时间戳、随机串、签名值)。你的服务器要做两件事:先验证"这确实是微信发来的",再处理"这笔订单该变成什么状态"。

验签为什么不能省?因为 notify_url 是一个公网地址,任何人都能往上面发 POST。如果你不验签就直接相信报文内容,攻击者可以伪造一条"支付成功"的通知,让你的系统把订单标记为已付款,造成资损。验签的标准做法是:用微信的平台证书公钥,对"时间戳+随机串+请求体"按约定算法算出签名,再和请求头里的签名值比对,一致才可信。拿到可信报文后,还要用 APIv3 密钥把报文里的敏感字段解密出来。

幂等是第二道坎,而且比验签更容易被忽略。微信不保证回调只通知一次——网络抖动、你的服务超时未返回成功响应,微信都会重试,于是同一笔订单你可能收到好几条内容相同的结果通知。如果每次通知都重复"加库存、发权益、写流水",用户就白赚了好几份。正确的写法是以 out_trade_no 为唯一键做去重:处理前先查这笔订单当前状态,已经是"已支付"就直接返回成功;或者依赖数据库唯一索引,重复插入会抛异常,捕获后忽略即可。

// 简化示意:回调里先做幂等判断,再做业务处理
$out_trade_no = $notify['out_trade_no'];
$order = $db->query("SELECT status FROM orders WHERE order_no = ?", [$out_trade_no]);
if ($order && $order['status'] === 'PAID') {
    // 已经处理过,直接告知微信成功,避免重复入账
    echo '{"code":"SUCCESS"}';
    exit;
}
$db->update("UPDATE orders SET status='PAID' WHERE order_no = ?", [$out_trade_no]);
echo '{"code":"SUCCESS"}';

这段做了什么:先按订单号查状态,已支付就直接返回成功,没支付才更新。这样无论微信重试多少次,业务只生效一次。回调方法最后一定要返回微信约定的成功响应,否则微信会一直重试,反而把你的接口打挂。

五、查单补单:通知丢失时的兜底方案

再严谨的回调也可能丢。极端情况下微信的通知因为网络或服务故障没送到你这边,用户钱扣了,你的订单却还停在"待支付",这时候用户会来找你。解决思路是"不要只被动等通知",要主动去问微信:这笔订单到底付没付。

具体做法是建一个定时任务,每隔一段时间扫一遍"待支付超过 N 分钟"的订单,调用微信的查单接口,按 out_trade_no 拉取最新状态。如果查到已支付,就走和回调里一样的幂等更新逻辑;如果还是未支付,就保持原状继续等。这个机制叫"补单",它和回调组成双保险:回调负责快,查单负责稳。

补单逻辑一定要和回调复用同一套状态更新代码,否则两套写法容易出现"回调认为成功、查单认为失败"的不一致。一个稳妥的模式是:把"根据订单号把状态置为已支付"封装成一个函数,回调和查单都只调用它,幂等判断也放在这个函数内部,从根源上避免重复处理。

PHP 微信扫码支付链路走到哪一步了

总结

微信扫码支付(Native 支付)的核心就是五步:配好商户密钥与证书、调用统一下单拿到 code_url、把 code_url 生成二维码、在异步回调里验签并处理业务、用查单接口给通知丢失兜底。整条链路里,前几步是"你主动调用微信",出错立刻可见;真正的高风险在最后一环——回调既要验签确认来源可信,又要用订单号做幂等,防止重复通知造成重复入账。把验签和幂等这两道坎迈过去,再补上主动查单,你的扫码支付就基本稳了。

下一步建议系统地把支付相关的知识补齐:跟着 PHP微信扫码支付课程(图文微课形式)从配置一路跟到上线,把整体手感建立起来;遇到时间戳、金额换算这类细节问题时,可以参考讲时间戳转换在线工具的笔记,里面有可以直接用的处理方式;如果你想把支付能力扩展到更多端,支付宝小程序教程也值得一并了解。具体入口都放在文末「延伸学习」里。

延伸学习

想把这块知识系统补齐,可以按这个顺序来:

  1. 先跟着 PHP微信扫码支付课程 把从商户配置到上线的完整流程走一遍,建立整体手感;
  2. 遇到时间戳、金额单位换算等细节卡点时,参考 时间戳转换在线工具笔记 里的实操办法;
  3. 如果后续要把收款能力扩展到支付宝侧,支付宝小程序教程 能帮你对照着理解另一套支付体系。

常见问题

Q:回调通知一直收不到,订单状态怎么更新?

A:先确认 notify_url 是公网可访问的 HTTPS 地址,且返回的是微信约定的成功响应。如果通知确实丢了,不要只等回调,用查单接口主动轮询"待支付超时"的订单来补单,这是标准兜底手段。

Q:同一笔订单收到多次回调,会重复发货吗?

A:会,所以必须做幂等。以 out_trade_no 为唯一键,处理前先查订单状态,已支付就直接返回成功;或依赖数据库唯一索引捕获重复插入。回调和查单要复用同一套更新函数。

Q:验签失败一般是什么原因?

A:最常见的是平台证书用错版本、或者验签原文拼接顺序和微信不一致。确认用的是微信下发的当前有效平台证书,并且严格按"时间戳+随机串+请求体"的顺序拼接后再验签,不要自己篡改拼接方式。

0 人点赞