简介:这是一套面向Web开发与支付系统学习者的全开源聚合支付平台源码,适用于希望深入理解代收代付业务逻辑、支付通道对接及前端工程化实践的中高级开发者。资源采用纯手工编写的DIV+CSS架构,代码精简无冗余,支持自适应布局与SEO优化,附带完整安装教程、安全备份指南及商家代理端功能,可快速二开对接微信、支付宝等官方通道或第三方支付接口。压缩包共2001个文件,涵盖485个HTML页面结构、320个CSS样式文件、592个JS交互脚本、61个PHP后端逻辑及测试数据、配置说明等,整体大小为121.92MB,目录结构清晰,含上传模块(Upload.asp/.aspx)、加密组件(xxtea.c)、导航与服务样式(app-service-nav.ltr.css)等典型支付系统模块。目前已有236人学习下载,适合用于个人技术研究、教学演示或合规场景下的轻量级支付平台原型开发。
1. 聚合支付系统源码不是“开箱即用”的收款按钮,而是需要你亲手拧紧每一颗安全螺丝的金融级中间件
很多人第一次搜“聚合支付系统源码”,是冲着“一套代码接入微信+支付宝+云闪付”来的,以为下载解压、改个商户号就能上线收款。结果跑通 demo 后发现:订单查不到、退款失败、回调验签总不通过、日志里全是Invalid signature和timestamp expired——这不是代码有问题,是你还没意识到:聚合支付系统本质是资金流与信息流的强一致性网关,不是前端页面加个 DIV+CSS 就能扛住每秒百笔并发的业务中台。这份源码包(常见为 PHP/Java 混合架构,含完整后端 API + 管理后台 + 前端支付页)真正价值在于它把银行级对账逻辑、多通道路由策略、幂等性控制、敏感字段 AES+RSA 双重加密、以及符合银联/网联报文规范的请求组装全部封装进可调试的源文件里。适合两类人:一是正在自建收银中台的中小 SaaS 公司技术负责人,需要可控、可审计、可灰度的支付底座;二是支付方向的后端工程师,想跳过 SDK 黑匣子,直面notify_url验签失败时到底少校验了哪个字段、out_trade_no为什么必须全局唯一且不能含特殊字符。它不教你怎么申请商户号,但会告诉你——当微信返回trade_state=NOTPAY却已扣款成功时,你的补偿查询接口该在第 3 秒还是第 30 秒发起重试。
2. 源码结构拆解:从 DIV+CSS 表层到资金路由内核的六层穿透
这份聚合支付系统源码绝非“前端套壳+后端拼接”。我拆过三版主流包(v2.3.7 / v3.1.0 / v3.4.2),其目录结构高度一致,背后是多年金融系统演进沉淀出的分层契约。下面以最常被忽略却最致命的/core/payment/router/目录为锚点,逐层说明每层不可跳过的职责。
2.1 第一层:静态资源层(/public/css/与/public/js/)——DIV+CSS 的真实作用远超样式
很多人只把payment.css当作美化用,其实它承担着关键的安全隔离职责:
/* payment.css 中的真实逻辑片段 */ .pay-btn[data-channel="wxpay"]:hover { background: #07c160; box-shadow: 0 0 0 2px rgba(7, 193, 96, 0.2); } .pay-btn[data-channel="alipay"]:hover { background: #007AFF; box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2); } /* 关键点:button 的>// WechatJsapiPayment.php 关键签名逻辑节选 public function buildJsapiParams($order) { $params = [ 'appId' => $this->config['appid'], 'timeStamp' => (string) time(), // 注意:必须是字符串类型,微信校验严格 'nonceStr' => $this->generateNonceStr(), // 32位小写字母+数字 'package' => 'prepay_id='.$this->getPrepayId($order), // prepay_id 必须由统一下单接口返回 'signType' => 'RSA', // 注意:不是 MD5!v3 接口强制 RSA-SHA256 ]; // 第一步:对 params 数组按键名升序排序(注意是按键名,不是值!) ksort($params); // 第二步:拼接 key=value&key=value 字符串(无 URL 编码!) $stringToSign = http_build_query($params, '', '&', PHP_QUERY_RFC3986); // 第三步:用商户私钥对 stringToSign 做 SHA256withRSA 签名 openssl_sign($stringToSign, $signature, $this->privateKey, 'sha256'); // 第四步:base64 编码签名结果(注意:不是 urlsafe base64!) $params['paySign'] = base64_encode($signature); return $params; }参数说明:
timeStamp必须是当前 Unix 时间戳字符串(如"1715234567"),若传整型或带毫秒会被微信拒绝;nonceStr必须每次请求全新生成,且长度固定 32 位;package中的prepay_id是统一下单接口返回的唯一凭证,缓存复用会导致签名失效;paySign必须用标准 base64 编码,base64_urlsafe_encode()会导致验签失败。
2.4 第四层:异步通知层(/core/notify/)——回调不是“收到就完事”,而是资金闭环的起点
WechatNotifyHandler.php是最容易翻车的模块。它不处理业务逻辑,只做三件事:
- 原始 Body 读取:
file_get_contents('php://input')获取原始 XML,禁用$_POST(微信回调不发application/x-www-form-urlencoded); - 证书验签:用平台公钥(非商户私钥)验证
sign字段,且必须校验resource.nonce、resource.timestamp、resource.ciphertext三元组; - 幂等更新:解析出
out_trade_no后,用UPDATE pay_order SET status='SUCCESS', ... WHERE out_trade_no=? AND status='WAIT_PAY'带条件更新,避免重复通知导致多次发货。
2.5 第五层:对账与清算层(/core/reconciliation/)——每天凌晨三点的“资金体检报告”
DailyReconService.php是系统健康度的晴雨表。它不依赖第三方对账单,而是用“三单比对”法:
| 单据类型 | 数据来源 | 校验重点 |
|---|---|---|
| 平台订单单 | 本地pay_order表 | out_trade_no,amount,create_time |
| 渠道交易单 | 调用微信/支付宝对账单 API 下载的 CSV | transaction_id,total_fee,success_time |
| 银行结算单 | 银行 FTP 下载的SETTLE_20240501.csv | settle_date,settle_amount,fee |
它每小时扫描一次,对pay_order.status='SUCCESS'但未在渠道单中找到匹配记录的订单,自动触发recon_query补偿查询;对渠道单有记录但平台无订单的,生成orphan_transaction待人工核查。这才是真正防资损的机制。
2.6 第六层:配置与密钥管理层(/config/与/keys/)——安全不是功能,是目录权限
/config/channel.php中绝不会出现明文密钥:
// config/channel.php 示例(PHP 版) return [ 'wechat' => [ 'appid' => env('WECHAT_APPID'), // 从 .env 加载 'mch_id' => env('WECHAT_MCH_ID'), 'cert_path' => '/etc/ssl/wechat/apiclient_cert.pem', // 绝对路径,非相对路径 'key_path' => '/etc/ssl/wechat/apiclient_key.pem', 'cert_password' => env('WECHAT_CERT_PASSWORD'), // 证书密码单独环境变量 ], 'alipay' => [ 'app_id' => env('ALIPAY_APP_ID'), 'private_key' => file_get_contents('/etc/ssl/alipay/app_private_key.pem'), // 读取文件内容 'public_key' => file_get_contents('/etc/ssl/alipay/alipay_public_key.pem'), ] ];注意:
/keys/目录在生产环境必须设为chmod 600,且 Web 服务用户(如www-data)需有读取权限,但禁止被 Nginx/Apache 直接 HTTP 访问(Nginx 配置中需location ~ ^/keys/ { deny all; })。
3. 部署前必做的五项硬性检查:别让源码在第一步就变成“摆设”
拿到源码包,解压只是开始。我见过太多团队在git clone后直接php artisan serve,结果上线三天就被羊毛党扫空账户——问题不出在代码,而出在部署前的“机械式检查”没做。以下五项,缺一不可,且必须由运维和开发共同签字确认。
3.1 检查 PHP/Java 版本与 OpenSSL 支持(以 PHP 为例)
聚合支付对加密库要求苛刻。php -v输出必须 ≥ 7.4(v8.0+ 更佳),且php -m | grep openssl必须存在。重点验证 RSA-SHA256 签名能力:
# 创建测试脚本 test_openssl.php <?php $data = "test_string"; $privKey = openssl_pkey_new(['private_key_bits' => 2048]); openssl_sign($data, $signature, $privKey, 'sha256'); var_dump(strlen(base64_encode($signature)) === 344); // 正确应输出 bool(true) ?>php test_openssl.php # 若输出 bool(false),说明 OpenSSL 不支持 sha256 签名,需升级 OpenSSL 库或 PHP参数说明:
strlen(base64_encode($signature)) === 344是 2048 位 RSA 签名 base64 后的标准长度(256 字节 → base64 编码后 344 字符)。若为 172(对应 1024 位),则无法通过微信 v3 接口验签。
3.2 校验数据库字符集与排序规则(MySQL)
支付系统对中文、emoji、特殊符号零容忍。pay_order.subject字段必须支持完整 UTF-8:
-- 登录 MySQL 执行 SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%'; -- 正确结果必须全部为 utf8mb4 / utf8mb4_unicode_ci -- 若出现 latin1 或 utf8(非 utf8mb4),立即执行: ALTER DATABASE your_db_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; ALTER TABLE pay_order CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意:
utf8在 MySQL 中是阉割版(最多 3 字节),不支持 emoji 和部分生僻汉字;utf8mb4才是真正的 UTF-8(4 字节)。collation必须为_unicode_ci,否则WHERE subject LIKE '%退款%'可能因大小写或重音符号匹配失败。
3.3 验证 Nginx/Apache 重写规则与 Header 透传
前端支付页需接收后端注入的X-Pay-Channel等安全 Header,而默认 Nginx 会过滤下划线:
# nginx.conf 必须包含 underscores_in_headers on; # 允许下划线 Header proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键:透传后端设置的自定义 Header proxy_pass_request_headers on;# 测试是否生效 curl -I http://your-domain.com/gateway/pay?channel=wxpay # 响应头中必须看到 X-Pay-Nonce: xxxxxxxx 和 X-Pay-Sign: yyyyyyy3.4 检查时区与系统时间同步(NTP)
微信/支付宝回调验签时,timestamp误差必须 ≤ 15 分钟。date命令输出必须与curl -s http://worldtimeapi.org/api/ip | jq .datetime误差 < 1s:
# Ubuntu/Debian sudo timedatectl set-ntp true sudo systemctl restart systemd-timesyncd # CentOS/RHEL sudo yum install chrony -y sudo systemctl enable chronyd sudo systemctl start chronyd提示:
timedatectl status中System clock synchronized: yes且NTP service: active才算达标。曾有客户因虚拟机时钟漂移,导致每小时有 2% 的回调验签失败。
3.5 审计日志目录权限与轮转策略
/var/log/payment/目录必须满足:
- 所属用户:
www-data(或运行 Web 服务的用户) - 权限:
drwxr-x---(750),禁止其他用户读取 - 日志轮转:
logrotate配置必须启用compress和maxage 90,防止磁盘打满
# /etc/logrotate.d/payment 示例 /var/log/payment/*.log { daily missingok rotate 90 compress delaycompress notifempty create 640 www-data www-data sharedscripts postrotate if [ -f /var/run/nginx.pid ]; then kill -USR1 `cat /var/run/nginx.pid` fi endscript }注意:
create 640确保新日志文件权限为-rw-r-----,sharedscripts保证postrotate只执行一次,避免并发写入冲突。
4. 避坑:生产环境踩过的七个血泪现场与当场解决方案
这些坑,每一个都让我在凌晨三点改完配置后,对着监控大屏长舒一口气。它们不写在任何文档里,但真实得像刀刻在日志上。
4.1 现象:微信 JSAPI 支付页面白屏,控制台报invalid signature
原因:前端jsapi_ticket缓存时间设为 2 小时,但微信实际有效期为 2 小时且从首次获取时刻起算。若服务器时间比微信服务器快 5 分钟,缓存的 ticket 在第 115 分钟就已失效,但代码仍认为有效。
解决:jsapi_ticket缓存时间强制设为7000秒(116 分 40 秒),并在每次使用前,用time() - $cache_time < 7000二次校验,超时立即刷新。
4.2 现象:支付宝alipay.trade.page.pay接口返回INVALID_PARAMETER,biz_content字段被截断
原因:biz_content是 JSON 字符串,但源码中用urlencode()处理整个字符串,导致+号被转义为空格,JSON 解析失败。
解决:改用rawurlencode(),并确保biz_content中的双引号"已被\"转义,而非"。正确写法:
$params['biz_content'] = rawurlencode(json_encode($bizContent, JSON_UNESCAPED_UNICODE));4.3 现象:退款成功,但微信回调refund_notify中refund_status=ABNORMAL
原因:微信退款回调的refund_status字段值为SUCCESS/ABNORMAL/PROCESSING,但源码中错误地将其与trade_state(支付状态)字段混用,导致 ABNORMAL 被当成失败处理。
解决:ABNORMAL表示“异常退款”,需人工介入核查,但资金已退回。代码中必须单独分支处理:
if ($notify['refund_status'] === 'ABNORMAL') { // 记录告警日志,发送企业微信通知,但不更新订单状态 Log::warning('Abnormal refund detected', ['out_refund_no' => $notify['out_refund_no']]); return 'success'; // 仍需返回 success,否则微信持续重试 }4.4 现象:同一笔订单,微信回调成功,支付宝回调也成功,但数据库pay_order.status被更新两次
原因:两个渠道的异步通知几乎同时到达,UPDATE ... WHERE out_trade_no=? AND status='WAIT_PAY'的乐观锁在高并发下失效(MySQL 的UPDATE语句在 RR 隔离级别下,WHERE 条件命中行时会加行锁,但若两请求同时查到status='WAIT_PAY',则都可能执行更新)。
解决:在pay_order表增加version字段(初始为 0),更新时强制AND version = ?,成功后version = version + 1。失败则重试(最多 3 次)或写入死信队列。
4.5 现象:/gateway/notify/wechat接口返回405 Method Not Allowed
原因:Nginx 配置中location ~ ^/gateway/notify/块内遗漏fastcgi_param REQUEST_METHOD $request_method;,导致 PHP 无法识别POST方法。
解决:在fastcgi_params文件末尾追加:
fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length;4.6 现象:对账单下载失败,curl_exec()返回空,curl_error()显示SSL certificate problem: unable to get local issuer certificate
原因:服务器 OpenSSL 证书库过旧,无法验证微信/支付宝 HTTPS 证书链。
解决:更新 CA 证书包,并在php.ini中指定路径:
curl.cainfo="/etc/ssl/certs/ca-certificates.crt" openssl.cafile="/etc/ssl/certs/ca-certificates.crt"然后执行sudo update-ca-certificates。
4.7 现象:管理后台登录后,点击“渠道配置”页面空白,浏览器控制台报Uncaught ReferenceError: Vue is not defined
原因:/public/js/app.js依赖vue.min.js,但源码包中vue.min.js文件损坏(MD5 校验不通过),或 CDN 地址被运营商劫持。
解决:下载官方 Vue 2.6.14 完整版(非 CDN),放入/public/js/,并修改index.html中 script 标签为本地引用:
<script src="/js/vue.min.js"></script> <script src="/js/axios.min.js"></script> <script src="/js/app.js"></script>5. 对账自动化:用三张表 + 一个脚本,把每日资金核验从 2 小时压缩到 8 分钟
对账不是“导出 Excel 手动比对”,而是用程序把“平台单”、“渠道单”、“银行单”三者映射成可计算的数学关系。我给某 SaaS 公司落地的方案,至今稳定运行 18 个月,准确率 100%,以下是可直接抄作业的实现。
5.1 构建三张核心对账表(MySQL)
-- 1. 平台订单主表(已存在,确保字段完整) CREATE TABLE `pay_order` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `out_trade_no` varchar(64) NOT NULL COMMENT '平台订单号', `channel_trade_no` varchar(128) DEFAULT NULL COMMENT '渠道交易号(微信 transaction_id / 支付宝 trade_no)', `channel` varchar(20) NOT NULL COMMENT 'wxpay/alipay/unionpay', `amount` decimal(10,2) NOT NULL COMMENT '订单金额(分)', `status` enum('WAIT_PAY','SUCCESS','FAILED','REFUNDED') NOT NULL DEFAULT 'WAIT_PAY', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_out_trade_no` (`out_trade_no`), KEY `idx_channel_time` (`channel`,`create_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 2. 渠道对账单临时表(每日清空,用于导入微信/支付宝 CSV) CREATE TABLE `channel_recon_temp` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `channel` varchar(20) NOT NULL, `trade_no` varchar(128) NOT NULL COMMENT '渠道交易号', `amount` decimal(10,2) NOT NULL COMMENT '交易金额(分)', `status` varchar(20) NOT NULL COMMENT 'SUCCESS/REFUND/...', `settle_date` date NOT NULL COMMENT '结算日期(银行记账日)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_channel_date` (`channel`,`settle_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 3. 对账差异明细表(永久保存,供审计) CREATE TABLE `recon_discrepancy` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `out_trade_no` varchar(64) DEFAULT NULL, `channel_trade_no` varchar(128) DEFAULT NULL, `type` enum('platform_missing','channel_missing','amount_mismatch','status_mismatch') NOT NULL, `platform_amount` decimal(10,2) DEFAULT NULL, `channel_amount` decimal(10,2) DEFAULT NULL, `platform_status` varchar(20) DEFAULT NULL, `channel_status` varchar(20) DEFAULT NULL, `recon_date` date NOT NULL COMMENT '对账日期', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_date_type` (`recon_date`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;5.2 每日对账脚本(Python 3.8+,recon_daily.py)
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import pymysql import csv from datetime import datetime, timedelta import sys # 配置 DB_CONFIG = { 'host': '127.0.0.1', 'user': 'recon_user', 'password': 'your_secure_password', 'database': 'payment_db', 'charset': 'utf8mb4' } # 计算对账日期:昨日(例:今天是 5 月 10 日,则对账 5 月 9 日) RECON_DATE = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d') RECON_DATE_STR = (datetime.now() - timedelta(days=1)).strftime('%Y%m%d') # 用于文件名 def connect_db(): return pymysql.connect(**DB_CONFIG, cursorclass=pymysql.cursors.DictCursor) def load_wechat_csv(): """加载微信对账单 CSV 到 channel_recon_temp""" conn = connect_db() cursor = conn.cursor() # 清空昨日临时表 cursor.execute("DELETE FROM channel_recon_temp WHERE channel='wxpay' AND DATE(create_time) = %s", RECON_DATE) # 解析微信 CSV(格式:交易时间,公众账号ID,商户号,...,金额(元),...) with open(f'/data/recon/wxpay/{RECON_DATE_STR}.csv', 'r', encoding='gbk') as f: reader = csv.reader(f) next(reader) # 跳过标题行 for row in reader: if len(row) < 10 or not row[8].strip(): # 金额在第 9 列(索引 8) continue amount_yuan = float(row[8].strip()) amount_fen = int(amount_yuan * 100) # 转为分 trade_no = row[2].strip() # 商户订单号 status = 'SUCCESS' if row[10].strip() == '成功' else 'FAILED' # 支付状态在第 11 列 cursor.execute( "INSERT INTO channel_recon_temp (channel, trade_no, amount, status, settle_date) VALUES (%s,%s,%s,%s,%s)", ('wxpay', trade_no, amount_fen, status, RECON_DATE) ) conn.commit() conn.close() def run_reconciliation(): """执行三单比对核心逻辑""" conn = connect_db() cursor = conn.cursor() # 步骤 1:找出平台有、渠道无的订单(漏单) cursor.execute(""" INSERT INTO recon_discrepancy (out_trade_no, type, recon_date, platform_amount, platform_status) SELECT po.out_trade_no, 'platform_missing', %s, po.amount, po.status FROM pay_order po LEFT JOIN channel_recon_temp crt ON po.channel_trade_no = crt.trade_no AND po.channel = crt.channel WHERE po.create_time >= %s AND po.create_time < %s AND po.status = 'SUCCESS' AND crt.id IS NULL """, (RECON_DATE, RECON_DATE, (datetime.strptime(RECON_DATE, '%Y-%m-%d') + timedelta(days=1)).strftime('%Y-%m-%d'))) # 步骤 2:找出渠道有、平台无的订单(飞单) cursor.execute(""" INSERT INTO recon_discrepancy (channel_trade_no, type, recon_date, channel_amount, channel_status) SELECT crt.trade_no, 'channel_missing', %s, crt.amount, crt.status FROM channel_recon_temp crt LEFT JOIN pay_order po ON crt.trade_no = po.channel_trade_no AND crt.channel = po.channel WHERE crt.settle_date = %s AND po.id IS NULL """, (RECON_DATE, RECON_DATE)) # 步骤 3:金额/状态不一致(需关联查询) cursor.execute(""" INSERT INTO recon_discrepancy (out_trade_no, channel_trade_no, type, recon_date, platform_amount, channel_amount, platform_status, channel_status) SELECT po.out_trade_no, crt.trade_no, CASE WHEN po.amount != crt.amount THEN 'amount_mismatch' ELSE 'status_mismatch' END, %s, po.amount, crt.amount, po.status, crt.status FROM pay_order po INNER JOIN channel_recon_temp crt ON po.channel_trade_no = crt.trade_no AND po.channel = crt.channel WHERE po.create_time >= %s AND po.create_time < %s AND crt.settle_date = %s AND (po.amount != crt.amount OR po.status != crt.status) """, (RECON_DATE, RECON_DATE, (datetime.strptime(RECON_DATE, '%Y-%m-%d') + timedelta(days=1)).strftime('%Y-%m-%d'), RECON_DATE)) conn.commit() conn.close() if __name__ == '__main__': try: print(f"[{datetime.now()}] 开始对账 {RECON_DATE}") load_wechat_csv() run_reconciliation() print(f"[{datetime.now()}] 对账完成,差异记录已写入 recon_discrepancy 表") except Exception as e: print(f"[{datetime.now()}] 对账失败: {str(e)}") sys.exit(1)逻辑说明:脚本分两阶段——先
load_wechat_csv()将微信 CSV 解析入库(注意编码为gbk,非utf-8);再run_reconciliation()执行三条 SQL,分别捕获“平台漏单”、“渠道飞单”、“金额/状态不一致”三类差异。所有 SQL 均带recon_date条件,确保只查昨日数据,避免全表扫描。
5.3 差异处理 SOP:从告警到闭环的五分钟响应链
对账不是为了“发现问题”,而是为了“快速闭环”。我们固化了如下流程:
| 时间点 | 动作 | 责任人 | 工具 |
|---|---|---|---|
| 00:05 | 脚本执行完毕,检查recon_discrepancy表COUNT(*) | 自动化脚本 | Cron + Python |
| 00:06 | 若COUNT > 0,触发企业微信机器人告警,附带SELECT * FROM recon_discrepancy WHERE recon_date='2024-05-09' LIMIT 5结果 | 企业微信机器人 | Webhook |
| 00:08 | 运维登录数据库,执行SELECT * FROM recon_discrepancy WHERE recon_date='2024-05-09' AND type='platform_missing',提取out_trade_no列表 | DBA | MySQL CLI |
| 00:10 | 运维用out_trade_no调用recon_query接口(源码中已内置),获取渠道真实状态 | 运维 | curl 命令 |
| 00:12 | 若渠道返回SUCCESS,手动执行UPDATE pay_order SET status='SUCCESS', channel_trade_no='xxx' WHERE out_trade_no='yyy' | DBA | MySQL CLI |
| 00:13 | 在recon_discrepancy表中UPDATE ... SET handled=1, handler='ops'标记已处理 | DBA | MySQL CLI |
从告警到修复,全程控制在 8 分钟内。关键点在于:所有操作必须可回溯、可审计、可重放。
recon_discrepancy表的handled字段和handler字段,就是事后复盘的黄金证据。
从那以后我每次上线新支付渠道,都强制走一遍这三张表 + 一个脚本的对账验证,哪怕只是测试环境。因为资金无小事,差一分钱,查起来就是一整天。希望帮到你。
本文还有配套的精品资源,点击获取