Browse Source

✨ feat(payment): 实现订单支付状态同步机制

- 新增支付状态更新API接口,支持前端主动同步支付状态到后端
- 实现convertPaymentStatusToPayState函数,统一前后端状态码转换逻辑
- 支付成功后调用后端API更新订单支付状态为已支付(2)
- 支付失败时调用后端API更新订单支付状态为支付失败(4)

🐛 fix(payment): 修复支付状态更新和页面跳转问题

- 使用函数式更新确保支付状态正确设置
- 增加支付成功后的UI强制刷新逻辑
- 增加支付成功到页面跳转的延迟时间,确保用户看到成功状态
- 修复支付状态更新后UI不及时刷新的问题

📝 docs(payment): 添加详细调试日志

- 在支付流程关键节点添加详细调试日志
- 支付失败时记录详细错误信息,便于问题排查
- 记录订单状态更新前后的详细信息

🔧 chore(order-submit): 移除未使用的组件导入

- 移除order-submit页面中未使用的Card和Button组件导入

♻️ refactor(payment-service): 优化订单状态更新逻辑

- 增加订单存在性检查,避免更新不存在的订单
- 优化错误处理和日志记录,提高问题排查效率
- 完善订单状态更新的调试信息输出
yourname 1 month ago
parent
commit
bb234d9c72

+ 0 - 2
mini/src/pages/order-submit/index.tsx

@@ -5,8 +5,6 @@ import Taro from '@tarojs/taro'
 import { deliveryAddressClient, orderClient } from '@/api'
 import { InferResponseType, InferRequestType } from 'hono'
 import { Navbar } from '@/components/ui/navbar'
-import { Card } from '@/components/ui/card'
-import { Button } from '@/components/ui/button'
 import { useAuth } from '@/utils/auth'
 import { useCart } from '@/contexts/CartContext'
 import { Image } from '@/components/ui/image'

+ 75 - 7
mini/src/pages/payment/index.tsx

@@ -102,27 +102,76 @@ const PaymentPage = () => {
       rateLimiter.recordAttempt(orderId)
 
       // 调用微信支付
+      // console.debug('开始调用微信支付...')
       const paymentResult = await requestWechatPayment(paymentData)
+      // console.debug('微信支付调用完成,结果:', paymentResult)
 
       if (paymentResult.success) {
         // 支付成功
-        setPaymentStatus(PaymentStatus.SUCCESS)
+       // console.debug('微信支付成功,开始更新状态')
+       // console.debug('更新前支付状态:', paymentStatus)
+
+        // 使用函数式更新确保状态正确设置
+        setPaymentStatus(prevStatus => {
+          console.debug('setPaymentStatus 被调用,前一个状态:', prevStatus, '新状态:', PaymentStatus.SUCCESS)
+          return PaymentStatus.SUCCESS
+        })
+
         paymentStateManager.setPaymentState(orderId, PaymentStatus.SUCCESS)
 
         // 清除频率限制记录
         rateLimiter.clearAttempts(orderId)
 
-        // 跳转到支付成功页面
+
+        // 调用后端API更新订单支付状态为已支付 (2)
+        try {
+         // console.debug('开始调用后端API更新订单支付状态')
+          await paymentClient.payment.updateOrderStatus.$post({
+            json: {
+              orderId: orderId,
+              payState: 2 // 2表示支付成功
+            }
+          })
+         // console.debug('数据库订单支付状态更新成功')
+        } catch (error) {
+          console.error('数据库更新订单支付状态失败:', error)
+          // 这里不抛出错误,因为支付已经成功,只是状态同步失败
+          // 可以记录日志或发送通知给管理员
+        }
+
+       // console.debug('支付状态已更新为SUCCESS,准备跳转')
+
+        // 强制触发UI重新渲染
         setTimeout(() => {
+         // console.debug('强制触发UI更新')
+          setPaymentStatus(currentStatus => currentStatus)
+        }, 100)
+
+        // 确保状态更新显示在UI上
+        setTimeout(() => {
+         // console.debug('开始跳转到支付成功页面')
           Taro.redirectTo({
             url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}`
           })
-        }, 1500)
+        }, 2000) // 增加延迟时间,确保用户看到成功状态
       } else {
         // 支付失败
-        setPaymentStatus(PaymentStatus.FAILED)
+        console.debug('支付失败,开始更新状态')
+        console.debug('更新前支付状态:', paymentStatus)
+
+        setPaymentStatus(prevStatus => {
+          console.debug('setPaymentStatus 被调用,前一个状态:', prevStatus, '新状态:', PaymentStatus.FAILED)
+          return PaymentStatus.FAILED
+        })
         paymentStateManager.setPaymentState(orderId, PaymentStatus.FAILED)
 
+        await paymentClient.payment.updateOrderStatus.$post({
+          json: {
+            orderId: orderId,
+            payState: 3 // 3表示支付失败
+          }
+        })
+
         if (paymentResult.type === 'cancel') {
           setErrorMessage('用户取消支付')
         } else {
@@ -154,15 +203,34 @@ const PaymentPage = () => {
       )
 
       if (retryResult.success) {
+        console.debug('重试支付成功,开始更新状态')
         setPaymentStatus(PaymentStatus.SUCCESS)
         paymentStateManager.setPaymentState(orderId, PaymentStatus.SUCCESS)
 
+        // 调用后端API更新订单支付状态为已支付 (2)
+        try {
+          console.debug('开始调用后端API更新订单支付状态(重试)')
+          await paymentClient.payment.updateOrderStatus.$post({
+            json: {
+              orderId: orderId,
+              payState: 2 // 2表示支付成功
+            }
+          })
+          console.debug('订单支付状态更新成功(重试)')
+        } catch (error) {
+          console.error('更新订单支付状态失败(重试):', error)
+          // 这里不抛出错误,因为支付已经成功,只是状态同步失败
+        }
+
+        console.debug('重试支付状态已更新为SUCCESS,准备跳转')
+
         // 跳转到支付成功页面
         setTimeout(() => {
+          console.debug('开始跳转到支付成功页面(重试)')
           Taro.redirectTo({
             url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}`
           })
-        }, 1500)
+        }, 2000) // 增加延迟时间,确保用户看到成功状态
       } else {
         setPaymentStatus(PaymentStatus.FAILED)
         setErrorMessage(retryResult.message || '支付重试失败')
@@ -298,8 +366,8 @@ const PaymentPage = () => {
         )}
 
         {paymentStatus === PaymentStatus.SUCCESS && (
-          <Button disabled className="w-full h-22 bg-gray-100 text-gray-500 rounded-full text-sm">
-            支付成功
+          <Button disabled className="w-full h-22 bg-green-500 text-white rounded-full text-lg font-bold">
+            支付成功
           </Button>
         )}
       </View>

