经常有人问我:都2025年了,还有必要折腾H5商城吗?我的回答是——看场景。如果你只是想快速验证一个卖货想法、给公众号或者个人站点配一个下单入口、或者给客户交付一套“商品展示+在线支付”的轻量方案,那么一套结构清晰、带支付接口的H5迷你商城源码,依然是最省力的选择。它不需要过应用商店审核,不需要写两套客户端,手机浏览器一开就能用。
这套项目核心就是三个字:轻、全、通。前端是移动端适配的H5页面,后端是PHP写的标准MVC结构,数据库用MySQL,支付环节直接对接易支付接口,一套下来从商品上架、购物车、下单支付到后台订单管理,流程完整能跑通。尤其适合个人开发者、小团队和自由职业者拿来二次开发或者直接上线。下面我从设计思路、模块拆解、支付对接、部署运维到二次开发,把整套东西的细节都摊开讲清楚。
1. 整体设计思路:为什么是H5 + PHP + 易支付
1.1 H5形态解决的是“触达”问题
选H5而不是小程序或者原生App,最核心的原因就是触达成本低。用户点开你发的链接就能进商城,不用下载App、不用跳转小程序,在微信里聊着天就能把东西买了。对于一个迷你商城来说,转化路径短一厘米,订单量可能就差一个量级。
从技术角度看,H5商城还有一个隐藏优势:跨端一致性。一套代码,iOS Safari、安卓浏览器、微信内置浏览器、PC浏览器都能跑,只是内核差异带来一些样式微调,不需要像小程序那样考虑平台规则差异。当然代价也有,比如微信内置浏览器里部分支付能力受限,这个我在第三节讲支付对接时会详细说怎么处理。
1.2 PHP后端最契合“小而快”的诉求
后端选PHP,不是因为它技术最先进,而是因为它对这个小项目来说效率最高。PHP天然就是为Web而生的语言,每次请求独立执行、生命周期短、部署简单,配宝塔面板或者LNMP环境几分钟就能跑起来。在迷你商城这种并发不高的场景下,PHP的IO模型完全够用,强行上Go或者Java只会在开发效率上吃亏。
目录结构上,这套源码按函数模块拆分了文件,我习惯是:
/ ├── index.php # 商城首页入口 ├── api/ # 小程序/H5接口层 ├── admin/ # 管理后台 ├── includes/ # 公共函数与数据库连接 ├── payment/ # 支付接口封装 └── templates/ # 前端模板这种扁平化结构的好处是,你自己加一个功能模块时,知道该往哪里放,不用在一堆框架目录里迷路。
1.3 易支付接口的本质:把多种支付通道收敛成一套HTTP API
易支付(Epay)并不是某一家公司的专有产品,而是支付接口聚合模式的一种通用叫法。它做的事情很简单:把微信、支付宝等不同支付通道的API再封装一层,对外暴露统一的HTTP接口,开发者只需要按照约定拼参数、做签名、发请求、处理回调,就能完成收款通知的整个闭环。
为什么要接这种中间层而不是直接对接原生支付接口?因为原生支付通道对商户资质、企业认证、开发流程的要求都比较重,个人开发者和小团队往往走不通。易支付这类聚合接口的价值在于,把复杂度吸收掉了一大半,让你专注在业务层。而且它的对接模式延续了原生支付的核心思路——预下单、请求签名、异步通知验签——学好这套逻辑,以后接什么支付接口都是类似套路。
2. 功能模块拆解:商品、订单、用户三条主线
2.1 商品模块:别在最基础的环节偷懒
商品模块是商城的脸面。这套源码里商品表的核心字段包括:商品ID、分类ID、标题、主图、轮播图、价格、原价、库存、销量、详情描述、上下架状态。从实战看,有四个细节很容易踩坑。
第一个是价格字段。建议数据库里用整数类型存储“分”而不是浮点数,比如19.99元存成1999。为什么?因为浮点数在计算和比较时会有精度误差,下单金额算错了是会出大问题的。PHP里可以用intval($price * 100)做转换,注意别用float直接比较。
第二个是图片处理。迷你商城的商品主图建议统一压缩到 750px 宽度以内,体积控制在200KB左右,否则微信里首屏加载会非常慢。图片要支持多张轮播,至少兼容jpg、png、webp三种格式。
第三个是库存扣减逻辑。要在提交订单的事务里做UPDATE goods SET stock = stock - 1 WHERE id = ? AND stock > 0这种原子操作,而不是先查出来判断再更新,否则并发场景下会超卖。
第四个是列表分页。H5页面下拉加载用id < lastId LIMIT 10这种游标分页最省事,因为SKU商品量级一般就几百上千个,游标分页能避免深分页的性能问题。
2.2 订单流程:状态机设计决定了后续扩展的余地
订单模块是整个系统的核心脉络。这套源码的订单状态我建议这么设计:
| 状态值 | 含义 | 触发条件 |
|---|---|---|
| 0 | 待支付 | 用户提交订单成功 |
| 1 | 已支付 | 支付回调验签通过且金额匹配 |
| 2 | 已发货 | 管理员在后台操作发货 |
| 3 | 已完成 | 用户确认收货或超时自动完成 |
| -1 | 已取消 | 用户主动取消或超时未支付 |
| -2 | 已退款 | 管理员后台退款操作 |
用户提交订单时,生成唯一订单号out_trade_no,这是支付系统对接的关键标识。订单号建议用date('YmdHis') . mt_rand(1000, 9999),保证同一秒内的并发也不重复。下单之后把订单信息连同跳转支付参数一起返回给前端,前端拿到参数后引导用户跳转支付。
订单和支付状态要保持双写一致。核心思路是:以支付回调为准。回调确认成功,订单状态才从待支付翻转为已支付。前端展示的轮询结果只能做辅助提示,不能直接改库,否则用户关闭支付页面、轮询没跑到就可能出现假死状态。
2.3 用户体系:免登录优先,收货信息兜底
迷你商城的用户体系不需要做的很重。我的建议是:非强制登录即可浏览、加购物车,下单时提供“手机号+验证码”或微信授权登录。如果口袋里没有稳定的微信授权能力,就退一步用openid作为用户唯一标识,配合手填手机号。这套源码默认的方案就是:用户第一次提交订单时要求填手机号,生成 user 记录,后续下单自动带出。
后台管理我建议单独拆出管理员表,不要跟普通用户混在一起。管理员的登录用 session 管理,密码字段必须用password_hash()加密,这是最低限度的安全底线。
3. 易支付接口对接全流程:签名、请求、回调
3.1 参数签名:MD5也能做得安全
易支付的接口对接遵循一个很常见的模式:业务参数 + 签名。也就是把请求参数按k按字母排序,拼成查询字符串,再加上你的商户密钥一起做MD5,用于防止参数被篡改。
这整套流程里,签名算法长这样:
// 商户ID和密钥在易支付后台获取 $pid = '10000'; $key = '你的商户密钥'; // 拼接请求参数 $params = [ 'pid' => $pid, 'type' => 'alipay', // 支付方式:alipay / wxpay 'out_trade_no' => '202501011200001234', 'notify_url' => 'https://yourdomain.com/payment/notify.php', 'return_url' => 'https://yourdomain.com/payment/return.php', 'name' => '测试商品', 'money' => '199.00', 'sign_type' => 'MD5' ]; // 按key升序排序,拼接为 url string ksort($params); $signStr = urldecode(http_build_query($params)) . $key; $params['sign'] = md5($signStr); // 发起跳转支付 $payUrl = 'https://pay.example.com/submit.php?' . http_build_query($params); header('Location: ' . $payUrl);这里重点在urldecode(http_build_query($params)),它会生成a=1&b=2&c=3这种纯字符串,再拼上密钥做MD5。注意不要漏掉urldecode,因为http_build_query默认会对参数值做URL编码,拼进去以后签名永远对不上。我见过不下五个人在这里卡了半天。
签名验证的逻辑完全一致:收到回调后,把除sign和sign_type以外的所有参数排好序拼出来,md5,比对是否一致。验证通过再处理业务。
3.2 发起支付与异步通知:谁才是权威消息源
支付跳转有两种方式,一种是用header('Location')直接302跳转到易支付收银台,另一种是把payUrl返回到前端,用window.location.href跳转。迷你商城推荐后者,前端跳转更可控,还能在跳转前弹出“正在拉起收银台”的提示。
这里有个经验:易支付在微信内置浏览器里跳支付宝会比较尴尬,微信会拦截外部支付链接。做H5商城时我的常规处理是:如果type=alipay,就让用户“点击右上角在浏览器中打开”;或者干脆根据 UA 判断——如果User-Agent包含MicroMessenger,默认引导走复制链接到外部浏览器。这个逻辑很值得写进支付模块里,线上效果立竿见影。
真正的支付结果,权威来源是异步通知,也就是notify_url。流程是这样的:用户支付完成后,易支付服务器向notify_url发起GET请求,携带订单号、金额、trade_status、sign等参数;服务器收到后验签、校验金额、校验订单号,然后更新订单状态,并返回字符串success告知易支付不要再重复回调。
3.3 回调处理的幂等和安全防护
回调处理这套代码,是支付模块里最值得认真写的地方。直接上简化代码:
// notify.php $data = $_GET; $sign = $data['sign'] ?? ''; unset($data['sign'], $data['sign_type']); if (md5Sign($data) !== $sign) { die('fail'); // 验签失败,绝不继续 } $orderNo = $data['out_trade_no']; $money = $data['money']; $status = $data['trade_status']; // 查询订单 $order = $db->find("SELECT * FROM orders WHERE out_trade_no = ?", [$orderNo]); if (!$order) { die('fail'); // 订单不存在 } // 金额校验:数据库存的是分,回调传的是元 if (PayHelper::yuanToFen($money) !== $order['amount']) { die('fail'); // 金额不一致,可能被篡改 } // 幂等处理:订单已经是已支付状态,直接返回success if (intval($order['status']) === 1) { die('success'); } // 更新订单状态 + 扣减库存 + 记录支付流水(放在事务里) $db->transaction(function () use ($db, $orderNo) { $db->exec("UPDATE orders SET status = 1, paid_at = NOW() WHERE out_trade_no = ?", [$orderNo]); $db->exec("UPDATE goods SET stock = stock - 1 WHERE id = ?", [$order['goods_id']]); }); die('success');这段代码有三个关键点:
第一是幂等。即使易支付因网络原因重复回调了十次,订单状态是已支付后直接返回success,不会第二次扣库存。
第二是以回调为准。前端轮询、用户手动刷新页面看到的支付状态,都应该以数据库的status为准,而不是依赖用户浏览器里的session状态。
第三是返回success必须原样输出且不输出任何多余字符。如果输出内容夹杂了HTML标签,易支付可能识别不到合法返回,会继续回调,造成更频繁的服务器压力。
4. 部署上线与日常运维:从源码到线上商城
4.1 环境准备与部署步骤
这套源码对运行环境要求极低,只要是PHP 7.0以上+MySQL 5.7以上,配Nginx或Apache都行。我强烈建议国内云服务器上直接用宝塔面板装LNMP,十几分钟就能把环境搭好。
部署的具体步骤拆成清单:
- 准备一台云服务器(最低1核1G就能跑,并发不大的话绰绰有余)。
- 安装PHP 7.4+、MySQL 5.7+、Nginx,开启伪静态配置。
- 创建数据库,导入源码目录下的
database.sql。 - 修改
includes/config.php,填写数据库名、账号、密码,以及易支付的商户ID和密钥。 - 把源码上传到站点根目录,设置运行目录为
/,站点使用HTTPS(免费证书即可)。 - 到易支付平台后台,把
notify_url和return_url配置成你自己的域名地址。
部署完成后,先下个1元测试商品跑一遍真实支付,这是验证支付链路最简单可靠的方式。
4.2 常见问题排查速查表
下面是这套源码上线后最常遇到的一批问题,我把排查思路整理成一张速查表:
| 现象 | 排查方向 | 解决方案 |
|---|---|---|
| 页面加载空白 | 检查PHP错误日志、模板路径 | 开启display_errors临时排查;确认templates目录可读 |
| 商品图片加载不出来 | 检查图片路径/OSS域名 | 图片地址要写绝对完整URL(含http(s)://) |
| 点击支付没反应 | 检查跳转URL是否被拦截、UA判断 | 用浏览器开发者工具观察Location响应;确认外链正常可访问 |
| 支付成功但订单未更新 | 重点查notify_url是否外网可达 | 服务器上用curl模拟get请求notify.php;确认验签代码没有bug |
| 回调日志显示验签失败 | 签名拼接顺序或密钥不对 | 重新检查ksort、urldecode(http_build_query())拼接 |
| 微信内打不开收银台 | 外部浏览器跳转方案未实现 | 增加UA判断,提示用户用浏览器打开 |
| 管理后台登录不上 | 检查session配置、数据库连接 | 确认PHP session目录可写;检查config.php数据库密码 |
排查支付问题有一个通用技巧:在支付模块加入文件日志。每次发起支付和收到回调用file_put_contents记录一条带时间戳的日志,线上出问题的时候翻日志定位,比自己盲猜快一百倍。
4.3 上线前的安全检查清单
迷你商城虽然小,安全底线不能省。我每次帮人部署这类系统,必做四项检查:
- 改掉默认后台路径
/admin,换成不规则的目录名,降低被扫描的风险。 - 管理员密码强制用
password_hash存储,数据库泄露了也不能明文逆推。 - 对所有查询用预处理(PDO prepared statements),防注入。
- 支付宝/微信回调通知必须验签,且对
money、out_trade_no做校验。
还有个小细节:return_url只是用户支付完成后浏览器跳转的页面,它不等同于支付成功凭证。很多新手会把业务逻辑写在里面,这是不对的,用户完全可以伪造这个跳转地址直接访问,拿不到任何实际效果。所有支付成功的业务处理都必须放在notify.php异步回调里。
5. 二次开发方向与我的实战体会
5.1 这套源码最常见的改造方向
验证跑通之后,大多数人都会根据自己的业务做改造。从我接触过的案例看,以下三个方向最热门:
加一个优惠券系统。在订单计算环节插入优惠券抵扣逻辑。需要新增coupon表(面额、门槛、有效期、每人限领次数)和user_coupon表(领取记录、使用状态)。下单时校验用户是否持有该券、是否满足金额门槛,核销后锁定使用。
接入分销裂变。在用户表加一个inviter_id,支付成功后写一条分销记录,把佣金以积分或者余额形式记入上级账户。注意分销体系的提现功能要格外设计好,尤其是提现审核和流水记录,否则财务对账会是一笔糊涂账。
改成多商户模式。在前端增加店铺维度,商品归属于商户,订单按商户拆分结算。这个改造量就大了,建议先理清数据模型再动手,核心是给goods加merchant_id,给订单加settle_status,然后写商户端的结算模块。
5.2 踩过几次坑之后的几点心里话
这套H5迷你商城,我从第一次部署到现在,穿插着改造过不少版本,最深的体会有三个。
第一个是:支付对接永远是第一优先级。商品、页面、用户都可以后续慢慢调,但支付链路从第一天就必须端到端跑通。支付的逻辑看起来就那么几个参数,真到线上环境,https、外网可达、签名编码、重复回调,一个问题叠一个问题,最好预留一个下午专门调支付。
第二个是:数据库结构要先想清楚再写代码。迷你商城业务简单,但订单、支付流水、商品库存这三张表的关联关系决定了后面所有功能的开发成本。比如想增加退款,记录流水表的字段就至关重要——至少要有amount、type、order_no、created_at、status五个字段,后面扩展时不需要回改表结构。
第三个是:H5商城的关键指标是转化率,不是功能数量。微信里打开商品详情页,2秒内能加载出首屏,下单按钮永远在最顺手的位置,支付引导没有多余的弹窗,这比你在首页堆十个营销组件有用得多。商城是拿来卖货的,每一个页面都在替你把用户往“付钱”这一步送。把链路精简到最短,比任何花哨的功能都值钱。
最后再分享一个小操作:线上跑稳定以后,把订单表按月归档,不删数据,只做冷热分离。这样后台查询历史订单依然很快,数据库也不会一直膨胀。别问我为什么强调这个,等你订单量上来、后台卡到怀疑人生的时候,你会回来谢谢我的。