☰
HTML支付页面源码解析:QQ支付与支付宝集成实战
2026/10/8 19:10:05 网站建设 项目流程

简介:一套集成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文档,根节点用了lang="zh-cn"。整个页面由一个商品信息区块、一个支付方式选择区块和一个订单确认区块组成。核心部分大概是这样的结构:

<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>收银台</title> <link rel="stylesheet" href="css/bootstrap.min.css"> <link rel="stylesheet" href="css/amazeui.min.css"> <link rel="stylesheet" href="css/style.css"> </head> <body> <div id="pay-box"> <h2>订单确认</h2> <div id="goods-info"> <span id="subject">商品名称</span> <span id="total-fee">0.00</span> </div> <form id="pay-form" method="post" action="/gateway/create_order.php"> <input type="hidden" name="order_id" id="order_id" value=""> <input type="hidden" name="amount" id="amount" value=""> <input type="hidden" name="pay_type" id="pay_type" value="qq"> <button type="button" id="btn-submit">去支付</button> </form> </div> <script src="js/app.min.js"></script> </body> </html>

这段结构说明三个信息:一是支付方式通过hidden的pay_type字段传递给后端,后端据此生成对应的支付请求;二是order_id和amount也是由页面提交给服务端,而不是服务端自己读session,说明这套源码的设计思路是“前端组参、后端签名”;三是按钮用的type="button",表单提交动作交给JS控制,方便在提交前做金额校验和格式检查。

在这种设计下,前端是黑匣子之外的部分——你能看到用户点了什么、提交了什么参数,但真正生成支付二维码和签名的工作在服务端。所以拿到这个页面后,你需要和后端约定好这几个字段名,否则提交过去对不上接口。

2.3 双框架并存的样式冲突

包里同时存在amazeui和bootstrap,这会让很多第一次接触的人疑惑:为什么不只用一个?实际上这是老一代商城模板的常态——bootstrap管框架布局,amazeui提供移动端专属组件,比如侧边栏、弹出层。但两个框架都用过的人知道,他们的栅格体系和按钮类名都有重叠。

常见的现象是:同是button.btn,bootstrap和amazeui的padding、字体大小不一样,谁生效取决于加载顺序。源码包的app.min.css里做了一堆覆盖式修复,这就是为什么它被放在最后引。如果你删掉这个文件,页面会立刻变得“不修边幅”。

3. 支付方式切换与订单参数组装:payment逻辑里的三个关键动作

3.1 切换支付方式的交互逻辑

用户最常用到的交互是切换QQ支付和支付宝支付。源码包在页面上用两个tab容器分别展示两家的图标和文案,切换时除了视觉反馈,还会同步修改pay_type的值。下面这端代码是这部分的典型实现:

var payTypes = document.querySelectorAll('input[name="pay_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排序,拼成key1=value1&key2=value2,再在末尾追加上商户密钥,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声明的是charset=utf-8,而回调服务端代码用了gb2312编码解析参数,两边的编码不一致。

解决:全链路统一UTF-8——页面声明、数据库连接、HTTP响应头、支付网关参数编码,四处保持一致。商品名最终传到网关时做一次URL编码再传,回调侧再解码,基本能消除乱码。

5.4 坑四:异步通知重复收到,订单状态被反复变更

现象:同一笔订单收到两三次回调,用户被重复发货,或者库存被扣了两次。

原因:支付网关在通知失败后会重试多次,通知时间可以从几秒延续到数天。业务逻辑里没有做幂等判断,每次收到回调都执行一次发货。

解决:订单表加一个支付状态字段,回调处理前先查询订单当前状态,只在待支付状态下执行更新。执行更新时用SQL条件带上WHERE order_status = 0,确保并发下也只会有一个请求真正改状态。

6. 从联调到上线:一套能复用的支付页面验证习惯

支付功能的联调有一套固定的验证顺序,我每次都会严格走一遍。

先用沙箱环境验通全链路。支付宝开放平台有沙箱环境,提供独立的AppID和测试买家账号。配置好沙箱密钥后,把前端页面action指到自己的后端测试接口,后端下单时走支付宝沙箱网关,整个流程和线上一致。沙箱的好处是你可以模拟“支付成功”“支付失败”“重复通知”三种情形,不用真花钱。QQ支付同样有测试商户号,虽然没有支付宝的沙箱那么成熟,但基本的下单、回调能力是有的,适合验证签名和金额位。

沙箱通过后,再做一轮真实订单的极小金额验证。我的习惯是用一分钱去测。如果你们财务允许,让用户付一分钱再退款,这一分钱能够验证回调公网可达性、数据库状态更新、库存扣减和可能的发货逻辑。线上环境有一个特别容易漏的问题:回调地址必须是公网可访问的HTTPS地址,且不能被防火墙规则卡住。很多项目本地联调一切正常,一上服务器就收不到通知,十有八九是安全组没放行回调端口。

最后一步是加支付日志。我会在四个位置写日志:创建订单时、收到回调时、验签通过时、更新订单状态时。每行日志都带上order_id和金额,这样出了问题能按订单号把整条链路的日志捞出来。

支付功能没有“感觉没问题”这个说法。从那以后我每次接管支付项目,都会强制走一遍这套验证流程,从沙箱到一分钱再到全链路日志,缺一个都不往上线走。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询