# Epic 011 - Mini小程序支付退款完整流程 - Brownfield Enhancement

## Epic Status
**进度:** 75% (3/4 故事完成)
**当前状态:** Story 1、Story 2 和 Story 3 已完成，支付回调、订单取消和退款功能已实现并通过测试
**下一步:** 开始 Story 4 - Mini小程序前端支付页面和流程集成

## Epic Goal

跑通当前mini提交创建订单后，调用微信小程序支付，支付回调，更新订单状态。订单支付成功后，在min订单列表进入订单详情，点击取消订单后，可以取消订单并调用微信支付sdk退款，最后更新订单状态。

## Epic Description

### Existing System Context

**Current relevant functionality:**
- **多租户支付模块**已实现微信小程序支付功能，支持租户隔离和系统配置集成
- **多租户订单模块**已实现订单创建和管理功能，支持租户隔离
- **多租户退款模块**已实现退款记录管理，但缺少与微信支付SDK的集成
- **多租户系统配置模块**已实现租户隔离的支付配置管理，支付模块已集成
- **Redis缓存**已用于session_key存储和系统配置缓存
- **共享CRUD包**已提供完整的多租户支持
- **Mini小程序前端**已实现：
  - 订单提交页面（`/pages/order-submit/index.tsx`）- 支持订单创建，跳转到订单详情
  - 订单列表页面（`/pages/order-list/index.tsx`）- 支持订单状态显示和筛选，有"去支付"按钮
  - 订单详情页面（`/pages/order-detail/index.tsx`）- 支持取消订单和申请退款UI
  - 支付工具函数（`/utils/payment.ts`）- 完整的微信支付工具类，包含支付调用、验证、重试、状态管理等

**Technology stack:**
- TypeORM + PostgreSQL (数据库)
- Hono + Zod OpenAPI (API框架)
- Redis (缓存)
- 微信小程序SDK (认证和支付)
- @d8d/shared-crud (共享CRUD工具包)

**Integration points:**
- 多租户支付模块的PaymentMtService (小程序支付，已集成系统配置)
- 多租户订单模块的OrderMtService (订单管理)
- 多租户退款模块的UserRefundsMtService (退款记录管理)
- 多租户系统配置模块的SystemConfigServiceMt (支付配置)
- Redis缓存工具 (配置缓存)
- 微信支付SDK (退款功能 - 需要集成)

### Enhancement Details

**What's being added/changed:**
- 完善支付回调处理逻辑，确保订单状态正确更新（支付模块已有TODO注释）
- 实现订单取消功能，支持支付订单的取消和退款（当前缺少取消订单功能）
- 集成微信支付SDK退款功能（支付模块缺少退款功能）
- 优化订单状态流转逻辑
- 完善退款记录和状态跟踪（退款模块已有基础功能）
- **Mini小程序前端增强：**
  - 添加支付页面（`/pages/payment/index.tsx`）- 当前缺少支付页面
  - 集成支付工具函数到订单流程中
  - 完善取消订单和退款的前端交互

**How it integrates:**
- **复用多租户支付模块**的支付功能，扩展退款功能
- **扩展多租户订单模块**支持取消和退款操作
- **使用多租户系统配置模块**获取租户特定的支付配置（已集成）
- **集成微信支付SDK**实现退款功能
- **使用Redis缓存**优化配置访问（已集成）
- **Mini小程序前端集成：**
  - 订单提交页面调用支付API获取支付参数
  - 支付页面调用微信支付SDK完成支付
  - 订单详情页面集成取消订单和退款功能
  - 使用现有的支付工具函数库

**Success criteria:**
- 用户可以在mini小程序中成功创建订单并支付
- 支付回调正确更新订单状态
- 用户可以在订单详情中取消已支付订单
- 取消订单时自动调用微信支付退款
- 退款回调正确更新订单和退款状态
- 整个流程支持多租户隔离

## Stories

