# Epic 011 - Mini小程序支付退款完整流程 - Brownfield Enhancement ## Epic Status **进度:** 100% (4/4 故事完成) **当前状态:** Story 1、Story 2、Story 3 和 Story 4 全部完成,完整的支付退款流程已实现并通过测试 **完成时间:** 2025-11-21 ## 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/payment-success/index.tsx` - 创建支付成功页面 - `mini/src/pages/order-submit/index.tsx` - 集成支付页面跳转 - `mini/src/pages/order-list/index.tsx` - 集成"去支付"按钮功能 - `mini/src/pages/order-detail/index.tsx` - 集成取消订单功能 - `mini/src/utils/payment.ts` - 增强支付状态管理 - `mini/src/app.config.ts` - 添加页面路由 - **验收标准:** - ✅ 创建完整的支付页面,支持微信支付调用 - ✅ 订单提交页面成功跳转到支付页面 - ✅ 订单列表页面的"去支付"按钮正常工作 - ✅ 订单详情页面支持取消订单操作 - ✅ 支付成功/失败有相应的页面跳转和提示 - ✅ 复用现有的支付工具函数库 - ✅ 实现完整的订单状态机流转 - ✅ 支持退款失败的重试机制 - **完成状态:** 所有功能实现完成,类型检查通过,样式统一使用Tailwind 4 ## 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] Story 4完成且验收标准满足 - [x] 支付回调功能通过测试验证 - [x] 订单取消功能通过测试验证 - [x] 退款功能通过测试验证 - [x] 前端支付流程完整实现 - [x] 订单状态流转正确 - [x] 多租户隔离正常工作 - [x] 现有功能无回归 - [x] 前端样式统一使用Tailwind 4 - [x] 类型检查通过,无支付相关错误 ## Technical Implementation Details ### 支付回调处理 ```typescript // 支付回调接口 - 修复PaymentMtService中的TODO class PaymentMtService { async handlePaymentCallback(callbackData: any, headers: any, rawBody: string): Promise { // ... 现有验证逻辑 ... // 根据回调结果更新支付状态 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 { // 验证签名 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 完成情况总结 - ✅ **支付页面创建** - 创建完整的支付页面,支持微信支付调用 - ✅ **支付流程集成** - 订单提交、订单列表、订单详情页面完整集成支付流程 - ✅ **支付状态管理** - 实现完整的支付状态机流转,支持重试机制 - ✅ **用户界面优化** - 支付成功页面、错误处理、状态提示完整 - ✅ **样式统一** - 所有页面使用Tailwind 4,清理SCSS文件 - ✅ **类型安全** - 支付相关代码类型检查通过,无类型错误 ### 技术实现亮点 1. **支付页面架构** - 完整的支付流程,包括支付中、成功、失败状态管理 2. **状态流转控制** - PaymentStateFlowManager实现完整的支付状态流转验证 3. **错误处理机制** - 支付失败重试、频率限制、超时处理 4. **样式系统统一** - 从SCSS迁移到Tailwind 4,保持设计一致性 5. **API集成优化** - 使用Hono RPC客户端,状态码验证正确 6. **用户体验完善** - 支付成功页面、操作按钮、温馨提示完整 ### 史诗完成总结 Epic 011 - Mini小程序支付退款完整流程已100%完成,所有4个故事全部实现并通过验证。完整的支付退款流程包括: 1. **后端支付退款功能** - Story 1-3完成支付回调、订单取消、退款功能 2. **前端支付流程集成** - Story 4完成支付页面和流程集成 3. **多租户数据隔离** - 所有功能支持多租户数据隔离 4. **完整测试覆盖** - 后端集成测试通过,前端类型检查通过 **完整流程验证:** - 用户创建订单 → 跳转支付页面 → 微信支付 → 支付回调 → 订单状态更新 - 用户取消订单 → 调用退款API → 微信退款 → 退款回调 → 订单状态更新 Epic目标已完全达成,系统现在支持完整的支付退款业务流程。