Bladeren bron

订单支付各种漏洞修复

徐滕 2 weken geleden
bovenliggende
commit
c4ba0ed8aa

File diff suppressed because it is too large
+ 793 - 0
forge-server/forge-business/forge-hotel/酒店订单支付系统开发文档.md


+ 0 - 547
酒店订单支付系统开发文档.md

@@ -1,547 +0,0 @@
-# 酒店订单支付系统开发文档
-
-> 开发时间:2026-08-17  
-> 功能模块:酒店顾客端支付流程(Mock 模式)
-
----
-
-## 一、功能概述
-
-实现了酒店顾客端完整的支付流程,支持**先支付后接单**的业务模式。当前采用 Mock 支付模式,后续可无缝对接微信支付、支付宝小程序支付。
-
-### 核心特性
-
-- ✅ **支付前置**:订单创建后进入"待支付"状态,支付成功后才进入"待接单"
-- ✅ **15 分钟超时**:未支付订单自动取消,定时任务扫描处理
-- ✅ **并发安全**:订单号生成、菜品销量累加均解决并发问题
-- ✅ **支付流水**:完整的支付日志记录,支持对账
-- ✅ **多状态管理**:待支付(0) → 已支付/待接单(1) → 后续流程;超时取消(10)
-- ✅ **H5 适配**:支持微信小程序、支付宝小程序、H5、Mock 四种支付环境
-
----
-
-## 二、数据库变更
-
-### 2.1 Flyway 迁移脚本
-
-**文件**:`forge-server/db/migration/V1.0.105__add_hotel_order_payment_fields.sql`
-
-> MySQL 8.0 兼容:使用 `information_schema` + `PREPARE/EXECUTE` 模式替代 MariaDB 专有的 `IF NOT EXISTS` 扩展语法。
-
-#### hotel_order 表新增字段
-
-```sql
-pay_status       TINYINT        -- 支付状态:0=待支付, 1=已支付, 2=已退款, 3=支付超时已取消
-pay_expire_time  DATETIME       -- 支付截止时间
-pay_time         DATETIME       -- 实际支付时间
-pay_trade_no     VARCHAR(128)   -- 第三方支付交易号
-paid_amount      DECIMAL(10,2)  -- 实付金额
-pay_source       VARCHAR(32)    -- 支付来源:WECHAT/ALIPAY/H5/MOCK
-```
-
-#### 清理遗留字段
-
-- `pay_method`:V1.0.104 创建但实体已移除,迁移脚本自动 DROP COLUMN
-
-#### 新增索引
-
-- `idx_pay_expire`:加速支付超时订单扫描(`pay_status`, `pay_expire_time`)
-- `uk_order_no`:已在 V1.0.104 中创建,不重复
-
-#### hotel_pay_log 表
-
-记录每笔支付的完整流水,包括:
-- 订单关联(order_id, order_no)
-- 支付渠道(pay_channel: WECHAT/ALIPAY/MOCK)
-- 支付来源(pay_source: WECHAT/ALIPAY/H5/WECHAT_MP/ALIPAY_MP/MOCK)
-- 交易流水号(out_trade_no: 商户生成,trade_no: 第三方返回)
-- 支付金额、状态、时间等
-
-#### 字典数据
-
-新增字典类型:
-- `hotel_pay_status`:支付状态(0=待支付, 1=已支付, 2=已退款, 3=支付超时已取消)
-- `hotel_pay_source`:支付来源(WECHAT/ALIPAY/H5/MOCK 等)
-- `hotel_order_status`:补充 0=待支付、10=超时取消
-
----
-
-## 三、后端变更
-
-### 3.1 常量定义
-
-**文件**:`HotelOrderConstants.java`
-
-```java
-// 订单状态
-STATUS_PENDING_PAY = 0      // 待支付(新增)
-STATUS_PLACED = 1           // 待接单(已支付)
-STATUS_PAY_TIMEOUT = 10     // 超时取消(新增)
-
-// 支付状态
-PAY_STATUS_PENDING = 0      // 待支付
-PAY_STATUS_SUCCESS = 1      // 已支付
-PAY_STATUS_TIMEOUT = 2      // 支付超时
-
-// 支付来源
-PAY_SOURCE_WECHAT = "WECHAT"
-PAY_SOURCE_ALIPAY = "ALIPAY"
-PAY_SOURCE_H5 = "H5"
-PAY_SOURCE_MOCK = "MOCK"
-
-// 超时配置
-PAY_TIMEOUT_MINUTES = 15    // 支付超时时间(分钟)
-```
-
-### 3.2 实体类
-
-#### HotelOrder.java
-
-新增字段:
-- `payStatus`(Integer):支付状态
-- `payExpireTime`(LocalDateTime):支付截止时间
-- `payTime`(LocalDateTime):实际支付时间
-- `payTradeNo`(String):支付交易号
-- `paidAmount`(BigDecimal):实付金额
-- `paySource`(String):支付来源
-
-移除字段:
-- `payMethod`(已废弃,改用 paySource)
-
-#### HotelPayLog.java(新建)
-
-支付流水实体,包含完整的支付记录字段。
-
-### 3.3 Mapper 层
-
-#### HotelOrderMapper.java
-
-新增方法:
-```java
-List<Long> selectExpiredUnpaidOrderIds()
-```
-查询所有超时未支付的订单 ID。
-
-#### HotelOrderMapper.xml
-
-- `resultMap` 和 `OrderColumns`:添加支付相关字段映射
-- `selectDashboard`、`countTodayOrders`、`sumTodayRevenue`:改为只统计已支付订单(`pay_status = 1`)
-- `selectOrderPage`:筛选条件从 `payMethod` 改为 `payStatus`、`paySource`
-- 新增 `selectExpiredUnpaidOrderIds`:查询超时订单
-
-#### HotelDishMapper.java
-
-新增方法:
-```java
-int incrementSales(@Param("dishId") Long dishId, @Param("quantity") Integer quantity)
-```
-原子更新菜品销量。
-
-#### HotelDishMapper.xml
-
-新增 SQL:
-```xml
-<update id="incrementSales">
-    UPDATE hotel_dish
-    SET sales = IFNULL(sales, 0) + #{quantity},
-        update_time = NOW()
-    WHERE id = #{dishId} AND del_flag = 0
-</update>
-```
-
-#### HotelPayLogMapper.java(新建)
-
-```java
-HotelPayLog selectByOutTradeNo(@Param("outTradeNo") String outTradeNo)
-```
-根据商户订单号查询支付流水。
-
-### 3.4 Service 层
-
-#### HotelOrderServiceImpl.java
-
-**orderCreate() 方法重构**:
-- 订单创建后状态设为 `STATUS_PENDING_PAY`(待支付)
-- 设置 `payStatus = PAY_STATUS_PENDING`
-- 设置 `payExpireTime = now + 15 分钟`
-- 移除 `payMethod` 字段赋值
-
-**generateOrderNo() 并发修复**:
-- 添加 3 次重试机制
-- 每次重试前检查唯一性(`selectCount`)
-- 防御性唯一性检查:插入前再次验证订单号是否已存在
-
-**incrementDishSales() 并发修复**:
-- 从"先查后写"改为原子 SQL 更新
-- 调用 `dishMapper.incrementSales()` 直接执行 `SET sales = IFNULL(sales, 0) + #{quantity}`
-
-**customerRefund() 新增逻辑**:
-- 待支付订单(status=0):直接取消,状态改为 `STATUS_PAY_TIMEOUT`
-- 已支付订单(status=1):进入退单流程
-
-#### HotelPayService.java(新建)
-
-支付服务接口,定义核心方法:
-```java
-Map<String, Object> createPay(Long orderId, String paySource)
-PayResultVO mockPaySuccess(Long orderId)
-void handlePayCallback(String outTradeNo, String tradeNo, boolean success, String rawContent)
-PayResultVO queryPayStatus(Long orderId)
-void cancelTimeoutOrder(Long orderId)
-```
-
-#### HotelPayServiceImpl.java(新建)
-
-**createPay()**:
-- 校验订单状态(必须为待支付)
-- 检查是否已过期
-- 生成商户订单号(`PAY + 时间戳 + 订单ID后6位`)
-- 创建支付流水(`hotel_pay_log`)
-- 返回支付参数(Mock 模式返回简化参数)
-
-**mockPaySuccess()**:
-- 模拟支付成功流程
-- 创建 Mock 支付流水(如果不存在)
-- 触发支付回调处理
-
-**handlePayCallback()**:
-- 幂等校验:已处理的订单直接返回
-- 支付成功:更新流水状态、更新订单状态(0→1)、记录支付时间和交易号
-- 支付失败:更新流水状态为失败
-
-**cancelTimeoutOrder()**:
-- 更新订单状态为 `STATUS_PAY_TIMEOUT`
-- 更新支付状态为 `PAY_STATUS_TIMEOUT`
-
-**queryPayStatus()**:
-- 返回订单支付状态、金额、时间等信息
-
-### 3.5 Controller 层
-
-#### HotelPayController.java(新建)
-
-路径:`/hotel/open/pay/**`(免登录,已在 SaToken 白名单中)
-
-接口列表:
-```
-POST /create        发起支付(创建支付流水)
-POST /mockSuccess   模拟支付成功(开发阶段)
-GET  /status        查询支付状态(前端轮询)
-POST /notify        支付回调通知(微信/支付宝异步通知入口,Mock 占位)
-```
-
-所有接口都需要传入 `tenantId` 参数,通过 `TenantContextHolder.executeWithTenant()` 在指定租户上下文中执行。
-
-### 3.6 定时任务
-
-#### PayTimeoutTask.java(新建)
-
-```java
-@Scheduled(fixedDelay = 120000, initialDelay = 30000)
-public void cancelExpiredOrders()
-```
-
-- 每 2 分钟执行一次(启动后 30 秒开始)
-- 使用 `TenantContextHolder.executeIgnore()` 跨租户扫描
-- 查询所有超时未支付的订单 ID
-- 逐条调用 `payService.cancelTimeoutOrder()` 取消
-
----
-
-## 四、前端变更(H5)
-
-### 4.1 API 层
-
-**文件**:`forge-h5-ui/src/api/index.js`
-
-新增支付相关 API:
-```javascript
-hotelPayCreate: (data) => request.post('/hotel/open/pay/create', data)
-hotelPayMockSuccess: (data) => request.post('/hotel/open/pay/mockSuccess', data)
-hotelPayStatus: (params) => request.get('/hotel/open/pay/status', { params })
-```
-
-### 4.2 支付页面
-
-**文件**:`forge-h5-ui/src/pages/hotel/customer/pay.vue`
-
-**核心功能**:
-- **15 分钟倒计时**:显示剩余时间(分:秒),超时自动跳转
-- **环境自动识别**:
-  - 微信小程序:`wx.miniProgram.getPhoneNumber()`
-  - 支付宝小程序:`my.tradePay()`
-  - H5:跳转第三方支付页面
-  - Mock:直接调用模拟支付接口
-- **支付流程**:
-  1. 调用 `hotelPayCreate` 创建支付流水
-  2. 根据环境调用对应支付方法(Mock 直接调用 `hotelPayMockSuccess`)
-  3. 支付成功后跳转到订单状态页
-
-**UI 元素**:
-- 订单信息展示(订单号、金额、房间号)
-- 倒计时显示(红色高亮)
-- 支付方式选择(当前仅 Mock)
-- 支付按钮(根据环境显示不同文案)
-
-### 4.3 订单状态页
-
-**文件**:`forge-h5-ui/src/pages/hotel/customer/order-status.vue`
-
-新增状态处理:
-- **状态 0(待支付)**:
-  - 图标:💳
-  - 文案:"等待支付"
-  - 提示:"请在 15 分钟内完成支付"
-  - 按钮:"去支付"(跳转到支付页)
-- **状态 10(超时取消)**:
-  - 图标:⏰
-  - 文案:"支付超时,订单已取消"
-  - 提示:"您可以重新下单"
-  - `isFinished()` 返回 true(订单结束)
-
-### 4.4 订单列表页
-
-**文件**:`forge-h5-ui/src/pages/hotel/customer/orders.vue`
-
-**状态标签更新**:
-```javascript
-statusLabel(status) {
-  const map = {
-    0: '待支付', 1: '待接单', 2: '已接单', 3: '备餐中',
-    4: '已出餐', 5: '配送中', 6: '已完成',
-    7: '已拒单', 8: '退单审核中', 9: '已退单',
-    10: '超时取消',
-  }
-  return map[status] || '未知'
-}
-```
-
-**状态颜色**:
-- `.o-st--0`:黄色背景(待支付)
-- `.o-st--10`:灰色背景(超时取消)
-
-**待支付订单特殊处理**:
-- 显示"去支付"按钮
-- 点击卡片跳转到支付页(而非订单状态页)
-
-### 4.5 订单确认页
-
-**文件**:`forge-h5-ui/src/pages/hotel/customer/order-confirm.vue`
-
-更新跳转逻辑:
-- 下单成功后跳转到支付页(而非订单状态页)
-- 传递参数:`orderId`、`tenantId`、`amount`
-
----
-
-## 五、并发问题修复
-
-### 5.1 订单号并发
-
-**问题**:多个订单同时生成可能产生重复订单号
-
-**解决方案**:
-1. 数据库层面:`order_no` 添加唯一索引
-2. 代码层面:`generateOrderNo()` 添加 3 次重试
-3. 防御性检查:插入前 `selectCount` 验证唯一性
-
-### 5.2 菜品销量并发
-
-**问题**:先查后写导致并发时销量丢失
-
-**解决方案**:
-- 使用原子 SQL 更新:`SET sales = IFNULL(sales, 0) + #{quantity}`
-- 避免"读取-计算-写入"的竞态条件
-
----
-
-## 六、支付流程时序图
-
-```
-顾客                H5前端              后端API              数据库
- |                  |                   |                    |
- |--下单----------->|                   |                    |
- |                  |--创建订单-------->|                    |
- |                  |                   |--插入订单(status=0)|
- |                  |                   |<-------------------|
- |                  |<--返回orderId-----|                    |
- |                  |                   |                    |
- |--跳转支付页----->|                   |                    |
- |                  |--创建支付-------->|                    |
- |                  |                   |--插入pay_log       |
- |                  |                   |<-------------------|
- |                  |<--返回支付参数----|                    |
- |                  |                   |                    |
- |--点击支付------->|                   |                    |
- |                  |--模拟支付成功---->|                    |
- |                  |                   |--更新pay_log       |
- |                  |                   |--更新订单(status=1)|
- |                  |                   |<-------------------|
- |                  |<--返回支付结果----|                    |
- |                  |                   |                    |
- |--跳转订单状态--->|                   |                    |
- |                  |--轮询支付状态---->|                    |
- |                  |                   |--查询订单          |
- |                  |                   |<-------------------|
- |                  |<--返回已支付------|                    |
- |                  |                   |                    |
-```
-
----
-
-## 七、后续对接真实支付
-
-### 7.1 微信支付(小程序)
-
-1. 在 `HotelPayServiceImpl.createPay()` 中调用微信统一下单接口
-2. 返回微信支付所需参数(prepay_id、sign 等)
-3. 前端 `pay.vue` 中调用 `wx.requestPayment()`
-4. 微信异步通知 `/hotel/open/pay/notify`
-5. 验签、解密、更新订单状态
-
-### 7.2 支付宝支付(小程序)
-
-1. 在 `HotelPayServiceImpl.createPay()` 中调用支付宝订单创建接口
-2. 返回支付宝所需参数(trade_no 等)
-3. 前端 `pay.vue` 中调用 `my.tradePay()`
-4. 支付宝异步通知 `/hotel/open/pay/notify`
-5. 验签、更新订单状态
-
-### 7.3 配置项
-
-需要在 `application.yml` 中添加:
-```yaml
-forge:
-  pay:
-    wechat:
-      appId: xxx
-      mchId: xxx
-      apiKey: xxx
-    alipay:
-      appId: xxx
-      privateKey: xxx
-      alipayPublicKey: xxx
-```
-
----
-
-## 八、文件清单
-
-### 后端新增文件
-
-1. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/pay/domain/HotelPayLog.java`
-2. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/pay/mapper/HotelPayLogMapper.java`
-3. `forge-server/forge-business/forge-hotel/src/main/resources/mapper/business/hotel/pay/HotelPayLogMapper.xml`
-4. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/pay/service/HotelPayService.java`
-5. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/pay/service/impl/HotelPayServiceImpl.java`
-6. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/pay/vo/PayResultVO.java`
-7. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/controller/open/HotelPayController.java`
-8. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/pay/task/PayTimeoutTask.java`
-
-### 后端修改文件
-
-1. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/constant/HotelOrderConstants.java`
-2. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/domain/HotelOrder.java`
-3. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/mapper/HotelOrderMapper.java`
-4. `forge-server/forge-business/forge-hotel/src/main/resources/mapper/business/hotel/order/HotelOrderMapper.xml`
-5. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/mapper/HotelDishMapper.java`
-6. `forge-server/forge-business/forge-hotel/src/main/resources/mapper/business/hotel/HotelDishMapper.xml`
-7. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/service/impl/HotelOrderServiceImpl.java`
-8. `forge-server/forge-business/forge-hotel/src/main/java/com/mdframe/forge/business/core/hotel/order/vo/HotelOrderVO.java`
-
-### 数据库脚本
-
-1. `forge-server/db/migration/V1.0.105__add_hotel_order_payment_fields.sql`
-
-### 前端新增文件
-
-无
-
-### 前端修改文件
-
-1. `forge-h5-ui/src/api/index.js`
-2. `forge-h5-ui/src/pages/hotel/customer/pay.vue`
-3. `forge-h5-ui/src/pages/hotel/customer/order-status.vue`
-4. `forge-h5-ui/src/pages/hotel/customer/orders.vue`
-5. `forge-h5-ui/src/pages/hotel/customer/order-confirm.vue`
-
----
-
-## 九、测试建议
-
-### 9.1 功能测试
-
-1. **正常支付流程**:
-   - 下单 → 跳转支付页 → 点击支付 → 支付成功 → 订单状态变为"待接单"
-   
-2. **超时取消**:
-   - 下单 → 等待 15 分钟 → 订单自动取消 → 状态变为"超时取消"
-   
-3. **主动取消**:
-   - 下单 → 在支付页点击"取消订单" → 订单状态变为"超时取消"
-   
-4. **重复支付防护**:
-   - 已支付订单再次调用支付接口 → 返回"订单已支付"错误
-
-### 9.2 并发测试
-
-1. **订单号唯一性**:
-   - 同时创建 100 个订单 → 验证订单号无重复
-   
-2. **菜品销量准确性**:
-   - 同时完成 10 个包含同一菜品的订单 → 验证销量累加正确
-
-### 9.3 边界测试
-
-1. **支付超时边界**:
-   - 在第 14 分 59 秒支付 → 应该成功
-   - 在第 15 分 01 秒支付 → 应该返回"订单已超时"
-
-2. **库存不足**:
-   - 下单时菜品库存不足 → 应该返回错误
-
----
-
-## 十、已知问题与优化建议
-
-### 10.1 已知问题
-
-1. **Mock 模式安全性**:
-   - 当前 Mock 支付接口无需鉴权,生产环境需要移除或限制访问
-   
-2. **定时任务精度**:
-   - 数据库轮询方案最大延迟 2 分钟,对业务影响可接受
-
-### 10.2 优化建议
-
-1. **支付回调重试**:
-   - 添加重试机制,防止网络问题导致回调失败
-   
-2. **支付对账**:
-   - 添加每日对账任务,比对本地流水与第三方支付平台数据
-   
-3. **支付统计**:
-   - 添加支付成功率、支付金额等统计报表
-   
-4. **退款流程**:
-   - 完善已支付订单的退款流程(当前只支持未支付取消)
-
----
-
-## 十一、总结
-
-本次开发完成了酒店订单支付系统的核心功能,包括:
-
-- ✅ 数据库结构设计(支付字段、流水表、索引)
-- ✅ 后端支付服务(Mock 模式、回调处理、超时取消)
-- ✅ 前端支付页面(倒计时、环境识别、支付调用)
-- ✅ 并发问题修复(订单号、菜品销量)
-- ✅ 订单状态管理(待支付、已支付、超时取消)
-
-当前系统已支持完整的 Mock 支付流程,后续可无缝对接微信支付、支付宝小程序支付。所有代码已通过编译验证,无语法错误。
-
----
-
-**文档版本**:v1.0  
-**最后更新**:2026-08-17  
-**维护人员**:AI Assistant