简介:ARYA云支付1.1 Java版是一套聚合支付源码,面向需要搭建支付宝个人码转银行卡、免签聚合支付的开发者或企业技术团队,适合具备Java基础、正在研究第三方支付对接的人群。系统基于Java开发,整合支付宝、微信、银联等多种渠道,通过个人二维码即可完成转账,并以短信、指纹等验证替代传统签名,在简化流程的同时保障交易安全。包内共2000个文件,其中JS、HTML、CSS主要用于前端交互界面,Java与XML构成后端核心逻辑和配置,Properties、Shell等辅助环境部署;压缩包整体182.28MB,附完整的搭建教程文档、部署文档和使用说明,目录结构清晰,便于快速上线和二次开发。目前已有141人学习下载,通过阅读源码可深入理解聚合支付的路由、对账、免签与安全校验机制,也可按自身业务需求定制功能,是一份实用价值较高的参考工程。
1. 先把“支付宝个码转卡免签聚合”拆开看:ARYA云支付1.1到底解决什么问题
拿到一套ARYA云支付1.1 Java版源码,很多人第一反应是:个人支付宝收款码也能接成聚合支付?不用签约商户号,用户扫了码,系统自动把订单推给商户后台,钱还能自动转到银行卡。这套链路听起来像黑匣子,拆开看就是“监控+累计单+转卡”三个动作:安卓端盯着支付宝到账通知,服务端负责生成订单、回调商户、调度转账。
它最直接的价值,是把个人码收款从“人盯手机、手动记账”变成“系统盯、自动回调”,适合私域电商、外包项目快速收款、以及想研究免签聚合支付原理的Java开发者。但要注意:免签模式下拿不到支付宝官方订单上下文,匹配全靠金额、时间和备注,稳定性和合规性都有限,只建议在合法业务场景里做技术落地。下面就从链路原理讲到部署配置、上线避坑,把这套体系讲透。
2. 支付链路的真实原理:监控、累计单、回调、转卡是怎么串起来的
2.1 先搞懂钱在哪:个人码收款进的是余额,不是银行卡
个人码收的钱第一落点是支付宝余额,不是直接进银行卡。付款方扫你的个人收款码,资金从对方账户划到你的支付宝余额,通知栏弹一条“支付宝到账15.00元”。这跟签约商户号最大的区别是:官方支付接口会把订单号、买家ID、支付时间一起推给你,个人码什么都没有,只有一条金额和付款人昵称。所以“免签”的本质,就是用旁路监控替代官方异步通知。
ARYA云支付1.1做的事情,是给这条缺少订单上下文的收款通道补一套“人工替代”:监控端看到到账,上报服务端;服务端拿金额、时间、备注去匹配那张待支付订单;匹配上就把订单置为已支付,再触发回调;最后再把余额里的钱按规则转出到银行卡。这个“转卡”动作也不是银行直连,而是调用支付宝App内部的转账能力,或者由监控端模拟执行,这也是整套系统里最需要谨慎对待的一环。
2.2 监控端怎么感知到账:通知栏监听与无障碍兜底
监控端通常是一台不锁屏的低端安卓机或云手机,登录收款支付宝账号,装ARYA配套的监控插件。插件用系统 NotificationListenerService 拦截通知栏,只要出现“支付宝到账”就抓取通知文本。文本解析是关键:各家ROM通知文案不完全一致,常见的有“支付宝到账15.00元”“收到一笔转账,金额15.00元”,所以不能用 String.indexOf 硬匹配,得用正则提取,并且用 BigDecimal 接收,避免浮点精度问题。
// 监控端通知解析:兼容多种到账文案 // 注意匹配“15.00”这类两位小数金额,并过滤掉“转账失败”等负向文案 private static final Pattern AMOUNT_PATTERN = Pattern.compile("(\\d{1,3}(?:,\\d{3})*)(?:\\.(\\d{2}))?"); private static final Pattern REJECT_PATTERN = Pattern.compile("失败|取消|退款|未支付"); public BigDecimal parseAmount(String notificationText) { if (REJECT_PATTERN.matcher(notificationText).find()) { throw new AmountParseException("非到账通知,忽略:" + notificationText); } Matcher m = AMOUNT_PATTERN.matcher(notificationText); if (m.find()) { String integerPart = m.group(1).replace(",", ""); String decimalPart = m.group(2) == null ? "00" : m.group(2); return new BigDecimal(integerPart + "." + decimalPart); } throw new AmountParseException("无法从通知中解析金额: " + notificationText); }逻辑说明:正则把大额数字的千分位逗号剥掉后再拼小数,因为部分ROM会把“15.00元”拆成多个文本片段,直接取整段偶尔会漏小数位。REJECT_PATTERN 用来过滤退款和失败通知,否则一笔退款会被当成新收款上报,造成重复回调。金额只用 BigDecimal,不用 Double,15.00 和 15.0 在序列化和比对时精度不一致,后面做幂等匹配会埋雷。
参数说明:这个解析在监控端做,上报时同时携带原始通知文本和解析出的金额,服务端可以做二次校验。如果两台监控端同时挂着同一个支付宝号,上报去重由服务端的收款流水号完成——同一时间、同一金额、同一备注只接受第一条。
通知栏监听省电,但 Android 8 以上对后台服务限制很严,部分 ROM 几分钟就把进程杀了。所以正规做法是保留一条兜底链路:用 AccessibilityService 定期读支付宝的消息中心列表,把最近 N 条收款记录拉一遍,再和服务端流水比对补漏。常见配置是通知栏作为实时主通道,无障碍兜底每 60 秒扫一次,能覆盖多数掉通知的场景。
2.3 服务端累计单:匹配、幂等、转卡的状态机
监控端上报的是“一笔到账”,不是“一个订单”,中间差着匹配层。ARYA服务端在收到上报后,先用收款账号、金额、到账时间三个条件,去订单表里找状态为 0 的待支付订单。这里最容易出现两个单金额相同、时间接近的情况,所以建议下单时带上支付备注,备注里含订单尾号,能显著提高匹配精度。
| 状态值 | 含义 | 触发动作 |
|---|---|---|
| 0 | 已创建 | 等待监控端上报 |
| 1 | 已支付 | 安排回调、进入转卡队列 |
| 2 | 转卡中 | 定时任务捞单,调用转出 |
| 3 | 已完成 | 回调成功且转卡成功 |
| 4 | 失败 | 回调或转卡重试超限,人工介入 |
状态机要禁止逆向流转:1 到 0、3 到 1 这种回退在并发场景下会触发重复发货。所以每张订单的更新语句都带当前状态条件,比如 UPDATE 只更新 status=1 的行,影响行数为 0 说明这单已经被别人处理过。Java 服务端的数据一致性,很大程度就是靠这种“带条件的更新”撑住的,而不是靠分布式事务。
2.4 回调转发:ARYA在替支付宝发“支付宝回调”
这里有个容易混淆的点:ARYA转发给商户的异步通知,只是借用了支付宝回调的字段格式,并不是支付宝官方发的。官方回调只存在于签约模式下,个人码收款根本没有回调。因此校验责任全部落在ARYA自身:转发时带自己的签名、商户系统要验签、ARYA要幂等和重试。
ARYA一般在收到监控上报后立即回调一次,失败则按指数退避重试,最多 N 次。商户系统只需要把ARYA当作一个“类支付宝网关”接入即可,回调报文里的 orderNo、amount、status 三个字段是商户对账的核心依据,后面代码章节会给出可复制的实现。
3. 部署落地:把ARYA 1.1服务端跑起来的最小动作与核心参数
3.1 部署拓扑与最小资源
ARYA落地需要两个角色:一台能跑 Java 服务端的 Linux 服务器,一台挂支付宝账号的监控端。服务端推荐 2核4G 起步,跑 Java、MySQL、Redis 勉强够用;监控端可以是旧安卓机,也可以是云手机,验证阶段用一台就够,业务量大再按渠道横向扩展。单商户、单码的场景,一个监控端加一台服务器就能把整套链路跑通。
服务端技术栈常见是 Spring Boot + MyBatis,配套 MySQL 和 Redis。MySQL 存订单、商户、渠道、流水四类核心数据;Redis 承担回调幂等锁、监控端心跳和转卡任务队列。这两样缺一个,ARYA 都跑不稳,所以部署顺序一定是先把依赖装好,再启动 jar。
3.2 服务器初始化:Java、MySQL、Redis 一键准备
# Ubuntu 22.04 安装 Java 17 运行时、MySQL、Redis sudo apt update sudo apt install -y openjdk-17-jdk-headless mysql-server redis-server # 启动并设开机自启 sudo systemctl enable --now mysql sudo systemctl enable --now redis-server # 建库:ARYA 默认 utf8mb4,保证金额和大段回调 URL 不乱码 mysql -uroot -p -e "CREATE DATABASE arya_pay DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 单独建账号,避免应用层直接用 root mysql -uroot -p -e "CREATE USER 'arya'@'localhost' IDENTIFIED BY 'ChangeMe_2024';" mysql -uroot -p -e "GRANT ALL PRIVILEGES ON arya_pay.* TO 'arya'@'localhost'; FLUSH PRIVILEGES;"逻辑说明:JDK 装 headless 版本就够,服务端不跑图形界面。MySQL 用 utf8mb4 是因为回调地址和支付备注里可能带中文和生僻字符。应用账号单独建,权限限定在 arya_pay 库,尽量避免应用层拿 root 连接数据库,否则误操作代价很大。Redis 默认监听 127.0.0.1,服务器上不需要对外暴露端口。
参数说明:数据库密码建议换成随机生成的长串,示例里的 ChangeMe_2024 只是占位。源码包里通常会带 SQL 导入脚本,导入前确认 MySQL 是大版本 5.7 还是 8.0,连接驱动和 timezone 参数会略有差异。
3.3 初始化数据库:订单表、商户表、渠道表的核心字段
源码包里的 SQL 脚本要重点看三张表:t_order 支付订单表、t_mch 商户表、t_channel 渠道表。下面这张 t_order 能看出整套设计的一半思路。
-- 支付订单表:钱先到账,后匹配订单,所以金额、渠道、状态都要冗余 CREATE TABLE t_order ( order_no VARCHAR(32) NOT NULL COMMENT 'ARYA生成的订单号', mch_id VARCHAR(32) NOT NULL COMMENT '商户号', channel_code VARCHAR(16) NOT NULL COMMENT '渠道编码,如 ali_qr', amount DECIMAL(10,2) NOT NULL COMMENT '订单金额,单位元', status TINYINT NOT NULL DEFAULT 0 COMMENT '0创建 1已支付 2转卡中 3完成 4失败', notify_url VARCHAR(255) NOT NULL COMMENT '下单时的回调地址,历史单不随商户配置变更', paid_order_no VARCHAR(64) DEFAULT NULL COMMENT '监控端上报的支付宝流水号,幂等唯一', remark VARCHAR(64) DEFAULT NULL COMMENT '支付备注,用于金额相同时二次匹配', paid_time DATETIME DEFAULT NULL, callback_time DATETIME DEFAULT NULL, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (order_no), UNIQUE KEY uk_paid_order_no (paid_order_no), KEY idx_mch_status (mch_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;逻辑说明:order_no 是 ARYA 自己生成的主键,不是支付宝订单号。paid_order_no 存监控端上报的支付宝流水号或通知文本ID,加唯一索引后,整个系统的幂等就有了数据库层的兜底,重复上报会被直接挡在门外。notify_url 冗余到订单表很关键:商户如果后来改了全局回调地址,历史单仍应回调到下单时的地址,否则对账会乱。
参数说明:amount 用 DECIMAL(10,2) 存元,不要换算成分再存,否则 SQL 统计时到处要除以 100,还容易出错。单笔限额取决于支付宝个人码自身的限额规则,DECIMAL(10,2) 的量级足够覆盖。
3.4 配置文件六个必改项与启动命令
ARYA 服务端是 Spring Boot 工程,配置集中在 application.yml。启动前把下面几项改掉,其他保持默认也能跑起来。
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/arya_pay?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 username: arya password: ChangeMe_2024 redis: host: localhost port: 6379 timeout: 3000ms arya: pay: # 给商户转发回调的兜底地址,实际以订单表里的 notify_url 为准 callback-base-url: http://your-mch-server.com/api/pay/callback # 监控端心跳超时时间,秒 monitor-heartbeat-timeout: 120 # 下单后未支付的过期时间,分钟 order-expire-minutes: 10 # 回调重试次数,超过进人工队列 callback-retry-times: 5 # 定时扫单间隔,毫秒 transfer-scan-interval: 3000逻辑说明:serverTimezone 必须写成 Asia/Shanghai,否则 Java 8 以上的日期时间类型和 MySQL 的 DATETIME 会差 8 小时,回调日志里看到的时间全是乱的。callback-base-url 只是兜底,真正的回调地址在下单接口里由商户传入,订单表里存的那份才是最终执行值。order-expire-minutes 建议设到 10 分钟以上,付款动作本身很快,但监控端上报可能延迟,太短容易误关单。
启动命令:
# 后端打包成 jar 后直接启动,生产环境建议指定堆内存 java -Dfile.encoding=UTF-8 -Xms1g -Xmx2g -jar arya-pay-1.1.jar启动后看日志,出现 Started AryaApplication 才算真正起来,再用 ss -lntp 确认 8080 端口在监听。先别急着接业务,把健康检查接口和监控端心跳注册测通,再进入商户配置阶段。
4. 商户、渠道与回调配置:从创建订单到通知商户系统的Java实现
4.1 商户与渠道:一个商户多码,费率按渠道算
ARYA 里商户是业务主体,渠道是可收款的码。一个商户可以绑多个渠道,每个渠道对应一台监控端和一个收款码。渠道表需要关注几个字段:渠道编码、所属商户、费率、单笔限额、单日限额、状态。
-- 添加测试商户 INSERT INTO t_mch (mch_id, mch_name, api_key, status) VALUES ('10001', '测试商户', 'a3f2c8e91b7d4f6a0c2255e9d8b12345', 1); -- 给商户加一个支付宝个人码渠道 INSERT INTO t_channel (channel_code, mch_id, pay_type, fee_rate, daily_limit, status) VALUES ('ali_qr_1', '10001', 'ALI_QR', 0.0038, 50000.00, 1);逻辑说明:api_key 按商户维度存,下单验签、回调验签都用它,泄露等于别人能冒充你下单。fee_rate 是ARYA在代收场景下算自己收入用的,和支付宝官方费率无关;个人码场景的真实成本主要是提现手续费,这笔费率要覆盖掉才有得赚,这也是做聚合支付方案时要重点核算的地方。
参数说明:daily_limit 按渠道限制,每收到一笔就累加,超过限额的渠道在查询时会被过滤,避免一个码收太多触发风控。这里有个实际经验:限额别顶格设,个人码按日控制在一个相对保守的范围内,翻车概率低很多。
4.2 统一下单与回调验签:Java侧的核心代码
商户系统调用ARYA的 POST /api/pay/unified 下单,ARYA 校验签名通过后创建订单,同步返回二维码内容。这里最容易被忽略的是签名拼接规则:常见规则是把 mchId、orderNo、amount、channelCode 按固定顺序拼起来,加 apiKey 做 MD5。注意 amount 要先转成字符串,别用 double 直接拼接,否则 1.10 会变成 1.1。
@PostMapping("/api/pay/unified") public ResponseEntity<Map<String, Object>> unifiedOrder(@RequestBody UnifiedOrderReq req) { // 1. 校验签名:拼串顺序必须和商户端一致,多一个字段少一个字段都会验签失败 String raw = req.getMchId() + req.getOrderNo() + req.getAmount() + req.getChannelCode() + apiKeyOf(req.getMchId()); String expectSign = DigestUtils.md5Hex(raw); if (!expectSign.equalsIgnoreCase(req.getSign())) { return ResponseEntity.badRequest().body(Map.of("code", "SIGN_ERROR", "msg", "sign invalid")); } // 2. 幂等创建订单,状态初始为 0 PayOrder order = new PayOrder(); order.setOrderNo("A" + System.currentTimeMillis()); order.setMchId(req.getMchId()); order.setChannelCode(req.getChannelCode()); order.setAmount(new BigDecimal(req.getAmount())); order.setNotifyUrl(req.getNotifyUrl()); order.setStatus(0); orderMapper.insert(order); // 3. 返回二维码内容;二维码由渠道绑定,监控端上挂的是同一个码 return ResponseEntity.ok(Map.of( "code", "OK", "orderNo", order.getOrderNo(), "qrCode", qrCodeProvider.getQrCode(req.getChannelCode()))); }逻辑说明:API key 从库里按商户取,不要写死到配置。校验签名放在创建订单前,防止恶意刷单。orderNo 用时间戳加随机串生成,别用数据库自增ID,因为订单号会暴露业务量,多机部署时自增ID也容易冲突。qrCode 是监控端对应渠道的收款码内容,可以是二维码图片URL,也可以是码串,商户端直接展示给用户扫。
接着是回调转发。监控端上报后,服务端匹配到订单,下一步就是把“已支付”事件推给商户系统。ARYA的实现一般是一个回调任务:按订单表 notify_url 去 POST 报文,报文里带 orderNo、amount、status、支付流水号和ARYA侧签名。
public void notifyMch(PayOrder order) { // 用 Redis SETNX 做回调幂等:一个订单只允许首个线程回调 Boolean first = redisTemplate.opsForValue().setIfAbsent( "cb:" + order.getOrderNo(), "1", Duration.ofHours(24)); if (Boolean.FALSE.equals(first)) { return; } Map<String, String> body = Map.of( "orderNo", order.getOrderNo(), "amount", order.getAmount().toPlainString(), "status", "SUCCESS", "paidOrderNo", order.getPaidOrderNo() ); String sign = DigestUtils.md5Hex( body.get("orderNo") + body.get("amount") + body.get("status") + mchApiKey(order.getMchId())); body.put("sign", sign); // 重试:第一次失败后按 1s、2s、4s 退避,最多 5 次 for (int i = 0; i < callbackRetryTimes; i++) { try { httpClient.post(order.getNotifyUrl(), body); return; } catch (Exception e) { log.warn("回调失败,第{}次重试,orderNo={}", i + 1, order.getOrderNo()); Thread.sleep((long) Math.pow(2, i) * 1000); } } // 重试耗尽,标记订单进入人工处理队列 orderMapper.markManual(order.getOrderNo()); }逻辑说明:幂等锁的 key 用订单号,TTL 设为 24 小时,覆盖整个重试窗口。为什么用 Redis 而不用数据库唯一索引:回调频率高,SETNX 是原子操作,数据库方案要额外加字段和索引,成本更高。重试间隔指数退避,避免监控端批量上报时把商户系统打崩。重试超限不要丢弃,落到人工队列,由后台页面处理才有后悔药。
4.3 转卡任务:定时扫单与状态机防重复
订单状态变成 1 之后,转卡任务开始工作。常见做法是用 Spring 自带的 @Scheduled,简单够用;如果要在多实例集群上跑,记得加分布式锁,否则两台机器会同时把同一笔单转出去。
// 每 3 秒扫描一次已支付未转卡的订单 @Scheduled(fixedDelay = 3000, initialDelay = 5000) public void scanPaidOrders() { List<PayOrder> paidOrders = orderMapper.scanByStatus(1, 10); for (PayOrder order : paidOrders) { // compareAndSet:从 1 -> 2,只有真正抢到更新的实例才继续 int updated = orderMapper.compareAndSetStatus(order.getOrderNo(), 1, 2); if (updated != 1) { continue; } try { transferClient.transferToCard(order.getChannelCode(), order.getAmount(), order.getPaidOrderNo()); orderMapper.updateStatus(order.getOrderNo(), 3); } catch (TransferException e) { orderMapper.updateStatus(order.getOrderNo(), 4); } } }逻辑说明:fixedDelay=3000 表示上一次任务执行完再等 3 秒,不是固定频率,避免任务堆积。每次捞 10 笔,单机够用,量大就按商户拆分并行。compareAndSetStatus 是关键:SQL 里带 WHERE status=1,影响行数为 1 才意味着拿到这单的转卡权。转卡和回调的先后顺序我一般设计成先回调后转卡:哪怕转卡失败,商户侧订单已支付,后续人工处理不影响发货;反过来先转卡再回调,转卡成功但回调挂了,钱出去了订单没成,更难解释。
参数说明:转出涉及银行卡限额、到账时效、每日转出次数上限,ARYA 配置里通常会有一组 transfer 相关参数,上线前按实际银行卡规则填,别直接照抄默认值。如果转卡走的模拟操作,监控端的账号登录态和屏幕常亮状态也要纳入巡检,这是后面避坑章的重点。
5. 上线前避坑:ARYA云支付1.1的五个高频故障与排查顺序
5.1 监控端掉线:钱收了一下午,单子全是未支付
现象:下午一批订单全部卡在“未支付”,客户确实扫码付了款;重启监控端后瞬间补单。这种情况最容易发生在云手机或低端安卓机上。
原因:Android 系统清理后台,通知监听服务被杀;云手机偶发断网;ARYA 侧只标记了心跳超时,没有自动补单指令。
解决:监控端要做前台服务加定时自检;ARYA 侧加“心跳消失超过 2 分钟就把该渠道待支付订单挂起并告警”的机制;同时保留手动补单入口,监控端恢复后立刻扫一遍支付宝账单列表,把漏单捞回来。补单逻辑只比对金额和备注,能对上的自动置为已支付,对不上的进人工列表。
5.2 回调乱序与重复:商户先收到成功,又收到失败
现象:商户系统日志里同一笔单先是 SUCCESS,两分钟后又是 FAILED,结果发了两遍货。
原因:回调转发和转卡任务是两个异步流程;转卡失败时ARYA把状态改成 4,有的实现又触发了一次“失败”回调,和之前的成功回调形成乱序,商户端没做过幂等就中招。
解决:回调只以“已支付”事件为准,转卡失败不要触发面向商户系统的回调,只进内部人工队列。商户侧也要做幂等:以 order_no 加 status=SUCCESS 为准,状态不允许从 success 回退到 failed。这套约定要在接入文档里写死,不然每次上线都在接锅。
5.3 金额解析错位:12.34 被识别成 123.4
现象:用户付 12.34 元,商户订单金额却显示 123.4 元,匹配失败。第一次遇到会觉得是玄学,实际上就是把小数位吞了。
原因:通知文本里金额和备注混杂,正则贪婪匹配吃到了备注里的数字;或者部分 ROM 把金额文本拆成多个片段,解析时漏了小数部分。
解决:金额正则强制两位小数,解析后调用 BigDecimal.compareTo 和订单金额做精确比对,误差超过 0.01 就丢进待人工列表。同时要求支付备注带上订单尾号,用备注优先匹配、金额做二次校验,两个维度都一致才置为已支付。这套双因子匹配做完,误判率能降一个数量级。
5.4 个人码被风控:高频小额加秒转是翻车重灾区
现象:收款码用几天后被限制收款,提示“该收款码存在交易风险”,订单密度一高就开始零散失败。
原因:个人码本质是个人转账,不是商业收单。高频、金额规律、当天立即转出三大特征叠加,很容易命中支付宝的风控模型,这也是整个免签模式里最难绕开的坎。
解决:控制单渠道日订单量,别把全部流量压在一个码上。转卡不要实时秒转,改成每半小时或每小时批量转一次,加随机延时。金额不要全部落在 99、199 这类整数档,模拟真实交易分布。这里必须说清楚:整套ARYA只适合合法合规的业务量级,拿来做刷单、赌博、资金归集这类高风险场景,码封了是小事,法律风险才是大事。
5.5 转卡失败与账不平:收款、转出两套流水对不上
现象:订单状态已经变成“完成”,但银行流水里找不到对应转出记录;或者转出金额和收款金额对不上,差在手续费上。
原因:监控端登录态过期导致转账失败;或一条收款流水被重复转出;或转账手续费扣了,但订单表里存的还是原金额。账不平是这类系统上线后最大的雷,必须靠对账任务兜住。
解决:转卡记录单独建表,每次转出都保存渠道流水号。状态机里转卡失败置 4 后,自动进入人工转账页面,不能直接置回已支付。每天凌晨跑对账:收款流水、支付订单、转卡流水、银行流水四个来源比对,差额挂账到待处理表。我接手这类系统的第一周只做一件事,就是跑对账,把差异率磨到零再谈放量。
6. 进阶验证:用一笔1分钱订单把整条链路验到可交付
ARYA 这套系统能不能交付,不在于代码能不能编译,而在于一笔真实的 1 分钱订单能不能走完“下单、扫码、到账、回调、转卡、对账”六步。我的验证顺序固定如下,也建议照这个顺序来。
第一步,在库里插入一个测试商户和测试渠道,把渠道绑定到一台监控端。第二步,用 curl 模拟商户系统调统一下单接口,拿到 qrCode。第三步,用另一个支付宝账号扫这个码,付 1 分钱,备注里填订单尾号。第四步,看ARYA日志里状态从 0 到 1 的瞬间,监控端上报的金额是否和订单表匹配,测试回调接口是否收到 SUCCESS 报文。第五步,等状态变 2 再变 3,查银行流水确认到账。第六步,把监控端进程主动杀掉再下一笔单,验证心跳告警和补单入口是否生效。
# 模拟商户系统查询订单状态,status 字段是最终验收信号 curl -s "http://127.0.0.1:8080/api/pay/query?mchId=10001&orderNo=A1730000000000&sign=..." | jq .验证通过后再加一道对账脚本,把每日应收和已转出金额对齐:
SELECT DATE(paid_time) AS pay_date, COUNT(*) AS order_cnt, SUM(amount) AS paid_amount, SUM(CASE WHEN status = 3 THEN amount ELSE 0 END) AS settled_amount FROM t_order WHERE mch_id = '10001' AND paid_time >= NOW() - INTERVAL 1 DAY GROUP BY DATE(paid_time);跑完这套验证,我还会故意让回调重试一次,看商户侧幂等键是否挡得住重复报文;再关掉监控端模拟一次掉线,看补单流程能不能把漏单捞回来。这两关过了,才敢说这套 ARYA 云支付 1.1 在你手里是可交付的。转卡这类敏感动作,量越大越要克制,稳定的前提是每一笔账都能对上,速度反而是其次。这套代码改造成支持更多个码的聚合层也顺路,核心的累计单和回调体系不用动,只要在监控端多挂几个监听器。希望帮到你。
本文还有配套的精品资源,点击获取