简介彩虹易支付6合一支付插件是一款面向个人站长及PHP开发者的轻量级支付集成工具支持微信、支付宝、QQ、京东、银联云闪付与PayPal六种主流支付渠道可在一个收银台内统一完成多支付方式的接入与切换适合需要在网站或小程序中快速集成多通道收款、避免重复开发的场景。资源包共10个文件压缩后仅11KB以PHP文件、TXT说明及HTML使用文档为主其中EpayCore.class.php为支付核心类epay.config.php提供参数配置范例另有使用说明.html与README.txt帮助快速部署。内容预览还包含源码库.url快捷入口便于读者获取扩展资料。该插件部署简单上传至/plugins/目录即可调用并已支持PayPal等国际支付兼顾国内与海外收款需求。目前已有250人学习适合具备基础PHP知识、希望低成本实现六合一收银方案的开发者参考。1. 彩虹易支付6合一它不是多了一个支付按钮是少了一套对账逻辑你接好支付宝和微信之后产品经理递过来一张表QQ钱包、银联、余额……一周内都要上。每个渠道都有各自的商户号、密钥、验签规则和回调地址如果按照原生接口一个一个接业务代码里会全是if分支对账的时候更是灾难。彩虹易支付6合一支付插件做的事情就是把六种支付方式收口到同一个发起接口、同一个异步通知入口、同一套订单状态机。适合PHP技术栈的独立开发者和中小团队尤其是那些已经跑过原生支付、被多渠道维护成本折磨过的人。我强烈建议从最小验证开始先跑通一笔1元支付再去碰复杂场景。2. 先弄明白6合一的边界渠道选型与统一封装原理2.1 6合一是哪6种渠道选型的三个判断标准常见做法里6合一覆盖的是支付宝当面付、支付宝PC、微信Native、微信H5、QQ钱包、银联在线这6种。不是说每个项目都要全上而是这套插件把渠道差异统一掉你只管传一个本地订单号、一个金额、一个渠道标识剩下的跳转或二维码由插件完成。选渠道时我一般看三个标准一是费率扫码类通常在0.38%到0.6%之间H5会高一些二是结算周期T1是常态部分渠道能到D0但手续费会更多三是回调稳定性这个最容易被忽视——我给几个项目接通道时最稳定的反而未必是大厂因为大厂风控规则变化频繁回调字段说加就加。先看一张渠道对比表它决定你最先该接哪个渠道标识典型场景回调特点配置门槛alipay_desk桌面收银台扫码通知及时但存在重复通知个人版可用企业版需营业执照alipay_h5手机浏览器内拉起支付宝依赖UA判断回调正常需要备案域名wx_nativePC网站扫码二维码有效期较短需处理过期需微信商户号wx_h5手机端微信内支付对域名要求严格商户号JSAPI支付权限qq_wallet社交场景扫码部分地区回调有延迟受风控影响较大unionpay企业对公场景回调偶发延迟需要一定流水门槛选型不是越多越好。某开发者上来就要求6个全开结果QQ钱包审核卡了三周银联那边因为没有对公流水直接被拒白白浪费排期。我现在的习惯是先看现有用户用哪个渠道支付最多把Top 2优先接剩下的作为备选。如果只做国内个人站支付宝当面付和微信Native基本覆盖90%的日常需求要做企业场景才需要动银联。2.2 插件如何统一6套支付订单状态机与金额基准插件内部并不为每个渠道写一套完整的支付流程而是把差异压缩到两个点发起支付时的参数组装和回调时的签名验签。中间层的订单状态只认4个状态pending已创建、paid已支付、closed已关闭、refunded已退款。所有渠道的回调进来最终都只做一件事——把pending置为paid。金额基准必须是整数分不能用浮点数。PHP里float存金额的经典问题0.10.2不等于0.3在支付场景里哪怕差一分也会对不上账。插件里通常已经封装了金额转换方法但如果你要自己写一定要用round否则0.29乘100会得到28.999999转int直接成了28。这是血泪经验// 金额换算统一走一个方法禁止在业务里直接金额*100 public function toCents($yuan): int { // round必须带否则浮点误差会让0.29变成28.999999 return (int) round((float) $yuan * 100); } // 反向换算给前端展示用 public function toYuan($cents): string { return number_format($cents / 100, 2, ., ); }逻辑说明toCents负责把用户输入的元转换成整数分入库toYuan负责把分格式化成带两位小数的字符串展示。参数说明$yuan是用户输入金额允许字符串或浮点返回int是数据库字段能直接存的值。不要在业务代码里自己写乘100的逻辑因为很容易漏掉round或者不同地方用不同的舍入方式最后对账差一分找不到原因。状态流转是插件的核心骨架。我用一个统一入口管理所有渠道的状态变更无论是支付宝回调还是微信回调最终都走同一个方法// 订单状态统一入口所有渠道回调最终都调用这个方法 protected function changeOrderStatus(int $orderId, string $status, array $rawNotify): bool { // 幂等检查先从缓存取当前状态 $current $this-cache-get(order_status_ . $orderId); if ($current paid) { // 已经处理过直接返回避免重复发货 return true; } $updated $this-db-execute( UPDATE orders SET status ?, pay_time NOW(), raw_notify ? WHERE id ? AND status IN (?, ?), [$status, json_encode($rawNotify, JSON_UNESCAPED_UNICODE), $orderId, pending, closed] ); if ($updated 0) { $this-cache-set(order_status_ . $orderId, $status); // 状态变更后触发业务动作比如发放虚拟商品 $this-dispatch(order_paid, $orderId); } return $updated 0; }逻辑说明这里用UPDATE语句的status IN条件做乐观锁只有pending和closed状态能置为paid天然避免了重复回调的问题。$this-cache是二级防重防止同一请求重复进入更新逻辑。参数说明$status只能是插件约定的4个状态之一$rawNotify是渠道回传的原始数据入库保存用于后续争议查证。最后$this-dispatch触发业务事件比如订单支付成功后给用户发放卡密或虚拟商品这块业务自己接上就行。2.3 回调验签为什么不能省六个渠道的签名算法各不相同支付宝用RSA2微信用MD5或HMAC-SHA256QQ钱包和微信同源但有细微差异银联走的是RSA证书体系。插件把这层封装成verifySignature($channel, $params)业务侧完全不用关心算法细节。手写验签太容易翻车尤其是URL解码和大小写问题——同样的参数underscore和query string顺序不同签名结果就不同。另一个容易忽略的是验签时机。必须在解析订单号之前验签而不是先查订单再验签。从设计上讲验签是对“这笔通知确实是渠道发来的”的确认一旦验签失败就该直接拒绝连数据库查询都不做这样能挡住伪造回调的攻击。我见过一个半路接手的项目回调里先查订单再验签结果有人拿着另一个订单的trade_no伪造通知直接给任意订单刷成已支付当天就被人薅了十几笔。3. 部署与配置从下载到跑通第一个支付3.1 环境准备PHP扩展与目录权限彩虹易支付插件的运行依赖几个PHP扩展部署第一步就是确认扩展齐全否则会白屏、验签失败或者二维码生成不出来。以Debian系为例检查命令如下# 检查支付插件必需的PHP扩展缺哪个就要装哪个 php -m | grep -E curl|openssl|fileinfo|mbstring # 没有的话以 Debian/Ubuntu 为例 sudo apt update sudo apt install php-curl php-openssl php-fileinfo php-mbstring # 确认命令行PHP版本 php -v逻辑说明curl用于向渠道网关发起支付请求和订单查询openssl用于RSA2签名验签fileinfo和mbstring是框架层依赖少了它们后台可能报错或图片上传失败。参数说明请把php替换成你服务器实际PHP路径比如php8.1或/usr/local/php/bin/php使用宝塔面板时直接在软件商店装扩展更省事。目录权限是另一个高频坑。runtime、logs目录不可写会导致支付请求发出去但回调进不来或者后台疯狂报500。建议直接检查并赋予写权限# runtime与logs目录必须可写否则支付流程会在中途断掉 chown -R www:www /www/wwwroot/pay/runtime chown -R www:www /www/wwwroot/pay/logs chmod -R 755 /www/wwwroot/pay注意如果你用的是Nginx PHP-FPMPHP进程执行用户是www或nginx目录属主要匹配。我用宝塔时见过有人把目录设成root结果支付回调一直失败排查半天发现是PHP没权限写日志异常被吞掉了。3.2 后台配置参数6个渠道的必填项插件后台的渠道配置页通常长一个样每个渠道一组配置项。核心参数见下表配置项支付宝当面付微信NativeQQ钱包银联在线商户号必填签约后获得必填商户号必填必填应用ID必填开放平台创建必填AppID必填不必填应用私钥必填RSA2不必填不必填必填证书平台公钥必填用于验签不必填不必填必填证书API密钥不必填必填MD5或SHA256必填不必填异步通知URL必填必填必填必填二维码支付类渠道最关键的配置项是异步通知URL。所有渠道的异步通知地址统一填同一个入口例如https://pay.example.com/api/notify.php插件内部会读取channel参数分流处理。这里有几个注意点必须外网可访问不能填localhost或127.0.0.1必须是HTTPS部分渠道强制要求同一个URL不能频繁换换一次渠道后台要重新配置。测试环境用内网穿透可以临时验证回调但正式环境千万别这么干。某开发者因为用内网穿透调试穿透域名不稳定用户支付成功后回调丢了好几次订单支付状态一直没更新最后只能手工补单。吃了一次亏之后我都是先把正式域名配好用1元小额测试单跑完整链路再上线。3.3 跑通第一笔1元支付配置完成后用最小的流程验证整个链路。下面这段代码是发起支付的核心调用// 发起支付统一入口只需传订单号、金额、渠道标识 require_once lib/init.php; // 创建本地订单金额100分即1元 $orderId createOrder(100); $channel alipay_desk; $pay new SixPay(); $result $pay-unify([ order_id $orderId, amount 100, // 单位是分不要传元 channel $channel, notify_url https://pay.example.com/api/notify.php, return_url https://mall.example.com/order/done?id . $orderId, ]); if ($result[code] 0) { // 返回二维码图片流或跳转链接页面上展示即可 echo $result[pay_body]; } else { logError(支付发起失败, $result); }逻辑说明createOrder负责在本地订单表插入一条pending状态记录并返回自增ID$pay-unify把本地订单号、金额、渠道标识组装成渠道要求的格式并发起支付。参数说明amount单位是分如果你从表单拿到的是元一定要先走toCents转换notify_url和return_url的区别在于前者是服务端异步通知用户是否停留在页面都触发后者是用户支付完跳回的地址。跑通这一笔验证点有三个一、二维码能正常展示二、用支付宝扫码支付成功后库里那条订单状态从pending变成paid三、异步通知URL收到渠道的POST请求且插件返回success。三个都过了说明基础链路是通的。如果卡在第二步先看日志目录有没有渠道回调原始数据再检查验签配置。4. 二次开发把6合一的接口接进你自己的业务系统4.1 统一发起支付接口参数与安全建议彩虹易支付6合一的接口设计思路是“业务系统只管下单支付插件管渠道”。业务系统拿到本地订单号后调用插件接口不需要关心当前用的是支付宝还是微信。接口最常见的调用方式是PHP直接方法调用也可以走HTTP接口取决于你插件部署的位置。HTTP模式下请求参数如下参数名类型必填说明order_idint是本地订单号对应你业务系统里订单表的主键amountint是订单金额单位分channelstring是渠道标识如alipay_desk、wx_nativeclient_ipstring否用户IP风控字段建议传attachstring否自定义附加数据回调原样返回可用于标记来源安全方面有几个细节本地订单号不要直接暴露业务主键插件内部通常会再生成一个支付单号发给渠道渠道看不出来你的真实订单量amount只信任服务端计算值不要从前端表单直接取attach参数不要放敏感信息回调时会原样返回但渠道不会对它做加密保护。4.2 异步通知处理验签、幂等、补单异步通知是所有支付集成的核心。处理得好不好直接决定用户支付后能不能立即看到订单完成。下面是notify.php的标准处理逻辑require_once lib/init.php; // 所有渠道的回调都指向这里靠channel参数分流 $channel $_GET[channel] ?? ($_POST[channel] ?? ); $notifyData $_GET $_POST; $pay new SixPay(); // 第一步验签验不过直接拒绝不给后续逻辑任何机会 if (!$pay-verifySignature($channel, $notifyData)) { http_response_code(400); exit(bad sign); } // 第二步从通知参数里解析出本地订单号 $orderId $pay-resolveOrderId($channel, $notifyData); if (!$orderId) { http_response_code(400); exit(order not found); } // 第三步主动向渠道查一次单确认真的支付成功 $confirm $pay-queryOrder($channel, $notifyData[trade_no]); if (!$confirm || $confirm[status] ! SUCCESS) { http_response_code(400); exit(not payed); } // 第四步走统一状态机幂等更新订单状态 $pay-changeOrderStatus($orderId, paid, $notifyData); // 第五步给渠道回success渠道收到后才会停止重试 echo success;逻辑说明第一步验签防伪造第二步把渠道订单号映射成本地订单第三步主动查单是关键——仅仅依赖通知内容不可靠因为部分渠道的通知里不带支付金额或支付状态主动查单能确认这笔支付在渠道侧的真实状态第四步复用状态入口解决重复通知问题第五步应答渠道。参数说明$_GET $_POST是PHP的合并写法注意key冲突时$_POST覆盖$_GET具体渠道依赖的字段要以插件文档为准。有一个常见误区回调里直接执行发货逻辑不在状态机里判断当前状态。这会导致同一笔订单重复发货。正确做法是只更新状态然后通过事件钩子触发发货发货逻辑里面再判断一次当前订单是否已经发过货双保险更稳妥。4.3 同步跳转与订单查询给用户一个查询入口异步通知负责改状态同步跳转负责给用户看结果。用户支付完从渠道跳回return_url时页面不要直接显示“已支付”因为这个跳转只说明用户付过钱订单状态可能还没更新。正确做法是跳转后立即调主动查询接口确认// 页面跳转回来后主动查询一次订单状态 $orderId (int) $_GET[id]; $pay new SixPay(); $result $pay-queryLocalOrder($orderId); if ($result[status] paid) { // 已确认支付展示成功页 renderOrderSuccess($result); } else if ($result[status] pending) { // 可能是回调延迟提示用户等待,不要让他重复支付 renderOrderWaiting($result); } else { // 订单关闭或异常引导重新下单 renderOrderFailed($result); }逻辑说明queryLocalOrder查的是本地数据库不会实时查渠道。因为异步通知通常比用户跳转先到绝大多数情况跳转回来时订单已经是paid状态。如果还是pending大概率是回调延迟提示用户等待30秒刷新别让用户在多个支付单之间反复横跳。5. 六渠道集成避坑指南每一条都是真金白银买来的5.1 支付宝回调的“双重通知”现象现象用户支付成功后数据库里同一笔订单出现了两次支付成功记录或者虚拟商品发了两次。原因支付宝在交易状态变更时会向notify_url发异步通知同时用户从支付页跳回return_url时业务系统如果也调了一次主动查询并触发状态更新两个请求几乎同时到达后一个请求把前一个的“已发货”状态误判为未发货。解决回调处理入口强制走changeOrderStatus因为UPDATE语句带status IN条件后面到的请求会因为状态已不是pending而更新0行自然跳过发货逻辑。缓存键order_status_订单号可以作为第二道防线但我后来发现只要UPDATE条件写对连缓存都可以不要。5.2 微信Native二维码过期问题现象用户把二维码截图发给同事过了半小时再扫页面提示“二维码已过期”然后订单卡在pending状态。原因微信Native支付生成的二维码有有效期限制过期后不能继续扫码支付但本地订单还是pending。解决前端二维码页面做定时检查发现剩余时间小于60秒就自动调接口重新发起支付同时把旧订单标记为closed。注意这里不要直接覆盖原订单ID而要新建一个支付单号否则回调验单时无法区分新旧二维码。5.3 QQ钱包/银联回调延迟或不回调现象用户在QQ钱包或银联页面支付成功但站内订单一直显示未支付。去渠道商户后台查交易状态是成功。原因这类渠道的异步通知依赖渠道内部队列高峰期有分钟级延迟另外如果notify_url返回了非200响应渠道会重试几次然后放弃。解决不要指望异步通知是即时的。我现在的做法是给每个订单加一个“待核对”状态支付成功后立即返回待支付结果。后台每5分钟跑一次批量查询把最近2小时处于pending状态的订单拿去渠道侧核对查到了就主动补单。这套逻辑我放到下一章详细写它才是整个6合一稳定性兜底的关键。5.4 金额浮点精度问题导致对账差一分现象某天对账发现本地订单金额和渠道账单差了0.01元查了半天发现是0.29元、0.57元这类金额在浮点运算时出了问题。原因PHP浮点数无法精确表示部分十进制小数0.29乘100得到28.999999转int后变成28。虽然大多数支付接口要求传分为单位的整数但如果你的代码在转换时用了intval而不是round就会误差1分钱。解决所有金额字段在数据库里用int类型存分所有金额运算在PHP里只用整数用户输入的元统一经toCents转换。另一个细节是前端展示金额时用number_format不要用echo $cents / 100否则0.29会显示成0.2899999。5.5 重复回调导致订单重复发货现象用户买了一个虚拟商品支付成功后收到了两条卡密。原因渠道重试机制 业务代码没做幂等。支付宝等渠道在没收到success应答时会每隔一段时间重发一次通知有的渠道重试四五次。如果业务代码收到通知就发货必然重复。解决除了状态机乐观锁还要在发货业务里做一次“这笔订单是否已经发过货”的校验。虚拟商品场景可以在卡密表加order_id唯一索引第一次插入成功第二次插入直接因为唯一索引报错被捕获。实体商品则要依赖发货单表一单只能有一条出库记录。6. 生产环境兜底用定时对账脚本把“玄学”变成可查数据异步通知做得再好也无法保证100%送达。我见过太多“用户说付了钱但订单没掉单”的case渠道后台查得到成功记录本地就是pending。排查过程非常憋屈因为无法复现。与其赌渠道的可靠性不如默认回调会丢然后加一个定时对账脚本兜底。# 每天凌晨2点跑一次对账把异常订单查出来 0 2 * * * php /www/pay/cron/reconcile.php /var/log/pay-reconcile.log 21// /www/pay/cron/reconcile.php require_once dirname(__DIR__) . /lib/init.php; $pay new SixPay(); // 筛选最近24小时内创建的、状态为pending的订单 $orders $pay-findPendingOrders(24); foreach ($orders as $order) { // 渠道侧查询真实支付状态 $query $pay-queryOrder($order[channel], $order[trade_no]); if ($query[status] SUCCESS) { // 渠道已扣款本地未更新主动补单 $pay-changeOrderStatus($order[id], paid, $query); logReconcile(order auto-paid: . $order[id]); } else if ($query[status] CLOSED || $query[status] NOT_EXIST) { // 渠道侧没有这笔交易说明用户根本没付,把订单关掉 $pay-changeOrderStatus($order[id], closed, []); } }这段代码不复杂但它把“回调丢了”从一次性事故变成了每天自动修复的运维事件。我第一次上线这套脚本的当天晚上就抓到三笔支付宝回调丢失的单子全部自动补上了用户没有任何感知从那以后我再也不会半夜爬起来查订单了。如果你用的是MySQL还可以给对账脚本加一个输出报表把每天补单的订单号、渠道、金额、延迟分钟数写进一张reconcile_log表月底拉出来看哪个渠道延迟大再针对性调整notify_url的重试策略或增加专项监控。这个习惯帮我发现过某渠道在特定时段回调成功率低到80%的问题后来通过业务层调整推荐支付顺序规避掉了。最后一件事给所有订单表和支付单表加上索引按channel status create_time建索引对账脚本就是靠这个索引快速捞出可疑订单。如果表里数据量到了百万级把pending状态订单单独抽到一张小表对账效率会高很多。希望帮到你。本文还有配套的精品资源点击获取