1. ✅ **Story 1:** 完善支付回调处理逻辑 - 确保支付回调正确更新订单状态，支持多租户隔离
   - **修改位置:**
     - `packages/mini-payment-mt/src/services/payment.mt.service.ts` - 修复TODO注释，实现订单状态更新
     - `packages/mini-payment-mt/src/routes/payment/callback.mt.ts` - 支付回调接口
   - **验收标准:**
     - ✅ 支付回调接口正确处理微信支付通知
     - ✅ 订单状态从"待支付"正确更新为"已支付"（支付状态从0更新为2）
     - ✅ 支付时间、支付流水号等字段正确记录
     - ✅ 支持多租户隔离，不同租户的支付回调互不影响
     - ✅ 添加支付回调日志记录
     - ✅ 修复支付模块中的TODO：更新订单状态
   - **完成状态:** 所有7个集成测试通过，包括多租户数据隔离验证

2. ✅ **Story 2:** 实现订单取消功能 - 支持已支付订单的取消和退款流程
   - **修改位置:**
     - `packages/orders-module-mt/src/services/order.mt.service.ts` - 添加cancelOrder方法
     - `packages/orders-module-mt/src/routes/user/orders.mt.ts` - 添加取消订单API
     - `mini/src/pages/order-detail/index.tsx` - 集成取消订单UI
   - **验收标准:**
     - ✅ 在订单详情页面添加取消订单按钮
     - ✅ 取消订单时验证订单状态（仅允许取消"待支付"和"已支付"订单）
     - ✅ 对于已支付订单，触发退款流程
     - ✅ 订单状态正确更新为"已取消"（支付状态更新为5）
     - ✅ 取消原因和操作时间正确记录
     - ✅ 在OrderMtService中添加cancelOrder方法
   - **完成状态:** 所有12个集成测试通过，包括多租户数据隔离和用户权限验证

