
简介一套集成QQ支付与支付宝支付的完整PHP源码面向需要为网站快速接入在线支付的开发者兼顾前端展示与后端支付处理逻辑。包内共219个文件以PHP后端脚本、JavaScript交互文件和CSS样式表为主同时含有数据库表结构文件.sql/.frm以及图片字体等静态资源压缩包仅3.9MB轻量且目录结构清晰便于直接部署或拆解学习。源码覆盖商户参数配置、订单生成、支付接口调用、回调验签、订单状态更新等关键环节并考虑HTTPS传输、异常处理和浏览器兼容性等实践要点可作为自建支付模块的参考实现。已有315人学习浏览适合具备基础PHP知识、希望理解或复用QQ支付与支付宝支付完整对接流程的初中级开发者。1. 一套HTML支付页面源码QQ支付与Alipay在网页端的“最后一公里”做网站接入支付的人应该都有同感最难的不是申请商户号也不是读懂接口文档而是把支付页面、金额参数、回调验签这一整串流程串起来。这套源码包提供的正是这么一套完整的前端工程——一个把QQ支付和支付宝支付都做进HTML页面的模板里面带amazeui、bootstrap两套样式、支付方式切换逻辑以及订单参数组装代码。它解决的是从“用户点支付”到“页面跳转支付网关”之间的所有页面层工作适合商城、充值页面、会员开通这类需要快速上车的场景。特别适合前端工程师拿来当骨架再让后端补齐签名和回调处理。2. 先拆文件结构bootstrap与amazeui双框架页面的构成逻辑2.1 文件清单里藏着的信息拿到源码包第一件事不是打开index.html而是先看CSS文件列表。这份资源里的样式文件相当有代表性几乎是国内早期商城模板的标配组合bootstrap.css和bootstrap.min.css是同一份文件的两个版本app.css和app.min.css同理。我一般建议页面里引min版开发调试时再换回未压缩版方便在浏览器里定位样式。文件作用引入优先级bootstrap.min.css栅格系统、按钮、表单基础样式最先引入amazeui.min.css移动端组件样式补充bootstrap没有的UI其次animate.min.css页面元素动画效果主要用于按钮和弹窗随后app.min.css / main.css / style.css业务样式覆盖上面框架的默认值最后引入这里有个关键约定业务样式必须放在框架样式之后。因为CSS同名选择器的优先级相同时后加载的样式会覆盖先加载的。如果style.css在bootstrap前面引入你会发现改半天按钮圆角死活不生效那就是被后加载的框架样式盖住了。这个包的文件顺序没问题但如果你要自己拆页面记得保持这个顺序。2.2 页面骨架从doctype到支付表单容器页面结构是标准的HTML5文档根节点用了langzh-cn。整个页面由一个商品信息区块、一个支付方式选择区块和一个订单确认区块组成。核心部分大概是这样的结构!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title收银台/title link relstylesheet hrefcss/bootstrap.min.css link relstylesheet hrefcss/amazeui.min.css link relstylesheet hrefcss/style.css /head body div idpay-box h2订单确认/h2 div idgoods-info span idsubject商品名称/span span idtotal-fee0.00/span /div form idpay-form methodpost action/gateway/create_order.php input typehidden nameorder_id idorder_id value input typehidden nameamount idamount value input typehidden namepay_type idpay_type valueqq button typebutton idbtn-submit去支付/button /form /div script srcjs/app.min.js/script /body /html这段结构说明三个信息一是支付方式通过hidden的pay_type字段传递给后端后端据此生成对应的支付请求二是order_id和amount也是由页面提交给服务端而不是服务端自己读session说明这套源码的设计思路是“前端组参、后端签名”三是按钮用的typebutton表单提交动作交给JS控制方便在提交前做金额校验和格式检查。在这种设计下前端是黑匣子之外的部分——你能看到用户点了什么、提交了什么参数但真正生成支付二维码和签名的工作在服务端。所以拿到这个页面后你需要和后端约定好这几个字段名否则提交过去对不上接口。2.3 双框架并存的样式冲突包里同时存在amazeui和bootstrap这会让很多第一次接触的人疑惑为什么不只用一个实际上这是老一代商城模板的常态——bootstrap管框架布局amazeui提供移动端专属组件比如侧边栏、弹出层。但两个框架都用过的人知道他们的栅格体系和按钮类名都有重叠。常见的现象是同是button.btnbootstrap和amazeui的padding、字体大小不一样谁生效取决于加载顺序。源码包的app.min.css里做了一堆覆盖式修复这就是为什么它被放在最后引。如果你删掉这个文件页面会立刻变得“不修边幅”。3. 支付方式切换与订单参数组装payment逻辑里的三个关键动作3.1 切换支付方式的交互逻辑用户最常用到的交互是切换QQ支付和支付宝支付。源码包在页面上用两个tab容器分别展示两家的图标和文案切换时除了视觉反馈还会同步修改pay_type的值。下面这端代码是这部分的典型实现var payTypes document.querySelectorAll(input[namepay_type_radio]); var selectedType qq; function switchPayType(type) { selectedType type; document.getElementById(pay_type).value type; document.querySelectorAll(.pay-tab).forEach(function(tab) { tab.classList.remove(active); }); document.querySelector(.pay-tab[data-type type ]).classList.add(active); } payTypes.forEach(function(input) { input.addEventListener(change, function() { switchPayType(this.value); }); }); document.getElementById(btn-submit).addEventListener(click, function() { var amount document.getElementById(amount).value; if (!amount || parseFloat(amount) 0) { alert(订单金额不正确); return; } document.getElementById(pay-form).submit(); });这段逻辑有两点值得注意。第一pay_type的值不是表单提交时才读而是切换tab时立刻写进hidden input这样即使用户中途刷新页面已选支付方式也可能被浏览器恢复。第二提交前的金额校验用的是parseFloat后大于0的判断能挡住大多数空值或负数的误提。如果拿到手的源码里没有这段切换逻辑我建议自己补上因为让用户手动选择支付方式的分流按钮往往直接决定支付转化率。之前见过直接把两个支付按钮都裸露提交的版本用户的注意力会被分走犹豫一会儿就流失了。3.2 订单参数哪些该前端传哪些不该传一个重要原则是金额和订单号可以前端传但签名必须后端签。这套源码的做法是前端把order_id、amount、subject通过POST给服务端服务端拿到后用自己的商户密钥去调QQ支付或支付宝的统一下单接口。sign字段不在页面上出现这是底线设计。有一种常见的翻车做法是为了省事把商户密钥直接写进JS里前端自己生成签名。这在本地demo里能跑通暴露到线上就是灾难——任何人打开浏览器调试器都能看到你的密钥。所以这套源码把签名隔离在服务端是站在正确一侧的。你在复用时要守住这条线。订单参数要注意的还有金额精度。页面显示和表单提交都用字符串形式后端在接收时如果直接转float再传给支付网关容易出现精度误差。稳妥的做法是前端只展示两位小数后端按“元转分”的整数传给网关。// 前端只负责展示和提交原值 var amountInput document.querySelector(#total-fee); document.getElementById(amount).value parseFloat(amountInput.textContent).toFixed(2);这里toFixed(2)确保传给后端的金额永远只有两位小数避免用户在页面上看到0.1元实际提交0.10元这样肉眼察觉不到的差异。3.3 用iframe承载支付二维码H5页面的常规操作PC端网页接入QQ支付和Alipay时常见做法是服务端返回一段form自动提交表单或者返回一个二维码图片地址。这套源码里采用的是第二种思路——页面预留一个二维码容器JS收到后端返回的qrcode_url后生成一个iframe或用img标签加载它。function showQrcode(qrcodeUrl) { var wrapper document.getElementById(qrcode-wrap); wrapper.innerHTML ; var img document.createElement(img); img.src qrcodeUrl; img.width 200; img.height 200; wrapper.appendChild(img); // 二维码下方的轮询状态 pollPayStatus(); }二维码渲染出来后还需要轮询支付结果。常见做法是每隔3到5秒请求一次后端接口查询订单状态。这段逻辑在源码包里一般是放在app.min.js里的解压后找pollPayStatus或者checkOrder这样的函数名就可以了。这里要留心的点是二维码图片地址如果是HTTP协议在HTTPS页面会被浏览器拦截。当年被这个坑折磨过的人不在少数后面避坑章节专门讲。4. 从页面到回调QQ支付与Alipay在HTML场景下的完整闭环4.1 同步跳转与异步通知两位一体的结果回收支付结果回收有两条通道同步跳转return_url和异步通知notify_url。同步跳转是用户付完款后浏览器被支付宝或QQ支付网关302回你的网站这个跳转本身不可信——因为用户可以手动改地址伪造一个成功跳转。真正可信的是异步通知支付网关用自己的服务器直连你的后端接口带上签名参数。这套HTML源码本身不包含后端回调实现但它预留了action地址和参数位。你需要在自己的服务端补两个接口一个是/gateway/create_order.php一个是/notify/pay_callback.php。前者接收前端POST参数并生成支付单后者接收支付网关异步通知并更新订单状态。4.2 签名校验一切回调安全的地基QQ支付和Alipay都要求验签。以支付宝为例异步通知参数里有sign_type和sign服务端需要把所有非空参数按字典序排序拼成query字符串后用支付宝公钥做RSA2验签。QQ支付则是在参数串末尾拼接商户密钥后做MD5。// PHP 侧支付宝异步通知验签核心逻辑 public function verifyNotify(array $params): bool { $sign $params[sign]; $signType $params[sign_type] ?? RSA2; $values array_filter($params, function ($key) { return $key ! sign $key ! sign_type $params[$key] ! ; }, ARRAY_FILTER_USE_KEY); ksort($values); $content urldecode(http_build_query($values)); $publicKey openssl_pkey_get_public(file://alipay_public_key.pem); return (bool) openssl_verify( $content, base64_decode($sign), $publicKey, $signType RSA2 ? OPENSSL_ALGO_SHA256 : OPENSSL_ALGO_SHA1 ); }验签不过时不要轻易把订单标成已支付这是个纪律问题。验签通过的下一步才是查单——务必主动调一次支付宝的查询接口确认交易状态因为异步通知存在重复推送的可能先查后改能帮你避免在重复通知里做重复业务操作。QQ支付的验签逻辑类似把接收到的参数按key做ASCII排序拼成key1value1key2value2再在末尾追加上商户密钥MD5后的结果与sign字段比对。注意QQ支付的MD5结果是32位小写有些老教程给的是大写对不上就去检查大小写。4.3 参数对应关系前端字段与网关字段的映射从这套HTML页面的hidden input到支付网关的下单参数字段名往往对不上。你需要在后端做一次映射前端字段支付宝字段QQ支付字段说明order_idout_trade_nomch_order_no商户订单号必须唯一amounttotal_amounttotal_fee支付宝用元为单位QQ支付用分为单位subjectsubjectproduct_name商品名称中文需做URL编码pay_type无需传无需传决定走哪家网关这里最容易出问题的就是金额单位不一致。支付宝的total_amount单位是元QQ支付的total_fee单位是分。同一个金额在支付宝侧提交1.00到QQ支付侧就要提交100。很多payment was not approved的报错源头不是签名错而是金额格式非法——提交了小数或负数网关直接拒绝受理。5. 避坑从“payment was not approved”到金额差一百倍四个高频翻车点5.1 坑一金额单位错乱回调对不上账现象用户支付成功但后台记录的订单金额显示是实际付款的100倍或者相反缩小100倍。还有部分订单直接返回“payment was not approved”。原因支付宝和QQ支付的金额单位不同。支付宝用元QQ支付用分。前端HTML按元展示后端在调用QQ支付时没做元转分导致金额被当成“分”解释。解决在后端维护一个金额转换函数入库统一存分为单位调各网关时再做一次格式化输出。我一般会在创建订单的地方打印一行日志把“原始金额、支付宝提交金额、QQ提交金额”全部打出来调试时一目了然。5.2 坑二二维码图片被浏览器插拦截现象页面不出二维码控制台提示Mixed Content或者图片一片空白。原因页面是HTTPS协议而二维码图片地址是HTTP。浏览器默认拦截混合内容图片加载被阻断。解决支付二维码地址尽量由后端拼好HTTPS链接再返回前端如果网关返回的就是HTTP链接可以把iframe的src先转成https再试。另外在微信内置浏览器里支付宝旧版H5链接经常被屏蔽遇到这种场景建议引导用户“在浏览器中打开”。5.3 坑三中文商品名乱码网关报illegal charset现象支付网关回调里能看到订单但商品名是乱码部分网关直接拒绝请求。原因页面meta声明的是charsetutf-8而回调服务端代码用了gb2312编码解析参数两边的编码不一致。解决全链路统一UTF-8——页面声明、数据库连接、HTTP响应头、支付网关参数编码四处保持一致。商品名最终传到网关时做一次URL编码再传回调侧再解码基本能消除乱码。5.4 坑四异步通知重复收到订单状态被反复变更现象同一笔订单收到两三次回调用户被重复发货或者库存被扣了两次。原因支付网关在通知失败后会重试多次通知时间可以从几秒延续到数天。业务逻辑里没有做幂等判断每次收到回调都执行一次发货。解决订单表加一个支付状态字段回调处理前先查询订单当前状态只在待支付状态下执行更新。执行更新时用SQL条件带上WHERE order_status 0确保并发下也只会有一个请求真正改状态。6. 从联调到上线一套能复用的支付页面验证习惯支付功能的联调有一套固定的验证顺序我每次都会严格走一遍。先用沙箱环境验通全链路。支付宝开放平台有沙箱环境提供独立的AppID和测试买家账号。配置好沙箱密钥后把前端页面action指到自己的后端测试接口后端下单时走支付宝沙箱网关整个流程和线上一致。沙箱的好处是你可以模拟“支付成功”“支付失败”“重复通知”三种情形不用真花钱。QQ支付同样有测试商户号虽然没有支付宝的沙箱那么成熟但基本的下单、回调能力是有的适合验证签名和金额位。沙箱通过后再做一轮真实订单的极小金额验证。我的习惯是用一分钱去测。如果你们财务允许让用户付一分钱再退款这一分钱能够验证回调公网可达性、数据库状态更新、库存扣减和可能的发货逻辑。线上环境有一个特别容易漏的问题回调地址必须是公网可访问的HTTPS地址且不能被防火墙规则卡住。很多项目本地联调一切正常一上服务器就收不到通知十有八九是安全组没放行回调端口。最后一步是加支付日志。我会在四个位置写日志创建订单时、收到回调时、验签通过时、更新订单状态时。每行日志都带上order_id和金额这样出了问题能按订单号把整条链路的日志捞出来。支付功能没有“感觉没问题”这个说法。从那以后我每次接管支付项目都会强制走一遍这套验证流程从沙箱到一分钱再到全链路日志缺一个都不往上线走。希望帮到你。本文还有配套的精品资源点击获取