# 支付模块设计方案(DDD架构) ## 一、整体架构与技术栈 ### 技术栈 - **Java**: 1.8 - **框架**: Spring Boot 2.5.15 + ruoyi-framework - **持久化**: MyBatis / MyBatis-Plus - **架构模式**: DDD(领域驱动设计) - **支付渠道**: 微信支付v2、支付宝当面付 ### 服务定位 - 独立服务,可单独运行 - 单商户模式 - 配置统一在 `application.yml` 管理 --- ## 二、DDD分层与包结构 ### 分层映射至 RuoYi 结构 #### 1. Interfaces 层(接口层) **包路径**: `com.ruoyi.payment.interfaces.controller` **职责**: - 对外 REST API(发起支付、查询订单) - 渠道异步回调 Webhook(微信/支付宝) - 管理端操作接口(关闭、重发回调、对账查询) #### 2. Application 层(应用层) **包路径**: `com.ruoyi.payment.application.service` **职责**: - 用例编排(发起支付、处理渠道回调、触发业务回调) - 事务控制与跨聚合协调 - 定时任务(每日对账、订单过期处理) #### 3. Domain 层(领域层) **包路径**: - `com.ruoyi.payment.domain.model` - 聚合根、实体、值对象 - `com.ruoyi.payment.domain.service` - 领域服务 - `com.ruoyi.payment.domain.repository` - 仓储接口 **职责**: - 核心业务规则与状态机 - 聚合根封装与不变性约束 - 领域事件定义 #### 4. Infrastructure 层(基础设施层) **包路径**: - `com.ruoyi.payment.infrastructure.persistence` - Mapper 实现 - `com.ruoyi.payment.infrastructure.channel.wechat` - 微信客户端 - `com.ruoyi.payment.infrastructure.channel.alipay` - 支付宝客户端 - `com.ruoyi.payment.infrastructure.config` - 配置加载 - `com.ruoyi.payment.infrastructure.schedule` - 定时调度 - `com.ruoyi.payment.infrastructure.audit` - 操作审计 **职责**: - 仓储接口实现(数据库访问) - 渠道客户端封装与适配 - 证书与密钥管理 - 定时任务与消息发布 --- ## 三、核心领域模型 ### 1. PaymentOrder(支付订单 - 聚合根) **字段**: | 字段 | 类型 | 说明 | |------|------|------| | id | Long | 内部订单ID(主键,雪花ID) | | bizOrderId | String | 外部业务订单号 | | amount | Integer | 金额(单位:分) | | currency | String | 币种(固定 CNY) | | channel | Enum | 支付渠道(WECHAT/ALIPAY) | | status | Enum | 订单状态 | | subject | String | 订单标题 | | description | String | 订单描述 | | callbackUrl | String | 业务回调地址 | | expireAt | DateTime | 过期时间(创建后+2小时) | | latestTransactionId | Long | 最新交易ID | | channelTradeNo | String | 渠道交易号(支付成功后) | | paidAt | DateTime | 支付成功时间 | | version | Integer | 乐观锁版本号 | | createdAt | DateTime | 创建时间 | | updatedAt | DateTime | 更新时间 | **订单状态枚举** (`status`): - `INIT`: 初始化 - `PENDING`: 待支付 - `SUCCEEDED`: 支付成功 - `FAILED`: 支付失败 - `CANCELED`: 已关闭 - `EXPIRED`: 已过期 **业务规则**: 1. 金额必须 > 0 2. 币种固定为 CNY 3. 同一订单同时只允许存在**一笔进行中的交易**(PENDING) 4. 订单超过2小时未支付自动标记为 EXPIRED 5. 订单成功后触发外部业务回调 ### 2. PaymentTransaction(支付交易流水 - 实体) **字段**: | 字段 | 类型 | 说明 | |------|------|------| | id | Long | 交易ID(主键) | | orderId | Long | 所属订单ID | | channel | Enum | 支付渠道 | | clientType | Enum | 客户端类型(NATIVE/ALIPAY_PRECREATE) | | status | Enum | 交易状态 | | codeOrQr | String | 渠道返回的二维码内容(code_url/qr_code) | | qrBase64 | Text | 服务端生成的Base64二维码图片 | | requestParams | Text | 渠道请求参数快照 | | responseSnapshot | Text | 渠道响应快照 | | channelTradeNo | String | 渠道交易号 | | createdAt | DateTime | 创建时间 | | paidAt | DateTime | 支付完成时间 | **交易状态枚举** (`status`): - `PENDING`: 待支付 - `SUCCEEDED`: 支付成功 - `FAILED`: 支付失败 - `CANCELED`: 已取消 **业务规则**: 1. 每次发起支付时,若订单已有 PENDING 交易,则复用该交易并返回其二维码 2. 若无 PENDING 交易,则创建新交易 3. 一旦交易成功,订单状态同步为 SUCCEEDED ### 3. NotifyLog(渠道回调日志 - 实体) **字段**: | 字段 | 类型 | 说明 | |------|------|------| | id | Long | 日志ID | | channel | Enum | 支付渠道 | | notifyIdOrSerial | String | 渠道通知唯一标识 | | orderId | Long | 订单ID | | transactionId | Long | 交易ID | | payload | Text | 回调原始报文 | | verified | Boolean | 签名验证是否通过 | | processed | Boolean | 是否已处理 | | result | String | 处理结果 | | createdAt | DateTime | 接收时间 | **幂等控制**: - 唯一索引:`channel + notifyIdOrSerial` - 重复通知直接返回成功应答 ### 4. BizCallbackLog(业务回调日志 - 实体) **字段**: | 字段 | 类型 | 说明 | |------|------|------| | id | Long | 日志ID | | orderId | Long | 订单ID | | transactionId | Long | 交易ID | | callbackUrl | String | 回调地址 | | payload | Text | 回调请求体 | | httpStatus | Integer | HTTP 状态码 | | response | Text | 响应内容 | | success | Boolean | 是否成功 | | retryCount | Integer | 重试次数 | | lastRetryAt | DateTime | 最后重试时间 | | createdAt | DateTime | 创建时间 | **重试策略**: - 间隔:0/1/5/15/60 分钟 - 最多重试:10次 - 支持管理端手工重发 ### 5. OperationAudit(操作审计 - 实体) **字段**: | 字段 | 类型 | 说明 | |------|------|------| | id | Long | 审计ID | | operator | String | 操作人 | | operationType | Enum | 操作类型 | | orderId | Long | 订单ID(可选) | | transactionId | Long | 交易ID(可选) | | params | Text | 操作参数 | | approved | Boolean | 是否通过(记录,不走审批流程) | | createdAt | DateTime | 操作时间 | **操作类型**: - `CLOSE_ORDER`: 关闭订单 - `RESEND_CALLBACK`: 重发业务回调 - `MANUAL_FIX`: 手工修正对账差异 - `UPDATE_CHANNEL_ACCOUNT`: 更新渠道账号 ### 6. 值对象 - **Money**: `{ amountInCents, currency }` - **BizOrderId**: 外部业务订单号封装 - **CallbackUrl**: 业务回调地址封装 - **ChannelTradeNo**: 渠道交易号封装 --- ## 四、核心用例与流程 ### 1. 发起二维码支付 **入参**: - `bizOrderId`: 外部业务订单号 - `amount`: 金额(分) - `subject`: 订单标题 - `description`: 订单描述 - `channel`: 支付渠道(WECHAT/ALIPAY) - `callbackUrl`: 业务回调地址 **流程**: ``` 1. 查询订单是否存在 - 不存在 → 创建订单(状态 PENDING,expireAt = now + 2小时) - 存在 → 加载订单 2. 检查订单是否有进行中的交易(PENDING) - 有 → 直接返回已有交易的二维码 Base64 - 无 → 创建新交易 3. 调用渠道下单接口 【微信】 - 接口:v2 unifiedorder - trade_type: native - 签名方式:MD5 - notify_url: https://api.966120.com/api/pay/notify/wechat - 返回:code_url 【支付宝】 - 接口:alipay.trade.precreate - notify_url: https://api.966120.com/api/pay/notify/alipay - 返回:qr_code 4. 生成二维码 - 将 code_url/qr_code 生成 300×300 PNG 二维码 - 编码为 Base64 字符串 - 保存到交易记录(qrBase64) 5. 返回给前端 { "orderId": xxx, "transactionId": xxx, "status": "PENDING", "qrBase64": "data:image/png;base64,iVBORw0KG...", "expireAt": "2025-11-23T12:00:00" } ``` **幂等控制**: - 同一订单仅允许一笔 PENDING 交易存在 - 重复请求直接返回已有交易数据 ### 2. 渠道回调处理(Webhook) **微信v2回调**: ``` 1. 接收 XML 报文 2. 验证签名(MD5) 3. 解析报文,提取交易号与支付结果 4. 查询订单与交易 5. 状态转换 - 成功 → 交易状态 SUCCEEDED,订单状态 SUCCEEDED - 失败 → 交易状态 FAILED 6. 持久化更新(乐观锁) 7. 记录 NotifyLog(幂等控制) 8. 触发外部业务回调 9. 返回微信要求的 XML 应答 ``` **支付宝回调**: ``` 1. 接收表单参数 2. 验证签名(RSA2) 3. 解析交易状态 4. 同步订单与交易状态 5. 记录 NotifyLog 6. 触发外部业务回调 7. 返回 "success" ``` **幂等保障**: - 基于 `channel + notifyIdOrSerial` 唯一索引 - 重复通知直接返回成功应答,不重复处理 ### 3. 外部业务回调 **触发时机**: 订单状态变更为 SUCCEEDED 后 **协议**: HTTP POST JSON **请求头**: - `Content-Type: application/json` - `X-Signature`: HMAC-SHA256(payload + nonce + timestamp, callbackSignSecret) - `X-Nonce`: 随机字符串 - `X-Timestamp`: 毫秒时间戳 **请求体**: ```json { "tradeId": 1234567890, "orderId": 9876543210, "bizOrderId": "BIZ20251123001", "channel": "WECHAT", "amount": 10000, "currency": "CNY", "status": "SUCCEEDED", "channelTradeNo": "4200001234567890", "paidAt": "2025-11-23T10:30:00", "subject": "商品订单支付", "description": "订单详情" } ``` **鉴权方式**: - 使用 HMAC-SHA256 签名 - 密钥:配置文件中的 `callbackSignSecret` - 签名内容:`payload + nonce + timestamp` - 外部系统使用相同密钥验证签名 **重试策略**: - 失败重试间隔:0/1/5/15/60 分钟 - 最多重试:10次 - 支持管理端手工重发 ### 4. 订单过期处理 **触发方式**: 定时任务(建议每10分钟) **逻辑**: ``` 1. 查询状态为 PENDING 且 expireAt < now 的订单 2. 将订单状态更新为 EXPIRED 3. 记录操作日志 ``` **过期时间**: - 从订单首次创建时间起算 +2小时 - 发起新交易不刷新过期时间 ### 5. 每日对账(自动修复) **调度时间**: 每天 02:00 **流程**: ``` 1. 确定对账日期(T-1) 2. 查询本地该日所有订单与交易 3. 调用渠道查询接口(批量/单笔) - 微信:orderquery - 支付宝:alipay.trade.query 4. 对比本地状态与渠道状态 5. 自动修复 - 本地 PENDING,渠道已成功 → 修正为 SUCCEEDED - 本地 PENDING,渠道已关闭 → 修正为 FAILED 6. 差异记录 - 金额不一致 - 渠道无记录但本地存在 - 其他异常情况 7. 生成对账报告 - 总订单数 - 成功数 - 失败数 - 差异数 - 自动修复数 8. 保存对账任务与结果明细 ``` **差异处理**: - 状态差异:自动修复并记录 - 金额差异:仅记录,不自动修复,需人工介入 - 严重差异:告警通知 --- ## 五、渠道适配与客户端封装 ### 1. 微信支付 v2(Native) **配置项** (`application.yml`): ```yaml payment: wechat: appId: wx70f6a7346ee842xx mchId: 1573728xxx mchKey: Xz0ClPK3f5sCeT6SGa1vpVmyUFcbpxxx notifyUrl: https://api.966120.com/api/pay/notify/wechat signType: MD5 ``` **接口封装** (`WxPayV2Client`): - `createNativeOrder()`: 统一下单,返回 code_url - `queryOrder()`: 查询订单状态 - `closeOrder()`: 关闭订单 - `verifyNotify()`: 验证回调签名 - `buildNotifyResponse()`: 构造应答报文 **签名方式**: MD5 **关键流程**: 1. 参数按 ASCII 排序拼接 2. 追加 `&key=mchKey` 3. MD5 加密并转大写 ### 2. 支付宝当面付(Precreate) **配置项** (`application.yml`): ```yaml payment: alipay: appId: 2021xxxxxxxxxxxxx privateKey: MIIEvQIBADANBgkqhki...(商户RSA2私钥) alipayPublicKey: MIIBIjANBgkqhki...(支付宝RSA2公钥) serverUrl: https://openapi.alipay.com/gateway.do notifyUrl: https://api.966120.com/api/pay/notify/alipay signType: RSA2 ``` **接口封装** (`AlipayF2FClient`): - `precreate()`: 当面付下单,返回 qr_code - `query()`: 查询订单状态 - `close()`: 关闭订单 - `verifyNotify()`: 验证回调签名 **签名方式**: RSA2 **SDK**: 使用官方 `alipay-sdk-java` ### 3. 渠道客户端统一接口 ```java public interface ChannelClient { /** * 创建二维码订单 */ QrOrderResponse createQrOrder(QrOrderRequest request); /** * 查询订单状态 */ OrderQueryResponse queryOrder(String channelTradeNo); /** * 关闭订单 */ void closeOrder(String channelTradeNo); /** * 验证回调签名 */ boolean verifyNotify(Map params); } ``` **工厂模式选择实现**: - `WxPayV2Client implements ChannelClient` - `AlipayF2FClient implements ChannelClient` --- ## 六、二维码生成规范 **技术方案**: 使用 ZXing 库 **参数**: - 尺寸:300 × 300 - 格式:PNG - 纠错等级:M(默认) - 编码:UTF-8 **输出格式**: ``` data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA... ``` **实现**: ```java // 伪代码 BufferedImage qrImage = QRCodeGenerator.generate(codeUrl, 300, 300); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(qrImage, "PNG", baos); String base64 = "data:image/png;base64," + Base64.getEncoder().encodeToString(baos.toByteArray()); ``` --- ## 七、数据表设计 ### 1. pay_order(支付订单表) ```sql CREATE TABLE `pay_order` ( `id` BIGINT NOT NULL PRIMARY KEY COMMENT '订单ID', `biz_order_id` VARCHAR(64) NOT NULL COMMENT '业务订单号', `amount` INT NOT NULL COMMENT '金额(分)', `currency` VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '币种', `channel` VARCHAR(16) NOT NULL COMMENT '支付渠道', `status` VARCHAR(16) NOT NULL COMMENT '订单状态', `subject` VARCHAR(128) NOT NULL COMMENT '订单标题', `description` VARCHAR(512) COMMENT '订单描述', `callback_url` VARCHAR(512) NOT NULL COMMENT '业务回调地址', `expire_at` DATETIME NOT NULL COMMENT '过期时间', `latest_transaction_id` BIGINT COMMENT '最新交易ID', `channel_trade_no` VARCHAR(64) COMMENT '渠道交易号', `paid_at` DATETIME COMMENT '支付成功时间', `version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', INDEX `idx_biz_order_id` (`biz_order_id`), INDEX `idx_channel_trade_no` (`channel_trade_no`), INDEX `idx_status` (`status`), INDEX `idx_expire_at` (`expire_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付订单表'; ``` ### 2. pay_transaction(支付交易流水表) ```sql CREATE TABLE `pay_transaction` ( `id` BIGINT NOT NULL PRIMARY KEY COMMENT '交易ID', `order_id` BIGINT NOT NULL COMMENT '订单ID', `channel` VARCHAR(16) NOT NULL COMMENT '支付渠道', `client_type` VARCHAR(32) NOT NULL COMMENT '客户端类型', `status` VARCHAR(16) NOT NULL COMMENT '交易状态', `code_or_qr` VARCHAR(512) COMMENT '二维码内容', `qr_base64` TEXT COMMENT 'Base64二维码图片', `request_params` TEXT COMMENT '请求参数快照', `response_snapshot` TEXT COMMENT '响应快照', `channel_trade_no` VARCHAR(64) COMMENT '渠道交易号', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `paid_at` DATETIME COMMENT '支付完成时间', INDEX `idx_order_id` (`order_id`), INDEX `idx_channel_trade_no` (`channel_trade_no`), INDEX `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付交易流水表'; ``` ### 3. notify_log(渠道回调日志表) ```sql CREATE TABLE `notify_log` ( `id` BIGINT NOT NULL PRIMARY KEY COMMENT '日志ID', `channel` VARCHAR(16) NOT NULL COMMENT '支付渠道', `notify_id_or_serial` VARCHAR(64) NOT NULL COMMENT '渠道通知唯一标识', `order_id` BIGINT COMMENT '订单ID', `transaction_id` BIGINT COMMENT '交易ID', `payload` TEXT NOT NULL COMMENT '回调原始报文', `verified` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '签名验证是否通过', `processed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已处理', `result` VARCHAR(128) COMMENT '处理结果', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '接收时间', UNIQUE KEY `uk_channel_notify` (`channel`, `notify_id_or_serial`), INDEX `idx_order_id` (`order_id`), INDEX `idx_transaction_id` (`transaction_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='渠道回调日志表'; ``` ### 4. biz_callback_log(业务回调日志表) ```sql CREATE TABLE `biz_callback_log` ( `id` BIGINT NOT NULL PRIMARY KEY COMMENT '日志ID', `order_id` BIGINT NOT NULL COMMENT '订单ID', `transaction_id` BIGINT NOT NULL COMMENT '交易ID', `callback_url` VARCHAR(512) NOT NULL COMMENT '回调地址', `payload` TEXT NOT NULL COMMENT '回调请求体', `http_status` INT COMMENT 'HTTP状态码', `response` TEXT COMMENT '响应内容', `success` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否成功', `retry_count` INT NOT NULL DEFAULT 0 COMMENT '重试次数', `last_retry_at` DATETIME COMMENT '最后重试时间', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', INDEX `idx_order_id` (`order_id`), INDEX `idx_success` (`success`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='业务回调日志表'; ``` ### 5. operation_audit(操作审计表) ```sql CREATE TABLE `operation_audit` ( `id` BIGINT NOT NULL PRIMARY KEY COMMENT '审计ID', `operator` VARCHAR(64) NOT NULL COMMENT '操作人', `operation_type` VARCHAR(32) NOT NULL COMMENT '操作类型', `order_id` BIGINT COMMENT '订单ID', `transaction_id` BIGINT COMMENT '交易ID', `params` TEXT COMMENT '操作参数', `approved` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否通过', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', INDEX `idx_operator` (`operator`), INDEX `idx_operation_type` (`operation_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作审计表'; ``` ### 6. reconciliation_task(对账任务表) ```sql CREATE TABLE `reconciliation_task` ( `id` BIGINT NOT NULL PRIMARY KEY COMMENT '任务ID', `task_date` DATE NOT NULL COMMENT '对账日期', `status` VARCHAR(16) NOT NULL COMMENT '任务状态', `total_count` INT NOT NULL DEFAULT 0 COMMENT '总订单数', `success_count` INT NOT NULL DEFAULT 0 COMMENT '成功数', `failed_count` INT NOT NULL DEFAULT 0 COMMENT '失败数', `diff_count` INT NOT NULL DEFAULT 0 COMMENT '差异数', `fixed_count` INT NOT NULL DEFAULT 0 COMMENT '自动修复数', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `finished_at` DATETIME COMMENT '完成时间', UNIQUE KEY `uk_task_date` (`task_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对账任务表'; ``` ### 7. reconciliation_result(对账差异明细表) ```sql CREATE TABLE `reconciliation_result` ( `id` BIGINT NOT NULL PRIMARY KEY COMMENT '明细ID', `task_id` BIGINT NOT NULL COMMENT '任务ID', `order_id` BIGINT NOT NULL COMMENT '订单ID', `transaction_id` BIGINT COMMENT '交易ID', `local_status` VARCHAR(16) COMMENT '本地状态', `channel_status` VARCHAR(16) COMMENT '渠道状态', `diff_type` VARCHAR(32) NOT NULL COMMENT '差异类型', `fixed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已修复', `note` VARCHAR(512) COMMENT '备注', `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', INDEX `idx_task_id` (`task_id`), INDEX `idx_order_id` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='对账差异明细表'; ``` --- ## 八、RESTful API 接口清单 ### 1. 发起微信 Native 支付 ``` POST /api/pay/wechat/native ``` **请求体**: ```json { "bizOrderId": "BIZ20251123001", "amount": 10000, "subject": "商品订单支付", "description": "购买商品A", "callbackUrl": "https://your-business.com/notify" } ``` **响应**: ```json { "code": 200, "msg": "success", "data": { "orderId": 1234567890, "transactionId": 9876543210, "status": "PENDING", "qrBase64": "data:image/png;base64,iVBORw0KG...", "expireAt": "2025-11-23T12:00:00" } } ``` ### 2. 发起支付宝当面付 ``` POST /api/pay/alipay/precreate ``` **请求体/响应**: 同上 ### 3. 微信回调通知 ``` POST /api/pay/notify/wechat ``` **说明**: 接收微信 XML 报文,验签后处理,返回 XML 应答 ### 4. 支付宝回调通知 ``` POST /api/pay/notify/alipay ``` **说明**: 接收表单参数,验签后处理,返回 "success" ### 5. 查询订单 ``` GET /api/pay/orders/{orderId} ``` **响应**: ```json { "code": 200, "msg": "success", "data": { "orderId": 1234567890, "bizOrderId": "BIZ20251123001", "amount": 10000, "currency": "CNY", "channel": "WECHAT", "status": "SUCCEEDED", "subject": "商品订单支付", "channelTradeNo": "4200001234567890", "paidAt": "2025-11-23T10:30:00", "expireAt": "2025-11-23T12:00:00", "createdAt": "2025-11-23T10:00:00" } } ``` ### 6. 查询最新交易 ``` GET /api/pay/orders/{orderId}/transactions/latest ``` **响应**: ```json { "code": 200, "msg": "success", "data": { "transactionId": 9876543210, "orderId": 1234567890, "status": "PENDING", "qrBase64": "data:image/png;base64,iVBORw0KG...", "createdAt": "2025-11-23T10:00:00" } } ``` ### 7. 关闭订单 ``` POST /api/pay/orders/{orderId}/close ``` **响应**: ```json { "code": 200, "msg": "订单已关闭" } ``` ### 8. 重发业务回调 ``` POST /api/pay/orders/{orderId}/callback/resend ``` **响应**: ```json { "code": 200, "msg": "回调已重新发送" } ``` ### 9. 查询对账任务列表 ``` GET /api/pay/reconciliation/tasks?date=2025-11-22 ``` **响应**: ```json { "code": 200, "msg": "success", "data": { "taskId": 12345, "taskDate": "2025-11-22", "status": "FINISHED", "totalCount": 1000, "successCount": 980, "failedCount": 15, "diffCount": 5, "fixedCount": 3, "createdAt": "2025-11-23T02:00:00", "finishedAt": "2025-11-23T02:15:00" } } ``` ### 10. 查询对账差异明细 ``` GET /api/pay/reconciliation/results?taskId=12345 ``` **响应**: ```json { "code": 200, "msg": "success", "data": [ { "orderId": 1234567890, "transactionId": 9876543210, "localStatus": "PENDING", "channelStatus": "SUCCEEDED", "diffType": "STATUS_DIFF", "fixed": true, "note": "已自动修复" } ] } ``` --- ## 九、配置文件示例 **application.yml**: ```yaml spring: application: name: dryad-payment # 支付配置 payment: # 微信支付配置 wechat: appId: wx70f6a7346ee842xx mchId: 1573728xxx mchKey: Xz0ClPK3f5sCeT6SGa1vpVmyUFcbpxxx notifyUrl: https://api.966120.com/api/pay/notify/wechat signType: MD5 # 支付宝配置 alipay: appId: 2021xxxxxxxxxxxxx privateKey: MIIEvQIBADANBgkqhki... alipayPublicKey: MIIBIjANBgkqhki... serverUrl: https://openapi.alipay.com/gateway.do notifyUrl: https://api.966120.com/api/pay/notify/alipay signType: RSA2 # 业务回调配置 business: callbackSignSecret: your-hmac-secret-key-here callbackRetryMaxCount: 10 callbackRetryIntervals: 0,1,5,15,60 # 分钟 # 二维码配置 qrcode: size: 300 format: PNG # 对账配置 reconciliation: enabled: true cron: "0 0 2 * * ?" # 每天凌晨2点 ``` --- ## 十、管理端页面功能清单 ### 1. 支付订单管理 **路径**: `/payment/orders` **功能**: - 列表展示(支持分页) - 条件检索:业务订单号、渠道、状态、时间范围 - 详情查看:订单信息、交易记录、回调日志 - 操作:手工关闭订单 ### 2. 交易流水管理 **路径**: `/payment/transactions` **功能**: - 列表展示(支持分页) - 查看二维码(Base64图片) - 查看渠道请求/响应快照 - 按订单过滤 ### 3. 渠道回调日志 **路径**: `/payment/notify-logs` **功能**: - 列表展示(支持分页) - 查看原始报文 - 验签结果展示 - 处理状态与结果 ### 4. 业务回调日志 **路径**: `/payment/callback-logs` **功能**: - 列表展示(支持分页) - 查看回调请求/响应 - 重试次数与状态 - 手工重发回调 ### 5. 对账管理 **路径**: `/payment/reconciliation` **功能**: - 对账任务列表(按日期) - 查看对账统计 - 差异明细列表 - 自动修复记录查看 ### 6. 操作审计 **路径**: `/payment/audit` **功能**: - 审计日志列表(支持分页) - 按操作人、操作类型过滤 - 查看操作参数与时间 **说明**: 不提供导出功能,所有数据仅在线查看 --- ## 十一、安全与合规 ### 1. 密钥管理 - 配置文件权限受控(仅服务器可读) - 日志脱敏(密钥、证书内容不输出) - 定期更换密钥机制(预留接口) ### 2. 签名验证 - 所有渠道回调必须验签 - 外部业务回调采用 HMAC-SHA256 - 签名失败记录告警 ### 3. 幂等与防重 - 渠道回调:notify 唯一键 - 发起支付:订单级别控制 - 业务回调:重试机制 + 日志记录 ### 4. 数据安全 - 敏感字段加密存储(如有需要) - 传输层 HTTPS 强制 - 回调 URL 白名单校验(可选) ### 5. 操作审计 - 所有关键操作记录审计日志 - 操作人、时间、参数完整记录 - 支持回溯与追责 --- ## 十二、监控与告警 ### 1. 关键指标 - 订单支付成功率 - 渠道回调验签失败率 - 业务回调成功率 - 对账差异数量 - 订单过期率 ### 2. 告警规则 - 回调验签失败 > 阈值 - 业务回调失败率 > 阈值 - 对账差异数 > 阈值 - 渠道接口异常(超时/错误码) ### 3. 日志 - 统一 traceId 贯穿全链路 - 关键节点日志输出 - 错误堆栈完整记录 --- ## 十三、后续扩展规划 ### 1. 退款功能 - RefundOrder 聚合根 - 部分退款支持 - 退款对账 ### 2. 分账功能 - 支持微信/支付宝分账 - 分账接收方管理 - 分账明细与对账 ### 3. 多商户支持 - ChannelAccount 实体激活 - 按商户维度管理密钥 - 多租户隔离 ### 4. 更多支付方式 - 微信 H5/APP/小程序 - 支付宝 APP/WAP - 银行卡快捷支付 ### 5. 对账优化 - 支持下载渠道对账文件 - 自动解析与比对 - 差异自动修复策略升级 --- ## 十四、开发与交付计划 ### 阶段一:核心支付功能(预计 2 周) - 领域模型与数据库表 - 微信 Native 支付 - 支付宝当面付 - 渠道回调处理 - 二维码生成与 Base64 返回 ### 阶段二:业务回调与审计(预计 1 周) - 外部业务回调逻辑 - HMAC 鉴权实现 - 重试机制 - 操作审计记录 ### 阶段三:对账与管理端(预计 1.5 周) - 每日对账任务 - 自动修复逻辑 - 管理端页面开发 - 权限菜单集成 ### 阶段四:测试与优化(预计 1 周) - 单元测试 - 集成测试(沙箱环境) - 性能测试与优化 - 安全测试 --- ## 十五、技术风险与应对 ### 1. 并发控制 **风险**: 同一订单并发创建多笔交易 **应对**: 订单级别乐观锁 + 数据库唯一约束 ### 2. 回调丢失 **风险**: 渠道回调未成功送达 **应对**: 每日对账补偿 + 主动查询机制 ### 3. 业务回调失败 **风险**: 外部系统不可用导致回调失败 **应对**: 重试机制 + 手工重发 ### 4. 对账差异 **风险**: 本地与渠道状态不一致 **应对**: 自动修复 + 差异告警 + 人工介入 ### 5. 密钥泄露 **风险**: 配置文件泄露导致密钥暴露 **应对**: 文件权限控制 + 日志脱敏 + 定期更换 --- ## 十六、总结 本设计方案基于 DDD 架构,充分考虑了支付业务的复杂性与稳健性需求: 1. **清晰的领域模型**: 聚合根、实体、值对象职责明确 2. **严格的状态控制**: 订单与交易状态机、过期机制 3. **可靠的回调机制**: 幂等、重试、鉴权 4. **自动化对账**: 每日修复、差异告警 5. **完整的审计追溯**: 操作日志、渠道日志、业务日志 6. **可扩展架构**: 预留退款、分账、多商户等扩展点 该方案已对齐: - ✅ Java 1.8 + Spring Boot 2.5.15 + ruoyi-framework - ✅ 微信支付 v2 统一下单(MD5签名) - ✅ 支付宝当面付(RSA2签名) - ✅ 服务端返回 300×300 Base64 二维码 - ✅ 外部业务回调 HMAC 鉴权 - ✅ 同一订单仅一笔进行中交易 - ✅ 订单 2 小时自动过期 - ✅ 每日 2 点自动对账修复 - ✅ 单商户配置文件加载 - ✅ 管理端页面(不导出) **方案已确认,可进入代码实现阶段。**