Ver Fonte

fix: 修复 wx.openBusinessView mch_id 为空导致无法拉起支付分确认页

预创建响应增加 mchId,前端用后端返回的商户号替代硬编码空字符串。
同时修复预创建产生孤儿订单的 bug:不再预创建本地 Order,
统一由 AI 识别回调创建订单后通过 Redis 关联支付分信息。
API 路径收拢到 config.ts 统一管理。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline há 2 semanas atrás
pai
commit
43e4b75370

+ 8 - 7
haha-mp/src/api/payscore.ts

@@ -3,6 +3,7 @@
  */
 
 import { get, post } from '../utils/request';
+import { API_PATHS } from '../utils/config';
 
 /**
  * 预创建支付分服务订单(需确认模式,开门前调用)
@@ -13,14 +14,14 @@ export interface PreCreatePayscoreOrderRequest {
 
 export interface PreCreatePayscoreOrderResponse {
   success: boolean;
-  orderId: number;
   outOrderNo: string;
   package: string;
+  mchId?: string;
   errorMsg?: string;
 }
 
 export const preCreatePayscoreOrder = (params: PreCreatePayscoreOrderRequest): Promise<PreCreatePayscoreOrderResponse> => {
-  return post<PreCreatePayscoreOrderResponse>('/payscore/pre-create', params);
+  return post<PreCreatePayscoreOrderResponse>(API_PATHS.preCreatePayscoreOrder, params);
 };
 
 /**
@@ -40,7 +41,7 @@ export interface CreatePayscoreOrderResponse {
 }
 
 export const createPayscoreOrder = (params: CreatePayscoreOrderRequest): Promise<CreatePayscoreOrderResponse> => {
-  return post<CreatePayscoreOrderResponse>('/payscore/create', params);
+  return post<CreatePayscoreOrderResponse>(API_PATHS.createPayscoreOrder, params);
 };
 
 /**
@@ -54,14 +55,14 @@ export interface ModifyPayscoreOrderRequest {
 }
 
 export const modifyPayscoreOrder = (params: ModifyPayscoreOrderRequest): Promise<{ outOrderNo: string; state: string }> => {
-  return post<{ outOrderNo: string; state: string }>('/payscore/modify', params);
+  return post<{ outOrderNo: string; state: string }>(API_PATHS.modifyPayscoreOrder, params);
 };
 
 /**
  * 同步订单支付信息(用户已通过其他方式支付)
  */
 export const syncPayscoreOrderInfo = (orderId: number, detail?: string): Promise<{ outOrderNo: string; state: string }> => {
-  return post<{ outOrderNo: string; state: string }>(`/payscore/sync-info/${orderId}`, { detail });
+  return post<{ outOrderNo: string; state: string }>(API_PATHS.syncPayscoreOrderInfo(orderId), { detail });
 };
 
 /**
@@ -73,12 +74,12 @@ export interface RefundPayscoreOrderRequest {
 }
 
 export const refundPayscoreOrder = (orderId: number, params: RefundPayscoreOrderRequest): Promise<{ outOrderNo: string; refundId: string }> => {
-  return post<{ outOrderNo: string; refundId: string }>(`/payscore/refund/${orderId}`, params);
+  return post<{ outOrderNo: string; refundId: string }>(API_PATHS.refundPayscoreOrder(orderId), params);
 };
 
 /**
  * 查询支付分退款状态
  */
 export const queryPayscoreRefund = (orderId: number, outRefundNo: string): Promise<{ outOrderNo: string; status: string; amount: number }> => {
-  return get<{ outOrderNo: string; status: string; amount: number }>(`/payscore/refund/${orderId}/${outRefundNo}`);
+  return get<{ outOrderNo: string; status: string; amount: number }>(API_PATHS.queryPayscoreRefund(orderId, outRefundNo));
 };

+ 1 - 16
haha-mp/src/pages/index/index.vue

@@ -61,7 +61,6 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import { scanDoor } from '../../api/device'
 import { preCreatePayscoreOrder } from '../../api/payscore'
@@ -75,20 +74,6 @@ onShow(() => {
   logger.log('[首页 onShow] token 状态:', token ? '存在' : '不存在')
 })
 
