Просмотр исходного кода

✨ feat(cloudflared): 新增本地开发隧道配置文件

- 添加cloudflared隧道配置文件,支持本地开发环境通过公网访问
- 配置隧道指向本地8080端口服务,提供404回退服务

✨ feat(order): 实现确认收货功能并集成微信支付验证

- 在订单按钮栏组件中添加确认收货功能,支持微信openBusinessView调用
- 实现确认收货API端点,包含订单状态验证和支付记录查询
- 添加支付记录查询API,用于获取微信支付交易信息
- 优化确认收货流程,包含参数验证、错误处理和降级方案
- 添加确认收货状态加载指示器,提升用户体验

🔧 chore(config): 更新测试环境支付回调地址

- 将支付回调地址从localhost改为公网可访问的URL,支持微信支付回调
- 更新小程序版本号至v0.0.9
yourname 1 месяц назад
Родитель
Сommit
aacad531ad

+ 7 - 0
cloudflared-config.yml

@@ -0,0 +1,7 @@
+tunnel: local-dev-tunnel
+credentials-file: /root/.cloudflared/cert.pem
+
+ingress:
+  - hostname: local-dev.d8d.fun
+    service: http://localhost:8080
+  - service: http_status:404

+ 399 - 11
mini/src/components/order/OrderButtonBar/index.tsx

@@ -2,7 +2,7 @@ import { View, Text } from '@tarojs/components'
 import Taro from '@tarojs/taro'
 import { useMutation, useQueryClient } from '@tanstack/react-query'
 import { InferResponseType } from 'hono'
-import { orderClient } from '@/api'
+import { orderClient, paymentClient } from '@/api'
 import { useState } from 'react'
 import CancelReasonDialog from '@/components/common/CancelReasonDialog'
 
@@ -96,7 +96,7 @@ export default function OrderButtonBar({ order, onViewDetail, onCancelOrder, hid
           setShowCancelDialog(false)
           // 调用取消订单API
           cancelOrderMutation.mutate({
-            orderId: order.id,
+            orderId: order.id!,
             reason
           })
         }
@@ -143,22 +143,391 @@ export default function OrderButtonBar({ order, onViewDetail, onCancelOrder, hid
     })
   }
 
