Переглянути джерело

docs: 更新退款功能文档,反映审核流程、多次退款、部分退款状态

- 支付状态新增 PARTIAL_REFUND 说明
- 重写退款流程:用户端提交→审核 vs 管理后台直接退款
- 补充多次退款、拒绝后重新申请、逐商品校验等逻辑
- AGENTS.md 扩展 7.6 退款管理规则,修复重复编号

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 тижнів тому
батько
коміт
8bf15899d3
2 змінених файлів з 66 додано та 29 видалено
  1. 8 2
      AGENTS.md
  2. 58 27
      docs/订单与支付模块.md

+ 8 - 2
AGENTS.md

@@ -643,12 +643,18 @@ public class XxxConfig {
 - 退款数据使用独立的 `t_refund`(退款记录)和 `t_refund_item`(退款商品明细)表管理
 - `t_order` 上的 `refund_status`、`refund_amount` 等字段保留作为汇总冗余,但数据源头在 `t_refund`
 - 退款金额支持三种模式(优先级):选择商品计算 > 自定义金额 > 全额
-- 退款金额校验:累计退款金额 ≤ `paidAmount`(实付金额),防止"多退"
+- 退款金额校验:累计退款金额 ≤ `paidAmount`(实付金额);逐商品校验退款数量 ≤ 剩余可退数量
 - `t_order.current_refund_id` 关联当前退款记录,便于快速查询
+- 支付状态新增 `PARTIAL_REFUND`(部分退款),与 `REFUND`(全额退款)区分
 - 退款服务:`RefundService` / `RefundServiceImpl`,实体:`Refund` / `RefundItem`
+- **用户端退款**:提交后创建 PENDING 记录,需管理员审核。审核通过后执行第三方退款,拒绝后用户可重新提交
+- **管理后台直接退款**:传入 `immediate=true`,即时执行第三方退款
+- **多次退款**:支持同一订单多次部分退款,累计不超过实付金额。已退商品禁用勾选
+- **退款追溯**:`t_refund_item` 记录每笔退款的商品明细;小程序订单详情展示退款时间线
+- **支付分退款**:每次退款生成唯一 `out_refund_no`(含时间戳),避免重试参数不一致
 - 管理后台退款审核入口:`RefundApplicationController`,基于 `t_refund` 查询
 
-### 7.6 微信支付分集成
+### 7.7 微信支付分集成
 
 - 扫码开门流程强制检查支付分开通状态
 - 订单创建后初始化支付分服务订单

+ 58 - 27
docs/订单与支付模块.md

@@ -20,7 +20,7 @@
 | totalAmount | BigDecimal | 订单总金额(原价),设备回调时写入 |
 | discountAmount | BigDecimal | 订单优惠金额(营销活动 + 优惠券),默认0 |
 | paidAmount | BigDecimal | 实付总金额 = totalAmount - discountAmount |
-| payStatus | String | 支付状态:UNPAID / PAID / REFUND |
+| payStatus | String | 支付状态:UNPAID / PAID / PARTIAL_REFUND / REFUND |
 | payType | String | 支付方式 |
 | payChannel | String | 支付渠道:wechat / alipay / alipay_credit / balance / wechat_payscore |
 | transactionId | String | 第三方交易号 |
@@ -55,8 +55,9 @@
 | code | 枚举值 | 说明 |
 |------|--------|------|
 | UNPAID | 未支付 | 订单已创建,等待支付 |
-| PAID | 已支付 | 扣费成功 |
-| REFUND | 已退款 | 已完成退款 |
+| PAID | 已支付 | 扣费成功,未发生退款 |
+| PARTIAL_REFUND | 部分退款 | 累计退款 > 0 且 < 实付金额,可继续退款 |
+| REFUND | 全额退款 | 累计退款 ≥ 实付金额,退款入口关闭 |
 
 ---
 
@@ -328,41 +329,71 @@ PayScoreSyncTask 每 5 分钟同步
 | 2 | 自定义金额退款 | 传入 `refundAmount` |
 | 3 | 全额退款 | 两者都不传,金额 = paidAmount |
 
-**金额校验**:退款金额 ≤ `paidAmount - 该订单历史累计已退款金额`,确保多次部分退款总额不超过实付金额。
+**金额校验**:
+- 退款金额 ≤ `paidAmount - 该订单历史累计已退款金额`
+- 逐商品校验:退款数量 ≤ `购买数量 - 该商品已退款数量`
+- 确保多次部分退款总额不超过实付金额,已全额退款的商品不可重复选择
 
-#### 退款入口
+#### 退款流程
+
+用户端和管理后台走不同的退款路径:
+
+**用户端(小程序)**:提交申请 → 等待审核
+
+```
+用户提交退款(refund.vue)
+  → POST /payment/refund
+  → PaymentServiceImpl.refund(Map)  // immediate=false(默认)
+  → RefundService.createRefundRecord()
+      ├── 创建 t_refund(status=PENDING)
+      ├── 创建 t_refund_item(商品明细)
+      ├── 更新 t_order.refundStatus = PENDING
+      └── 返回”退款申请已提交,请等待审核”
+  → 管理员在运营平台审核
+      ├── 通过 → approveRefund()
+      │         → 执行第三方退款 API
+      │         → t_refund.status = REFUNDED
+      │         → t_order.refundAmount 累加
+      │         → 累计 ≥ 实付 → payStatus = REFUND
+      │         → 累计 < 实付 → payStatus = PARTIAL_REFUND
+      └── 拒绝 → rejectRefund()
+                → t_refund.status = REJECTED
+                → 清除 t_order.refundStatus / currentRefundId
+                → 保留 t_order.refundAmount(累计已退金额)
+                → 用户可重新提交申请
+```
+
+**管理后台直接退款**:即时执行
+
+```
+管理员在订单列表点击退款(hook.tsx handleRefund)
+  → POST /order/{id}/refund
+  → PaymentServiceImpl.refund(Map)  // immediate=true
+  → RefundService.createRefundRecord()
+  → 立即执行第三方退款 API
+  → t_refund.status = REFUNDED
+```
+
+**多次退款**:
 
 ```
-┌─────────────────────────────────────────────────────┐
-│                  退款发起入口                          │
-├──────────────┬──────────────────┬───────────────────┤
-│ 小程序端      │ 管理后台直接退款   │ 管理后台审核退款     │
-│ 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() + 更新订单汇总字段     │    │
-│  │  └── 否 → 全额退款(兼容旧行为)                 │    │
-│  └─────────────────────────────────────────────┘    │
-└─────────────────────────────────────────────────────┘
+订单实付 100 元,购 3 件商品:
+  第一次退 2 件,金额 60 元 → payStatus = PARTIAL_REFUND,refundAmount = 60
+  第二次退 1 件,金额 40 元 → 累计 100 ≥ 100 → payStatus = REFUND
+  退款入口关闭,已退商品不可再选
 ```
 
 #### 退款追溯
 
-每笔退款在 `t_refund_item` 中记录了退款商品明细(`order_goods_id`、`product_id`、`product_name`、`quantity`、`price`),可通过 `refund_id` 关联查询。管理后台退款详情页展示的是实际退款商品,而非订单全部商品。
+- 每笔退款在 `t_refund_item` 中记录了退款商品明细(`order_goods_id`、`product_id`、`product_name`、`quantity`、`price`),可通过 `refund_id` 关联查询
+- 管理后台退款详情页展示的是实际退款商品,而非订单全部商品
+- 小程序订单详情页展示退款记录时间线(绿色=已退款,红色=已拒绝,橙色=审核中),含每次申请的金额、原因、商品明细和拒绝原因
 
 #### 退款特殊处理
 
-- **支付分退款**:支付分订单完结后走微信退款接口,需传入原始支付分订单号
+- **支付分退款**:支付分订单完结后走微信退款接口,需传入原始支付分订单号。每次退款生成唯一 `out_refund_no`(含时间戳),避免重试时参数不一致导致微信 `INVALID_REQUEST`
 - **优惠券不可退回**:订单退款时,已使用的优惠券状态不会回滚为”未使用”,这是当前的业务规则
-- **部分退款**:支持选择商品退款(金额由商品自动计算)或自定义金额退款,多次退款累计不超过实付金额
+- **部分退款**:支持选择商品退款(金额由商品自动计算)或自定义金额退款,多次退款累计不超过实付金额。已全额退款的商品前端禁选、后端校验
 - **异物放入订单**(orderType=IN)不展示给用户端,退款需在运营平台操作
 
 ---