进度: 100% (4/4 故事完成) 当前状态: Story 1、Story 2、Story 3 和 Story 4 全部完成,完整的支付退款流程已实现并通过测试 完成时间: 2025-11-21
跑通当前mini提交创建订单后,调用微信小程序支付,支付回调,更新订单状态。订单支付成功后,在min订单列表进入订单详情,点击取消订单后,可以取消订单并调用微信支付sdk退款,最后更新订单状态。
Current relevant functionality:
/pages/order-submit/index.tsx)- 支持订单创建,跳转到订单详情/pages/order-list/index.tsx)- 支持订单状态显示和筛选,有"去支付"按钮/pages/order-detail/index.tsx)- 支持取消订单和申请退款UI/utils/payment.ts)- 完整的微信支付工具类,包含支付调用、验证、重试、状态管理等Technology stack:
Integration points:
What's being added/changed:
/pages/payment/index.tsx)- 当前缺少支付页面How it integrates:
Success criteria:
✅ Story 1: 完善支付回调处理逻辑 - 确保支付回调正确更新订单状态,支持多租户隔离
packages/mini-payment-mt/src/services/payment.mt.service.ts - 修复TODO注释,实现订单状态更新packages/mini-payment-mt/src/routes/payment/callback.mt.ts - 支付回调接口✅ Story 2: 实现订单取消功能 - 支持已支付订单的取消和退款流程
packages/orders-module-mt/src/services/order.mt.service.ts - 添加cancelOrder方法packages/orders-module-mt/src/routes/user/orders.mt.ts - 添加取消订单APImini/src/pages/order-detail/index.tsx - 集成取消订单UI✅ 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 - 创建退款集成测试✅ 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 - 添加页面路由Primary Risk: 退款功能可能影响现有支付流程的稳定性
Mitigation: 分阶段实施,先完善支付回调,再实现退款功能
Rollback Plan: 如果出现问题,可以暂时禁用退款功能,保持现有支付流程正常工作
// 支付回调接口 - 修复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);
}
}
// 订单取消服务 - 需要在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);
}
}
// 在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);
}
}
}
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: 回调响应
// 支付页面 - 需要创建 /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' });
}
}
}
Story Manager Handoff:
"请为这个棕地史诗开发详细的用户故事。关键考虑因素:
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 011 - Mini小程序支付退款完整流程已100%完成,所有4个故事全部实现并通过验证。完整的支付退款流程包括:
完整流程验证:
Epic目标已完全达成,系统现在支持完整的支付退款业务流程。