十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PHP微信支付扫码收银台实战:Native下单与APIv3回调验签全解析

PHP微信支付扫码收银台实战:Native下单与APIv3回调验签全解析 简介一款基于PHP的微信公众号支付收银台源码面向需要快速实现扫码收款、预约收款、面对面收款等场景的商家及PHP开发者须配合微信认证服务号与微信支付商户号使用客户扫码后进入商家自定义支付页面输入金额及对应信息即可提交微信支付完成快速收款。资源包共1885个文件核心为1327个PHP业务文件同时包含前端展示所需的JS、CSS、PNG图片以及各类配置、说明文档压缩包整体约8.01MB目录结构清晰便于部署与二次开发。功能支持多店铺独立管理每个店铺可配置完全自定义的表单字段涵盖单行文本、多行文本、单选、多选、下拉选择、上传图片、时间等类型收款金额可设为固定值或由客户输入通过自定义表单可轻松搭建快捷收款、微信收银台、商品预约预订等支付场景帮助商户精准收集订单数据完成账款统计提升收银效率与客户体验。对于需要快速落地微信支付收银台或进行二次开发的开发者而言这套源码提供了完整PHP实现和灵活字段设计具备较高参考价值已有253人学习下载。1. 商家收银台的支付体验往往败在最后一公里扫码付款这件事最紧张的不是用户扫码那一刻而是用户手机已经弹出“支付成功”收银台这边还在转圈。实体店场景里这种不同步直接带来两个后果顾客觉得商家不认账商家担心钱没到账不敢放行。标题里这套 PHP 收银台源码把微信支付扫码付款的链路落到一个具体业务形态上收银端向微信支付发起下单拿到 code_url 生成二维码顾客用微信扫这个码完成付款PHP 服务端通过回调通知和主动查单确认订单真正成单。这套流程涉及 Native 下单、APIv3 签名、回调验签、轮询查单、退款和对账任何一个环节有偏差线上都会出问题。这篇文章以 PHP 为技术主线把商户接入微信支付收银台时最常遇到的参数配置、代码实现和验证方法逐一讲透适合正在接支付或者准备二次开发收银系统的 PHP 工程师。2. 用 PHP 接收银台扫码付款前先理清微信支付的消息流2.1 用户扫的不是支付链接而是微信支付返回的 code_url收银台扫码付款在微信支付里对应的是 Native 支付。Native 的含义是“原生扫码”商家屏幕上出现的二维码图片本身不携带任何支付能力它只是一段用 URL 编码过的字符串也就是微信支付下单接口返回的 code_url。用户用微信扫这个码微信客户端拿着这个参数去微信支付后台确认订单然后才进入支付流程。这个设计和很多人的直觉相反收银台页面发起请求后不能自己拼一个二维码去收钱必须由微信支付后台生成 code_url否则微信客户端不认。理解这一点后PHP 服务端的职责就清晰了向微信支付 API 提交订单参数拿到 code_url再交给前端渲染二维码同时启动一个轮询任务去问微信支付“这笔订单到底付了没有”。注意这里说的是用户拿手机扫商家屏幕反过来商家拿扫码枪扫用户付款码是另一种支付产品叫付款码支付接口和签约要求完全不同别在收银台里混用。2.2 商户号、APIv3 密钥与 API 证书三样缺一不可现在微信支付接口已经全面升级到 APIv3老的 v2 接口虽然还在维护但新商户默认使用 APIv3。接入之前需要在微信支付商户平台准备好下面的凭证每一项在后面代码里都会用到。凭证获取位置用途商户号 mchid商户平台首页所有接口请求必带标识收款主体APIv3 密钥商户平台 - API 安全32 字节字符串用于解密回调通知里的加密内容商户 API 证书 apiclient_key.pem商户平台申请并下载用于生成请求签名私钥保存在服务端商户证书序列号 serial_no证书详情或平台可查拼在 Authorization 头里微信支付据此找到对应公钥微信支付平台证书APIv3 密钥管理页面下载用于验证回调通知的签名防止伪造通知这里有一个经常踩的坑APIv3 密钥和 API 证书不是一回事。APIv3 密钥是一串自己设置的随机字符串回调内容用这个密钥做 AES-256-GCM 解密API 证书是申请下来的公私钥文件请求签名用它来算。如果两者互相混淆常见的报错是验签失败、解密失败或者请求头格式错误。2.3 下单到回调的完整时序PHP 侧要留三个状态位把时序理清楚后面写代码就有边界了。收银台向微信支付提交订单微信支付返回 code_url用户扫码后微信支付内部确认支付成功然后把结果异步通知到 PHP 服务端的 notify_url与此同时收银台页面也在不停向 PHP 服务端查订单状态PHP 服务端可以主动调微信支付“查单”接口核对。整个链路里PHP 的数据表至少要存三个字段来支撑状态流转trade_state、transaction_id 和 time_expire。trade_state 记录支付状态取值范围是 SUCCESS、NOTPAY、CLOSED、REFUND 等transaction_id 是微信支付生成的交易号退款时要用time_expire 是订单失效时间超过这个时间用户就不能再支付。常见做法是再加一个 notify_status 字段标记回调是否到达方便事后排查到底是回调丢单还是回调成功但业务处理失败。3. 用 PHP 在本地跑通微信支付扫码收银台的最小实现3.1 第一步封装微信支付 APIv3 的请求签名与 HTTP 调用APIv3 所有接口都要求请求头里带一个 Authorization 字段格式是固定的也是新手最容易写错的地方。签名串的原始内容是把请求方法、请求路径、时间戳、随机字符串和请求体拼起来中间用换行符隔开最后再用商户私钥做 SHA256 签名。?php function buildAuthHeader($method, $urlPath, $body, $mchid, $serialNo, $privateKeyPath) { $timestamp time(); $nonceStr bin2hex(random_bytes(16)); // APIv3 要求的签名串格式method \n urlPath \n timestamp \n nonce \n body \n $message $method . \n . $urlPath . \n . $timestamp . \n . $nonceStr . \n . $body . \n; $privateKey openssl_pkey_get_private(file_get_contents($privateKeyPath)); openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256); return sprintf( WECHATPAY2-SHA256-RSA2048 mchid%s,nonce_str%s,timestamp%d,serial_no%s,signature%s, $mchid, $nonceStr, $timestamp, $serialNo, base64_encode($signature) ); }签名逻辑里有几个细节需要解释。nonce_str 每次请求都必须重新生成如果复用随机字符串被拦截后存在重放风险。urlPath 只需要路径部分比如/v3/pay/transactions/native不需要带域名但查询参数必须完整保留。body 是请求体的原始字符串不是 JSON 编码后的对象更不是数组。请求体为空时body 拼接空字符串最后的换行符依然保留。成对的引号和逗号是微信支付要求的标准格式少一个逗号请求直接报错。3.2 第二步生成预支付订单把 code_url 转成收银台二维码调用 Native 下单接口路径是/v3/pay/transactions/native请求方法 POST。这个接口接收订单描述、商户订单号、金额、回调地址等字段返回 code_url。code_url 的有效期默认是 2 小时收银台场景通常不会让订单挂那么久。?php $orderNo date(YmdHis) . mt_rand(1000, 9999); $amount 1; // 金额单位是分1 元传入 100 $body json_encode([ appid wx1234567890abcdef, // 商户号绑定的 AppID mchid 1230000109, // 商户号 description POS 收银台订单 . $orderNo, out_trade_no $orderNo, notify_url https://pay.example.com/notify.php, // 必须是公网可达的 HTTPS 地址 amount [ total $amount, currency CNY ] ]); $auth buildAuthHeader(POST, /v3/pay/transactions/native, $body, $mchid, $serialNo, $apiclientKey); $curl curl_init(https://api.mch.weixin.qq.com/v3/pay/transactions/native); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $body); curl_setopt($curl, CURLOPT_HTTPHEADER, [ Content-Type: application/json, Accept: application/json, Authorization: . $auth ]); $response curl_exec($curl); $result json_decode($response, true); $codeUrl $result[code_url] ?? ;下单成功后把 $codeUrl 输出到前端二维码组件里。这里建议服务端把 code_url 缓存起来不要把二维码生成逻辑拷到前端。因为 code_url 与订单绑定用户如果刷新页面导致重复下单会生成多个订单容易出现用户在旧二维码上付款、新订单永远等不到回调的情况。3.3 第三步收银台页面轮询订单状态付款成功立刻出票收银台场景和普通商城不一样顾客就站在柜台前面几秒钟内就要给反馈。常见做法是收银台页面拿到 code_url 后用 JavaScript 每隔 2 到 3 秒请求一次 PHP 的查单接口直到订单状态变成 SUCCESS 或者超时。// 收银台页面轮询短轮询在局域网点单场景下比长连接更可靠 const pollTimer setInterval(async () { const res await fetch(/check_order.php?out_trade_no202505010001); const data await res.json(); if (data.trade_state SUCCESS) { clearInterval(pollTimer); showSuccess(data.transaction_id); } else if (data.trade_state CLOSED) { clearInterval(pollTimer); showClosed(); } }, 2500);轮询接口不要直接透传微信支付接口的返回而是在 PHP 侧先读本地订单状态的缓存再决定是否去微信支付查单。原因是微信支付查单接口有频率限制每个收银台同时挂着十几台终端持续轮询峰值请求量很容易打上去。把轮询间隔控制在 2.5 秒以上本地没有结果再查上游收银台体验和接口压力可以取得一个平衡。页面轮询超过 120 秒仍无结果给出提示让收银员手动确认顾客手机上的支付结果不要无限制转下去。3.4 第四步支付结果回调的验签与幂等处理微信支付后台在交易成功后会向 notify_url 推送一条 JSON 通知内容是加密的。PHP 服务端收到通知后第一件事不是去更新订单而是先验签再去解密。验签的目的是确认这条通知真的来自微信支付解密则是拿到订单号和金额明细。?php // 解密回调中的 resource 字段微信支付使用 AES-256-GCM function decryptCallback(array $resource, string $apiV3Key): array { $ciphertext base64_decode($resource[ciphertext]); $nonce $resource[nonce]; $associatedData $resource[associated_data] ?? ; $tag substr($ciphertext, -16); // GCM 模式下 tag 在密文末尾 $ciphertextWithoutTag substr($ciphertext, 0, -16); $decrypted openssl_decrypt( $ciphertextWithoutTag, aes-256-gcm, $apiV3Key, OPENSSL_RAW_DATA, $nonce, $tag, $associatedData ); return json_decode($decrypted, true); }解密后的数据里有 out_trade_no、transaction_id、trade_state 和 amount 等字段。确认 trade_state 为 SUCCESS 后先检查本地订单是否已经是 SUCCESS如果是就直接返回成功应答这叫幂等处理因为微信支付会重复通知重试次数最多可达数十次。再核对金额是否与本地订单一致金额不一致时记录告警但不要直接确认入账。全部校验通过后更新订单状态扣减库存或者触发小票打印然后返回 HTTP 200 和{code: SUCCESS, message: 成功}注意是 JSON 格式不是纯文本。4. 收银台扫码付款的参数怎么设金额、超时与退款对账4.1 金额单位、精度与虚拟支付的区别微信支付 Native 下单接口的金额单位是分而且必须是整数。无论 PHP 业务系统里存的是元还是分提交给微信支付前都要转成整数分。有人习惯用round($amount * 100)这在小数上没问题但遇到浮点精度误差会出现 19.99 元变成 1998 分的情况正确做法是先转字符串再计算或者用 PHP 的整数类型存储金额。?php // 金额转换用字符串计算避免浮点误差 function convertYuanToFen($amount) { return (int) round((string) $amount * 100); }标题涉及收银台大概率是线下实体商品金额精度要求比虚拟支付低。目前很多虚拟商品渠道在讨论代币数量是否支持小数点微信支付侧给出的答案是金额字段按分传整数业务侧想卖 1.5 个代币就在自己系统里把代币数量映射成整数分的价格。收银台如果同时卖虚拟商品建议金额计算统一走服务端前端只负责展示避免用户改请求参数影响价格。4.2 收银台的三类超时参数time_expire、轮询与关单收银台订单生命周期短默认 2 小时的 code_url 有效期显然太长。下单时可以传 time_expire 字段把订单失效时间控制在几分钟内既减少用户扫了码不付款占住订单也避免过期二维码被误扫。参数推荐值说明time_expire支付时间 5 分钟格式为 RFC 3339如 2025-05-01T12:05:0008:00前端轮询上限120 秒超过后提示收银员人工确认本地订单自动关闭超时后调用关单接口关单后用户扫码会提示订单已关闭超时字段设了之后前端轮询也要相应缩短两者要匹配。更稳妥的做法是订单创建时只给 5 分钟有效时间用户长时间不付款PHP 端用一个定时任务批量调微信支付关单接口/v3/pay/transactions/out-trade-no/{out_trade_no}/close。注意关单后不能马上重新生成同号订单同一个 out_trade_no 只能下单一次关单后要换新单号。4.3 退款、对账与投诉回调收银台做的再细也绕不开退款。退款接口的路径是/v3/refund/domestic/refunds需要传原订单号 out_trade_no、退款单号 out_refund_no 和退款金额退款金额不能大于原订单实付金额。退款成功后微信支付会异步通知退款结果通知内容和支付成功通知类似同样需要验签和幂等。对账方面微信支付提供下载交易账单的接口按天拉取前一天的全部交易明细。收银台系统建议每天凌晨跑一次对账任务把微信支付账单和本地订单表做差集找出微信侧有记录但本地没有成功回调的订单。还有一种场景是用户支付成功但收银台界面没有反应这时候要优先看本地有没有收到回调而不是直接退款因为退款后用户既拿到了商品又多了一笔退款处理起来更麻烦。投诉回调是很多团队容易遗漏的接口。微信支付在处理用户投诉时会向配置的投诉回调地址推送投诉信息投诉单如果超时处理会影响商户信用。接入收银台时至少要把投诉回调接口的日志接住确保收到投诉后能定位到对应的订单号。5. 收尾技巧用回调日志和主动查单做微信支付闭环验证5.1 一条命令看回调通没通回调接口写完了第一件事不是连数据库而是看微信支付有没有把通知送到。在 PHP 回调入口临时加一行日志把原始请求体写到文件里是最快的验证方式。tail -f /tmp/wx_notify.log?php // 临时调试用验证通过后务必移除 $rawBody file_get_contents(php://input); file_put_contents(/tmp/wx_notify.log, date(Y-m-d H:i:s) . . $rawBody . PHP_EOL, FILE_APPEND); $data json_decode($rawBody, true);如果这个文件一直不更新先检查 notify_url 在商户平台配置的地址和代码里实际监听的路由是否一致然后用浏览器直接访问一下 notify_url 看是不是返回了 200。微信支付回调要求公网可以访问的 HTTPS 地址证书不是有效或者域名没备案也会导致回调不投递。5.2 用主动查单兜底回调与轮询的双通道回调不是唯一确认支付结果的手段。微信支付提供查单接口请求路径是/v3/pay/transactions/out-trade-no/{out_trade_no}?mchid{mchid}请求方法 GET。查单接口走的是同一个签名逻辑只是 $method 变成 GET$urlPath 带上查询参数$body 为空字符串。查单返回的 trade_state 字段用来判断最终状态SUCCESS 表示已支付NOTPAY 表示未支付CLOSED 表示已关闭。收银台轮询接口在本地缓存未命中时可以调这个接口兜底。当回调一直不来主动查单显示 SUCCESS 时PHP 端可以代回调完成订单确认但要记录一个confirm_source字段标注这笔订单是回调确认还是查单兜底确认方便对账时复盘。5.3 投诉回调要和支付回调共用一套验签通道最后建议把投诉回调的接口和支付回调放在同一个验签方法里。投诉通知的加密字段结构同样是 resource解密后是 complaint_id、openid、order_id 和 amount 等字段。收银台系统不一定要实现完整的投诉处理流程但至少要记录投诉内容和关联订单号再考虑人工介入。投诉回调的时效要求比支付回调高微信支付侧有处理时限超时未响应会影响商户评级。可以在回调里先把投诉单落库返回 SUCCESS再通过企业微信机器人或者邮件通知运营人员这样既不会漏接投诉也给后续处理留出时间。这部分不是支付主链路的必需环节但真正跑过一段时间收银台的人会明白一套能定位到订单的投诉记录比什么都管用。本文还有配套的精品资源点击获取
返回列表