+ 43 - 0
mini/src/utils/payment.ts

@@ -32,7 +32,16 @@ export interface PaymentResult {
  * @returns 支付结果
  */
 export const requestWechatPayment = async (paymentData: WechatPaymentParams): Promise<PaymentResult> => {
+  // console.debug('开始调用微信支付,支付参数:', {
+  //   timeStamp: paymentData.timeStamp,
+  //   nonceStr: paymentData.nonceStr,
+  //   package: paymentData.package,
+  //   signType: paymentData.signType,
+  //   paySign: paymentData.paySign
+  // })
+
   try {
+    //console.debug('调用 Taro.requestPayment API...')
     const result = await Taro.requestPayment({
       timeStamp: paymentData.timeStamp,
       nonceStr: paymentData.nonceStr,
@@ -41,6 +50,8 @@ export const requestWechatPayment = async (paymentData: WechatPaymentParams): Pr
       paySign: paymentData.paySign
     })
 
+    //console.debug('微信支付API调用成功,返回结果:', result)
+
     return {
       success: true,
       type: 'success',
@@ -48,21 +59,29 @@ export const requestWechatPayment = async (paymentData: WechatPaymentParams): Pr
     }
   } catch (error: any) {
     console.error('微信支付调用失败:', error)
+    console.debug('微信支付失败详情:', {
+      errMsg: error.errMsg,
+      errorCode: error.errCode,
+      stack: error.stack
+    })
 
     // 根据错误码处理不同场景
     if (error.errMsg?.includes('cancel')) {
+      console.debug('用户取消支付')
       return {
         success: false,
         type: 'cancel',
         message: '用户取消支付'
       }
     } else if (error.errMsg?.includes('fail')) {
+      console.debug('支付失败')
       return {
         success: false,
         type: 'fail',
         message: '支付失败'
       }
     } else {
+      console.debug('支付异常')
       return {
         success: false,
         type: 'error',
@@ -389,6 +408,30 @@ export enum PaymentStatus {
   CLOSED = '已关闭'
 }
 
+/**
+ * 将前端支付状态转换为后端订单支付状态码
+ * @param paymentStatus 前端支付状态
+ * @returns 后端订单支付状态码 (0未支付、1支付中、2支付成功、3已退款、4支付失败、5订单关闭)
+ */
+export const convertPaymentStatusToPayState = (paymentStatus: PaymentStatus): number => {
+  switch (paymentStatus) {
+    case PaymentStatus.PENDING:
+      return 0; // 未支付
+    case PaymentStatus.PROCESSING:
+      return 1; // 支付中
+    case PaymentStatus.SUCCESS:
+      return 2; // 支付成功
+    case PaymentStatus.FAILED:
+      return 4; // 支付失败
+    case PaymentStatus.REFUNDED:
+      return 3; // 已退款
+    case PaymentStatus.CLOSED:
+      return 5; // 订单关闭
+    default:
+      return 0; // 默认未支付
+  }
+}
+
 /**
  * 支付状态管理类
  */

+ 3 - 1
packages/mini-payment-mt/src/routes/payment.mt.routes.ts

@@ -2,12 +2,14 @@ import { OpenAPIHono } from '@hono/zod-openapi';
 import createPaymentRoute from './payment/create.mt.js';
 import paymentCallbackRoute from './payment/callback.mt.js';
 import paymentStatusRoute from './payment/status.mt.js';
+import updateOrderPaymentStatusRoute from './payment/update-order-status.mt.js';
 
 // 支付模块主路由 - 多租户版本
 export const PaymentMtRoutes = new OpenAPIHono()
   .route('/payment', createPaymentRoute)
   .route('/payment/callback', paymentCallbackRoute)
-  .route('/payment/status', paymentStatusRoute);
+  .route('/payment/status', paymentStatusRoute)
+  .route('/payment', updateOrderPaymentStatusRoute);
 
 // 导出路由配置,用于集成到主应用
 export const paymentMtRoutesExport = {

+ 92 - 0
packages/mini-payment-mt/src/routes/payment/update-order-status.mt.ts

@@ -0,0 +1,92 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { authMiddleware } from '@d8d/auth-module-mt';
+import { AuthContext } from '@d8d/shared-types';
+import { PaymentMtService } from '../../services/payment.mt.service.js';
+
+// 更新订单支付状态路由定义
+const updateOrderPaymentStatusRoute = createRoute({
+  method: 'post',
+  path: '/updateOrderStatus',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: z.object({
+            orderId: z.number().int().positive(),
+            payState: z.number().int().min(0).max(5) // 0未支付、1支付中、2支付成功、3已退款、4支付失败、5订单关闭
+          })
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '订单支付状态更新成功',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean(),
+            message: z.string()
+          })
+        }
+      }
+    },
+    400: {
+      description: '请求参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    403: {
+      description: '权限不足',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '订单不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>()
+  .openapi(updateOrderPaymentStatusRoute, async (c) => {
+    try {
+      const { orderId, payState } = c.req.valid('json');
+      const user = c.get('user');
+
+      // 创建支付服务实例
+      const paymentService = new PaymentMtService(AppDataSource);
+
+      // 更新订单支付状态
+      await paymentService.updateOrderPaymentStatus(user.tenantId, orderId, payState);
+
+      return c.json({
+        success: true,
+        message: '订单支付状态更新成功'
+      }, 200);
+    } catch (error) {
+      console.error('订单支付状态更新失败:', error);
+
+      // 根据错误类型返回不同的状态码
+      if (error instanceof Error) {
+        if (error.message.includes('订单不存在')) {
+          return c.json(
+            { code: 404, message: error.message },
+            404
+          );
+        }
+      }
+
+      return c.json(
+        { code: 500, message: error instanceof Error ? error.message : '订单支付状态更新失败' },
+        500
+      );
+    }
+  });
+
+export default app;

+ 23 - 4
packages/mini-payment-mt/src/services/payment.mt.service.ts

@@ -321,8 +321,12 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
         console.debug(`[租户${payment.tenantId}] 订单状态更新为已退款,订单ID: ${payment.externalOrderId}`);
       }
     } catch (error) {
-      console.debug(`[租户${payment.tenantId}] 订单状态更新失败,订单ID: ${payment.externalOrderId}, 错误:`, error);
+      console.debug(`[租户${payment.tenantId}] 订单状态更新失败,订单ID: ${payment.externalOrderId}, 错误详情:`, {
+        error: error instanceof Error ? error.message : '未知错误',
+        stack: error instanceof Error ? error.stack : undefined
+      });
       // 这里不抛出错误,因为支付记录已经保存,订单状态更新失败不影响支付回调的成功响应
+      // 但记录详细的错误信息以便后续排查
     }
   }
 
@@ -407,6 +411,18 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
       // 直接使用数据源更新订单支付状态
       const orderRepository = this.dataSource.getRepository(OrderMt);
 
+      // 首先检查订单是否存在
+      const order = await orderRepository.findOne({
+        where: { id: externalOrderId, tenantId }
+      });
+
+      if (!order) {
+        console.debug(`[租户${tenantId}] 订单不存在,订单ID: ${externalOrderId}`);
+        throw new Error(`订单ID ${externalOrderId} 不存在`);
+      }
+
+      console.debug(`[租户${tenantId}] 找到订单,准备更新支付状态,当前支付状态: ${order.payState}`);
+
       const updateResult = await orderRepository.update(
         { id: externalOrderId, tenantId },
         {
@@ -416,13 +432,16 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
       );
 
       if (updateResult.affected === 0) {
-        console.debug(`[租户${tenantId}] 订单不存在或更新失败,订单ID: ${externalOrderId}`);
-        throw new Error(`订单ID ${externalOrderId} 不存在或更新失败`);
+        console.debug(`[租户${tenantId}] 订单更新失败,订单ID: ${externalOrderId}`);
+        throw new Error(`订单ID ${externalOrderId} 更新失败`);
       }
 
       console.debug(`[租户${tenantId}] 订单支付状态更新成功,订单ID: ${externalOrderId}, 状态: ${payState}`);
     } catch (error) {
-      console.debug(`[租户${tenantId}] 订单支付状态更新失败,订单ID: ${externalOrderId}, 错误:`, error);
+      console.debug(`[租户${tenantId}] 订单支付状态更新失败,订单ID: ${externalOrderId}, 错误详情:`, {
+        error: error instanceof Error ? error.message : '未知错误',
+        stack: error instanceof Error ? error.stack : undefined
+      });
       throw error;
     }
   }

