简介:生成QQ、微信、支付宝三合一收款二维码的PHP源码,定位轻量实用,适合个人站长、中小商户或需要聚合收款码的开发者直接部署使用,不依赖任何第三方接口即可自行搭建在线收款码生成系统。压缩包内共4个文件,包含PHP主程序与二维码生成组件、一个HTML辅助说明页和一个CSS样式文件,整体仅33KB,结构精简、便于二次修改和快速上线。已有443人浏览学习。通过简单修改index.php中的解码地址即可替换为自己的收款码,并可按需调整页面样式与配色,生成可同时识别QQ、微信、支付宝扫码支付的三合一收款码。源码内置phpqrcode二维码生成库,无需额外安装扩展,同时附带404跳转页面,对熟悉PHP虚拟主机部署的用户十分友好,适合作为轻量级收款码工具用于博客、官网或线下展示场景。
1. 收款码生成器三合一,不是拼图,是扫码路由
把微信、支付宝、QQ 三个收款码合并成一张二维码,最容易踩的坑是直接在图片层面做“拼接”。实际扫一下就会发现,系统要么只识别出叠在最上面那张,要么直接报错——二维码的纠错机制会优先解出离摄像头最近的码,后续内容全被截断。三合一收款码生成器真正要解决的,是把三个支付链接收敛成一个跳转地址,再对这个地址做二维码生成。用户扫到的永远是同一个 URL,服务端拿到请求特征后再分流到对应收银台。这个思路也解释了为什么市面上的收款码生成器基本都是“在线工具”形态:它需要一个能跑脚本的服务端,单靠静态图片做不到。本文按最常见的 PHP 源码方案来拆解,从识别原理、路由源码、二维码渲染到部署排错,给出能直接落地用的代码和参数。
2. 三合一收款码识别原理:UA 分流与二维码落地页设计
2.1 为什么三合一必须走跳转页,而不是把三个链接塞进一张图
支付宝收款链接形如https://qr.alipay.com/xxxx,微信支付链接是https://wpay.weixin.qq.com/xxxx,QQ 钱包链接则指向i.qianbao.qq.com。有人曾尝试把三个 URL 直接拼接成一个字符串再用二维码生成器编码,这个做法有两层隐患。先不说支付平台会不会效验链接格式,单看二维码本身:QR Code 的数据容量上限虽然接近 3KB,但内容越长,编码版本就越高,模块越密。三个长链接拼起来通常超过 600 字符,版本会升到 15 以上,打印在小票、名片上以后,超市灯光、手机镜头畸变、纸张折痕都会让识别率断崖式下跌。
所以成熟的三合一收款码生成器,二维码里只放一个带路由参数的短地址,比如https://pay.yourdomain.com/pay.php?m=10086。三个原始收款链接存到服务端配置里,二维码只负责把用户带到路由页,由路由决定下一步跳去哪。这个设计把“码的容量问题”转换成了“服务端路由问题”,也是后面所有源码实现的基础。
2.2 微信、支付宝、QQ 的 UA 特征与分流判断
路由页拿到请求后,靠什么判断当前环境?最可靠、也最省资源的办法是读User-Agent。微信内置浏览器的 UA 里固定带MicroMessenger,支付宝客户端带AlipayClient,手机 QQ 带QQ/和MQQBrowser,这几个特征串从移动端到 PC 端都相对稳定,用字符串匹配就够了。
| 扫码环境 | UA 特征串 | 路由目标 | 典型触发场景 |
|---|---|---|---|
| 微信 | MicroMessenger | 微信支付链接 | 微信扫一扫、聊天内长按识别 |
| 支付宝 | AlipayClient | 支付宝收款链接 | 支付宝扫一扫、生活号内打开 |
QQ/或MQQBrowser | QQ 钱包收款链接 | QQ 扫一扫、QQ 内置浏览器 | |
| 其它浏览器 | 无上述特征 | 展示支付方式选择页 | 抖音、钉钉、系统相机扫码 |
用 PHP 实现这个判断只需要一个strpos循环,代码写出来大约 20 行。下面这段是路由判断的核心逻辑,实际项目里可以直接抄进pay.php:
<?php // pay.php 三合一收款路由入口 $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; $routes = [ 'MicroMessenger' => 'wechat', 'AlipayClient' => 'alipay', 'MQQBrowser' => 'qq', 'QQ/' => 'qq', ]; foreach ($routes as $keyword => $platform) { if (strpos($ua, $keyword) !== false) { header('Location: ' . getPayUrl($platform), true, 302); exit; } } // 未命中则加载选择页,见 2.3 include 'select_page.html';这段代码的匹配顺序有讲究:MQQBrowser要写在QQ/前面,因为手机 QQ 的 UA 里两个特征可能同时出现,但MQQBrowser更精确。header()的第二个参数302表示临时重定向,避免浏览器缓存跳转结果,万一以后要换链接还能立即生效。getPayUrl()是从配置文件读链接的封装函数,下一章会给出完整实现。
2.3 落地页还要做好“未知 App”兜底
UA 判断并不是万能的。抖音、钉钉、企业微信的 UA 不包含上面任何特征串,系统相机扫码也可能直接调用默认浏览器访问,这时候路由必须回退到一个“选择页”。选择页上展示微信、支付宝、QQ 三个按钮,每个按钮都是一个a标签,href直接指向对应的支付链接。用户点击某个按钮后,当前浏览器会尝试拉起对应 App,拉不起来也能复制链接到浏览器打开。
这层兜底不是可选项。很多收款码生成器源码把 UA 匹配做得很好,却忽略了“未识别环境”,结果用户扫出来是一张纯白页面。选择页还可以加一个navigator.userAgent的实时显示,方便排查扫码环境,这个调试信息上线后可以隐藏。
3. 收款码生成器源码(PHP 版):路由、配置与二维码生成
3.1 源码目录结构与收款码参数配置表
一套能直接部署的三合一收款码源码,至少需要四个文件:配置文件存三个支付链接、路由文件做 UA 分流、生成器页面提供表单输入、二维码库负责渲染。常见的目录结构是这样:
paycode/ ├── config.php # 收款链接和商户配置 ├── pay.php # 扫码落地路由(UA 分流) ├── index.php # 收款码生成器页面(前端表单) ├── qrcode.min.js # 二维码渲染库 ├── select_page.html # 未识别环境时的兜底选择页 └── self_check.php # 自检脚本(第 6 章用到)config.php里配置三个支付链接,字段设计成关联数组,便于后续从数据库读取扩展。参数说明如下表:
| 配置键 | 示例值 | 说明 |
|---|---|---|
merchant | 张三的小店 | 商户名称,显示在选择页 |
wechat_pay_url | https://wpay.weixin.qq.com/xxxx | 微信支付/收款链接 |
alipay_pay_url | https://qr.alipay.com/xxxx | 支付宝收款码链接 |
qq_pay_url | https://i.qianbao.qq.com/xxxx | QQ 钱包收款链接 |
配置文件的写法不复杂,重点是要把三个链接单独拎出来,不要让它们散落在业务代码里。下面这段可以直接用:
<?php // config.php 收款链接配置 return [ 'merchant' => '张三的小店', 'wechat_pay_url' => 'https://wpay.weixin.qq.com/xxxx', 'alipay_pay_url' => 'https://qr.alipay.com/xxxx', 'qq_pay_url' => 'https://i.qianbao.qq.com/xxxx', ];把配置做成return数组,而不是普通变量,是为了配合require的返回值赋值:调用处写$config = require 'config.php';就能直接拿到数组,不用关心全局变量污染。后续如果要做多商户版本,把这份配置改成数据库表行即可,路由代码不用动。
3.2 用 PHP 写 UA 跳转路由,核心 30 行
路由文件把配置和跳转逻辑串起来,完整实现如下。这个文件同时承担“生成”和“落地”两个职责:生成时它接收参数生成跳转链接;扫码时它根据 UA 做重定向。两份代码并不冲突,合并写在一个入口里反而省去额外路由规则。
<?php // pay.php 三合一收款码统一入口 $config = require 'config.php'; // 1. 从生成器传来的参数中读取商户标识 // 用 base64_decode 而不是明文传链接,避免 URL 过长 $payload = $_GET['m'] ?? ''; $payload = base64_decode($payload); // 2. 从配置中取出当前商户的三个支付链接 $payUrls = [ 'wechat' => $config['wechat_pay_url'], 'alipay' => $config['alipay_pay_url'], 'qq' => $config['qq_pay_url'], ]; // 3. 根据 UA 特征串分流 $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; $uaRules = [ 'MicroMessenger' => 'wechat', 'AlipayClient' => 'alipay', 'MQQBrowser' => 'qq', 'QQ/' => 'qq', ]; foreach ($uaRules as $keyword => $platform) { if (strpos($ua, $keyword) !== false) { header('Location: ' . $payUrls[$platform], true, 302); exit; } } // 4. 未识别环境时展示选择页 $merchantName = $config['merchant']; include 'select_page.html';这里我做了一个取舍:用base64_decode从m参数还原链接,而不是直接把支付链接拼进 URL。因为支付宝和微信的收款链接本身就有 60 到 100 个字符,三个拼一起会使最终二维码长度超标。改进后可把链接存到数据库,m参数只传自增 ID,这样二维码内容能控制在 80 字符以内,识别率最高。
参数说明上,$_GET['m']是生成器在创建二维码时写入的;如果没传或解码失败,代码会走进include select_page.html,这其实也是一种容错。生产环境建议把include后面加一个exit和日志记录,方便追踪哪些场景反复落到兜底页。
3.3 二维码生成端与路由端如何共用一份源码
生成器页面index.php和路由pay.php是同一份 PHP 源码里的两个角色。生成端的职责是把用户填写的三个链接拼成跳转链接,路由端再把链接解开。中间传递的数据结构必须约定好,常见做法是生成端输出一个完整跳转 URL 给二维码库,而路由端只负责消费这个 URL。
<?php // 生成端核心逻辑(index.php 内联代码) // 用户提交三个链接后,拼成 pay.php 可识别的参数 $wx = $_POST['wechat_pay_url'] ?? ''; $ali = $_POST['alipay_pay_url'] ?? ''; $qq = $_POST['qq_pay_url'] ?? ''; $payload = base64_encode(json_encode([ 'wx' => $wx, 'ali' => $ali, 'qq' => $qq, ])); $finalUrl = '//' . $_SERVER['HTTP_HOST'] . '/pay.php?m=' . urlencode($payload);这里把三个链接放进一个 JSON 再 base64,严格说容量压力转移到了 JSON 本身。如果商户数量多,还是推荐用数据库短 ID。判断一份收款码源码成不成熟,就看它有没有把“链接入库”做成可选模块。短码方案下,生成端生成的二维码内容短、模板少,扫码解析速度快,排错也容易。
4. 在线三合一收款码生成器前端:表单交互与二维码渲染
4.1 用 qrcode.js 在前端直接出码,不依赖后端图片接口
二维码渲染有两种路径:后端用 PHP GD 库生成图片,前端用 JavaScript 库直接画 canvas。做收款码生成器时,我一般推荐后者。qrcode.js 是一个无依赖的纯前端库,不需要后端安装任何图像扩展,生成速度也快。更重要的是,二维码内容本来就是前端拼好的跳转链接,在前端直接生成,省掉一次 AJAX 请求,部署时只需静态文件。
下面是index.php里的完整表单和渲染逻辑:
<!-- index.php 收款码生成器页面 --> <script src="qrcode.min.js"></script> <div class="form-row"> <label>微信收款链接</label> <input id="payWx" type="url" placeholder="https://wpay.weixin.qq.com/..."> </div> <div class="form-row"> <label>支付宝收款链接</label> <input id="payAli" type="url" placeholder="https://qr.alipay.com/..."> </div> <div class="form-row"> <label>QQ钱包收款链接</label> <input id="payQq" type="url" placeholder="https://i.qianbao.qq.com/..."> </div> <div id="qrcode"></div> <button onclick="renderQrcode()">生成三合一收款码</button> <script> let qr = null; function buildPayUrl() { const wx = document.getElementById('payWx').value.trim(); const ali = document.getElementById('payAli').value.trim(); const qq = document.getElementById('payQq').value.trim(); // 参数顺序固定,与 pay.php 解包顺序保持一致 const payload = btoa(unescape(encodeURIComponent( JSON.stringify({ wx: wx, ali: ali, qq: qq }) ))); return location.origin + '/pay.php?m=' + encodeURIComponent(payload); } function renderQrcode() { const url = buildPayUrl(); const box = document.getElementById('qrcode'); box.innerHTML = ''; qr = new QRCode(box, { text: url, width: 320, height: 320, correctLevel: QRCode.CorrectLevel.H }); } </script>btoa在遇到中文时会报错,所以先用encodeURIComponent编码一遍,再用unescape还原成 UTF-8 字节序列,最后btoa。这个过程和 PHP 的base64_encode是互逆的,注意前后端必须使用同一套字符编码,否则 base64 解码出来是乱码。correctLevel: H把容错率调到最高一档,这样后面加 Logo 仍有冗余纠错空间。
4.2 表单参数与二维码内容的一致性校验
用户从支付宝后台复制的链接可能带换行或空格,某些浏览器还会自动补齐https://。生成器前端至少要做一个基础校验,避免生成一个扫不开的二维码:
// 生成前校验,返回错误信息 function validatePayUrl(input) { const val = input.value.trim(); if (val === '') return '收款链接不能为空'; if (!/^https?:\/\//i.test(val)) return '链接必须以 http(s):// 开头'; if (val.length > 500) return '链接长度超过 500 字符,请改用短链'; // 简单域名白名单,防止复制错 if (!/(wpay\.weixin\.qq\.com|qr\.alipay\.com|qianbao\.qq\.com)/.test(val)) { return '链接域名不在微信/支付宝/QQ 钱包白名单内'; } return ''; }白名单校验值得多说一句。它不能真正验证链接是否属于当前商户,但能拦截 90% 的复制错误,比如把微信码填到支付宝栏里。要拿到更严格的校验,得在服务端请求一次支付链接,看返回状态码和重定向目标,这部分放到第 6 章自检脚本里做。
4.3 加 Logo、调容错率、留白边:扫描率的关键参数
生成器给二维码加 Logo 是常见需求,但 Logo 会遮挡数据模块。QR 码有四级容错:L(7%)、M(15%)、Q(25%)、H(30%),加 Logo 必须用 H 级。日常常见的做法是 Logo 占码面不超过四分之一,并且放在三个定位角以外的中心区域。
| 使用场景 | 建议容错级 | Logo 大小 | 白边模块数 |
|---|---|---|---|
| 屏幕展示 | M | 无 Logo | 2 |
| 打印小票 | Q | 无 Logo | 4 |
| 带 Logo 的收款码 | H | 边长 ≤ 码的 25% | 4 |
| 布料/陶瓷印刷 | H | 边长 ≤ 码的 15% | 6 |
白边(quiet zone)是二维码四周的空白区域,很多生成器默认不画,导致扫码器无法定位。qrcode.js 在 canvas 模式下可以通过 CSS 给容器加 padding 来模拟白边,更稳妥的做法是让后端在输出 PNG 时把白边画进图片,这样无论用户怎么存图都不会丢边距。
5. 部署在线收款码生成器:Nginx、域名与“无法识别二维码”排错
5.1 最小可用的 Nginx + PHP-FPM 站点配置
把源码传上服务器后,需要让pay.php和index.php都能被访问。以 Nginx 为例,配置一个站点的核心片段如下:
server { listen 80; server_name pay.example.com; root /var/www/paycode; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.1-fpm.sock; } location ~* \.(png|jpg|js|css)$ { expires 7d; access_log off; } }try_files那一行保证 SEO 友好的路由入口;.php$的 location 把 PHP 请求交给 fastcgi;静态资源单独缓存 7 天,二维码库和 Logo 图片都能被浏览器缓存。部署时如果只有 HTTP 没有 HTTPS,微信和支付宝内置浏览器会拦截重定向,所以上线前务必配好 HTTPS 证书,腾讯云和阿里云的免费证书都够用。
5.2 微信扫码后“无法识别二维码格式”的排查路径
这个问题在收款码生成场景里出现频率最高。二维码本身是一张图片,微信扫码后提示“无法识别”,九成原因不是二维码坏了,而是解析出来的内容有问题。
| 现象 | 常见原因 | 检查方法 |
|---|---|---|
| 扫码提示“无法识别二维码” | 二维码内容过长,版本过高 | 用短 ID 替代完整跳转链接 |
| 扫码出来一长串字母,不跳转 | Payload 未做 URL 编码 | 检查urlencode($payload) |
| 扫码后提示“网页无法打开” | 域名未备案或未配 HTTPS | 浏览器直接访问跳转链接 |
| 微信内打开后白屏 | UA 没命中,选择页路径写错 | 查看select_page.html相对路径 |
| 能打开但跳错平台 | UA 规则顺序配置错误 | 用 curl 模拟三种 UA 逐一验证 |
这里最容易忽略的是 URL 编码。生成器前端拼的?m=参数如果是 base64,其中会包含+、/、=,放进 URL 前必须做 encodeURIComponent。漏掉这一步,扫码后 PHP 拿到的$_GET['m']会被截断。
5.3 使用 curl 模拟 UA 验证跳转结果
代码改完别急着上线,先在命令行验证三种环境的跳转是否符合预期。curl 支持-A参数伪造 UA,用-I只取响应头,重点看Location:
curl -sI -A "MicroMessenger/8.0" \ "https://pay.example.com/pay.php?m=xxxx" | grep -E "HTTP|Location" curl -sI -A "AlipayClient/10.1" \ "https://pay.example.com/pay.php?m=xxxx" | grep -E "HTTP|Location" curl -sI -A "QQ/8.9 MQQBrowser/6.2" \ "https://pay.example.com/pay.php?m=xxxx" | grep -E "HTTP|Location"预期结果应该是三个请求分别返回302,且Location指向各自平台的收款链接。如果三个Location都一样,说明 UA 规则没写对,或者请求参数m解析出来是空的。注意不能加-L跟随重定向,否则 curl 会直接按跳转执行请求,输出会被污染。
6. 收款码生成器进阶:批量自检二维码内容与路由可达性
6.1 用 zbarimg 批量校验生成的二维码图片
二维码生成器上线后,用户可能一次性生成上百张码,人工一张张扫码不现实。Linux 上装好 zbar-tools 后,可以用脚本批量读取模板里的.png,并检查解码出来的文本是不是正确的跳转链接:
#!/bin/bash # check_qrcodes.sh 批量校验二维码图片内容 for png in ./qrcodes/*.png; do raw=$(zbarimg --raw "$png" 2>/dev/null) if [[ "$raw" == *"pay.php?m="* ]]; then echo "[OK] $png" else echo "[FAIL] $png -> $raw" fi donezbarimg --raw会跳过图形界面直接输出解码文本,脚本里的条件判断检查文本是否包含pay.php?m=,防止生成器把空 URL 或配置环境变量当成有效内容。这个自检对“印刷前”的码尤其重要,因为印刷后没法快速改。
6.2 自动化模拟三端跳转,校验路由可达性
除了二维码内容,路由本身也可能出问题,比如支付链接失效或者配置项被误改动。把第 5.3 节的 curl 验证写成脚本并加进 crontab,就能做到每日巡检。下面这段脚本用 UA 特征串做关键字匹配,输出每个环境的实际落点:
#!/bin/bash # check_routes.sh 模拟三种扫码环境,验证跳转目标 PAY_URL="https://pay.example.com/pay.php?m=dGVzdA==" check_redirect() { local ua="$1" local task="$2" local location location=$(curl -sI -A "$ua" "$PAY_URL" | grep -i '^Location:' | tr -d '\r') if [ -n "$location" ]; then echo "$task -> $location" else echo "$task -> [NO REDIRECT]" fi } check_redirect "MicroMessenger/8.0" "wechat" check_redirect "AlipayClient/10.1" "alipay" check_redirect "QQ/8.9 MQQBrowser" "qq"脚本里用tr -d '\r'把 Windows 换行符去掉,避免 Location 后面跟一个回程符导致后续判断不准。这里故意不检查 Location 的具体值,只检查有没有返回,因为支付链接可能因商户平台调整而变化。要更严格的话,可以把$location再和 config.php 里的期望值比对,发现不一致时直接告警。配合 CI 里的定时任务,这套自检能在用户扫到失效码之前先发现问题。
本文还有配套的精品资源,点击获取