Ver código fonte

docs: 更新退款功能文档,反映独立退款表与部分退款逻辑

- 订单与支付模块文档新增 t_refund / t_refund_item 表说明
- 重写退款流程章节,描述三种退款金额模式及追溯机制
- 更新 Order 实体字段表、核心代码文件索引
- 修正退款金额基数为 paidAmount
- AGENTS.md 新增 7.6 退款管理业务约束

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 semanas atrás
pai
commit
305fa09dbb
2 arquivos alterados com 119 adições e 16 exclusões
  1. 10 0
      AGENTS.md
  2. 109 16
      docs/订单与支付模块.md

+ 10 - 0
AGENTS.md

@@ -638,6 +638,16 @@ public class XxxConfig {
 - 前后端均需校验 7 天时效限制
 - 超期订单前端应给出明确提示
 
+### 7.6 退款管理
+
+- 退款数据使用独立的 `t_refund`(退款记录)和 `t_refund_item`(退款商品明细)表管理
+- `t_order` 上的 `refund_status`、`refund_amount` 等字段保留作为汇总冗余,但数据源头在 `t_refund`
+- 退款金额支持三种模式(优先级):选择商品计算 > 自定义金额 > 全额
+- 退款金额校验:累计退款金额 ≤ `paidAmount`(实付金额),防止"多退"
+- `t_order.current_refund_id` 关联当前退款记录,便于快速查询
+- 退款服务:`RefundService` / `RefundServiceImpl`,实体:`Refund` / `RefundItem`
+- 管理后台退款审核入口:`RefundApplicationController`,基于 `t_refund` 查询
+
 ### 7.6 微信支付分集成
 
 - 扫码开门流程强制检查支付分开通状态

+ 109 - 16
docs/订单与支付模块.md

@@ -24,8 +24,9 @@
 | payType | String | 支付方式 |
 | payChannel | String | 支付渠道:wechat / alipay / alipay_credit / balance / wechat_payscore |
 | transactionId | String | 第三方交易号 |
-| refundStatus | String | 退款状态 |
-| refundAmount | BigDecimal | 退款金额 |
+| currentRefundId | Long | 当前退款记录ID(关联 t_refund.id,冗余便于快速查询) |
+| refundStatus | String | 退款状态(PENDING/REFUNDED/REJECTED,汇总字段) |
+| refundAmount | BigDecimal | 退款金额(汇总字段) |
 | refundTime | LocalDateTime | 退款时间 |
 | refundReason | String | 退款原因 |
 | payScoreOrderId | String | 支付分服务订单号(微信侧) |
@@ -166,11 +167,14 @@ const displayAmount = order.paidAmount || order.totalAmount || 0;
 const amount = row.paidAmount || (row.totalAmount || 0) - (row.discountAmount || 0);
 ```
 
-**退款页面**使用 `totalAmount`(基于订单原价计算退款),不使用 `paidAmount`
+**退款页面**使用 `paidAmount` 作为退款金额上限,退款金额不能超过用户实际支付的金额
 
-#### 为什么退款基于 totalAmount 而非 paidAmount?
+#### 为什么退款基于 paidAmount?
 
-退款金额应基于用户实际支付的金额来计算。但当前场景下,`paidAmount` 是服务端计算的优惠券扣减后金额,而设备端可能还有营销活动优惠未体现在 `discountAmount` 中。为避免退款金额计算混乱,退款暂时基于 `totalAmount`(设备端传来的已含营销优惠的金额),后续如需精确退款需结合 `paidAmount` 使用。
+退款金额基于用户实际支付的金额(`paidAmount`)来计算和校验:
+- 全额退款时退 `paidAmount`(实付金额)
+- 部分退款时,累计退款金额不能超过 `paidAmount`
+- 这确保了用户不会退回超过自己实际支付的金额,避免因优惠券/营销活动优惠导致的"多退"问题
 
 ---
 
@@ -268,20 +272,97 @@ PayScoreSyncTask 每 5 分钟同步
 
 ### 4.4 退款流程
 
+#### 退款数据模型
+
+退款功能使用独立的 `t_refund` 和 `t_refund_item` 表管理,不再仅依赖 `t_order` 上的汇总字段。
+
+**t_refund(退款记录表)**:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT | 退款记录ID(雪花算法) |
+| order_id | BIGINT | 订单ID |
+| order_no | VARCHAR(64) | 订单号 |
+| refund_no | VARCHAR(64) | 退款单号(RF + orderNo + timestamp) |
+| refund_amount | DECIMAL(10,2) | 本次退款金额 |
+| total_amount | DECIMAL(10,2) | 订单原始总金额 |
+| status | VARCHAR(20) | 退款状态:PENDING/REFUNDED/REJECTED |
+| reason | VARCHAR(500) | 退款原因 |
+| remark | VARCHAR(500) | 审核备注 |
+| transaction_id | VARCHAR(100) | 第三方退款交易号 |
+| refund_time | DATETIME | 退款完成时间 |
+| reviewer_id | BIGINT | 审核人ID |
+| review_time | DATETIME | 审核时间 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+**t_refund_item(退款商品明细表)**:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | BIGINT | 退款商品明细ID |
+| refund_id | BIGINT | 退款记录ID(关联 t_refund.id) |
+| order_goods_id | BIGINT | 订单商品明细ID(关联 t_order_goods.id) |
+| product_id | BIGINT | 商品ID |
+| product_name | VARCHAR(255) | 商品名称 |
+| pic | VARCHAR(500) | 商品图片 |
+| quantity | INT | 退款数量 |
+| price | DECIMAL(10,2) | 商品单价 |
+| refund_amount | DECIMAL(10,2) | 退款金额(quantity * price) |
+
+**t_order 汇总字段**(旧字段保留,冗余便于订单列表直接展示):
+
+| 字段 | 说明 |
+|------|------|
+| currentRefundId | 当前退款记录ID |
+| refundStatus | 退款状态汇总 |
+| refundAmount | 退款金额汇总 |
+| refundTime | 退款时间 |
+| refundReason | 退款原因 |
+
+#### 退款金额三种模式
+
+| 优先级 | 模式 | 触发条件 |
+|--------|------|---------|
+| 1(最高) | 按商品退款 | 传入 `products` 列表,金额 = Σ(quantity × price) |
+| 2 | 自定义金额退款 | 传入 `refundAmount` |
+| 3 | 全额退款 | 两者都不传,金额 = paidAmount |
+
+**金额校验**:退款金额 ≤ `paidAmount - 该订单历史累计已退款金额`,确保多次部分退款总额不超过实付金额。
+
+#### 退款入口
+
 ```
-运营平台发起退款
-    ↓
-OrderServiceImpl.refund()
-    - 验证订单状态和支付状态
-    - 调用 PaymentService.refund()
-    - 更新 refundStatus / refundAmount / refundTime
+┌─────────────────────────────────────────────────────┐
+│                  退款发起入口                          │
+├──────────────┬──────────────────┬───────────────────┤
+│ 小程序端      │ 管理后台直接退款   │ 管理后台审核退款     │
+│ refund.vue   │ 订单列表→退款按钮  │ 退款申请管理页面     │
+│ POST         │ POST             │ POST              │
+│ /payment     │ /order/{id}      │ /refund/{id}      │
+│ /refund      │ /refund           │ /review            │
+├──────────────┴──────────────────┴───────────────────┤
+│              PaymentServiceImpl.refund()              │
+│  ┌─────────────────────────────────────────────┐    │
+│  │ 有 products/refundAmount?                    │    │
+│  │  ├── 是 → RefundService.createRefundRecord() │    │
+│  │  │      创建 t_refund + t_refund_item         │    │
+│  │  │      → 执行第三方退款(金额=计算后的退款金额)  │    │
+│  │  │      → markRefunded() + 更新订单汇总字段     │    │
+│  │  └── 否 → 全额退款(兼容旧行为)                 │    │
+│  └─────────────────────────────────────────────┘    │
+└─────────────────────────────────────────────────────┘
 ```
 
+#### 退款追溯
+
+每笔退款在 `t_refund_item` 中记录了退款商品明细(`order_goods_id`、`product_id`、`product_name`、`quantity`、`price`),可通过 `refund_id` 关联查询。管理后台退款详情页展示的是实际退款商品,而非订单全部商品。
+
 #### 退款特殊处理
 
 - **支付分退款**:支付分订单完结后走微信退款接口,需传入原始支付分订单号
-- **优惠券不可退回**:订单退款时,已使用的优惠券状态不会回滚为“未使用”,这是当前的业务规则,如需退回需额外开发
-- **部分退款**:运营人员可输入退款金额,部分退款是允许的(如仅退某个商品)
+- **优惠券不可退回**:订单退款时,已使用的优惠券状态不会回滚为”未使用”,这是当前的业务规则
+- **部分退款**:支持选择商品退款(金额由商品自动计算)或自定义金额退款,多次退款累计不超过实付金额
 - **异物放入订单**(orderType=IN)不展示给用户端,退款需在运营平台操作
 
 ---
@@ -292,20 +373,32 @@ OrderServiceImpl.refund()
 |------|------|
 | `haha-entity/.../Order.java` | 订单实体 |
 | `haha-entity/.../OrderGoods.java` | 订单商品明细 |
+| `haha-entity/.../Refund.java` | 退款记录实体(t_refund) |
+| `haha-entity/.../RefundItem.java` | 退款商品明细实体(t_refund_item) |
+| `haha-entity/.../dto/RefundDTO.java` | 退款请求 DTO(含 products、refundAmount) |
+| `haha-mapper/.../RefundMapper.java` | 退款 Mapper |
+| `haha-mapper/.../RefundItemMapper.java` | 退款商品明细 Mapper |
+| `haha-service/.../RefundService.java` | 退款服务接口 |
+| `haha-service/.../impl/RefundServiceImpl.java` | 退款服务实现(创建、审核、查询退款) |
 | `haha-service/.../OrderServiceImpl.java` | 订单服务(创建、查询、退款) |
 | `haha-service/.../HahaCallbackServiceImpl.java` | 设备回调处理(订单回调、优惠券扣减) |
 | `haha-service/.../payment/PaymentService.java` | 支付门面接口 |
-| `haha-service/.../payment/impl/PaymentServiceImpl.java` | 支付门面实现 |
+| `haha-service/.../payment/impl/PaymentServiceImpl.java` | 支付门面实现(含退款编排逻辑) |
 | `haha-service/.../payment/payscore/PayScoreServiceImpl.java` | 支付分服务实现(含扣款失败 USER_PAYING 处理) |
 | `haha-service/.../payment/payscore/impl/WxPayScoreStrategy.java` | 微信支付分 API 策略实现 |
 | `haha-admin/.../task/PayScoreSyncTask.java` | 支付分 USER_PAYING 状态定时同步任务 |
 | `haha-admin/.../OrderController.java` | 运营平台订单API |
+| `haha-admin/.../RefundApplicationController.java` | 运营平台退款申请管理API |
 | `haha-miniapp/.../OrderController.java` | 小程序订单API |
-| `haha-miniapp/.../PaymentController.java` | 小程序支付API |
+| `haha-miniapp/.../PaymentController.java` | 小程序支付API(含退款接口) |
 | `haha-miniapp/.../PayScoreController.java` | 小程序支付分API |
 | `haha-common/.../constant/OrderConstants.java` | 订单常量定义 |
+| `haha-common/.../constant/PaymentConstants.java` | 支付常量(退款前缀、退款期限等) |
 | `haha-common/.../enums/OrderStatus.java` | 订单状态枚举 |
 | `haha-common/.../enums/PayStatus.java` | 支付状态枚举 |
 | `haha-common/.../enums/PaymentChannel.java` | 支付渠道枚举 |
+| `haha-common/.../enums/RefundStatus.java` | 退款状态枚举 |
 | `haha-common/.../enums/PayScoreState.java` | 支付分状态枚举 |
-| `docs/database/add_payscore_fail_reason.sql` | 支付分扣款失败原因字段迁移 SQL |
+| `haha-common/.../vo/RefundApplicationVO.java` | 退款申请视图对象 |
+| `haha-common/.../vo/RefundProductVO.java` | 退款商品视图对象 |
+| `haha-admin/.../db/migration/V5__create_refund_tables.sql` | 退款表 DDL 迁移脚本 |