-// 处理支付分开通成功
-const onPayscoreEnabled = () => {
-  logger.log('[首页] 用户已开通支付分')
-  // 可以自动触发扫码
-  setTimeout(() => {
-    scanCode()
-  }, 500)
-}
-
-// 定义暴露给其他页面的方法
-defineExpose({
-  onPayscoreEnabled
-})
-
 const scanCode = async () => {
   // 检查登录状态
   if (!isLoggedIn()) {
@@ -187,7 +172,7 @@ const confirmPayScoreAndOpenDoor = async (deviceId: string) => {
     wx.openBusinessView({
       businessType: 'payscore',
       extraData: {
-        mch_id: '',
+        mch_id: result.mchId || '',
         package: result.package,
       },
       success: async () => {

+ 5 - 0
haha-mp/src/utils/config.ts

@@ -47,7 +47,12 @@ export const API_PATHS = {
   cancelOrder: '/order/cancel',
   
   // 支付分相关
+  preCreatePayscoreOrder: '/payscore/pre-create',
   createPayscoreOrder: '/payscore/create',
+  modifyPayscoreOrder: '/payscore/modify',
+  syncPayscoreOrderInfo: (orderId: number) => `/payscore/sync-info/${orderId}`,
+  refundPayscoreOrder: (orderId: number) => `/payscore/refund/${orderId}`,
+  queryPayscoreRefund: (orderId: number, outRefundNo: string) => `/payscore/refund/${orderId}/${outRefundNo}`,
   
   // 优惠券相关
   getMyCoupons: '/coupon/my',

+ 18 - 31
haha-service/src/main/java/com/haha/service/payment/payscore/impl/PayScoreServiceImpl.java

@@ -558,6 +558,13 @@ public class PayScoreServiceImpl implements PayScoreService {
         return "PS" + (System.currentTimeMillis() / 1000) + orderId;
     }
 
+    private String generatePreAuthOutOrderNo(String deviceId) {
+        // 预授权模式:无本地订单ID,PS(2) + 时间戳(10) + 设备ID后10位 = 最多22字符
+        String safeDeviceId = deviceId.replaceAll("[^A-Za-z0-9]", "");
+        String suffix = safeDeviceId.length() > 10 ? safeDeviceId.substring(safeDeviceId.length() - 10) : safeDeviceId;
+        return "PS" + (System.currentTimeMillis() / 1000) + suffix;
+    }
+
     private String formatDateTime(LocalDateTime dateTime) {
         return dateTime.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
     }
@@ -725,7 +732,6 @@ public class PayScoreServiceImpl implements PayScoreService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
     public Map<String, Object> preCreatePayScoreOrder(Long userId, String deviceId, String openId) {
         log.info("[支付分服务] 预创建服务订单(需确认模式)- userId: {}, deviceId: {}", userId, deviceId);
 
@@ -749,23 +755,12 @@ public class PayScoreServiceImpl implements PayScoreService {
             openId = user.getOpenid();
         }
 
-        // 1. 创建本地预订单(金额为0,识别后回调更新)
-        Order order = new Order();
-        order.setOrderNo(com.haha.common.utils.OrderUtils.getOrderNo());
-        order.setUserId(userId);
-        order.setDeviceId(deviceId);
-        order.setTotalAmount(BigDecimal.ZERO);
-        order.setPayChannel(PaymentChannel.WECHAT_PAYSCORE.getCode());
-        order.setPayType("微信支付分");
-        order.setStatus(OrderStatus.PENDING_PAYMENT.getCode());
-        order.setPayStatus(PayStatus.UNPAID.getCode());
-        order.setCreateTime(LocalDateTime.now());
-        orderService.save(order);
-        log.info("[支付分服务] 预订单已创建 - orderId: {}", order.getId());
-
-        // 2. 创建微信支付分服务订单
+        // 生成商户服务订单号(不创建本地订单,由 AI 识别回调统一创建)
+        String outOrderNo = generatePreAuthOutOrderNo(deviceId);
+
+        // 创建微信支付分服务订单
         PayScoreCreateRequest request = new PayScoreCreateRequest();
-        request.setOutOrderNo(generateOutOrderNo(order.getId()));
+        request.setOutOrderNo(outOrderNo);
         request.setServiceIntroduction("智能零售服务");
         request.setOpenId(openId);
         request.setRiskFundName("DEPOSIT");
@@ -776,16 +771,7 @@ public class PayScoreServiceImpl implements PayScoreService {
         PayScoreResult result = payScoreStrategy.createServiceOrder(request);
 
         if (result.isSuccess()) {
-            // 更新订单的支付分字段
-            order.setPayScoreOrderId(result.getOutOrderNo());
-            order.setPayScoreState(result.getState());
-            if (wxPayConfig != null) {
-                order.setServiceId(wxPayConfig.getServiceId());
-            }
-            order.setServiceStartTime(LocalDateTime.now());
-            orderService.updateById(order);
-
-            // 3. 将支付分信息存入 Redis,供回调创建订单时关联
+            // 将支付分信息存入 Redis,供 AI 识别回调创建订单时关联
             if (stringRedisTemplate != null) {
                 String redisKey = REDIS_KEY_PREFIX + deviceId + ":" + userId;
                 JSONObject json = new JSONObject();
@@ -794,20 +780,21 @@ public class PayScoreServiceImpl implements PayScoreService {
                 if (wxPayConfig != null) {
                     json.put("serviceId", wxPayConfig.getServiceId());
                 }
-                json.put("orderId", order.getId());
                 stringRedisTemplate.opsForValue().set(redisKey, json.toJSONString(), REDIS_TTL_MINUTES, TimeUnit.MINUTES);
                 log.info("[支付分服务] 支付分信息已存入Redis - key: {}, ttl: {}min", redisKey, REDIS_TTL_MINUTES);
             }
 
             Map<String, Object> data = new HashMap<>();
             data.put("success", true);
-            data.put("orderId", order.getId());
             data.put("outOrderNo", result.getOutOrderNo());
             data.put("package", result.getPackageStr());
-            log.info("[支付分服务] 预创建服务订单成功 - orderId: {}, outOrderNo: {}", order.getId(), result.getOutOrderNo());
+            if (wxPayConfig != null) {
+                data.put("mchId", wxPayConfig.getMchId());
+            }
+            log.info("[支付分服务] 预创建服务订单成功 - outOrderNo: {}", result.getOutOrderNo());
             return data;
         } else {
-            log.error("[支付分服务] 预创建服务订单失败 - orderId: {}, error: {}", order.getId(), result.getErrorMsg());
+            log.error("[支付分服务] 预创建服务订单失败 - error: {}", result.getErrorMsg());
             Map<String, Object> errorData = new HashMap<>();
             errorData.put("success", false);
             errorData.put("errorMsg", result.getErrorMsg());