|
@@ -1,9 +1,9 @@
|
|
|
# Epic 011 - Mini小程序支付退款完整流程 - Brownfield Enhancement
|
|
# Epic 011 - Mini小程序支付退款完整流程 - Brownfield Enhancement
|
|
|
|
|
|
|
|
## Epic Status
|
|
## Epic Status
|
|
|
-**进度:** 50% (2/4 故事完成)
|
|
|
|
|
-**当前状态:** Story 1 和 Story 2 已完成,支付回调和订单取消功能已实现并通过测试
|
|
|
|
|
-**下一步:** 开始 Story 3 - 集成微信支付退款功能
|
|
|
|
|
|
|
+**进度:** 75% (3/4 故事完成)
|
|
|
|
|
+**当前状态:** Story 1、Story 2 和 Story 3 已完成,支付回调、订单取消和退款功能已实现并通过测试
|
|
|
|
|
+**下一步:** 开始 Story 4 - Mini小程序前端支付页面和流程集成
|
|
|
|
|
|
|
|
## Epic Goal
|
|
## Epic Goal
|
|
|
|
|
|
|
@@ -103,19 +103,24 @@
|
|
|
- ✅ 在OrderMtService中添加cancelOrder方法
|
|
- ✅ 在OrderMtService中添加cancelOrder方法
|
|
|
- **完成状态:** 所有12个集成测试通过,包括多租户数据隔离和用户权限验证
|
|
- **完成状态:** 所有12个集成测试通过,包括多租户数据隔离和用户权限验证
|
|
|
|
|
|
|
|
-3. **Story 3:** 集成微信支付退款功能 - 调用微信支付SDK实现退款
|
|
|
|
|
|
|
+3. ✅ **Story 3:** 集成微信支付退款功能 - 调用微信支付SDK实现退款
|
|
|
- **修改位置:**
|
|
- **修改位置:**
|
|
|
- `packages/mini-payment-mt/src/services/payment.mt.service.ts` - 添加refund方法
|
|
- `packages/mini-payment-mt/src/services/payment.mt.service.ts` - 添加refund方法
|
|
|
- - `packages/mini-payment-mt/src/routes/payment/create.mt.ts` - 添加退款API
|
|
|
|
|
- - `packages/mini-payment-mt/src/entities/payment.mt.entity.ts` - 可能扩展退款相关字段
|
|
|
|
|
|
|
+ - `packages/mini-payment-mt/src/entities/payment.mt.entity.ts` - 扩展退款相关字段
|
|
|
|
|
+ - `packages/mini-payment-mt/src/entities/payment.types.ts` - 扩展退款相关类型定义
|
|
|
|
|
+ - `packages/orders-module-mt/src/services/order.mt.service.ts` - 集成退款功能到订单取消流程
|
|
|
|
|
+ - `packages/mini-payment-mt/tests/integration/payment-refund.integration.test.ts` - 创建退款集成测试
|
|
|
- **验收标准:**
|
|
- **验收标准:**
|
|
|
- - 在PaymentMtService中添加退款功能
|
|
|
|
|
- - 集成微信支付退款SDK
|
|
|
|
|
- - 实现退款请求构建和签名
|
|
|
|
|
- - 处理退款回调通知
|
|
|
|
|
- - 退款状态正确更新到订单和退款记录
|
|
|
|
|
- - 退款金额、退款流水号等字段正确记录
|
|
|
|
|
- - 支持部分退款和全额退款
|
|
|
|
|
|
|
+ - ✅ 在PaymentMtService中添加退款功能
|
|
|
|
|
+ - ✅ 集成微信支付退款SDK
|
|
|
|
|
+ - ✅ 实现退款请求构建和签名
|
|
|
|
|
+ - ✅ 处理退款回调通知
|
|
|
|
|
+ - ✅ 退款状态正确更新到订单和退款记录
|
|
|
|
|
+ - ✅ 退款金额、退款流水号等字段正确记录
|
|
|
|
|
+ - ✅ 支持部分退款和全额退款
|
|
|
|
|
+ - ✅ 多租户退款数据隔离验证
|
|
|
|
|
+ - ✅ 完整的退款集成测试覆盖
|
|
|
|
|
+ - **完成状态:** 所有6个集成测试通过,包括多租户数据隔离验证
|
|
|
|
|
|
|
|
4. **Story 4:** Mini小程序前端支付页面和流程集成 - 创建支付页面并集成支付流程
|
|
4. **Story 4:** Mini小程序前端支付页面和流程集成 - 创建支付页面并集成支付流程
|
|
|
- **修改位置:**
|
|
- **修改位置:**
|
|
@@ -153,9 +158,10 @@
|
|
|
|
|
|
|
|
- [x] Story 1完成且验收标准满足
|
|
- [x] Story 1完成且验收标准满足
|
|
|
- [x] Story 2完成且验收标准满足
|
|
- [x] Story 2完成且验收标准满足
|
|
|
|
|
+- [x] Story 3完成且验收标准满足
|
|
|
- [x] 支付回调功能通过测试验证
|
|
- [x] 支付回调功能通过测试验证
|
|
|
- [x] 订单取消功能通过测试验证
|
|
- [x] 订单取消功能通过测试验证
|
|
|
-- [ ] 退款功能通过测试验证
|
|
|
|
|
|
|
+- [x] 退款功能通过测试验证
|
|
|
- [x] 订单状态流转正确
|
|
- [x] 订单状态流转正确
|
|
|
- [x] 多租户隔离正常工作
|
|
- [x] 多租户隔离正常工作
|
|
|
- [x] 现有功能无回归
|
|
- [x] 现有功能无回归
|
|
@@ -239,29 +245,93 @@ class OrderMtService {
|
|
|
|
|
|
|
|
### 退款功能集成
|
|
### 退款功能集成
|
|
|
```typescript
|
|
```typescript
|
|
|
-// 在PaymentMtService中添加退款功能
|
|
|
|
|
|
|
+// 在PaymentMtService中添加退款功能 - 实际实现
|
|
|
class PaymentMtService {
|
|
class PaymentMtService {
|
|
|
- async refund(tenantId: number, orderNo: string, refundAmount: number): Promise<{
|
|
|
|
|
- refund_id: string;
|
|
|
|
|
- out_refund_no: string;
|
|
|
|
|
|
|
+ async refund(tenantId: number, outTradeNo: string, refundAmount: number, refundDesc?: string): Promise<{
|
|
|
|
|
+ refundId: string;
|
|
|
|
|
+ outRefundNo: string;
|
|
|
|
|
+ refundStatus: string;
|
|
|
|
|
+ refundAmount: number;
|
|
|
|
|
+ refundTime: string;
|
|
|
}> {
|
|
}> {
|
|
|
|
|
+ // 验证支付记录存在且状态正确
|
|
|
|
|
+ const payment = await this.findPaymentByOutTradeNo(tenantId, outTradeNo);
|
|
|
|
|
+ if (!payment) {
|
|
|
|
|
+ throw new Error(`支付记录不存在,订单号: ${outTradeNo}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (payment.paymentStatus !== PaymentStatus.PAID) {
|
|
|
|
|
+ throw new Error(`订单支付状态不正确,当前状态: ${payment.paymentStatus}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (refundAmount <= 0 || refundAmount > payment.totalAmount) {
|
|
|
|
|
+ throw new Error(`退款金额无效,退款金额: ${refundAmount}, 支付金额: ${payment.totalAmount}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化微信支付SDK
|
|
|
await this.initializeWxPay(tenantId);
|
|
await this.initializeWxPay(tenantId);
|
|
|
|
|
|
|
|
- const result = await this.wxPay.refund({
|
|
|
|
|
- out_trade_no: orderNo,
|
|
|
|
|
- out_refund_no: `REFUND_${orderNo}_${Date.now()}`,
|
|
|
|
|
|
|
+ // 生成退款订单号
|
|
|
|
|
+ const outRefundNo = `REFUND_${outTradeNo}_${Date.now()}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 调用微信支付退款API
|
|
|
|
|
+ const result = await this.wxPay.refunds({
|
|
|
|
|
+ out_trade_no: outTradeNo,
|
|
|
|
|
+ out_refund_no: outRefundNo,
|
|
|
amount: {
|
|
amount: {
|
|
|
refund: refundAmount,
|
|
refund: refundAmount,
|
|
|
- total: refundAmount,
|
|
|
|
|
|
|
+ total: payment.totalAmount,
|
|
|
currency: 'CNY'
|
|
currency: 'CNY'
|
|
|
- }
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ notify_url: `${this.config['wx.payment.notify.url']}/refund`
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ // 更新支付记录退款状态
|
|
|
|
|
+ payment.refundStatus = PaymentStatus.REFUNDED;
|
|
|
|
|
+ payment.refundTransactionId = result.id;
|
|
|
|
|
+ payment.refundAmount = refundAmount;
|
|
|
|
|
+ payment.refundTime = new Date();
|
|
|
|
|
+
|
|
|
|
|
+ await this.paymentRepository.save(payment);
|
|
|
|
|
+
|
|
|
return {
|
|
return {
|
|
|
- refund_id: result.refund_id,
|
|
|
|
|
- out_refund_no: result.out_refund_no
|
|
|
|
|
|
|
+ refundId: result.id,
|
|
|
|
|
+ outRefundNo: outRefundNo,
|
|
|
|
|
+ refundStatus: result.status,
|
|
|
|
|
+ refundAmount: refundAmount,
|
|
|
|
|
+ refundTime: payment.refundTime.toISOString()
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 处理退款回调
|
|
|
|
|
+ async handleRefundCallback(callbackData: any, headers: any, rawBody: string): Promise<void> {
|
|
|
|
|
+ // 验证签名
|
|
|
|
|
+ const isValid = await this.wxPay.verifySign(headers, rawBody);
|
|
|
|
|
+ if (!isValid) {
|
|
|
|
|
+ throw new Error('退款回调签名验证失败');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 解密回调数据
|
|
|
|
|
+ const decryptedData = this.wxPay.decipher_gcm(
|
|
|
|
|
+ callbackData.resource.ciphertext,
|
|
|
|
|
+ callbackData.resource.associated_data,
|
|
|
|
|
+ callbackData.resource.nonce
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const parsedData = JSON.parse(decryptedData);
|
|
|
|
|
+
|
|
|
|
|
+ // 根据退款回调更新支付状态
|
|
|
|
|
+ const payment = await this.findPaymentByRefundOrderNo(parsedData.out_refund_no);
|
|
|
|
|
+ if (payment) {
|
|
|
|
|
+ if (parsedData.refund_status === 'SUCCESS') {
|
|
|
|
|
+ payment.refundStatus = PaymentStatus.REFUNDED;
|
|
|
|
|
+ } else if (parsedData.refund_status === 'FAIL') {
|
|
|
|
|
+ payment.refundStatus = PaymentStatus.REFUND_FAILED;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await this.paymentRepository.save(payment);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -272,6 +342,13 @@ class PaymentMtService {
|
|
|
- **2支付成功** → **3已退款** (退款成功)
|
|
- **2支付成功** → **3已退款** (退款成功)
|
|
|
- **1支付中** → **4支付失败** (支付失败)
|
|
- **1支付中** → **4支付失败** (支付失败)
|
|
|
|
|
|
|
|
|
|
+### 支付状态机(PaymentStatus枚举)
|
|
|
|
|
+- **PENDING(0)** → **PROCESSING(1)** (发起支付)
|
|
|
|
|
+- **PROCESSING(1)** → **PAID(2)** (支付成功)
|
|
|
|
|
+- **PROCESSING(1)** → **FAILED(4)** (支付失败)
|
|
|
|
|
+- **PAID(2)** → **REFUNDED(3)** (退款成功)
|
|
|
|
|
+- **PAID(2)** → **REFUND_FAILED(6)** (退款失败)
|
|
|
|
|
+
|
|
|
### 支付退款流程时序图
|
|
### 支付退款流程时序图
|
|
|
|
|
|
|
|
```mermaid
|
|
```mermaid
|
|
@@ -309,10 +386,10 @@ sequenceDiagram
|
|
|
Mini-->>User: 显示取消结果
|
|
Mini-->>User: 显示取消结果
|
|
|
|
|
|
|
|
%% 退款回调
|
|
%% 退款回调
|
|
|
- WechatPay->>RefundAPI: 退款回调通知
|
|
|
|
|
- RefundAPI->>OrderAPI: 确认退款状态
|
|
|
|
|
- OrderAPI-->>RefundAPI: 状态确认成功
|
|
|
|
|
- RefundAPI-->>WechatPay: 回调响应
|
|
|
|
|
|
|
+ WechatPay->>PaymentAPI: 退款回调通知
|
|
|
|
|
+ PaymentAPI->>PaymentAPI: 验证签名和解密数据
|
|
|
|
|
+ PaymentAPI->>PaymentAPI: 更新支付记录退款状态
|
|
|
|
|
+ PaymentAPI-->>WechatPay: 回调响应
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
### Mini小程序前端实现
|
|
### Mini小程序前端实现
|
|
@@ -426,6 +503,22 @@ class OrderDetailPage {
|
|
|
6. **TypeScript类型安全** - 修复了测试工厂中的字段定义错误,确保类型安全
|
|
6. **TypeScript类型安全** - 修复了测试工厂中的字段定义错误,确保类型安全
|
|
|
7. **API响应验证** - 使用parseWithAwait确保API响应格式与Schema一致
|
|
7. **API响应验证** - 使用parseWithAwait确保API响应格式与Schema一致
|
|
|
|
|
|
|
|
|
|
+### Story 3 完成情况总结
|
|
|
|
|
+- ✅ **微信支付退款功能** - 完全实现并通过所有测试
|
|
|
|
|
+- ✅ **退款请求构建** - 正确构建微信支付退款请求参数和签名
|
|
|
|
|
+- ✅ **退款回调处理** - 完整处理微信支付退款回调通知
|
|
|
|
|
+- ✅ **退款状态同步** - 退款成功/失败状态正确更新到支付记录
|
|
|
|
|
+- ✅ **多租户数据隔离** - 验证通过,不同租户退款数据完全隔离
|
|
|
|
|
+- ✅ **错误处理** - 完善的错误验证和异常处理机制
|
|
|
|
|
+- ✅ **测试覆盖** - 6个集成测试全部通过,覆盖各种场景
|
|
|
|
|
+
|
|
|
|
|
+### 技术实现亮点
|
|
|
|
|
+1. **退款字段扩展** - 在PaymentMtEntity中添加退款状态、退款流水号、退款金额、退款时间字段
|
|
|
|
|
+2. **微信支付SDK集成** - 完整集成wechatpay-node-v3 SDK的退款功能
|
|
|
|
|
+3. **退款回调处理** - 实现退款回调的签名验证、数据解密和状态更新
|
|
|
|
|
+4. **订单服务集成** - 在OrderMtService中集成PaymentMtService退款方法
|
|
|
|
|
+5. **测试数据工厂** - 创建完整的退款测试数据,支持多租户场景
|
|
|
|
|
+6. **边界条件验证** - 测试不存在的支付记录、未支付订单、无效退款金额等边界情况
|
|
|
|
|
+
|
|
|
### 下一步计划
|
|
### 下一步计划
|
|
|
-- **Story 3**: 集成微信支付退款功能,调用微信支付SDK实现退款
|
|
|
|
|
- **Story 4**: Mini小程序前端支付页面和流程集成
|
|
- **Story 4**: Mini小程序前端支付页面和流程集成
|