3. ✅ **Story 3:** 集成微信支付退款功能 - 调用微信支付SDK实现退款
   - **修改位置:**
     - `packages/mini-payment-mt/src/services/payment.mt.service.ts` - 添加refund方法
     - `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
     - ✅ 实现退款请求构建和签名
     - ✅ 处理退款回调通知
     - ✅ 退款状态正确更新到订单和退款记录
     - ✅ 退款金额、退款流水号等字段正确记录
     - ✅ 支持部分退款和全额退款
     - ✅ 多租户退款数据隔离验证
     - ✅ 完整的退款集成测试覆盖
   - **完成状态:** 所有6个集成测试通过，包括多租户数据隔离验证

4. **Story 4:** Mini小程序前端支付页面和流程集成 - 创建支付页面并集成支付流程
   - **修改位置:**
     - `mini/src/pages/payment/index.tsx` - 创建支付页面
     - `mini/src/pages/order-submit/index.tsx` - 集成支付页面跳转
     - `mini/src/pages/order-list/index.tsx` - 集成"去支付"按钮功能
     - `mini/src/pages/order-detail/index.tsx` - 集成取消订单功能
   - **验收标准:**
     - 创建完整的支付页面，支持微信支付调用
     - 订单提交页面成功跳转到支付页面
     - 订单列表页面的"去支付"按钮正常工作
     - 订单详情页面支持取消订单操作
     - 支付成功/失败有相应的页面跳转和提示
     - 复用现有的支付工具函数库
     - 实现完整的订单状态机流转
     - 支持退款失败的重试机制

## Compatibility Requirements

- [ ] 现有支付API保持不变
- [ ] 现有订单API向后兼容
- [ ] 数据库schema变化向后兼容
- [ ] UI变化遵循现有模式
- [ ] 性能影响最小化

## Risk Mitigation

**Primary Risk:** 退款功能可能影响现有支付流程的稳定性

**Mitigation:** 分阶段实施，先完善支付回调，再实现退款功能

**Rollback Plan:** 如果出现问题，可以暂时禁用退款功能，保持现有支付流程正常工作

## Definition of Done

- [x] Story 1完成且验收标准满足
- [x] Story 2完成且验收标准满足
- [x] Story 3完成且验收标准满足
- [x] 支付回调功能通过测试验证
- [x] 订单取消功能通过测试验证
- [x] 退款功能通过测试验证
- [x] 订单状态流转正确
- [x] 多租户隔离正常工作
- [x] 现有功能无回归

## Technical Implementation Details

### 支付回调处理
```typescript
// 支付回调接口 - 修复PaymentMtService中的TODO
class PaymentMtService {
  async handlePaymentCallback(callbackData: any, headers: any, rawBody: string): Promise<void> {
    // ... 现有验证逻辑 ...

    // 根据回调结果更新支付状态
    if (parsedData.trade_state === 'SUCCESS') {
      payment.paymentStatus = PaymentStatus.PAID;
      payment.wechatTransactionId = parsedData.transaction_id;

      // TODO: 更新订单状态 - 需要实现
      await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, 2); // 支付成功
    } else if (parsedData.trade_state === 'FAIL') {
      payment.paymentStatus = PaymentStatus.FAILED;
      await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, 4); // 支付失败
    } else if (parsedData.trade_state === 'REFUND') {
      payment.paymentStatus = PaymentStatus.REFUNDED;
      await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, 3); // 已退款
    }

    await paymentRepository.save(payment);
  }
}
```

### 订单取消和退款
```typescript
// 订单取消服务 - 需要在OrderMtService中添加
class OrderMtService {
  async cancelOrder(tenantId: number, orderId: number, reason: string, userId: number) {
    const order = await this.repository.findOne({
      where: { id: orderId, tenantId }
    });

    if (!order) {
      throw new Error('订单不存在');
    }

    // 验证订单状态
    if (order.payState !== 0 && order.payState !== 2) {
      throw new Error('该订单状态不允许取消');
    }

    if (order.payState === 2) {
      // 已支付订单，触发退款
      const paymentService = new PaymentMtService(this.dataSource);
      const refundResult = await paymentService.refund(
        tenantId,
        order.orderNo,
        order.payAmount
      );

      // 创建退款记录
      const refundService = new UserRefundsMtService(this.dataSource);
      await refundService.createUserRefund({
        orderNo: order.orderNo,
        refundOrderNo: refundResult.refund_id,
        refundAmount: order.payAmount,
        state: 1 // 退款中
      }, userId, tenantId);
    }

    // 更新订单状态
    order.payState = 5; // 订单关闭
    order.closeTime = new Date();
    order.remark = `用户取消: ${reason}`;
    order.updatedBy = userId;

    await this.repository.save(order);
  }
}
```

### 退款功能集成
```typescript
// 在PaymentMtService中添加退款功能 - 实际实现
class PaymentMtService {
  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);

    // 生成退款订单号
    const outRefundNo = `REFUND_${outTradeNo}_${Date.now()}`;

    // 调用微信支付退款API
    const result = await this.wxPay.refunds({
      out_trade_no: outTradeNo,
      out_refund_no: outRefundNo,
      amount: {
        refund: refundAmount,
        total: payment.totalAmount,
        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 {
      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);
    }
  }
}
```

### 订单状态机（基于现有PayStatus枚举）
- **0未支付** → **2支付成功** (支付成功)
- **0未支付** → **5订单关闭** (用户取消)
- **2支付成功** → **5订单关闭** (用户取消 + 退款)
- **2支付成功** → **3已退款** (退款成功)
- **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
sequenceDiagram
    participant User as 用户
    participant Mini as Mini小程序
    participant OrderAPI as 订单API
    participant PaymentAPI as 支付API
    participant WechatPay as 微信支付
    participant RefundAPI as 退款API

    %% 支付流程
    User->>Mini: 提交订单
    Mini->>OrderAPI: 创建订单
    OrderAPI-->>Mini: 返回订单ID
    Mini->>PaymentAPI: 请求支付参数
    PaymentAPI-->>Mini: 返回支付参数
    Mini->>WechatPay: 调用微信支付
    WechatPay-->>Mini: 支付结果

    %% 支付回调
    WechatPay->>PaymentAPI: 支付回调通知
    PaymentAPI->>OrderAPI: 更新订单状态(已支付)
    OrderAPI-->>PaymentAPI: 状态更新成功
    PaymentAPI-->>WechatPay: 回调响应

    %% 退款流程
    User->>Mini: 取消订单
    Mini->>OrderAPI: 请求取消订单
    OrderAPI->>RefundAPI: 发起退款
    RefundAPI->>WechatPay: 调用退款API
    WechatPay-->>RefundAPI: 退款结果
    RefundAPI->>OrderAPI: 更新订单状态(已退款)
    OrderAPI-->>Mini: 取消成功
    Mini-->>User: 显示取消结果

    %% 退款回调
    WechatPay->>PaymentAPI: 退款回调通知
    PaymentAPI->>PaymentAPI: 验证签名和解密数据
    PaymentAPI->>PaymentAPI: 更新支付记录退款状态
    PaymentAPI-->>WechatPay: 回调响应
```

