常见问题
汇总 DaxPay 使用、部署、二次开发中高频出现的疑问。未覆盖的问题可至 GitHub Issues 或 交流群 反馈。
基础概念
DaxPay 是什么
DaxPay 是一款开源支付系统,提供支付、退款、查询、回调等支付核心能力,将支付宝、微信、银联等多种支付通道封装为统一的 HTTP 接口,业务系统只需对接一套标准协议即可接入多种支付方式。详见 项目介绍。
开源版和商业版有什么区别
开源版(本项目)交付完整的支付核心链路与多端管理界面,基于 LGPL v3.0 协议开源。
商业版(dax-pay-plus)在开源版基础上扩展:
- 更多聚合通道 — 20+ 渠道子模块
- 结算中心、更多国际支付通道等增强能力
转账与分账已包含在开源版交付范围内。
LGPL v3.0 协议可以商用吗
可以。LGPL v3.0 允许商业使用。你的业务系统可以通过接口调用或动态链接方式使用 DaxPay,无需开源你的业务代码。详见 开源协议。
部署运行
后端编译必须用 mvnd 吗
项目约定使用 mvnd(Maven Daemon)加速编译。普通 mvn 也能编译,但 mvnd 自带守护进程,增量编译速度显著更快,推荐使用。
Apache Artemis 能否替换为其他消息队列
当前版本强依赖 Artemis(用于支付延时通知等 JMS 场景),不支持直接替换为 RabbitMQ / Kafka。如需替换需自行改造消息中间件适配层。
延时消息提前或延后投递怎么办
现象:延时消息设置 10 秒,约 6 秒就被消费;或设置 5 秒,过了 8 秒才收到。
原因:Artemis 延时消息按绝对时间戳投递 —— 发送端用本机时钟计算「当前时间 + 延时」写入消息属性,broker 等到自己的时钟到达该时间戳才投递。当应用与 broker 跨机器部署且两机时钟不一致时,实际延时会平移两机时钟差:
- 应用机器时钟慢于 broker → 消息提前投递(如本例慢约 4 秒,10 秒延时约 6 秒即送达)
- 应用机器时钟快于 broker → 消息延后投递
排查:对比两机时钟。可借 broker 主机上任意 HTTP 服务(如 Artemis 控制台 8161 端口)的响应 Date 头与本机时间比对:
curl -sI http://<broker主机>:8161/ | grep -i '^date' # broker 侧时间(秒级精度)
date # 本机时间修复:两台机器均开启 NTP 对时。
Linux 服务器(broker 所在主机):
timedatectl # 查看 "System clock synchronized" 与 "NTP service" 状态
sudo timedatectl set-ntp true # 未开启时先开启 timesyncd
# 系统未装 timesyncd 时,安装 chrony 接管(RHEL 系用 dnf,服务名为 chronyd):
sudo apt install chrony && sudo systemctl enable --now chrony
chronyc tracking # 验证:Leap status 为 Normal 即已同步Windows 开发机(本地跑后端连远程 broker 的场景):
w32tm /query /status # 查看时间服务状态与上次成功同步时间
# 以下两条需管理员终端执行:
w32tm /resync # 立即校时(机器休眠唤醒后时钟漂移,可手动执行)
w32tm /config /syncfromflags:manual /manualpeerlist:"ntp.aliyun.com time.windows.com" /update
# time.windows.com 通路不稳时切换国内 NTP 源TIP
官方安装器部署中,应用与 Artemis 为同一宿主机上的容器,共享内核时钟,天然不存在此问题。该问题常见于「本地跑后端 + 远程 broker」的开发拓扑,以及自行跨机部署 broker 的场景 —— 跨机部署时 NTP 对齐是延时消息精度的硬前提。
套了 CDN 后登录报「请求验证已失效」或「请求时间戳已过期」怎么办
现象:本地直连后端一切正常,套上 CDN(腾讯 EdgeOne、CloudFlare 等)后登录必报 「请求验证已失效,请重试」或「请求时间戳已过期,请重试」。
原因:登录防重放机制要求每次登录先取一个一次性 nonce,CDN 把这个 GET 接口的响应缓存后, 所有用户拿到同一份旧 nonce —— 缓存 5 分钟内报前者(nonce 已被消费),超 5 分钟报后者(时间戳超容差)。 图形验证码接口被缓存同理(所有人看到同一张图)。
排查:用一个仍是 GET 的接口(如公钥)连续请求两次,观察缓存状态:
curl -sD - -o /dev/null https://你的域名/api/token/public-key | grep -i "cache-status"
# EO-Cache-Status: HIT 说明命中缓存(腾讯 EdgeOne;CloudFlare 看 CF-Cache-Status)修复:在 CDN 给所有 API 路径(/api/* 及网关域名的 /unipay/*、/client/*)配置不缓存规则, 详见 CDN 部署注意事项。
支持哪些数据库
仅支持 PostgreSQL 14+。时间字段统一使用 timestamptz(6),实体类使用 OffsetDateTime,不支持 MySQL / Oracle 等。
容器化部署最低配置
建议最低 2 核 4G(含 PostgreSQL / Redis / Artemis / 主应用 / 通道子应用全部容器化)。生产环境建议 4 核 8G 起步,并根据交易量水平扩展通道子应用实例。
PowerShell 下 mvnd 编译参数报错
PowerShell 下含 = 的 -D 参数会被空格拆分,必须加引号:
# 错误 — 会被拆分
mvnd compile -Dmaven.test.skip=true
# 正确 — 加引号
mvnd compile "-Dmaven.test.skip=true"详见 项目构建。
通道对接
开源版已支持哪些支付通道
已对接 13 个国内支付通道:
- 直连通道(
channel-one)— 支付宝、微信、抖音、银联商务、银联(云闪付) - 聚合通道(
channel-two)— 拉卡拉、海科融通、斗拱(汇付天下)、乐刷、随行付、河马付(杉德)、Adapay、富友
详见 特色功能 - 通道总数与分布。
如何新增一个支付通道
- 在对应通道子应用(直连走
channel-one,聚合走channel-two)的daxpay-channel-impl下实现 SDK 调用与签名/验签 - 在主应用
daxpay-channel注册通道策略与配置数据 CRUD - 在
ChannelEnum补充通道枚举
新增通道只需实现 SDK 调用并注册策略,不影响主链路。
channel-one / channel-two / channel-three 有什么区别
三者架构完全相同,区别在于承载的通道集合:
channel-one— 对接支付宝、微信等直连通道(官方 SDK)channel-two— 对接拉卡拉、富友等聚合通道(聚合 SDK)channel-three— 对接 Stripe 等国际通道
主应用通过 daxpay.channel.one/two/three.base-url 路由,子应用编号固定不可改名。
通道子应用的 Java 版和 Go 版怎么选
channel-one 同时提供 Java(Spring Boot)与 Go(Gin)两套完全对等的实现,端口、路由、响应契约一致:
- Java 版 — 生态完整,通道官方 SDK 现成,适合快速对接
- Go 版 — 更高吞吐、更低内存占用,全部自研 HTTP 签名对接(无第三方 SDK)
按团队技术栈与性能诉求选用,两者二选一,端口冲突不可同时启动。
通道子应用必须独立部署吗
是的。通道子应用独立部署是 DaxPay 的核心设计 —— 将第三方 SDK 隔离到子应用,避免 SDK 依赖污染主应用,支持独立升级与弹性伸缩。主应用通过 @HttpExchange 声明式 HTTP 客户端调用子应用,链路 AES-GCM 加密。
功能边界
是否支持分账 / 提现
分账已支持:支付宝、微信、抖音三通道直连商户分账。使用流程:统一下单时声明 allocation=true 创建分账订单 → 通过开放接口 发起分账 / 查询 / 同步;分账接收方需提前在通道侧完成绑定(管理端「通道商户详情 → 分账接收方」提供绑定入口)。详见接口文档。
提现(代付)暂未实现,属后续增强方向。
是否支持国际支付(PayPal / Stripe)
Stripe 已通过 channel-three 子应用接入(持续完善中),支持国际卡组织支付。PayPal 等其他国际通道为后续规划。
沙箱环境怎么用
DaxPay 采用部署级沙箱隔离:
- 测试 / 开发环境配置
daxpay.platform.config.sandbox-enabled=true,允许沙箱联调 - 生产环境必须配置
sandbox-enabled=false,启动时会强制将所有activeEnv=sandbox的产品重置为prod,保证生产数据纯净
生产环境隔离
沙箱联调请走独立的测试环境部署,生产数据库严禁从测试环境导入数据。详见 配置说明。
是否支持多商户 / 多商户隔离
支持。DaxPay 原生支持多商户模式:
- 运营端 + 商户端双入口,数据行级隔离(商户编号自动隔离)
- 单商户可配置多个支付应用,独立凭证与回调地址
二次开发
能否修改源码用于自己的项目
可以。基于 LGPL v3.0,你可以自由修改、使用源码。若你修改了 DaxPay 库本身(而非通过接口调用),需以 LGPL 协议开源你的修改;若仅通过接口调用或动态链接,你的业务代码无需开源。
前端如何对接后端接口
前端通过 RESTful HTTP 接口对接主应用(端口 9999),接口请求 / 响应支持 RSA 签名防篡改,认证基于 Sa-Token(token name: Accesstoken)。详细接入方式见 接口文档。
如何新增一种界面语言(i18n)
- 后端 — 资源文件在
common-i18n/src/main/resources/i18n/{locale}/,按业务模块拆分,新增语种需同步维护zh-CN/en-US/zh-TW/zh-HK/ja-JP/ko-KR - 前端(Web/H5) — 10 语种,新增 key 需全语种同步
- 小程序 — 仅中英(小程序端条件编译剔除繁体 / 日韩 / 东盟)
详见各端 i18n 规范。
数据库表结构在哪?如何初始化
SQL 脚本位于 dax-pay-open/_config/sql/,全新安装顺序为 tables.sql(建表)→ datas.sql(初始数据)。仓内另提供升级脚本 update-tables.sql → update-datas.sql 与菜单种子 iam_perm_menu.sql。
注意
全量 tables.sql / datas.sql 不在仓库内(体积大),需从飞书安装包或历史版本获取。仓内 README.md 有说明。
为什么缓存会出现「只读 L1、不读写 Redis」的情况
读 MultiLevelCache#isL1Only 源码时容易疑惑:普通缓存只有「L1+L2 双层」和「仅 L2」(纯 Redis 模式)两种形态,为什么还存在「仅 L1」?
这不是普通缓存的常规形态,而是敏感缓存的安全降级保护。当缓存名命中敏感名单(secure: 前缀或 secureNames 配置)、且平台数据加密未启用时,敏感数据被禁止明文写入 Redis,此时:
- 写侧:仅写 L1 本机内存,不写 Redis;
- 读侧:连 L2 也不查,直接穿透到方法重新加载。
读侧跳过 L2 是 fail-safe 设计——Redis 里可能存在三类脏数据:①曾启用加密后关闭残留的密文(当前无法解密);②加密启用前明文写入的历史数据(无保护);③多节点密钥版本不一致的密文(解密失败)。宁可穿透重查,也不能把不对的数据当缓存值返回。
完整的缓存行为矩阵:
| 缓存类型 | 条件 | 读行为 |
|---|---|---|
| 普通缓存 | enabled=true, l1.enabled=true | L1 → L2 |
| 普通缓存 | enabled=true, l1.enabled=false | 仅 L2(纯 Redis 模式) |
| 普通缓存 | enabled=false | NoOp 穿透 |
| 敏感缓存 | 数据加密已启用 | L1 → L2(L2 整包 AES-GCM 加密) |
| 敏感缓存 | 数据加密未启用 | 仅 L1(本条目,安全降级) |
| 敏感缓存 | 加密未启用 + l1.enabled=false | 完全穿透(实质不缓存) |
一旦启用数据加密,敏感缓存即自动恢复 L1+L2 双层形态(仅 L2 value 为密文)。应用启动时若命中该降级分支,日志会输出 WARN「敏感缓存因未启用数据加密,仅使用 L1 本地缓存,不写 Redis」提示。
未找到答案?欢迎到 GitHub Issues 提问或加入 交流群 讨论。