|
|
@@ -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 迁移脚本 |
|