ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

三合一收款码在线生成源码:PHP实现UA识别与二维码聚合支付

三合一收款码在线生成源码:PHP实现UA识别与二维码聚合支付 简介一份面向商家与个人开发者的新款支付宝、微信、QQ三合一收款码在线生成源码解决多平台收款码分散、收银流程繁琐的问题无需后台与数据库基于前端JavaScript即可在本地快速合成三平台二维码。压缩包为RAR格式共44个文件、大小13.46MB包含首页HTML、两套CSS样式、10个JS脚本与二维码生成库、多张模板图片及字体图标资源结构清晰便于按需修改模板或集成到现有页面。已有1460人学习下载。源码内置多款可切换模板支持自定义外观风格使用本地生成方式不向网络传输支付信息兼顾隐私与效率离线环境也能完成收款码制作。适合独立网店、实体门店或小程序运营者快速搭建收款入口也可作为前端二维码生成与页面交互的参考实现。1. 三合一收款码在线生成源码为什么一个码要顶三个码用摆摊商户、个人开发者、工作室收款最烦的不是没生意是收银台上贴一排收款码支付宝一张、微信一张、QQ一张。顾客掏手机先问“老板我扫哪个”扫错了还要退款重扫。所谓新款支付宝微信QQ三合一收款码在线生成源码本质就是把三种收款码合并成一个二维码用户拿支付宝扫服务端识别UA后展示支付宝收款码微信、QQ同理。内置多款模板的意思是落地页不用自己写前端套皮就行。这是做支付场景外包、摊位收款系统、个人聚合收款页最常遇到的一类需求适合正在找现成方案或想自己维护一套源码的人。读完这篇你能从零搭一套能跑通三条链路、能换模板、能上线的聚合收款码系统。2. 三合一收款码的工作原理UA识别与渠道分流为什么PHP源码占主流2.1 先搞懂三合一的本质一个URL怎么分流三个支付渠道先说结论三合一收款码的二维码内容不是一个支付链接而是一个你自己服务器的URL。用户扫码后这个URL会被对应App的内置浏览器打开服务端读取HTTP头里的User-Agent识别当前浏览器属于支付宝、微信还是QQ然后决定返回什么内容。常见的UA指纹是这样微信内置浏览器UA里固定带MicroMessenger后面跟版本号比如MicroMessenger/8.0.50。支付宝App内置浏览器UA里带AlipayClient新版支付宝可能出现AlipayClient/10.5.88这种格式部分老版本Android端是AliApp(AP/10.x)。QQ内置浏览器UA里带MQQBrowser或者直接出现QQ/8.9.x具体看系统版本。识别到渠道之后服务端有两种做法。一种是302重定向直接把用户带到对应收款码图片或固定金额转账链接。另一种是渲染一个落地页把收款码图片嵌到模板里用户看到的是一个带边框、带商户名的漂亮页面。大部分内置多款模板的源码用的是第二种因为模板的价值就在这里——同一个码套不同的皮。这里有个关键点要提醒个人收款码本质上是一张静态图片微信和支付宝都没有提供“通过链接拉起个人转账”的公开接口。所以源码能做的只是把对应收款码图片展示给正确的人资金仍然直接进你的个人账户代码不碰钱。如果你的需求是动态订单金额、自动对账那需要签约商户号、走官方接口这类源码通常会标注为“聚合支付接口版”和今天说的图片版三合一不是一回事。2.2 选型为什么PHP源码在收款码领域占大头你搜“收款码在线生成源码”翻十页有七八页是PHP写的。这不是玄学是部署条件决定的。PHP源码几乎不需要编译上传到PHP虚拟主机就能跑面板一键创建站点连Nginx都不用碰。而Node.js和Python方案需要常驻进程、需要配反向代理很多个人开发者手里只有一台便宜的老牌虚拟主机跑不了这些。另外PHP生态里二维码生成方案非常成熟。最常用的是phpqrcode库单文件引入不依赖Composer也能跑框架层面ThinkPHP5在支付类外包项目里出现频率很高很多现成收款码源码就是基于TP5改的因为TP5自带数据库操作、模板渲染和URL路由做个三合一就是写个控制器的事。下面是最小可运行的UA分流逻辑单文件就能跑?php // go.php - 三合一收款码核心分流入口 $ua $_SERVER[HTTP_USER_AGENT] ?? ; // 判定渠道顺序很重要先判微信再判支付宝最后判QQ if (strpos($ua, MicroMessenger) ! false) { $channel wechat; } elseif (strpos($ua, AlipayClient) ! false || strpos($ua, AliApp) ! false) { $channel alipay; } elseif (strpos($ua, MQQBrowser) ! false || strpos($ua, QQ/) ! false) { $channel qq; } else { $channel unknown; } // 渠道对应的收款码图片实际项目里从数据库读取 $qrMap [ wechat /uploads/wechat.png, alipay /uploads/alipay.png, qq /uploads/qq.png, ]; if ($channel ! unknown isset($qrMap[$channel])) { header(Location: . $qrMap[$channel]); exit; } // 未知UA展示手动选择页 header(Location: /select.html); exit;这段代码的逻辑很直白从$_SERVER[HTTP_USER_AGENT]拿UA用strpos做子串匹配命中哪个渠道就302到哪张收款码图片。strpos返回的是子串首次出现的位置所以判断条件必须写成! false不能写成!否则子串恰好出现在第0个字符时会被当成没匹配到这个细节容易翻车。再说下顺序问题微信UA里不可能同时带AlipayClientQQ的UA里也不可能带MicroMessenger三个渠道理论上互斥。但QQ的UA比较杂Android版QQ内置浏览器有时带MQQBrowseriOS版则经常是QQ/8.9.5 QQ/MQPP这种格式所以QQ的判定条件建议写两个关键词同时建议放最后避免误伤。2.3 边界情况为什么有人扫码会落到“未知渠道”实际使用中落回unknown的情况比想象的多。第一种是用户用系统相机扫码而不是App内扫码iOS相机扫码后会先用Safari打开URL这时UA是Safari没有MicroMessenger也没有AlipayClient自然判不出来。这个问题的降级思路是不要让unknown直接报错而是渲染一个选择页让用户自己点图标进入对应渠道。这解释了为什么很多源码内置模板里有一张“三选一”引导页那不是摆设是兜底。第二种是PC端扫码。用户拿微信PC版扫一个收款码微信PC版的UA里也带MicroMessenger但打开的页面可能是电脑浏览器不一定能正常展示手机收款码。如果你给的是纯图片跳转那没问题如果落地页适配了手机尺寸PC端打开就尴尬了。所以扫码落地页要按设备自适应模板里一般会写两套CSS。第三种是QQ的特殊情况。QQ里打开的链接如果被QQ安全策略拦截会先进拦截页用户点“继续访问”后才到你的服务器这时候UA可能已经变成系统浏览器了。这种情况你代码改不了只能在生成二维码时把URL缩短、去掉敏感词降低拦截概率。3. 从零搭一套聚合收款码系统目录结构、数据表与两个核心接口3.1 先梳理目录结构模板与逻辑分离别什么都塞index.php原理搞清楚了接下来是把这套东西落到可维护的工程里。我看过很多标着“新款”的收款码源码下载下来就是一个index.php搞定所有事生成页、跳转页、上传接口全部混在一起想换模板你得在代码里找字符串非常痛苦。我一般会这样组织目录payqr/ ├── index.php # 收款码生成页入口管理端 ├── go.php # 扫码分流入口用户端 ├── qrcode.php # 二维码生成接口 ├── config.php # 数据库与站点配置 ├── templates/ # 内置多款模板目录 │ ├── default/ # 简约白模板 │ │ ├── index.html │ │ └── style.css │ ├── dark/ # 夜间深色模板 │ └── festival/ # 节日喜庆模板 ├── uploads/ # 原始收款码图片上传目录 └── data/ # SQLite数据库文件或JSON配置这个结构的好处是模板目录和PHP逻辑完全分离新增一款模板只需要在templates下加一个目录后端不用改uploads要记得在面板里设置禁止执行PHP否则别人传个webshell上来整个站就没了这是安全底线。数据存储我默认用SQLite或JSON文件而不是MySQL。理由很简单三合一个人收款码是典型低并发场景一个摊位一分钟能有几次扫码就不错了SQLite完全够用而且虚拟主机上不需要额外创建数据库。只有你要做平台级收款码托管、要给几百个商户分配码的时候才值得上MySQL。3.2 数据表设计一个收款码名称对应三条渠道记录如果用MySQL最小表结构是这样-- 收款码主表一个收款码名称对应三条渠道记录 CREATE TABLE pay_qr ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT 主键ID, name VARCHAR(64) NOT NULL COMMENT 收款码名称例如 老王煎饼摊, channel ENUM(wechat,alipay,qq) NOT NULL COMMENT 支付渠道, qr_image VARCHAR(255) NOT NULL COMMENT 渠道收款码图片路径, status TINYINT(1) NOT NULL DEFAULT 1 COMMENT 1启用 0停用, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT收款码记录表; -- 组合唯一索引防止同一个码重复添加同一渠道 ALTER TABLE pay_qr ADD UNIQUE KEY uniq_name_channel (name, channel);字段设计上有两个容易被忽略的点。一是qr_image不要存完整域名只存相对路径比如/uploads/wechat_001.png这样将来换域名不用批量改库。二是组合唯一索引uniq_name_channel能保证一个结算名下面最多一个微信码、一个支付宝码、一个QQ码后端保存时如果遇到Duplicate entry就直接提示用户“这个名称的微信收款码已经存在”避免重复数据。SQLite版本用起来更轻CREATE TABLE pay_qr ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, channel TEXT NOT NULL CHECK(channel IN (wechat,alipay,qq)), qr_image TEXT NOT NULL, status INTEGER DEFAULT 1, created_at TEXT DEFAULT (datetime(now, localtime)), UNIQUE(name, channel) );SQLite的CHECK约束在旧版本里不一定生效所以应用层还是要做一次渠道值校验不能只靠数据库。3.3 生成接口上传三张原图合成一个可扫码的URL生成流程不复杂管理端上传三张原始收款码图片写入数据库然后用二维码库把go.php?idxxx这个URL变成一张新二维码。下面代码是简化版的保存逻辑?php // save.php - 保存三合一收款码配置简化版 require config.php; $name trim($_POST[name] ?? ); $channels [wechat, alipay, qq]; if ($name ) { exit(收款码名称不能为空); } $pdo new PDO(sqlite:data/payqr.db); foreach ($channels as $channel) { if (empty($_FILES[$channel][tmp_name])) { continue; // 未上传该渠道图片跳过 } $ext pathinfo($_FILES[$channel][name], PATHINFO_EXTENSION); $ext in_array(strtolower($ext), [png, jpg, jpeg]) ? $ext : png; $filename sprintf(%s_%s.%s, date(YmdHis), $channel, $ext); $dest uploads/ . $filename; move_uploaded_file($_FILES[$channel][tmp_name], $dest); $stmt $pdo-prepare( INSERT INTO pay_qr (name, channel, qr_image) VALUES (?, ?, ?) ); $stmt-execute([$name, $channel, /uploads/ . $filename]); } $id $pdo-lastInsertId(); // 跳到生成二维码的接口 header(Location: /qrcode.php?id . $id);这里有几个参数值得注意。$ext做了白名单校验只允许png和jpg防止用户上传带恶意脚本的文件sprintf生成的文件名带秒级时间戳基本不会撞名move_uploaded_file是PHP处理上传文件的标准函数必须用这个而不是copy因为前者会校验文件是否确实通过HTTP POST上传安全性更高。当然这段代码省略了name是否已存在的判断也没做失败回滚。对于个人场景数据量小SQLite依靠事务能保证一致性。二维码生成接口用的是老牌的phpqrcode库?php // qrcode.php - 输出合成二维码图片 require config.php; require libs/phpqrcode.php; $id (int)($_GET[id] ?? 0); if ($id 0) { exit(参数错误); } // 演示URL实际应从数据库根据id生成 $payUrl https://yourdomain.com/go.php?id . $id; // 参数说明 // 第2个参数填false表示直接输出到浏览器填文件路径则保存到文件 // 第三个参数是容错率L7% Q25% H30%贴纸建议L小图建议H // 第四个参数是放大倍数8倍约等于每模块8像素贴纸上建议10以上 QRcode::png($payUrl, false, QR_ECLEVEL_L, 8);二维码容错率这个参数是很多人会忽略的。QR_ECLEVEL_L容错率最低生成出来的码最稀疏、最容易扫出来适合打印在贴纸上QR_ECLEVEL_H容错率30%哪怕二维码被折角、被油渍盖住一部分也能扫出来但点的密度大小尺寸下反而不容易识别。我的习惯是如果二维码会被打印成贴纸选L放大倍数10如果只是手机屏幕展示选H放大倍数6。4. 内置多款模板怎么选怎么改模板渲染参数与三个定制技巧4.1 模板的渲染流程从HTML占位符到真正落地页所谓内置多款模板在源码里的表现形式就是templates目录下几套独立的HTML/CSS。用户扫go.php?idxxx服务端识别渠道后不是直接302到图片而是加载一套模板把收款码图片、商户名、金额、提示文字替换进HTML再返回给用户。最简单的渲染方式是用PHP的strtr做模板字符串替换?php // render.php - 用模板字符串渲染落地页 $template file_get_contents(templates/default/index.html); // 需要替换的变量映射 $replace [ {{merchant_name}} htmlspecialchars($merchantName ?? 收款), {{channel_name}} $channelName, // 微信收款 / 支付宝收款 / QQ收款 {{qr_image}} $qrImagePath, // 渠道收款码图片 {{amount_text}} $amount 0 ? . $amount : 金额随意, {{footer_text}} 请使用 . $channelName . 扫描, ]; $html strtr($template, $replace); echo $html;用strtr而不是str_replace的好处是strtr从左到右依次替换替换后的字符串不会再次被匹配。如果你用str_replace而且模板里恰好有{{channel_name}}和{{amount_text}}这种互相包含的变量名可能出现二次替换错值的问题。strtr是PHP里做模板字符串替换最不容易翻车的做法缺点是变量多了之后维护麻烦。模板里对应的HTML片段大致是!-- templates/default/index.html -- div classqr-card h1{{merchant_name}}/h1 p classchannel{{channel_name}}/p img src{{qr_image}} alt收款码 / p classamount{{amount_text}}/p p classfooter{{footer_text}}/p /div这里有一个容易被新手忽略的点{{qr_image}}在模板里是图片路径渲染时直接放进src属性如果路径来自用户上传的文件名最好先用htmlspecialchars转义。否则用户把文件命名为script.../script.png这种带引号的内容模板就变成XSS漏洞。4.2 模板参数调整CSS变量、主题色与内置多款模板的差异很多收款码模板会把颜色、圆角、背景图做成CSS变量方便换肤。以默认模板为例:root { --primary-color: #07c160; /* 微信绿 */ --bg-image: url(../images/bg_default.png); --card-radius: 16px; --card-padding: 24px; }支付宝模板把--primary-color改成#1677ffQQ模板改成#12b7f5效果上有差异但结构完全一样。源码里内置多款模板的差异通常就在这里有的偏营销风格大标题、大按钮、传单式布局有的偏极简白底、无多余装饰有的偏节日春节红、圣诞款。如果你要做自己的模板不建议从零写CSS选一款内置模板复制到新目录改三个东西就够模板参数类型作用示例merchant_namestring商户名老王煎饼摊channel_namestring渠道显示名微信收款qr_imagestring收款码图片路径/uploads/wechat_001.pngamount_textstring金额提示3.00footer_textstring底部引导文案请使用微信扫描这样可以保证不同渠道下页面风格统一用户拿微信扫和拿支付宝扫看到的只是颜色和文字不同不会以为是两个商户。4.3 改模板的三个踩坑点缓存、编码、相对路径第一是缓存。HTML模板被file_get_contents读取后如果服务端开了OPcache或CDN改模板后用户看到的还是旧页面。上线前我一般会给模板文件加版本号在渲染时用filemtime拼一个版本参数或者干脆在模板文件名上加时间戳。第二是编码。模板文件必须是UTF-8无BOM否则标题里出现中文时页面顶部多一个不可见字符导致CSS布局异常。用编辑器保存模板时别选“UTF-8 with BOM”要选“UTF-8”。第三是相对路径。file_get_contents读的是服务器磁盘路径而HTML里的css/style.css是域名相对路径。如果站点部署在子目录比如https://yourdomain.com/payqr/模板里所有静态资源路径都要写成带根路径的形式或者用base标签统一处理。我见过有人改完模板后页面样式全丢最后发现是部署路径变了但CSS路径还写死成/templates/。5. 收款码上线避坑这5个坑让三合一收款码当场翻车5.1 微信扫码提示“已停止访问该网页”收款码直接哑火现象用户拿微信扫生成的二维码微信内置浏览器不展示收款码而是弹出“已停止访问该网页”的风险提示页。原因微信对个人域名的拦截机制。域名没有ICP备案、URL里有“支付”“收款”“转账”等敏感词、甚至同一个URL短时间被大量扫码都会触发拦截。很多源码生成的二维码内容是带money、pay参数的长链接命中率很高。解决三层方案。第一层域名必须备案并开启HTTPS第二层把二维码内容里的URL路径和参数改得不敏感比如https://yourdomain.com/g?t3f9a不要在链接里出现pay、money、alipay等字样第三层同一个收款码不要转帖到群聊、朋友圈大量传播短时间内高频访问会触发风控。注意微信的拦截策略不是源码能绕过的凡是教你在微信里做“强制跳转”的源码都要警惕合规性。5.2 支付宝扫码出来是空白页但同样链接在微信里正常现象同一台手机微信扫正常渲染落地页支付宝扫出来页面空白等几秒才出内容。原因支付宝内置浏览器的UA匹配没生效服务端把支付宝当成了unknown302到了select.html另一个常见原因是支付宝对未备案域名的拦截策略比微信更隐蔽它不提示风险直接白屏。还有一种情况是落地页里的图片是HTTP协议而页面本身是HTTPS支付宝对混合内容拦截比微信严格图片被屏蔽。解决先确认UA是否真的匹配在代码里临时写日志把$_SERVER[HTTP_USER_AGENT]打到文件里用支付宝扫一次看日志。支付宝UA常见格式是AlipayClient/10.5.88但老版本Android端会出现AliApp(AP/10.x)判定条件要两个都写。图片、CSS、JS一律用相对路径或HTTPS绝对路径不要混协议。5.3 QQ扫码跳到系统浏览器没有走QQ内置浏览器分流现象用手机QQ扫二维码链接在QQ内置浏览器打开但UA识别失败最后落到“请选择支付方式”的页面体验很差。原因QQ的UA格式比微信、支付宝都乱。Android版QQ内置浏览器的UA里有MQQBrowseriOS版经常是QQ/8.9.50 QQ/MQPP这种格式还有可能带V1_AND_SQ标识。判定条件只写了MQQBrowser的话iOS用户就全挂了。解决把QQ的判定条件放宽成多个关键词任一命中if (preg_match(/(MQQBrowser|V1_AND_SQ|QQ\/\d)/, $ua)) { $channel qq; }preg_match这个正则要求QQ/后面跟数字版本号能过滤掉被动出现的QQ单词。另外QQ的判定最好放在最后因为某些山寨浏览器UA里可能同时包含多个关键词先判微信和支付宝可以避免误判。5.4 收款码原图是聊天里收的打印出来怎么都扫不出现象商户把支付宝收款码截图通过微信发给你你存下来再上传到源码生成的二维码在屏幕上能扫打印成贴纸后怎么都扫不出来。原因收款码原图被压缩了。微信、QQ传输图片时会做压缩支付宝收款码这种精细的码一旦被二次压缩模块边缘发虚打印机再一放大识别率急剧下降。解决上传的收款码图片必须走“保存到手机→数据线传输→上传到后台”这条路或者直接从支付宝App里下载原始收款码文件绝不能从聊天记录里转发。这是做收款码项目最典型的血泪经验码源模糊是印刷后扫不出来的头号原因。另外生成合成二维码时容错率参数建议从L调整到H放大倍数从8调到12能弥补一部分原图清晰度损失。5.5 模板改完不生效页面还是旧的以为是PHP没改对现象改了templates/dark/index.html里的背景图重新扫码页面还是旧背景。原因三个地方有一处错了都会这样。第一模板路径没写对PHP读的是templates/dark/index.html你改的是templates/dark_v2/index.html第二PHP的file_get_contents结果被OPcache缓存第三页面响应头里没有Cache-Control: no-cache浏览器或微信缓存了整张页面。解决按顺序排查。先在代码里输出模板文件路径确认读的是哪个文件再看面板里有没有开OPcache的opcache.revalidate_freq有就改成0或注释掉最后在渲染接口的响应头加上Cache-Control: no-cache, must-revalidate。做完这三步改模板基本就能立即生效。我一般在模板文件路径里拼一个filemtime生成版本号参数这样浏览器和微信缓存都会自动失效。6. 进阶一步用curl模拟三种渠道UA验证跳转链路是否健康这一章的技巧可以让你不发手机也能把三条链路全测一遍。常见的做法是用curl模拟微信、支付宝、QQ的内置浏览器头信息发送HTTP HEAD请求看服务端返回的Location对不对。# 模拟微信内置浏览器扫码 curl -I -A Mozilla/5.0 MicroMessenger/8.0.50 https://yourdomain.com/go.php?id1 # 模拟支付宝内置浏览器扫码 curl -I -A Mozilla/5.0 AlipayClient/10.5.88 https://yourdomain.com/go.php?id1 # 模拟QQ内置浏览器扫码 curl -I -A Mozilla/5.0 MQQBrowser/6.2 V1_AND_SQ https://yourdomain.com/go.php?id1curl -I只拿响应头不下载body。重点看Location字段微信的模拟请求应该302到微信收款码图片路径支付宝的应该到支付宝图片路径QQ的应该到QQ图片路径。如果三条返回的Location都一样说明UA判定有问题回到第2章检查匹配顺序。这套命令我每次改完代码都会跑一遍比真机一台台扫效率高太多。再一个进阶技巧是给分流入口加UA日志上线后观察真实用户设备分布。写成这样file_put_contents( logs/ua.log, date(Y-m-d H:i:s) . | . $ua . | . $channel . PHP_EOL, FILE_APPEND );日志里如果你发现某种渠道的UA没被识别出来就是真实用户已经在踩坑了赶紧补关键词。这个日志文件会越来越大记得定期清空或按天拆分。如果你想把静态收款码升级成动态订单码也就是每个订单生成一个带金额、带订单号的临时码那支付宝回调验签是绕不开的。回调地址、签名算法、订单状态机是一套完全不同的工程但前提还是先把UA分流做得足够稳。我最早做三合一收款码的时候也被QQ的UA坑过一次那时候没有curl验证工具只能拿真机一台台扫后来把UA日志加上才定位到问题。项目能跑起来不难难的是把每个渠道的边界都摸清楚希望帮到你。本文还有配套的精品资源点击获取
返回列表