### Mini小程序前端实现
```typescript
// 支付页面 - 需要创建 /pages/payment/index.tsx
class PaymentPage {
  async handlePayment(orderId: number) {
    // 1. 调用后端API获取支付参数
    const paymentParams = await orderClient['create-payment']['$post']({
      json: { orderId }
    });

    // 2. 使用现有支付工具函数调用微信支付
    const result = await requestWechatPayment(paymentParams);

    // 3. 处理支付结果
    if (result.success) {
      // 支付成功，跳转到支付成功页面
      Taro.redirectTo({ url: '/pages/payment-success/index' });
    } else {
      // 支付失败，显示错误信息
      Taro.showToast({ title: result.message, icon: 'none' });
    }
  }
}

// 订单详情页面 - 集成取消订单功能
class OrderDetailPage {
  async handleCancelOrder(orderId: number, reason: string) {
    try {
      // 调用后端取消订单API
      await orderClient['cancel-order']['$post']({
        json: { orderId, reason }
      });

      Taro.showToast({ title: '订单取消成功', icon: 'success' });
      // 刷新页面或返回订单列表
    } catch (error) {
      Taro.showToast({ title: error.message, icon: 'none' });
    }
  }
}
```

## Validation Checklist

### Scope Validation
- [x] Epic可以在5个故事内完成
- [x] 不需要架构文档
- [x] 增强遵循现有模式
- [x] 集成复杂度可控

### Risk Assessment
- [x] 对现有系统风险可控
- [x] 回滚计划可行
- [x] 测试方法覆盖现有功能
- [x] 团队对集成点有足够了解

### Completeness Check
- [x] Epic目标清晰可实现
- [x] 故事范围适当
- [x] 成功标准可衡量
- [x] 依赖项已识别

---

**Story Manager Handoff:**

"请为这个棕地史诗开发详细的用户故事。关键考虑因素：

