
简介这份资源面向使用ThinkPHP5框架开发电商或在线业务的PHP开发者聚焦微信支付与支付宝支付的接口集成同时涉及网银支付对接思路。内容围绕商户信息配置、统一下单、预支付会话标识生成、支付回调处理及签名验证等关键环节展开帮助开发者理解支付接口的工作原理与安全传输要求。资源包共2321个文件以1545个php源码文件为主体辅以data数据文件、phpt测试用例、md说明文档、xml与yml配置、json数据及少量图片与字体资源压缩包约4.76MB目录结构完整便于按模块查阅与二次开发。目前已有926人学习下载适合需要快速搭建支付功能、对照配置参数与排错思路的中级开发者参考借鉴。1. 从一次支付回调翻车说起这个 TP5 支付集成项目到底能干什么去年帮一个做电商的朋友排查线上问题用户明明付了钱订单状态却一直卡在“待支付”客服电话被打爆。最后定位到是微信支付异步回调的签名验证写错了把sign字段直接丢进验签数组里参与计算导致验签永远失败。这种坑几乎每个第一次接支付的人都踩过。这份基于 ThinkPHP5 的支付集成项目就是把微信支付和支付宝支付两条链路完整跑通的实战代码涵盖统一下单、扫码支付、H5 支付、异步回调验签、订单状态更新、退款等核心环节。它适合正在用 TP5 做商城、知识付费、SaaS 后台的开发者尤其是那些不想从零啃官方 SDK、希望拿到就能嵌入现有业务的人。项目把两个支付渠道的差异封装在统一入口后面你只需要关注自己的订单逻辑剩下的签名、证书、回调处理它都替你兜住了。2. 项目结构与技术选型为什么是 TP5 加官方 SDK 的组合2.1 目录骨架与模块划分拿到源码后先别急着改配置。花五分钟把目录结构过一遍后面找文件能省不少时间。这个项目遵循 TP5 的标准模块化结构支付相关的代码集中在application/pay模块下同时依赖extend目录存放的官方 SDK 文件。project/ ├── application/ │ ├── pay/ │ │ ├── controller/ │ │ │ ├── Wxpay.php # 微信支付控制器 │ │ │ ├── Alipay.php # 支付宝支付控制器 │ │ │ └── Notify.php # 统一回调入口 │ │ ├── model/ │ │ │ └── Order.php # 订单模型含状态机 │ │ └── service/ │ │ ├── PayService.php # 支付服务层统一调度 │ │ └── RefundService.php │ └── config.php # 数据库、缓存等全局配置 ├── extend/ │ ├── wxpay/ │ │ ├── lib/WxPay.Api.php # 微信官方 SDK 核心 │ │ └── cert/ # 存放 apiclient_cert.pem 等证书 │ └── alipay/ │ ├── AopClient.php # 支付宝官方 SDK │ └── request/AlipayTradePagePayRequest.php ├── public/ │ └── index.php # 入口文件 └── route/ └── route.php # 支付回调路由定义application/pay是业务层extend是 SDK 层两者通过PayService桥接。这样做的好处是将来微信或支付宝 SDK 升级你只需要替换extend下的文件业务代码基本不用动。我一般会把证书文件放在extend/wxpay/cert/下并在.gitignore里排除掉避免私钥泄露。2.2 技术栈与依赖说明项目基于 ThinkPHP 5.1 开发PHP 版本要求 7.0 以上推荐 7.2 或 7.4因为官方 SDK 里有些语法在 8.0 下会报兼容性警告。数据库用的是 MySQL缓存建议开启 Redis用来存储支付流水号和防止重复回调。依赖管理没有走 Composer而是直接把官方 SDK 放进extend目录手动加载这是 TP5 项目里很常见的做法好处是部署简单坏处是升级麻烦。如果你习惯 Composer也可以把extend/wxpay和extend/alipay替换成composer require安装的版本但要注意命名空间和自动加载路径的调整。// application/pay/service/PayService.php namespace app\pay\service; use think\Loader; class PayService { // 统一支付入口根据渠道分发 public function unifiedOrder($channel, $orderData) { // 手动加载 extend 下的 SDKTP5 的 Loader 支持这种写法 Loader::import(wxpay.lib.WxPayApi, EXTEND_PATH, .php); Loader::import(alipay.AopClient, EXTEND_PATH, .php); switch ($channel) { case wechat: return $this-wechatPay($orderData); case alipay: return $this-alipayPay($orderData); default: throw new \Exception(不支持的支付渠道); } } }这段代码的关键在于Loader::import的第三个参数.phpTP5 默认会补全后缀但手动指定更稳妥。EXTEND_PATH是 TP5 内置常量指向extend目录。参数$channel决定走哪条分支$orderData里必须包含订单号、金额、商品描述等字段。注意金额单位微信要求分支付宝要求元这个转换在各自的wechatPay和alipayPay方法里处理不要混在一起。2.3 配置项与证书管理支付配置集中在application/pay/config.php里微信和支付宝各占一个数组。微信需要 appid、mch_id、api_key、apiclient_cert.pem 和 apiclient_key.pem 两个证书文件支付宝需要 app_id、merchant_private_key、alipay_public_key以及可选的 AES 密钥。这些敏感信息不要硬编码在代码里我一般会抽到.env文件TP5 原生支持环境变量读取。// application/pay/config.php return [ wechat [ appid env(WX_APPID), mch_id env(WX_MCHID), api_key env(WX_APIKEY), cert_path EXTEND_PATH . wxpay/cert/apiclient_cert.pem, key_path EXTEND_PATH . wxpay/cert/apiclient_key.pem, notify_url https://yourdomain.com/pay/notify/wechat, ], alipay [ app_id env(ALI_APPID), private_key env(ALI_PRIVATE_KEY), public_key env(ALI_PUBLIC_KEY), notify_url https://yourdomain.com/pay/notify/alipay, return_url https://yourdomain.com/pay/return, ], ];notify_url必须是公网可访问的 HTTPS 地址本地开发时可以用内网穿透工具临时映射但上线前一定要换成正式域名。证书路径用EXTEND_PATH拼接避免相对路径在不同入口文件下解析不一致。支付宝的private_key是一长串字符串建议用文件读取的方式加载直接写在环境变量里容易因为换行符问题导致签名失败。3. 核心链路拆解从统一下单到异步回调的完整实现3.1 微信支付统一下单与扫码模式微信支付的统一下单接口是WxPayUnifiedOrder项目里封装在Wxpay控制器的nativePay方法中。扫码支付模式二NATIVE最常用返回一个二维码链接前端用 qrcode.js 渲染成图片给用户扫。// application/pay/controller/Wxpay.php public function nativePay() { $orderNo input(post.order_no); $amount input(post.amount); // 单位分 $body input(post.body); // 组装统一下单参数 $input new \WxPayUnifiedOrder(); $input-SetBody($body); $input-SetOut_trade_no($orderNo); $input-SetTotal_fee($amount); $input-SetTrade_type(NATIVE); $input-SetProduct_id($orderNo); $input-SetNotify_url(config(pay.wechat.notify_url)); // 调用官方 SDK 统一下单 $result \WxPayApi::unifiedOrder($input); if ($result[return_code] SUCCESS $result[result_code] SUCCESS) { // code_url 就是二维码链接前端拿去生成图片 return json([code 0, url $result[code_url]]); } return json([code 1, msg $result[return_msg] ?? 下单失败]); }SetTotal_fee的单位是分如果你数据库里存的是元记得乘以 100 再取整。SetOut_trade_no是商户订单号必须全局唯一建议用“业务前缀 时间戳 随机数”的格式。SetNotify_url指向回调控制器后面会讲怎么处理。返回的code_url是一个weixin://wxpay/bizpayurl?prxxx格式的链接不是普通 HTTP 地址前端生成二维码时直接把这个字符串编码进去就行。3.2 支付宝电脑网站支付与签名机制支付宝的电脑网站支付用的是AlipayTradePagePayRequest它会返回一段自动提交的 HTML 表单浏览器加载后跳转到支付宝收银台。和微信不同支付宝的签名是在 SDK 内部完成的你只需要把参数组装好。// application/pay/controller/Alipay.php public function pagePay() { $orderNo input(post.order_no); $amount input(post.amount); // 单位元 $subject input(post.subject); $aop new \AopClient(); $aop-gatewayUrl https://openapi.alipay.com/gateway.do; $aop-appId config(pay.alipay.app_id); $aop-rsaPrivateKey config(pay.alipay.private_key); $aop-alipayrsaPublicKey config(pay.alipay.public_key); $aop-apiVersion 1.0; $aop-signType RSA2; $request new \AlipayTradePagePayRequest(); $request-setNotifyUrl(config(pay.alipay.notify_url)); $request-setReturnUrl(config(pay.alipay.return_url)); $request-setBizContent(json_encode([ out_trade_no $orderNo, total_amount $amount, subject $subject, product_code FAST_INSTANT_TRADE_PAY, ])); // 生成自动跳转的 HTML 表单 $html $aop-pageExecute($request); return $html; }signType推荐用 RSA2比 RSA 更安全。total_amount单位是元支持两位小数。product_code固定填FAST_INSTANT_TRADE_PAY这是电脑网站支付的标识。pageExecute返回的 HTML 里包含一个form表单和一段script自动提交直接输出到浏览器即可。注意rsaPrivateKey是一整行字符串如果你从文件读取要去掉头尾的-----BEGIN RSA PRIVATE KEY-----和换行符。3.3 异步回调验签与订单状态机回调处理是整个支付链路里最容易出问题的地方。微信的回调是 POST XML 格式支付宝的回调是 POST 表单格式两者都需要验签验签通过后才能更新订单状态。// application/pay/controller/Notify.php public function wechat() { // 微信回调数据是 XML用 php://input 读取原始流 $xml file_get_contents(php://input); $data \WxPayApi::xmlToArray($xml); // 验签官方 SDK 的 checkSign 方法 if (!\WxPayApi::checkSign($data)) { // 验签失败记录日志返回失败让微信重试 trace(微信回调验签失败: . $xml, error); return xmlreturn_code![CDATA[FAIL]]/return_code/xml; } // 处理业务逻辑更新订单状态 $orderNo $data[out_trade_no]; $order \app\pay\model\Order::where(order_no, $orderNo)-find(); if ($order $order-status 0) { $order-status 1; $order-pay_time time(); $order-transaction_id $data[transaction_id]; $order-save(); } // 返回成功微信不再重试 return xmlreturn_code![CDATA[SUCCESS]]/return_code/xml; }checkSign方法内部会自动剔除sign字段再计算签名你不需要手动处理。订单状态判断里加了$order-status 0的条件这是防止微信重复回调导致重复更新。返回的 XML 必须严格符合格式CDATA不能少否则微信会认为你处理失败并持续重试。支付宝的回调类似但验签用AopClient::rsaCheckV1返回字符串success即可。3.4 退款接口的调用差异微信退款需要双向证书支付宝退款只需要私钥签名。项目里把两者封装在RefundService中对外暴露统一的refund($channel, $orderNo, $amount)方法。// application/pay/service/RefundService.php public function refund($channel, $orderNo, $refundNo, $totalFee, $refundFee) { if ($channel wechat) { $input new \WxPayRefund(); $input-SetOut_trade_no($orderNo); $input-SetOut_refund_no($refundNo); $input-SetTotal_fee($totalFee); $input-SetRefund_fee($refundFee); // 微信退款必须加载证书 $result \WxPayApi::refund($input, 6, true); return $result[result_code] SUCCESS; } else { $aop new \AopClient(); // ... 配置省略 $request new \AlipayTradeRefundRequest(); $request-setBizContent(json_encode([ out_trade_no $orderNo, refund_amount $refundFee / 100, out_request_no $refundNo, ])); $result $aop-execute($request); return $result-code 10000; } }微信的refund方法第三个参数传true表示使用证书这个参数不传会报“证书不存在”的错误。支付宝的refund_amount单位是元如果你前面存的是分记得除以 100。退款请求号out_request_no必须唯一同一笔订单多次部分退款时每次都要生成新的请求号。4. 避坑与排查支付集成里那些血泪经验4.1 回调收不到先查这几个地方现象用户支付成功但订单状态没变日志里也没有回调记录。原因通常有三个一是notify_url配置成了内网地址或带参数的 URL微信和支付宝都要求是纯 HTTPS 公网地址不能带 query string二是服务器防火墙拦截了来自支付平台的 POST 请求尤其是宝塔面板默认会拦截一些异常 IP三是 TP5 的路由没配对回调地址被解析到了其他控制器。解决方法是先用 Postman 模拟一个回调请求看能否进入控制器再检查 Nginx 访问日志里有没有支付平台的 IP 记录。4.2 验签失败九成是编码和空格问题现象回调数据明明是对的但checkSign一直返回 false。原因往往是参数值里带了空格或换行符或者编码不是 UTF-8。微信的 XML 里如果body字段有中文必须确保php://input读到的原始流没有被trim或转义。支付宝的回调参数里sign字段本身不参与验签但有些开发者会手动urldecode整个数组导致签名对不上。解决方法是打印出原始回调和验签前的数组逐字段对比重点看total_fee和out_trade_no有没有被意外修改。4.3 金额单位混乱分和元千万别搞反现象用户付了 1 元订单里显示 100 元或者反过来。原因就是微信和支付宝的单位不一致微信是分支付宝是元。项目里在PayService层做了统一转换但如果你自己写业务代码很容易在某个环节漏掉。我一般会在订单表里存一个amount字段单位统一用分展示时再除以 100。退款时同样要注意微信退款传分支付宝退款传元。4.4 证书路径错误Linux 和 Windows 的差异现象本地 Windows 开发正常部署到 Linux 服务器后微信退款报“证书加载失败”。原因是证书路径用了反斜杠\或者相对路径Linux 下解析不到。解决方法是统一用EXTEND_PATH常量拼接并且确保cert目录有可读权限。另外apiclient_cert.pem和apiclient_key.pem必须成对使用缺一个都会报错。4.5 重复回调导致重复发货现象用户只付了一次钱但系统发了两次货。原因是微信和支付宝在未收到成功响应时都会重试回调如果你的业务逻辑没有做幂等处理就会重复执行。解决方法是在更新订单状态前加一个数据库唯一索引或 Redis 锁确保同一笔订单只处理一次。项目里用$order-status 0做判断但在高并发下仍有可能两个请求同时读到 status 为 0建议再加一个lock字段或使用UPDATE ... WHERE status 0的原子操作。5. 二次开发与改造把这套代码嵌进你自己的业务5.1 替换订单模型与状态机这套代码默认的Order模型只有status、pay_time、transaction_id几个字段实际业务里你肯定需要扩展。我一般会保留status字段作为支付状态另外加一个delivery_status表示发货状态两者解耦。状态机的流转用 TP5 的模型事件来触发比如onAfterUpdate里判断status从 0 变成 1 时自动发送通知或更新库存。// application/pay/model/Order.php protected static function init() { self::afterUpdate(function ($order) { if ($order-status 1 $order-getData(status) 0) { // 支付成功后的业务逻辑 \think\Queue::push(app\job\OrderPaid, [order_id $order-id]); } }); }getData(status)拿的是更新前的值$order-status是更新后的值两者对比就能判断状态是否发生了流转。把耗时操作丢进队列避免回调响应超时。5.2 增加新的支付渠道如果你要接入银联或 PayPal只需要在PayService里加一个case分支然后在extend下新建对应的 SDK 目录。回调控制器也照葫芦画瓢新增一个方法路由里注册好地址。关键是保持unifiedOrder的入参格式一致这样上层业务代码不用改。5.3 日志与监控的补全支付出问题时日志是唯一的后悔药。项目里用 TP5 的trace函数记录关键节点但默认只写到文件。我建议把回调的原始数据、验签结果、订单状态变更都写到独立的支付日志表里方便对账和排查。另外可以加一个定时任务每隔十分钟扫描一次“支付中”超过 30 分钟的订单主动去支付平台查单避免回调丢失导致订单一直挂起。5.4 一个具体的改造技巧用中间件统一验签如果你不想在每个回调控制器里都写一遍验签逻辑可以写一个 TP5 中间件在请求进入控制器之前完成验签和原始数据解析。这样控制器里只需要处理业务代码更干净。// application/pay/middleware/VerifyPay.php public function handle($request, \Closure $next) { $channel $request-param(channel); $raw $request-getContent(); if ($channel wechat) { $data \WxPayApi::xmlToArray($raw); if (!\WxPayApi::checkSign($data)) { return response(xmlreturn_code![CDATA[FAIL]]/return_code/xml); } $request-payData $data; } elseif ($channel alipay) { $data $request-post(); $aop new \AopClient(); // ... 配置 if (!$aop-rsaCheckV1($data, null, RSA2)) { return fail; } $request-payData $data; } return $next($request); }中间件里把验签通过的数据挂到$request-payData上控制器直接取用。这样以后新增渠道只需要在中间件里加一个分支控制器完全不用动。路由注册时把中间件绑到回调地址上即可。从那以后我每次接支付都会先把回调日志和幂等锁加上再写业务逻辑。支付这东西不出问题则已一出就是真金白银的损失。希望这套 TP5 的支付集成代码和上面的踩坑记录能帮你少走点弯路。本文还有配套的精品资源点击获取