+  // 确认收货mutation
+  const confirmReceiptMutation = useMutation({
+    mutationFn: async ({ orderId }: { orderId: number }) => {
+      const response = await orderClient['confirm-receipt'].$post({
+        json: { orderId }
+      })
+      if (response.status !== 200) {
+        throw new Error('确认收货失败')
+      }
+      return response.json()
+    },
+    onSuccess: () => {
+      // 刷新订单列表数据
+      queryClient.invalidateQueries({ queryKey: ['orders'] })
+      queryClient.invalidateQueries({ queryKey: ['order', order.id] })
+
+      // 显示成功信息
+      Taro.showToast({
+        title: '确认收货成功',
+        icon: 'success',
+        duration: 2000
+      })
+    },
+    onError: (error) => {
+      // 根据错误消息类型显示不同的用户友好提示
+      let errorMessage = '确认收货失败,请稍后重试'
+
+      if (error.message.includes('订单不存在')) {
+        errorMessage = '订单不存在或已被删除'
+      } else if (error.message.includes('只有已发货的订单才能确认收货')) {
+        errorMessage = '当前订单状态不允许确认收货'
+      } else if (error.message.includes('只有已支付的订单才能确认收货')) {
+        errorMessage = '订单未支付,无法确认收货'
+      } else if (error.message.includes('网络')) {
+        errorMessage = '网络连接失败,请检查网络后重试'
+      }
+
+      Taro.showToast({
+        title: errorMessage,
+        icon: 'error',
+        duration: 3000
+      })
+    }
+  })
+
   // 确认收货
   const handleConfirmReceipt = () => {
+    // 先显示确认对话框
     Taro.showModal({
       title: '确认收货',
       content: '确认已收到商品吗?',
       success: async (res) => {
         if (res.confirm) {
           try {
-            // 这里调用确认收货的API
-            Taro.showToast({
-              title: '已确认收货',
-              icon: 'success'
+            // 准备微信openBusinessView参数
+            const extraData: any = {
+              orderId: order.id!.toString(),
+              // 商户信息 - 需要根据实际微信商户配置填写
+              merchant_id: order.merchantId?.toString() || '1230000109'
+            }
+
+            // 设置商户订单号 - 优先使用订单号
+            extraData.merchant_trade_no = order.orderNo || `ORDER_${order.id}`
+            console.debug('使用商户订单号:', extraData.merchant_trade_no)
+
+            // 如果订单已支付,尝试获取支付记录信息(可选)
+            if (order.payState === 2) { // 已支付状态
+              console.debug('订单已支付,尝试查询支付记录...')
+
+              try {
+                // 使用订单API查询支付记录(新端点)
+                console.debug('使用订单API查询支付记录,订单ID:', order.id)
+
+                try {
+                  // 调用新的订单支付记录查询端点:GET /api/v1/orders/{orderId}/payment-record
+                  const paymentRecordResponse = await orderClient[order.id!.toString()]['payment-record'].$get()
+
+                  console.debug('支付记录API响应状态:', paymentRecordResponse.status, 'ok:', paymentRecordResponse.ok)
+
+                  if (paymentRecordResponse.ok) {
+                    const result = await paymentRecordResponse.json()
+                    console.debug('支付记录查询结果:', result)
+
+                    if (result.success && result.data?.exists) {
+                      const paymentInfo = result.data
+                      console.debug('找到支付记录信息:', paymentInfo)
+
+                      // 1. 优先使用支付记录中的商户订单号
+                      if (paymentInfo.outTradeNo) {
+                        extraData.merchant_trade_no = paymentInfo.outTradeNo
+                        console.debug('使用支付记录中的商户订单号:', paymentInfo.outTradeNo)
+                      } else {
+                        console.debug('支付记录中没有商户订单号,使用订单号:', extraData.merchant_trade_no)
+                      }
+
+                      // 2. 获取微信交易ID
+                      if (paymentInfo.wechatTransactionId) {
+                        extraData.transaction_id = paymentInfo.wechatTransactionId
+                        console.debug("获取到微信交易ID:", paymentInfo.wechatTransactionId);
+                      } else {
+                        console.debug('支付记录中没有微信交易ID,可能的原因:')
+                        console.debug('1. 微信支付回调还未到达(异步回调可能有延迟)')
+                        console.debug('2. 支付状态:', paymentInfo.paymentStatus)
+                        console.debug('3. 微信交易ID在支付回调成功时才会生成')
+                        console.debug('4. 当前时间:', new Date().toISOString())
+
+                        // 添加调试信息,帮助诊断问题
+                        console.debug('支付记录详情:', {
+                          订单ID: order.id,
+                          支付状态: paymentInfo.paymentStatus,
+                          是否存在微信交易ID: !!paymentInfo.wechatTransactionId,
+                          商户订单号: paymentInfo.outTradeNo,
+                          记录存在: paymentInfo.exists
+                        })
+                      }
+                    } else {
+                      console.debug('未找到支付记录或查询失败:', result.message)
+                      console.debug('支付记录查询结果:', result)
+                    }
+                  } else {
+                    console.warn('支付记录API请求失败,状态:', paymentRecordResponse.status)
+                  }
+                } catch (apiError) {
+                  console.debug('支付记录API调用失败:', apiError.message)
+                  // 如果新端点不可用,回退到旧方法
+                  console.debug('尝试使用旧方法查询支付记录...')
+
+                  // 方法1:尝试使用支付状态查询端点(如果可用)
+                  try {
+                    const paymentStatusResponse = await paymentClient['payment']['status'].$get({
+                      query: { orderId: order.id!.toString() }
+                    })
+
+                    if (paymentStatusResponse.ok) {
+                      const statusData = await paymentStatusResponse.json()
+                      console.debug('支付状态查询成功:', statusData)
+                      // 这里可以记录支付状态,但无法获取商户订单号和微信交易ID
+                    }
+                  } catch (statusError) {
+                    console.debug('支付状态查询端点不可用或失败:', statusError.message)
+                  }
+
+                  // 方法2:尝试使用通用的GET查询(可能不存在)
+                  // 注意:由于支付模块可能没有实现通用的CRUD路由,这里需要类型断言
+                  try {
+                    // 使用类型断言来访问可能的$get方法
+                    const paymentClientAny = paymentClient as any
+                    if (paymentClientAny.$get) {
+                      const paymentResponse = await paymentClientAny.$get({
+                        query: {
+                          filters: JSON.stringify([{ field: 'externalOrderId', operator: '=', value: order.id }]),
+                          pageSize: 1
+                        }
+                      })
+
+                      console.debug('支付API响应状态:', paymentResponse.status, 'ok:', paymentResponse.ok)
+
+                      if (paymentResponse.ok) {
+                        let payments
+                        try {
+                          const responseText = await paymentResponse.text()
+                          console.debug('支付API响应文本:', responseText.substring(0, 200)) // 只显示前200字符
+
+                          if (responseText) {
+                            // 检查响应是否是HTML(以<开头)
+                            const trimmedText = responseText.trim()
+                            if (trimmedText.startsWith('<')) {
+                              console.warn('支付API返回HTML响应,可能是404错误页面')
+                              console.debug('HTML响应开头:', trimmedText.substring(0, 100))
+                              payments = { data: [] }
+                            } else {
+                              // 尝试解析JSON
+                              payments = JSON.parse(responseText)
+                            }
+                          } else {
+                            console.warn('支付API返回空响应')
+                            payments = { data: [] }
+                          }
+                        } catch (jsonError) {
+                          console.error('支付API响应JSON解析失败:', jsonError)
+                          console.warn('跳过支付记录查询,使用订单信息')
+                          payments = { data: [] }
+                        }
+
+                        const payment = payments.data?.[0]
+
+                        if (payment) {
+                          console.debug('找到支付记录:', {
+                            paymentId: payment.id,
+                            outTradeNo: payment.outTradeNo,
+                            wechatTransactionId: payment.wechatTransactionId,
+                            paymentStatus: payment.paymentStatus,
+                          })
+
+                          // 1. 优先使用支付记录中的商户订单号
+                          if (payment.outTradeNo) {
+                            extraData.merchant_trade_no = payment.outTradeNo
+                            console.debug('使用支付记录中的商户订单号:', payment.outTradeNo)
+                          }
+
+                          // 2. 获取微信交易ID
+                          if (payment.wechatTransactionId) {
+                            extraData.transaction_id = payment.wechatTransactionId
+                            console.debug("获取到微信交易ID:", payment.wechatTransactionId);
+                          } else {
+                            console.debug('支付记录中没有微信交易ID')
+                          }
+                        } else {
+                          console.debug('未找到支付记录')
+                        }
+                      } else {
+                        console.warn('支付API请求失败,状态:', paymentResponse.status)
+                        // 对于非200响应,不尝试解析内容
+                        if (paymentResponse.status === 404) {
+                          console.warn('支付API端点不存在(404)')
+                        }
+                      }
+                    } else {
+                      console.debug('支付客户端没有$get方法,跳过通用查询')
+                    }
+                  } catch (getError) {
+                    console.debug('通用GET查询失败(可能端点不存在):', getError.message)
+                  }
+                }
+              } catch (error) {
+                console.warn('查询支付记录失败,但不影响主要功能:', error.message)
+                // 支付记录查询失败不影响确认收货功能
+              }
+            } else {
+              console.debug('订单未支付或支付状态不是已支付')
+            }
+
+            // 参数验证和优化
+            const businessType = 'weappOrderConfirm' // 固定配置的业务类型
+
+            // 优化extraData,确保必要参数存在
+            const optimizedExtraData = {
+              // 必需参数
+              orderId: extraData.orderId,
+              merchant_id: extraData.merchant_id,
+              merchant_trade_no: extraData.merchant_trade_no,
+              // 可选参数 - 只在存在时添加
+              ...(extraData.transaction_id && { transaction_id: extraData.transaction_id }),
+              ...(extraData.order_no && { order_no: extraData.order_no }),
+            }
+
+            console.debug('微信openBusinessView参数:', {
+              businessType,
+              optimizedExtraData,
+              // 检查必要参数是否存在
+              hasOrderId: !!optimizedExtraData.orderId,
+              hasMerchantId: !!optimizedExtraData.merchant_id,
+              hasMerchantTradeNo: !!optimizedExtraData.merchant_trade_no,
+              hasTransactionId: !!optimizedExtraData.transaction_id,
+              orderState: order.state,
+              payState: order.payState,
+              // 原始extraData用于调试
+              originalExtraData: extraData
             })
+
+            console.debug('开始调用微信openBusinessView...')
+
+            // 检查Taro.openBusinessView是否存在
+            if (typeof Taro.openBusinessView !== 'function') {
+              console.error('Taro.openBusinessView不存在!可能的原因:')
+              console.error('1. 微信基础库版本过低')
+              console.error('2. 该API需要特定的微信版本')
+              console.error('3. 开发工具需要更新')
+
+              Taro.showModal({
+                title: '功能不可用',
+                content: '当前微信版本过低,无法使用确认收货功能,请升级微信后重试。',
+                showCancel: false
+              })
+
+              // 降级到普通确认
+              Taro.showToast({
+                title: '正在确认收货...',
+                icon: 'none',
+                duration: 1500
+              })
+
+              // 直接调用后端API
+              confirmReceiptMutation.mutate({ orderId: order.id! })
+              return
+            }
+
+            // 使用固定的businessType: weappOrderConfirm
+            try {
+              console.debug(`调用微信openBusinessView,businessType: ${businessType}`)
+
+              await Taro.openBusinessView({
+                businessType,
+                extraData: optimizedExtraData,
+              })
+
+              console.log(`微信openBusinessView调用成功,businessType: ${businessType}`)
+
+            } catch (wxError) {
+              console.warn(`微信openBusinessView调用失败:`, wxError)
+
+              // 尝试分析失败原因
+              if (wxError.errMsg) {
+                console.warn('微信API错误信息:', wxError.errMsg)
+
+                // 检查是否是参数错误
+                if (wxError.errMsg.includes('parameter') || wxError.errMsg.includes('参数')) {
+                  console.warn('可能是参数错误,尝试简化参数...')
+
+                  // 尝试使用最简参数
+                  const minimalExtraData = {
+                    orderId: optimizedExtraData.orderId,
+                    merchant_trade_no: optimizedExtraData.merchant_trade_no,
+                  }
+
+                  try {
+                    console.debug('尝试使用简化参数:', minimalExtraData)
+                    await Taro.openBusinessView({
+                      businessType,
+                      extraData: minimalExtraData,
+                    })
+                    console.log('使用简化参数调用成功')
+                  } catch (minimalError) {
+                    console.warn('简化参数也失败:', minimalError)
+                  }
+                }
+              }
+
+              // 降级方案:使用微信showModal作为替代界面
+              try {
+                const modalRes = await Taro.showModal({
+                  title: '确认收货',
+                  content: '请确认您已收到商品。\n\n如商品有质量问题,请及时联系客服。',
+                  confirmText: '确认收货',
+                  cancelText: '取消',
+                  showCancel: true
+                })
+
+                if (modalRes.confirm) {
+                  console.log('用户通过微信Modal确认收货')
+                  // 显示处理中的提示
+                  Taro.showToast({
+                    title: '正在确认收货...',
+                    icon: 'none',
+                    duration: 1500
+                  })
+                } else {
+                  console.log('用户取消确认收货')
+                  Taro.showToast({
+                    title: '已取消',
+                    icon: 'none',
+                    duration: 1500
+                  })
+                  return // 用户取消,不继续后续流程
+                }
+              } catch (modalError) {
+                console.warn('微信Modal也失败,直接继续:', modalError)
+                // 即使Modal失败,也继续后续流程
+                Taro.showToast({
+                  title: '正在确认收货...',
+                  icon: 'none',
+                  duration: 1500
+                })
+              }
+
+              // 记录详细错误信息便于调试
+              console.error('微信openBusinessView详细错误:', {
+                error: wxError,
+                businessType,
+                optimizedExtraData,
+                originalExtraData: extraData,
+                orderId: order.id,
+                orderState: order.state,
+                payState: order.payState
+              })
+            }
+
+            // 微信界面操作成功后(或降级后),调用后端确认收货API
+            console.debug('开始调用后端确认收货API...')
+            confirmReceiptMutation.mutate({ orderId: order.id! })
+
           } catch (error) {
+            console.error('确认收货失败:', error)
             Taro.showToast({
-              title: '确认失败',
+              title: '确认收货失败',
               icon: 'error'
             })
           }
@@ -167,6 +536,7 @@ export default function OrderButtonBar({ order, onViewDetail, onCancelOrder, hid
     })
   }
 
+
   // 申请退款 - 调用取消订单逻辑
   const handleApplyRefund = () => {
     handleCancelOrder()
@@ -252,12 +622,30 @@ export default function OrderButtonBar({ order, onViewDetail, onCancelOrder, hid
               button.type === 'primary'
                 ? 'bg-primary text-white border-primary'
                 : 'bg-white text-gray-600 border-gray-300'
-            } ${cancelOrderMutation.isPending && button.text === '取消订单' ? 'opacity-50' : ''}`}
-            onClick={cancelOrderMutation.isPending && button.text === '取消订单' ? undefined : button.onClick}
-            data-testid={button.text === '取消订单' ? 'cancel-order-button' : undefined}
+            } ${
+              (cancelOrderMutation.isPending && button.text === '取消订单') ||
+              (confirmReceiptMutation.isPending && button.text === '确认收货')
+                ? 'opacity-50'
+                : ''
+            }`}
+            onClick={
+              (cancelOrderMutation.isPending && button.text === '取消订单') ||
+              (confirmReceiptMutation.isPending && button.text === '确认收货')
+                ? undefined
+                : button.onClick
+            }
+            data-testid={
+              button.text === '取消订单' ? 'cancel-order-button' :
+              button.text === '确认收货' ? 'confirm-receipt-button' :
+              undefined
+            }
           >
             <Text>
-              {cancelOrderMutation.isPending && button.text === '取消订单' ? '取消中...' : button.text}
+              {cancelOrderMutation.isPending && button.text === '取消订单'
+                ? '取消中...'
+                : confirmReceiptMutation.isPending && button.text === '确认收货'
+                ? '确认中...'
+                : button.text}
             </Text>
           </View>
         ))}

+ 1 - 1
mini/src/components/order/OrderCard/index.tsx

@@ -81,7 +81,7 @@ export default function OrderCard({ order, orderStatusMap, payStatusMap, onViewD
           </View>
         )}
       </View>
-
+                                                                                                                   
       {/* 订单底部信息 */}
       <View className="px-4 py-3 border-t border-gray-100">
         <View className="flex justify-between items-center mb-3">

+ 1 - 1
mini/src/pages/profile/index.tsx

@@ -364,7 +364,7 @@ const ProfilePage: React.FC = () => {
         {/* 版本信息 */}
         <View className="pb-8">
           <Text className="text-center text-xs text-gray-400">
-            v0.0.8 - 小程序版
+            v0.0.9 - 小程序版
           </Text>
         </View>
       </ScrollView>

+ 3 - 1
packages/mini-payment-mt/.env.test

@@ -2,7 +2,9 @@
 WECHAT_MERCHANT_ID=test_merchant_id
 WX_MINI_APP_ID=test_app_id
 WECHAT_V3_KEY=test_v3_key
-WECHAT_PAY_NOTIFY_URL=http://localhost:8080/api/v1/payment/callback
+# 使用公网可访问的地址(微信支付要求HTTPS,但测试环境可能允许HTTP)
+# 注意:正式环境必须使用HTTPS
+WECHAT_PAY_NOTIFY_URL=http://d8d-ai-vscode-8080-186-175-template-22-group.r.d8d.fun/api/v1/payment/callback
 WECHAT_MERCHANT_CERT_SERIAL_NO=test_cert_serial_no
 WECHAT_PUBLIC_KEY=test_public_key
 WECHAT_PRIVATE_KEY=test_private_key

+ 3 - 1
packages/mini-payment/.env.test

@@ -2,7 +2,9 @@
 WECHAT_MERCHANT_ID=test_merchant_id
 WX_MINI_APP_ID=test_app_id
 WECHAT_V3_KEY=test_v3_key
-WECHAT_PAY_NOTIFY_URL=http://localhost:8080/api/v1/payment/callback
+# 使用公网可访问的地址(微信支付要求HTTPS,但测试环境可能允许HTTP)
+# 注意:正式环境必须使用HTTPS
+WECHAT_PAY_NOTIFY_URL=http://d8d-ai-vscode-8080-186-175-template-22-group.r.d8d.fun/api/v1/payment/callback
 WECHAT_MERCHANT_CERT_SERIAL_NO=test_cert_serial_no
 WECHAT_PUBLIC_KEY=test_public_key
 WECHAT_PRIVATE_KEY=test_private_key

+ 99 - 0
packages/orders-module-mt/src/routes/user/confirm-receipt.mt.ts

@@ -0,0 +1,99 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { authMiddleware } from '@d8d/auth-module-mt';
+import { AppDataSource, ErrorSchema } from '@d8d/shared-utils';
+import { AuthContext } from '@d8d/shared-types';
+import { OrderMtService } from '../../services';
+import { ConfirmReceiptRequestDto, ConfirmReceiptResponseDto } from '../../schemas/confirm-receipt.schema';
+
+const confirmReceiptRoute = createRoute({
+  method: 'post',
+  path: '/confirm-receipt',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: ConfirmReceiptRequestDto
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '确认收货成功',
+      content: {
+        'application/json': {
+          schema: ConfirmReceiptResponseDto
+        }
+      }
+    },
+    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 confirmReceiptRoutes = new OpenAPIHono<AuthContext>()
+  // 确认收货路由
+  .openapi( confirmReceiptRoute, async (c) => {
+      const data = c.req.valid('json');
+      const user = c.get('user');
+
+      try {
+        const orderService = new OrderMtService(AppDataSource);
+        const result = await orderService.confirmReceipt(user.tenantId, data.orderId, user.id);
+
+        return c.json({
+          success: true,
+          message: '确认收货成功',
+          data: {
+            orderId: result.orderId,
+            state: result.state,
+            updatedAt: result.updatedAt.toISOString(),
+          }
+        }, 200);
+      } catch (error) {
+        console.error('确认收货失败:', error);
+
+        // 根据错误类型返回不同的状态码
+        if (error instanceof Error) {
+          if (error.message === '订单不存在') {
+            return c.json(
+              { code: 404, message: error.message },
+              404
+            );
+          } else if (error.message === '只有已发货的订单才能确认收货') {
+            return c.json(
+              { code: 403, message: error.message },
+              403
+            );
+          } else if (error.message === '只有已支付的订单才能确认收货') {
+            return c.json(
+              { code: 403, message: error.message },
+              403
+            );
+          }
+        }
+
+        return c.json(
+          { code: 500, message: error instanceof Error ? error.message : '确认收货失败' },
+          500
+        );
+      }
+    }
+  );
+
+export default confirmReceiptRoutes;

+ 4 - 0
packages/orders-module-mt/src/routes/user/orders.mt.ts

@@ -6,6 +6,8 @@ import { authMiddleware } from '@d8d/auth-module-mt';
 import { OpenAPIHono } from '@hono/zod-openapi';
 import createOrderRoutes from './create-order.mt';
 import cancelOrderRoutes from './cancel-order.mt';
+import confirmReceiptRoutes from './confirm-receipt.mt';
+import paymentRecordRoutes from './payment-record.mt';
 
 // 多租户用户订单路由 - 有数据权限限制,只能访问自己的订单
 const userOrderCrudRoutes = createCrudRoutes({
@@ -34,6 +36,8 @@ const userOrderCrudRoutes = createCrudRoutes({
 const userOrderRoutes = new OpenAPIHono()
   .route('/', createOrderRoutes)
   .route('/', cancelOrderRoutes)
+  .route('/', confirmReceiptRoutes)
+  .route('/', paymentRecordRoutes)
   .route('/', userOrderCrudRoutes)
 
 export default userOrderRoutes;

+ 131 - 0
packages/orders-module-mt/src/routes/user/payment-record.mt.ts

@@ -0,0 +1,131 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { z } from 'zod';
+import { AppDataSource } from '@d8d/shared-utils';
+import { authMiddleware } from '@d8d/auth-module-mt';
+import { AuthContext } from '@d8d/shared-types';
+import { OrderMtService } from '../../services/order.mt.service.js';
+
+// 支付记录查询路由定义 - 多租户版本
+const paymentRecordRoute = createRoute({
+  method: 'get',
+  path: '/:orderId/payment-record',
+  middleware: [authMiddleware],
+  request: {
+    params: z.object({
+      orderId: z.string().transform(val => parseInt(val)).pipe(z.number().int().positive())
+    })
+  },
+  responses: {
+    200: {
+      description: '支付记录查询成功',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean(),
+            data: z.object({
+              outTradeNo: z.string().nullable(),
+              wechatTransactionId: z.string().nullable(),
+              paymentStatus: z.string(),
+              exists: z.boolean()
+            }).nullable(),
+            message: z.string()
+          })
+        }
+      }
+    },
+    400: {
+      description: '参数错误',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean(),
+            message: z.string()
+          })
+        }
+      }
+    },
+    401: {
+      description: '未授权',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean(),
+            message: z.string()
+          })
+        }
+      }
+    },
+    404: {
+      description: '订单不存在',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean(),
+            message: z.string()
+          })
+        }
+      }
+    },
+    500: {
+      description: '服务器错误',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean(),
+            message: z.string()
+          })
+        }
+      }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>()
+  .openapi(paymentRecordRoute, async (c) => {
+    try {
+      const { orderId } = c.req.valid('param');
+      const user = c.get('user');
+
+      // 创建订单服务实例
+      const orderService = new OrderMtService(AppDataSource);
+
+      // 首先验证订单是否存在且属于当前用户
+      const orderRepository = AppDataSource.getRepository('OrderMt');
+      const order = await orderRepository.findOne({
+        where: {
+          id: orderId,
+          tenantId: user.tenantId,
+          userId: user.id
+        }
+      });
+
+      if (!order) {
+        return c.json({
+          success: false,
+          message: '订单不存在或无权访问'
+        }, 404);
+      }
+
+      // 查询支付记录信息
+      const paymentInfo = await orderService.getPaymentInfoByOrderId(
+        user.tenantId,
+        orderId,
+        user.id
+      );
+
+      return c.json({
+        success: true,
+        data: paymentInfo,
+        message: paymentInfo.exists ? '支付记录查询成功' : '未找到支付记录'
+      }, 200);
+
+    } catch (error) {
+      console.error('支付记录查询失败:', error);
+      return c.json({
+        success: false,
+        message: error instanceof Error ? error.message : '支付记录查询失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 21 - 0
packages/orders-module-mt/src/schemas/confirm-receipt.schema.ts

@@ -0,0 +1,21 @@
+import { z } from 'zod';
+
+// 确认收货请求DTO
+export const ConfirmReceiptRequestDto = z.object({
+  orderId: z.number().int().positive('订单ID必须为正整数'),
+});
+
+export type ConfirmReceiptRequestDto = z.infer<typeof ConfirmReceiptRequestDto>;
+
+// 确认收货响应DTO
+export const ConfirmReceiptResponseDto = z.object({
+  success: z.boolean(),
+  message: z.string(),
+  data: z.object({
+    orderId: z.number(),
+    state: z.number(),
+    updatedAt: z.string().datetime(),
+  }).optional(),
+});
+
+export type ConfirmReceiptResponseDto = z.infer<typeof ConfirmReceiptResponseDto>;

+ 197 - 0
packages/orders-module-mt/src/services/order.mt.service.ts

@@ -7,6 +7,7 @@ import { GoodsMt } from '@d8d/goods-module-mt';
 import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
 import { PaymentMtService } from '@d8d/mini-payment-mt';
 import { CreditBalanceService } from '@d8d/credit-balance-module-mt';
+import { PaymentMtEntity, PaymentStatus } from '@d8d/mini-payment-mt';
 import type { CreateOrderRequest } from '../schemas/create-order.schema';
 
 export class OrderMtService extends GenericCrudService<OrderMt> {
@@ -290,6 +291,75 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
     }
   }
 
+  /**
+   * 确认收货
+   * @param tenantId 租户ID
+   * @param orderId 订单ID
+   * @param userId 用户ID
+   * @returns 更新后的订单信息
+   */
+  async confirmReceipt(tenantId: number, orderId: number, userId: number): Promise<{
+    orderId: number;
+    state: number;
+    updatedAt: Date;
+  }> {
+    const queryRunner = this.dataSource.createQueryRunner();
+    await queryRunner.connect();
+    await queryRunner.startTransaction();
+
+    try {
+      // 查询订单信息
+      const order = await this.repository.findOne({
+        where: { id: orderId, tenantId, userId }
+      });
+
+      if (!order) {
+        throw new Error('订单不存在');
+      }
+
+      // 验证订单状态:只有已发货的订单才能确认收货
+      if (order.state !== 1) {
+        throw new Error('只有已发货的订单才能确认收货');
+      }
+
+      // 验证支付状态:只有已支付的订单才能确认收货
+      if (order.payState !== 2) {
+        throw new Error('只有已支付的订单才能确认收货');
+      }
+
+      // 更新订单状态为收货成功 (state = 2)
+      await queryRunner.manager.update(OrderMt, { id: orderId, tenantId }, {
+        state: 2, // 收货成功
+        updatedBy: userId,
+        updatedAt: new Date()
+      });
+
+      // 提交事务
+      await queryRunner.commitTransaction();
+
+      // 返回更新后的订单信息
+      const updatedOrder = await this.repository.findOne({
+        where: { id: orderId, tenantId }
+      });
+
+      if (!updatedOrder) {
+        throw new Error('订单更新后查询失败');
+      }
+
+      return {
+        orderId: updatedOrder.id,
+        state: updatedOrder.state,
+        updatedAt: updatedOrder.updatedAt
+      };
+
+    } catch (error) {
+      await queryRunner.rollbackTransaction();
+      throw error;
+    } finally {
+      await queryRunner.release();
+    }
+  }
+
   /**
    * 生成订单号
    * 格式:年月日时分秒 + 6位随机数
@@ -329,4 +399,131 @@ export class OrderMtService extends GenericCrudService<OrderMt> {
     await this.orderRefundRepository.save(refundRecord);
     console.debug(`[租户${tenantId}] 退款记录创建成功,退款订单号: ${refundResult.outRefundNo}`);
   }
+
+  /**
+   * 根据订单ID查询支付记录
+   * @param tenantId 租户ID
+   * @param orderId 订单ID
+   * @param userId 用户ID(可选,用于权限验证)
+   * @returns 支付记录信息,如果不存在则返回null
+   */
+  async getPaymentRecordByOrderId(
+    tenantId: number,
+    orderId: number,
+    userId?: number
+  ): Promise<PaymentMtEntity | null> {
+    try {
+      // 获取支付实体仓库
+      const paymentRepository = this.dataSource.getRepository(PaymentMtEntity);
+
+      // 构建查询条件
+      const whereCondition: any = {
+        externalOrderId: orderId,
+        tenantId
+      };
+
+      // 如果提供了用户ID,则添加用户ID过滤(确保用户只能查询自己的支付记录)
+      if (userId !== undefined) {
+        whereCondition.userId = userId;
+      }
+
+      // 查询支付记录
+      const paymentRecord = await paymentRepository.findOne({
+        where: whereCondition,
+        order: { createdAt: 'DESC' } // 按创建时间倒序,获取最新的支付记录
+      });
+
+      if (!paymentRecord) {
+        console.debug(`[租户${tenantId}] 未找到订单ID ${orderId} 的支付记录`);
+        return null;
+      }
+
+      console.debug(`[租户${tenantId}] 找到订单ID ${orderId} 的支付记录:`, {
+        paymentId: paymentRecord.id,
+        outTradeNo: paymentRecord.outTradeNo,
+        wechatTransactionId: paymentRecord.wechatTransactionId,
+        paymentStatus: paymentRecord.paymentStatus,
+        totalAmount: paymentRecord.totalAmount
+      });
+
+      return paymentRecord;
+    } catch (error) {
+      console.error(`[租户${tenantId}] 查询订单ID ${orderId} 的支付记录失败:`, error);
+      throw new Error(`查询支付记录失败: ${error instanceof Error ? error.message : '未知错误'}`);
+    }
+  }
+
+  /**
+   * 根据订单ID查询支付记录(简化版,返回必要信息)
+   * @param tenantId 租户ID
+   * @param orderId 订单ID
+   * @param userId 用户ID(可选)
+   * @returns 支付记录的关键信息
+   */
+  async getPaymentInfoByOrderId(
+    tenantId: number,
+    orderId: number,
+    userId?: number
+  ): Promise<{
+    outTradeNo: string | null;
+    wechatTransactionId: string | null;
+    paymentStatus: string;
+    exists: boolean;
+    paymentId?: number;
+    totalAmount?: number;
+    createdAt?: Date;
+    hasWechatTransactionId: boolean;
+    paymentStatusDetail: string;
+  }> {
+    try {
+      const paymentRecord = await this.getPaymentRecordByOrderId(tenantId, orderId, userId);
+
+      if (!paymentRecord) {
+        return {
+          outTradeNo: null,
+          wechatTransactionId: null,
+          paymentStatus: '未找到支付记录',
+          exists: false,
+          hasWechatTransactionId: false,
+          paymentStatusDetail: '支付记录不存在'
+        };
+      }
+
+      const hasWechatTransactionId = !!paymentRecord.wechatTransactionId;
+      let paymentStatusDetail = paymentRecord.paymentStatus || '未知状态';
+
+      // 添加详细的支付状态说明
+      if (!hasWechatTransactionId) {
+        if (paymentRecord.paymentStatus === PaymentStatus.PAID) {
+          paymentStatusDetail = '已支付(等待微信回调更新交易ID)';
+        } else if (paymentRecord.paymentStatus === PaymentStatus.PROCESSING) {
+          paymentStatusDetail = '支付处理中(微信交易ID将在支付成功后生成)';
+        } else if (paymentRecord.paymentStatus === PaymentStatus.PENDING) {
+          paymentStatusDetail = '待支付(尚未完成支付)';
+        }
+      }
+
+      return {
+        outTradeNo: paymentRecord.outTradeNo || null,
+        wechatTransactionId: paymentRecord.wechatTransactionId || null,
+        paymentStatus: paymentRecord.paymentStatus || '未知状态',
+        exists: true,
+        paymentId: paymentRecord.id,
+        totalAmount: paymentRecord.totalAmount,
+        createdAt: paymentRecord.createdAt,
+        hasWechatTransactionId,
+        paymentStatusDetail
+      };
+    } catch (error) {
+      console.error(`[租户${tenantId}] 获取订单ID ${orderId} 的支付信息失败:`, error);
+      return {
+        outTradeNo: null,
+        wechatTransactionId: null,
+        paymentStatus: '查询失败',
+        exists: false,
+        hasWechatTransactionId: false,
+        paymentStatusDetail: `查询失败: ${error instanceof Error ? error.message : '未知错误'}`
+      };
+    }
+  }
 }