28 KiB
28 KiB
支付中心 (RuoYi-Pay)
模块简介
ruoyi-pay 是 PoJie 项目的聚合支付中台模块,提供多支付渠道的统一接入、订单管理、退款管理、回调通知等功能。
定位
ruoyi-pay 是一个独立于业务系统的聚合支付中台。它向下屏蔽微信、支付宝、银联等渠道的协议差异,向上为电商、会员、充值等业务提供统一的支付、退款、查单接口。
核心能力
- ✅ 多商户/多应用: 支持多个业务线(如商城App、门店小程序)配置独立的支付参数
- ✅ 多渠道支持: 微信支付 (Native/JSAPI/App/H5/小程序)、支付宝 (PC/Wap/App/QR)
- ✅ 统一下单: 一套接口适配所有渠道,支持收银台模式
- ✅ 可靠回调: 包含验签、防重、重试机制,确保业务方能收到通知
- ✅ 防掉单机制: 自动轮询"支付中"订单,主动同步三方状态
- ✅ 退款管理: 支持退款申请、退款查询、退款回调处理
- ⏳ 钱包系统: 支持用户余额充值、提现、消费(待实现)
功能特性
1. 支付应用管理 ✅
- ✅ 支付应用CRUD操作
- ✅ 应用状态管理(开启/关闭)
- ✅ 回调地址配置(支付回调和退款回调)
2. 支付渠道管理 ✅
- ✅ 支付渠道CRUD操作
- ✅ 渠道配置管理(JSON格式,支持动态配置)
- ✅ 渠道状态管理
- ✅ 渠道费率配置
- ✅ 支持支付宝、微信支付等多种渠道
3. 支付订单管理 ✅
- ✅ 支付订单创建
- ✅ 订单状态查询
- ✅ 订单同步(防掉单)
- ✅ 订单过期处理
- ✅ 订单关闭
- ✅ 商户订单号唯一性校验
4. 退款管理 ✅
- ✅ 退款申请
- ✅ 退款状态查询
- ✅ 退款回调处理
- ✅ 部分退款支持
- ✅ 退款金额校验
5. 回调通知 ✅
- ✅ 支付回调处理
- ✅ 退款回调处理
- ✅ 异步通知重试机制
- ✅ 指数退避重试策略(15s → 30s → 1m → 2m → 5m)
- ✅ 回调验签和幂等性处理
6. 定时任务 ✅
- ✅ 支付订单过期处理(每分钟)
- ✅ 支付订单状态同步(每2分钟,防掉单)
- ✅ 支付回调通知重试(每30秒)
支持的支付渠道
支付宝
| 渠道编码 | 名称 | 说明 | 状态 |
|---|---|---|---|
alipay_pc |
支付宝 PC 支付 | 电脑网站支付 | ✅ |
alipay_wap |
支付宝 Wap 支付 | 手机网站支付 | ✅ |
alipay_app |
支付宝 App 支付 | 手机 App 支付 | ✅ |
alipay_qr |
支付宝扫码支付 | 当面付 | ✅ |
微信支付
| 渠道编码 | 名称 | 说明 | 状态 |
|---|---|---|---|
wx_pub |
微信 JSAPI 支付 | 公众号、小程序支付 | ✅ |
wx_lite |
微信小程序支付 | 独立小程序支付 | ✅ |
wx_app |
微信 App 支付 | 手机 App 支付 | ✅ |
wx_native |
微信 Native 支付 | 扫码支付 | ✅ |
wx_h5 |
微信 H5 支付 | 手机浏览器支付 | ✅ |
其他渠道
| 渠道编码 | 名称 | 说明 | 状态 |
|---|---|---|---|
mock |
模拟支付 | 开发测试用 | ⏳ 待实现 |
wallet |
钱包支付 | 余额支付 | ⏳ 待实现 |
核心架构设计
实体关系图 (ERD)
┌──────────┐
│ PayApp │ (支付应用)
└────┬─────┘
│ 1
│
│ N
┌────▼──────────┐
│ PayChannel │ (支付渠道)
└────┬──────────┘
│
│ N
┌────▼──────────┐
│ PayOrder │ (支付订单)
└────┬──────────┘
│ 1
│
│ N
┌────▼──────────┐ ┌──────────────┐
│ PayRefund │ │ PayNotifyTask│ (回调任务)
└───────────────┘ └──────────────┘
核心流程
- 商户入驻: 在管理后台创建
应用(App),并在该应用下配置渠道(Channel)参数(如微信AppID、支付宝公钥) - 业务下单: 业务方调用
pay/order/submit,传入appId和channelCode - 支付路由: 支付中心根据
appId + channelCode读取动态配置,实例化对应的支付客户端策略
设计模式
策略模式 (Strategy Pattern)
使用策略模式实现不同支付渠道的统一接口:
PayStrategy- 支付策略接口,定义统一的操作方法AlipayStrategy- 支付宝策略实现WechatPayStrategy- 微信支付策略实现PayStrategyFactory- 策略工厂,负责策略的注册和获取
事件驱动 (Event-Driven)
PaySuccessEvent- 支付成功事件PayOrderListener- 监听支付成功事件,创建回调任务
数据库设计
1. 支付应用表 (pay_app)
用于隔离不同业务线的支付配置。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 应用ID(主键) |
| name | VARCHAR(64) | 应用名 |
| status | TINYINT | 状态(0开启 1关闭) |
| remark | VARCHAR(255) | 备注 |
| pay_notify_url | VARCHAR(1024) | 支付结果回调地址 |
| refund_notify_url | VARCHAR(1024) | 退款结果回调地址 |
| create_by | VARCHAR(64) | 创建者 |
| create_time | DATETIME | 创建时间 |
| update_by | VARCHAR(64) | 更新者 |
| update_time | DATETIME | 更新时间 |
索引: PRIMARY KEY (id)
2. 支付渠道表 (pay_channel)
存储具体的支付参数(JSON格式),实现无需重启即可修改密钥。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 渠道ID(主键) |
| code | VARCHAR(32) | 渠道编码(alipay_pc, wx_pub) |
| status | TINYINT | 状态(0开启 1关闭) |
| remark | VARCHAR(255) | 备注 |
| fee_rate | DOUBLE | 渠道费率(%) |
| app_id | BIGINT | 应用ID |
| config | TEXT | 支付渠道配置(JSON) |
| create_by | VARCHAR(64) | 创建者 |
| create_time | DATETIME | 创建时间 |
| update_by | VARCHAR(64) | 更新者 |
| update_time | DATETIME | 更新时间 |
索引: PRIMARY KEY (id)
3. 支付订单表 (pay_order)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 订单ID(主键) |
| app_id | BIGINT | 应用ID |
| channel_id | BIGINT | 渠道ID |
| channel_code | VARCHAR(32) | 渠道编码 |
| merchant_order_id | VARCHAR(64) | 商户订单编号 |
| subject | VARCHAR(32) | 商品标题 |
| body | VARCHAR(128) | 商品描述 |
| notify_url | VARCHAR(1024) | 异步通知地址 |
| price | INT | 支付金额(分) |
| channel_fee_rate | DOUBLE | 渠道费率 |
| channel_fee_price | INT | 渠道手续费(分) |
| status | TINYINT | 状态(0未支付 10已支付 20已退款 30已关闭) |
| user_ip | VARCHAR(50) | 用户IP |
| expire_time | DATETIME | 订单失效时间 |
| success_time | DATETIME | 支付成功时间 |
| extension_id | BIGINT | 拓展业务ID |
| no | VARCHAR(32) | 支付单号(内部生成) |
| channel_order_no | VARCHAR(64) | 渠道订单号(微信/支付宝) |
| channel_user_id | VARCHAR(64) | 渠道用户OpenId |
| channel_extras | VARCHAR(1024) | 渠道额外参数(JSON) |
| create_by | VARCHAR(64) | 创建者 |
| create_time | DATETIME | 创建时间 |
| update_by | VARCHAR(64) | 更新者 |
| update_time | DATETIME | 更新时间 |
索引:
PRIMARY KEY (id)KEY idx_merchant_order_id (merchant_order_id)
4. 退款订单表 (pay_refund)
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 退款ID(主键) |
| no | VARCHAR(32) | 退款单号 |
| app_id | BIGINT | 应用ID |
| channel_id | BIGINT | 渠道ID |
| channel_code | VARCHAR(32) | 渠道编码 |
| order_id | BIGINT | 支付订单ID |
| order_no | VARCHAR(32) | 支付订单no |
| merchant_order_id | VARCHAR(64) | 商户订单编号 |
| merchant_refund_id | VARCHAR(64) | 商户退款编号 |
| notify_url | VARCHAR(1024) | 回调地址 |
| status | TINYINT | 状态(0等待 10成功 20失败) |
| pay_price | INT | 支付金额(分) |
| refund_price | INT | 退款金额(分) |
| reason | VARCHAR(256) | 退款原因 |
| user_ip | VARCHAR(50) | 用户IP |
| channel_order_no | VARCHAR(64) | 渠道订单号 |
| channel_refund_no | VARCHAR(64) | 渠道退款单号 |
| success_time | DATETIME | 退款成功时间 |
| error_code | VARCHAR(32) | 渠道错误码 |
| error_msg | VARCHAR(128) | 渠道错误描述 |
| create_by | VARCHAR(64) | 创建者 |
| create_time | DATETIME | 创建时间 |
| update_by | VARCHAR(64) | 更新者 |
| update_time | DATETIME | 更新时间 |
索引: PRIMARY KEY (id)
5. 支付通知任务表 (pay_notify_task)
用于记录支付/退款回调通知任务,支持重试机制。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 任务ID(主键) |
| order_id | BIGINT | 订单ID |
| order_no | VARCHAR(32) | 订单号 |
| notify_url | VARCHAR(1024) | 通知地址 |
| notify_body | TEXT | 通知内容(JSON) |
| retry_count | INT | 重试次数(默认0) |
| max_retry | INT | 最大重试次数(默认5) |
| status | TINYINT | 状态(0进行中 1成功 2失败) |
| next_notify_time | DATETIME | 下次通知时间 |
| create_by | VARCHAR(64) | 创建者 |
| create_time | DATETIME | 创建时间 |
| update_by | VARCHAR(64) | 更新者 |
| update_time | DATETIME | 更新时间 |
索引:
PRIMARY KEY (id)KEY idx_next_notify_time (next_notify_time)
6. 支付通知日志表 (pay_notify_log)
用于记录回调通知的请求和响应日志(可选)。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 日志ID(主键) |
| app_id | BIGINT | 应用ID |
| channel_id | BIGINT | 渠道ID |
| order_id | BIGINT | 支付订单ID |
| notify_type | VARCHAR(32) | 通知类型(pay, refund) |
| status | TINYINT | 处理状态(0处理中 10成功 20失败) |
| request_params | TEXT | 请求参数 |
| response_body | TEXT | 响应结果 |
| create_time | DATETIME | 创建时间 |
索引:
PRIMARY KEY (id)KEY idx_order_id (order_id)
数据库脚本位置: script/sql/ry_pay.sql
关键枚举定义
渠道编码 (PayChannelEnum)
见 支持的支付渠道 章节。
订单状态 (PayOrderStatusEnum)
| 状态码 | 描述 |
|---|---|
0 |
WAITING (未支付) |
10 |
SUCCESS (支付成功) |
20 |
REFUND (已退款) |
30 |
CLOSED (已关闭) |
退款状态 (RefundStatusEnum)
| 状态码 | 描述 |
|---|---|
0 |
WAITING (等待处理) |
10 |
SUCCESS (退款成功) |
20 |
FAIL (退款失败) |
模块结构
org.dromara.pay/
├── config/ # 配置类
│ └── PayProperties.java
├── controller/ # 控制器
│ ├── admin/ # 管理端
│ │ ├── PayAppController.java
│ │ ├── PayChannelController.java
│ │ ├── PayOrderController.java
│ │ └── PayRefundController.java
│ ├── app/ # 移动端
│ │ ├── AppPayOrderController.java
│ │ └── AppPayRefundController.java
│ └── PayNotifyController.java # 回调接口
├── core/ # 核心策略
│ ├── PayStrategy.java
│ ├── PayStrategyFactory.java
│ └── impl/
│ ├── AlipayStrategy.java
│ ├── WechatPayStrategy.java
│ └── WechatPayUtil.java
├── domain/ # 领域模型
│ ├── PayApp.java
│ ├── PayChannel.java
│ ├── PayOrder.java
│ ├── PayRefund.java
│ ├── PayNotifyTask.java
│ ├── bo/ # 业务对象
│ └── vo/ # 视图对象
├── enums/ # 枚举
│ ├── PayChannelEnum.java
│ ├── PayOrderStatusEnum.java
│ └── RefundStatusEnum.java
├── event/ # 事件
│ └── PaySuccessEvent.java
├── job/ # 定时任务
│ ├── PayNotifyJob.java
│ ├── PayOrderExpireJob.java
│ └── PayOrderSyncJob.java
├── listener/ # 监听器
│ └── PayOrderListener.java
├── mapper/ # 数据访问
│ ├── PayAppMapper.java
│ ├── PayChannelMapper.java
│ ├── PayOrderMapper.java
│ ├── PayRefundMapper.java
│ └── PayNotifyTaskMapper.java
├── service/ # 服务层
│ ├── PayAppService.java
│ ├── PayChannelService.java
│ ├── PayOrderService.java
│ ├── PayRefundService.java
│ ├── PayNotifyService.java
│ └── impl/
└── util/ # 工具类
└── WechatPayUtil.java
核心业务流程
1. 统一下单流程
业务方 → 支付中心 → 策略工厂 → 三方渠道
↓ ↓ ↓ ↓
提交订单 创建订单 获取策略 调用下单
↑ ↑ ↑ ↑
返回参数 返回参数 返回参数 返回参数
详细流程:
- 业务方调用
/app/pay/order/submit提交支付订单 - 支付中心校验 App 状态和参数
- 创建
pay_order记录(状态为 WAITING) - 通过策略工厂获取对应的支付策略(Alipay/Wechat)
- 读取
pay_channel配置,实例化支付客户端 - 调用三方渠道的下单接口
- 封装标准响应
PayOrderSubmitRespVO返回给业务方 - 业务方使用返回的支付参数调起支付
2. 支付回调流程
三方渠道 → 支付中心 → 业务方
↓ ↓ ↓
异步回调 验签更新 接收通知
↑ ↑ ↑
返回成功 创建任务 返回success
详细流程:
- 支付成功后,三方渠道异步回调
/pay/notify/{channelCode}/{channelId} - 支付中心解析参数并进行验签
- 加分布式锁(
lock:order:{id})确保幂等性 - 幂等校验(若已支付则忽略)
- 更新
pay_order状态为 SUCCESS - 发布支付成功事件(
PaySuccessEvent) - 监听器创建
pay_notify_task回调任务 - 立即执行一次 HTTP 回调通知业务方
- 业务方返回
"success"或{"code": 200} - 如果失败,定时任务按指数退避策略重试
3. 退款流程
业务方 → 支付中心 → 三方渠道
↓ ↓ ↓
申请退款 创建退款 调用退款
↑ ↑ ↑
回调通知 更新状态 返回结果
详细流程:
- 业务方调用
/app/pay/refund/apply申请退款 - 支付中心校验订单(是否已支付、金额是否足够)
- 创建
pay_refund记录(状态为 WAITING) - 调用三方渠道的退款接口
- 如果三方同步返回退款成功:
- 更新
pay_refund状态为 SUCCESS - 更新
pay_order状态(REFUND/部分退款) - 发送退款回调通知
- 更新
- 如果三方异步处理中:
- 保持 WAITING 状态等待回调
- 接收三方异步退款回调
- 更新
pay_refund和pay_order状态 - 发送退款回调通知
4. 防掉单轮询流程
定时任务 → 支付中心 → 三方渠道 → 业务方
↓ ↓ ↓ ↓
扫描订单 查询状态 返回状态 补发通知
↓ ↓
更新状态 触发逻辑
详细流程:
- 定时任务(每2分钟)扫描状态为
WAITING、创建时间超过2分钟、未过期的订单 - 调用三方渠道的查单接口
- 如果已支付(掉单补救):
- 更新
pay_order状态为 SUCCESS - 触发支付成功逻辑
- 补发支付成功通知给业务方
- 更新
- 如果支付关闭/超时:
- 更新
pay_order状态为 CLOSED - (可选)调用三方关单接口
- 更新
- 如果等待支付:
- 不做处理,继续等待
API 接口文档
管理端接口
支付应用管理 (/pay/app)
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| GET | /pay/app/list |
查询支付应用列表 | pay:app:list |
| GET | /pay/app/{id} |
查询支付应用详情 | pay:app:query |
| POST | /pay/app |
新增支付应用 | pay:app:add |
| PUT | /pay/app |
修改支付应用 | pay:app:edit |
| DELETE | /pay/app/{ids} |
删除支付应用 | pay:app:remove |
| POST | /pay/app/export |
导出支付应用列表 | pay:app:export |
支付渠道管理 (/pay/channel)
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| GET | /pay/channel/list |
查询支付渠道列表 | pay:channel:list |
| GET | /pay/channel/{id} |
查询支付渠道详情 | pay:channel:query |
| POST | /pay/channel |
新增支付渠道 | pay:channel:add |
| PUT | /pay/channel |
修改支付渠道 | pay:channel:edit |
| DELETE | /pay/channel/{ids} |
删除支付渠道 | pay:channel:remove |
| POST | /pay/channel/export |
导出支付渠道列表 | pay:channel:export |
支付订单管理 (/pay/order)
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| GET | /pay/order/list |
查询支付订单列表 | pay:order:list |
| GET | /pay/order/{id} |
查询支付订单详情 | pay:order:query |
| POST | /pay/order |
新增支付订单 | pay:order:add |
| POST | /pay/order/submit |
提交支付订单 | pay:order:submit |
| PUT | /pay/order |
修改支付订单 | pay:order:edit |
| DELETE | /pay/order/{ids} |
删除支付订单 | pay:order:remove |
| POST | /pay/order/export |
导出支付订单列表 | pay:order:export |
退款订单管理 (/pay/refund)
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| GET | /pay/refund/list |
查询退款订单列表 | pay:refund:list |
| GET | /pay/refund/{id} |
查询退款订单详情 | pay:refund:query |
| POST | /pay/refund |
新增退款订单 | pay:refund:add |
| POST | /pay/refund/apply |
申请退款 | pay:refund:apply |
| PUT | /pay/refund |
修改退款订单 | pay:refund:edit |
| DELETE | /pay/refund/{ids} |
删除退款订单 | pay:refund:remove |
| POST | /pay/refund/export |
导出退款订单列表 | pay:refund:export |
移动端接口
支付订单 (/app/pay/order)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /app/pay/order/submit |
提交支付订单 |
| GET | /app/pay/order/list |
查询支付订单列表 |
| GET | /app/pay/order/{id} |
查询支付订单详情 |
| GET | /app/pay/order/query |
根据商户订单号查询 |
提交支付订单请求示例:
{
"appId": 1,
"channelCode": "wx_pub",
"channelId": 1,
"merchantOrderId": "ORDER20240101001",
"subject": "商品订单",
"body": "商品订单-ORDER20240101001",
"price": 10000,
"notifyUrl": "https://your-domain.com/app/mall/pay/notify",
"userIp": "127.0.0.1",
"channelUserId": "openid_xxx"
}
响应示例:
{
"code": 200,
"msg": "操作成功",
"data": {
"payOrderNo": "PAY20240101001",
"payData": "{\"appId\":\"wx123\",\"timeStamp\":\"1234567890\",\"nonceStr\":\"abc\",\"package\":\"prepay_id=xxx\",\"signType\":\"RSA\",\"paySign\":\"xxx\"}",
"status": 0
}
}
退款 (/app/pay/refund)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /app/pay/refund/apply |
申请退款 |
| GET | /app/pay/refund/list |
查询退款订单列表 |
| GET | /app/pay/refund/{id} |
查询退款订单详情 |
| GET | /app/pay/refund/query |
根据商户退款编号查询 |
申请退款请求示例:
{
"orderNo": "PAY20240101001",
"merchantRefundId": "REFUND20240101001",
"refundPrice": 10000,
"reason": "用户申请退款",
"notifyUrl": "https://your-domain.com/app/mall/refund/notify",
"userIp": "127.0.0.1"
}
回调接口
支付回调 (/pay/notify)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /pay/notify/{channelCode}/{channelId} |
接收支付回调 |
回调通知内容:
{
"orderNo": "PAY20240101001",
"merchantOrderId": "ORDER20240101001",
"status": 10,
"price": 10000,
"channelUserId": "openid_xxx",
"successTime": "2024-01-01 12:00:00",
"channelOrderNo": "wx20240101001"
}
业务方响应: 返回 "success" 或 {"code": 200} 表示成功
退款回调 (/pay/notify/refund)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /pay/notify/refund/{channelCode}/{channelId} |
接收退款回调 |
配置说明
支付应用配置
{
"name": "电商应用",
"status": 0,
"remark": "电商业务支付应用",
"payNotifyUrl": "https://your-domain.com/pay/notify",
"refundNotifyUrl": "https://your-domain.com/pay/notify/refund"
}
支付宝渠道配置
{
"serverUrl": "https://openapi.alipay.com/gateway.do",
"appId": "your_app_id",
"privateKey": "your_private_key",
"publicKey": "your_public_key",
"signType": "RSA2"
}
说明:
serverUrl: 支付宝网关地址(生产环境/沙箱环境)appId: 支付宝应用IDprivateKey: 应用私钥(RSA2格式)publicKey: 支付宝公钥signType: 签名类型,固定为RSA2
微信支付渠道配置
{
"appId": "your_app_id",
"mchId": "your_mch_id",
"privateKey": "your_private_key",
"mchSerialNo": "your_serial_no",
"apiV3Key": "your_api_v3_key"
}
说明:
appId: 微信应用ID(公众号/小程序/App)mchId: 商户号privateKey: 商户私钥(PEM格式)mchSerialNo: 商户证书序列号apiV3Key: APIv3密钥
定时任务
支付订单过期处理 (PayOrderExpireJob)
- 执行频率: 每分钟 (
0 0/1 * * * ?) - 功能:
- 扫描状态为
WAITING且已过期的订单 - 调用支付渠道的关单接口
- 更新订单状态为
CLOSED
- 扫描状态为
- 限制: 每次处理最多100条,防止积压
支付订单状态同步 (PayOrderSyncJob)
- 执行频率: 每2分钟 (
0 0/2 * * * ?) - 功能:
- 扫描状态为
WAITING、创建时间超过2分钟、未过期的订单 - 调用支付渠道的查单接口
- 如果已支付,更新订单状态并触发支付成功逻辑
- 如果已关闭,更新订单状态
- 扫描状态为
- 目的: 防止掉单,主动同步三方状态
支付回调通知重试 (PayNotifyJob)
- 执行频率: 每30秒 (
0/30 * * * * ?) - 功能:
- 扫描状态为
进行中且到达通知时间的任务 - 执行HTTP回调通知
- 根据响应判断成功或失败
- 失败时按指数退避策略计算下次通知时间
- 扫描状态为
- 重试策略: 15s → 30s → 1m → 2m → 5m
- 最大重试次数: 5次
数据校验规则
支付订单校验
- ✅ 商户订单号唯一性校验
- ✅ 支付单号唯一性校验
- ✅ 删除时校验:已支付的订单不能删除
退款订单校验
- ✅ 退款单号唯一性校验
- ✅ 商户退款编号唯一性校验
- ✅ 退款金额不能超过支付金额
- ✅ 删除时校验:处理中的退款不能删除
支付应用校验
- ✅ 应用名称不能为空
- ✅ 回调地址格式校验(必须以 http:// 或 https:// 开头)
支付渠道校验
- ✅ 渠道编码不能为空
- ✅ 同一应用下渠道编码唯一性校验
- ✅ 渠道配置JSON格式校验
接入指南
Maven 依赖
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-pay</artifactId>
<version>${revision}</version>
</dependency>
快速开始
步骤1: 创建支付应用
在管理后台创建支付应用,配置回调地址。
步骤2: 配置支付渠道
在应用下配置支付渠道,填写渠道参数(JSON格式)。
步骤3: 调用统一下单接口
PayOrderBo bo = new PayOrderBo();
bo.setAppId(1L);
bo.setChannelCode("wx_pub");
bo.setMerchantOrderId("ORDER001");
bo.setSubject("商品订单");
bo.setPrice(10000);
bo.setNotifyUrl("https://your-domain.com/notify");
PayOrderSubmitRespVO resp = payOrderService.submitOrder(bo);
步骤4: 处理支付回调
实现回调接口,接收支付成功通知:
@PostMapping("/notify")
public String notify(@RequestBody Map<String, Object> data) {
String orderNo = (String) data.get("merchantOrderId");
// 处理业务逻辑
return "success";
}
扩展开发
添加新的支付渠道
- 实现
PayStrategy接口 - 在
@PostConstruct方法中注册到PayStrategyFactory - 在
PayChannelEnum中添加新的渠道枚举 - 配置渠道参数
示例:
@Component
@RequiredArgsConstructor
public class NewPayStrategy implements PayStrategy {
private final PayStrategyFactory payStrategyFactory;
@PostConstruct
public void init() {
payStrategyFactory.register("new_pay", this);
}
@Override
public PayOrderSubmitRespVO submitOrder(PayOrderBo bo, PayChannel channel) {
// 实现下单逻辑
}
// 实现其他接口方法...
}
错误码定义
| 错误信息 | 说明 |
|---|---|
应用不存在或已禁用 |
支付应用不存在或状态为关闭 |
支付渠道不存在或已禁用 |
支付渠道不存在或状态为关闭 |
商户订单号已存在 |
商户订单号重复 |
支付单号已存在 |
支付单号重复 |
退款单号已存在 |
退款单号重复 |
商户退款编号已存在 |
商户退款编号重复 |
退款金额不能超过支付金额 |
退款金额校验失败 |
订单不存在 |
支付订单不存在 |
订单未支付或已退款 |
订单状态不允许退款 |
不支持的支付渠道 |
渠道编码不在支持列表中 |
未找到支付策略 |
策略未注册 |
扩展功能规划
收银台模式 ⏳
- 提供统一的 H5 收银台页面
- 业务方只需传入订单号,跳转至收银台
- 用户在收银台选择微信/支付宝/余额支付
- 状态: ⏳ 待实现
钱包系统 (Wallet) ⏳
- 充值: 创建充值订单 -> 调起支付 -> 回调增加余额
- 消费: 余额扣减 -> 生成消费记录
- 提现: 申请提现 -> 管理员审核 -> 企业付款到零钱/银行卡
- 状态: ⏳ 待实现
模拟支付 (Mock) ⏳
- 开发环境专用
- 下单直接返回成功,不调用真实三方接口
- 用于前端联调和流程测试
- 状态: ⏳ 待实现
对账功能 ⏳
- 每日对账任务,对比支付中心订单与三方渠道订单
- 生成对账报表,标记差异订单
- 状态: ⏳ 待实现
支付统计报表 ⏳
- 按日/月统计支付金额、订单数、成功率等
- 按渠道统计支付数据
- 状态: ⏳ 待实现
注意事项
幂等性
- 支付回调需要保证幂等处理,避免重复处理
- 通过订单状态判断,已支付的订单直接返回成功
- 使用分布式锁确保并发安全
安全性
- 回调接口需要验签,确保请求来自支付渠道
- 使用 HTTPS 传输敏感信息
- 渠道配置中的密钥需要妥善保管
- 不要在日志中输出敏感信息(密钥、密码等)
超时处理
- 支付订单默认30分钟过期,可通过配置调整
- 过期订单会自动关闭,调用渠道关单接口
- 业务方需要及时处理订单,避免过期
重试机制
- 回调通知采用指数退避策略,最多重试5次
- 重试间隔:15s → 30s → 1m → 2m → 5m
- 业务方需要快速响应回调,返回
"success"或{"code": 200} - 回调响应时间建议控制在3秒内
防掉单
- 定时任务主动查询订单状态,防止掉单
- 查询创建时间超过2分钟且未过期的待支付订单
- 如果发现已支付但未收到回调,主动触发支付成功逻辑
金额单位
- 所有金额以"分"为单位存储和传递
- 前端展示时需要转换为"元"
- 计算时注意单位转换,避免精度问题
回调地址
- 回调地址必须是公网可访问的 HTTPS 地址
- 回调地址需要支持 POST 请求
- 回调响应需要快速返回,避免超时
- 建议使用内网地址 + 反向代理的方式处理回调
依赖模块
ruoyi-common-core- 核心工具类ruoyi-common-mybatis- 数据库操作ruoyi-common-web- Web框架支持ruoyi-common-security- 安全认证(Sa-Token)ruoyi-common-log- 日志记录ruoyi-common-idempotent- 幂等性控制ruoyi-common-redis- Redis缓存alipay-sdk-java- 支付宝SDKwechatpay-java- 微信支付SDKweixin-java-mp- 微信公众号SDK(用于JSAPI支付)