- 这是对运行TypeORM + PostgreSQL + Hono + Redis + @d8d/shared-crud的多租户系统的增强
- **核心简化**: 复用多租户支付模块和订单模块，扩展退款功能
- **Mini小程序前端**: 已有订单提交、列表、详情页面和支付工具函数，需要添加支付页面和集成支付流程
- 集成点：多租户支付模块的PaymentMtService、多租户订单模块的OrderMtService、多租户退款模块的UserRefundsMtService、多租户系统配置模块的SystemConfigServiceMt、微信支付SDK、Mini小程序前端
- 要遵循的现有模式：多租户支付模块的支付流程、多租户订单模块的CRUD模式、多租户实体模式、Mini小程序前端架构
- 关键兼容性要求：现有支付API保持不变，订单API向后兼容，Mini小程序现有页面功能无影响
- 每个故事必须包括验证现有功能保持完整的验证
- 特别注意：支付模块中已有TODO需要更新订单状态，需要实现
- **Mini小程序前端关键点：**
  - 订单提交页面（`mini/src/pages/order-submit/index.tsx`）需要集成支付页面跳转
  - 订单列表页面（`mini/src/pages/order-list/index.tsx`）的"去支付"按钮需要集成支付功能
  - 需要创建支付页面（`mini/src/pages/payment/index.tsx`）
  - 订单详情页面（`mini/src/pages/order-detail/index.tsx`）需要集成取消订单功能
  - 复用现有的支付工具函数库（`mini/src/utils/payment.ts`）

该史诗应在保持系统完整性的同时交付完整的支付退款流程功能。"

## Epic Completion Summary

### Story 1 完成情况总结
- ✅ **支付回调处理逻辑** - 完全实现并通过所有测试
- ✅ **多租户数据隔离** - 验证通过，不同租户支付回调互不影响
- ✅ **订单状态同步** - 支付成功/失败/退款状态正确更新到订单
- ✅ **日志记录** - 完整的调试日志记录，便于问题排查
- ✅ **测试覆盖** - 7个集成测试全部通过，覆盖各种场景

### Story 2 完成情况总结
- ✅ **订单取消功能** - 完全实现并通过所有测试
- ✅ **订单状态验证** - 仅允许取消"待支付"和"已支付"订单
- ✅ **退款流程集成** - 已支付订单触发退款流程占位符
- ✅ **多租户数据隔离** - 验证通过，不同租户订单数据完全隔离
- ✅ **用户权限验证** - 用户只能取消自己的订单
- ✅ **测试覆盖** - 12个集成测试全部通过，覆盖各种场景

### 技术实现亮点
1. **实体元数据修复** - 解决了DeliveryAddressMt、UserEntityMt、AreaEntityMt等实体的依赖问题
2. **模块导入优化** - 确保多租户版本正确导入，避免路径冲突
3. **测试数据工厂** - 创建了PaymentTestFactory和OrdersTestFactory简化测试数据创建
4. **回调数据处理** - 修复了商户订单号获取顺序和订单状态更新逻辑
5. **多租户隔离验证** - 通过特定回调数据确保租户间数据完全隔离
6. **TypeScript类型安全** - 修复了测试工厂中的字段定义错误，确保类型安全
7. **API响应验证** - 使用parseWithAwait确保API响应格式与Schema一致

### Story 3 完成情况总结
- ✅ **微信支付退款功能** - 完全实现并通过所有测试
- ✅ **退款请求构建** - 正确构建微信支付退款请求参数和签名
- ✅ **退款回调处理** - 完整处理微信支付退款回调通知
- ✅ **退款状态同步** - 退款成功/失败状态正确更新到支付记录
- ✅ **多租户数据隔离** - 验证通过，不同租户退款数据完全隔离
- ✅ **错误处理** - 完善的错误验证和异常处理机制
- ✅ **测试覆盖** - 6个集成测试全部通过，覆盖各种场景

### 技术实现亮点
1. **退款字段扩展** - 在PaymentMtEntity中添加退款状态、退款流水号、退款金额、退款时间字段
2. **微信支付SDK集成** - 完整集成wechatpay-node-v3 SDK的退款功能
3. **退款回调处理** - 实现退款回调的签名验证、数据解密和状态更新
4. **订单服务集成** - 在OrderMtService中集成PaymentMtService退款方法
5. **测试数据工厂** - 创建完整的退款测试数据，支持多租户场景
6. **边界条件验证** - 测试不存在的支付记录、未支付订单、无效退款金额等边界情况

### 下一步计划
- **Story 4**: Mini小程序前端支付页面和流程集成