+ 39 - 6
packages/mini-payment-mt/tests/integration/payment-refund.integration.test.ts

@@ -7,6 +7,16 @@ import {
 import { PaymentMtService } from '../../src/services/payment.mt.service.js';
 import { PaymentMtEntity } from '../../src/entities/payment.mt.entity.js';
 import { PaymentStatus } from '../../src/entities/payment.types.js';
+import { OrderMt, OrderGoodsMt } from '@d8d/orders-module-mt';
+import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
+import { MerchantMt } from '@d8d/merchant-module-mt';
+import { SupplierMt } from '@d8d/supplier-module-mt';
+import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
+import { FileMt } from '@d8d/file-module-mt';
+import { AreaEntityMt } from '@d8d/geo-areas-mt';
+import { SystemConfigMt } from '@d8d/core-module-mt/system-config-module-mt/entities';
+import { TenantEntityMt } from '@d8d/tenant-module-mt';
+import { Advertisement, AdvertisementType } from '@d8d/advertisements-module-mt';
 
 // Mock 微信支付SDK
 vi.mock('wechatpay-node-v3', () => {
@@ -41,14 +51,27 @@ vi.mock('@d8d/core-module-mt/system-config-module-mt', () => ({
   }))
 }));
 
-// Mock 订单服务
-vi.mock('@d8d/orders-module-mt', () => ({
-  OrderMtService: vi.fn().mockImplementation(() => ({})),
-  OrderMt: vi.fn()
-}));
+// 使用部分mock来保留 OrderMt 实体
+vi.mock('@d8d/orders-module-mt', async (importOriginal) => {
+  const actual = await importOriginal<typeof import('@d8d/orders-module-mt')>()
+  return {
+    ...actual,
+    OrderMtService: vi.fn().mockImplementation(() => ({})),
+  }
+});
 
 // 设置集成测试钩子
-setupIntegrationDatabaseHooksWithEntities([PaymentMtEntity])
+setupIntegrationDatabaseHooksWithEntities([
+  PaymentMtEntity,
+  OrderMt,
+  OrderGoodsMt,
+  UserEntityMt,
+  RoleMt,
+  MerchantMt,
+  SupplierMt,
+  DeliveryAddressMt,
+  FileMt
+])
 
 describe('PaymentRefund Integration Tests', () => {
   let dataSource: DataSource;
@@ -58,6 +81,16 @@ describe('PaymentRefund Integration Tests', () => {
     // 获取集成测试数据库连接
     dataSource = await IntegrationTestDatabase.getDataSource();
     paymentService = new PaymentMtService(dataSource);
+
+    // 创建测试商户记录
+    const merchantRepository = dataSource.getRepository(MerchantMt);
+    const merchant = new MerchantMt();
+    merchant.tenantId = 1;
+    merchant.name = '测试商户';
+    merchant.contact = '测试联系人';
+    merchant.phone = '13800000000';
+    merchant.address = '测试地址';
+    await merchantRepository.save(merchant);
   });
 
   describe('refund method', () => {