liaof
2026-07-27 037abf9592888698213c372618bfef6e75e00a27
feat: 支付功能
2个文件已添加
23个文件已修改
1035 ■■■■■ 已修改文件
src/pages/robotaxi/home/components/marketing-popup/index.less 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/marketing-popup/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/hooks/use-marketing.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/index.less 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/begin-route.ts 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/home.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/payment.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/types/begin-route.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/css-folder/wechat-pay-score-loading.less 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/release-be-being.tsx 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/wechat-pay-score-loading.tsx 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-credit-pay-return.ts 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-payment.ts 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-order-submit.ts 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/index.tsx 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/order/index.tsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/payment-setting-detail/index.tsx 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/personal-center/index.tsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/pages/robotaxi/home/components/marketing-popup/index.less
@@ -193,7 +193,7 @@
}
.marketing-popup-multi-panel {
  margin-top: 408px;
  margin-top: auto;
  z-index: 100010;
  width: 560px;
  height: auto;
@@ -261,6 +261,7 @@
.marketing-popup-single-panel {
  position: relative;
  margin-top: 388px;
  margin: auto;
  z-index: 100010;
  width: 540px;
  height: auto;
@@ -289,7 +290,7 @@
    }
  }
  .marketing-popup-coupon-title {
    margin-top: 64px;
    margin-top: 72px;
    font-size: 56px;
    line-height: 68px;
    display: flex;
@@ -336,7 +337,7 @@
    align-items: center;
    justify-content: center;
    margin: 0 auto;
    margin-top: 112px;
    margin-top: 120px;
    font-size: 29.79px;
    font-weight: 500;
    line-height: 44.68px;
@@ -344,6 +345,13 @@
    align-items: center;
    color: #5b290e;
  }
  // .marketing-popup-close-single{
  //   margin-top: 0 !important;
  // }
  // .marketing-popup-close-icon{
  //   position: absolute;
  //   bottom: -120rpx;
  // }
}
.marketing-popup.is-active {
src/pages/robotaxi/home/components/marketing-popup/index.tsx
@@ -67,7 +67,7 @@
        }
        const timer = setTimeout(() => {
            props.onClose();
            // props.onClose();
        }, AUTO_CLOSE_DURATION);
        return () => {
src/pages/robotaxi/home/hooks/use-marketing.ts
@@ -3,6 +3,8 @@
import { FLOAT_ACTIVITY_DEFAULT_IMAGE } from '../constants';
type MarketingData = Record<string, any>;
const DEFAULT_MARKETING_POPUP_TITLE = '恭喜获得优惠券';
const DEFAULT_MARKETING_POPUP_BUTTON_TEXT = '我知道了';
type Deps = {
  /** 首次进入营销弹窗查询。 */
@@ -58,6 +60,55 @@
  };
  /**
   * 兼容旧 firstEnter 与新 exposureCoupon 两种返回结构,统一为首页营销弹窗可消费的数据。
   */
  const normalizeMarketingData = (rawData: any): MarketingData => {
    if (Array.isArray(rawData)) {
      const activities = rawData.filter(Boolean);
      const selectedActivity = activities.find((item: any) => Array.isArray(item?.coupons) && item.coupons.length > 0)
        || activities[0]
        || {};
      const coupons = (Array.isArray(selectedActivity?.coupons) ? selectedActivity.coupons : [])
        .filter(Boolean)
        .map((item: any, index: number) => ({
          ...item,
          id: item?.id ?? item?.couponId ?? index,
          title: item?.title ?? item?.couponName ?? '',
          amount: item?.amount ?? item?.money ?? item?.discountAmount ?? 0,
          unit: item?.unit ?? (Number(item?.couponType) === 1 ? '折' : '元'),
          endTime: item?.endTime ?? item?.expirationTime ?? item?.expireText ?? '',
          quantity: item?.quantity ?? item?.count ?? 1,
          conditionalAmount: item?.conditionalAmount ?? item?.fullMoney ?? 0
        }));
      return {
        ...selectedActivity,
        isFirst: activities.length > 0,
        coupons,
        activityId: selectedActivity?.activityId ?? selectedActivity?.id,
        popupId: selectedActivity?.popupId ?? selectedActivity?.id,
        id: selectedActivity?.id ?? selectedActivity?.activityId,
        miniProgramPopupTitle: selectedActivity?.miniProgramPopupTitle || selectedActivity?.acticityName || DEFAULT_MARKETING_POPUP_TITLE,
        miniProgramBtnText: selectedActivity?.miniProgramBtnText || DEFAULT_MARKETING_POPUP_BUTTON_TEXT
      };
    }
    if (rawData && typeof rawData === 'object') {
      return {
        miniProgramPopupTitle: rawData?.miniProgramPopupTitle || DEFAULT_MARKETING_POPUP_TITLE,
        miniProgramBtnText: rawData?.miniProgramBtnText || DEFAULT_MARKETING_POPUP_BUTTON_TEXT,
        ...rawData
      };
    }
    return {
      isFirst: false,
      coupons: [],
      miniProgramPopupTitle: DEFAULT_MARKETING_POPUP_TITLE,
      miniProgramBtnText: DEFAULT_MARKETING_POPUP_BUTTON_TEXT
    };
  };
  /**
   * 刷新首页营销弹窗与悬浮活动状态。
   */
  const syncMarketingState = async (hasLogin: boolean) => {
@@ -71,15 +122,28 @@
        deps.firstEnter(),
        deps.getFloatActicity()
      ]);
      const firstEnterData = firstEnterResponse?.data ?? firstEnterResponse ?? {};
      const firstEnterData = normalizeMarketingData(firstEnterResponse?.data ?? firstEnterResponse ?? {});
      const floatData = floatResponse?.data ?? floatResponse ?? {};
      const isFloatActivityEnabled = Number(floatData?.enableFloatWindow) === 1;
      setShowMarketing(Boolean(firstEnterData?.isFirst));
      setShowMarketing(Array.isArray(firstEnterData?.coupons)
        ? firstEnterData.coupons.length > 0
        : Boolean(firstEnterData?.isFirst));
      setMarketingData(firstEnterData);
      setShowFloatActivity(isFloatActivityEnabled);
      setFloatActivityUrl(String(floatData?.floatWindowJumpUrl || ''));
      setFloatActivityImg(String(floatData?.coverImage || '') || FLOAT_ACTIVITY_DEFAULT_IMAGE);
      let parsedCoverUrl = '';
      try {
        const coverImage = floatData?.coverImage;
        if (coverImage) {
          const coverArray = typeof coverImage === 'string' ? JSON.parse(coverImage) : coverImage;
          parsedCoverUrl = Array.isArray(coverArray) ? coverArray[0]?.url : '';
        }
      } catch (e) {
        console.error('解析悬浮窗图片失败', e);
      }
      setFloatActivityImg(parsedCoverUrl || FLOAT_ACTIVITY_DEFAULT_IMAGE);
    } catch (error) {
      resetMarketingState();
    }
src/pages/robotaxi/home/index.less
@@ -221,15 +221,15 @@
  right: 6px;
  bottom: 234px;
  z-index: 10020;
  width: 96px;
  height: 96px;
  width: 108px;
  height: 108px;
  display: flex;
  align-items: center;
  justify-content: center;
  .activity-float-window-img {
    width: 96px;
    height: 96px;
    width: 108px;
    height: 108px;
  }
}
src/robotaxi/api/begin-route.ts
@@ -40,7 +40,8 @@
    UnlockVehiclePayload,
    UnlockVehicleResponse,
    OrderPaymentResponse,
    QueryOrderPaymentResultResponse
    QueryOrderPaymentResultResponse,
    QueryLedgerCreditOrderResultResponse
} from './types/begin-route';
// 导航-获取路线和费用
@@ -116,7 +117,16 @@
    return request({
        url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/creditPay/createOrder`,
        method: 'POST',
        payload
        payload,
        showToast: false
    }, true);
}
// 查询免密签约状态
export const queryCreditPaySign = async (): Promise<any> => {
    return request({
        url: _ROBOTAXI_HOST + '/order/api/v1/mini-program/order/creditPay/sign',
        method: 'POST'
    }, true);
}
@@ -322,6 +332,19 @@
    }, true);
}
// 查询信用分授权结果
export const queryLedgerCreditOrderResult = async (orderId: OrderId): Promise<QueryLedgerCreditOrderResultResponse> => {
    return request({
        url: _ROBOTAXI_HOST + `/order/api/v1/mini-program/order/queryLedgerCreditOrderResult`,
        method: 'POST',
        payload: {
            orderId: String(orderId)
        },
        skipException: true,
        showToast: false
    }, true);
}
// 订单查询实时轨迹路线
export const queryOrderRoute = async (
    payload: QueryOrderRoutePayload
src/robotaxi/api/home.ts
@@ -130,7 +130,8 @@
// 第一次进入应用
export const firstEnter = async () => {
    return request({
        url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/acticity/firstEnter',
        // url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/acticity/firstEnter',
        url: _ROBOTAXI_HOST + '/operation/api/v1/mini-program/acticity/exposureCoupon',
        method: 'GET'
    }, true);
}
src/robotaxi/api/payment.ts
@@ -26,6 +26,15 @@
};
/**
 * 查询支付分开通情况。
 */
export const getPayNoPwd = async (): Promise<GetPayPwdResponse> => {
  return request({
    url: _ROBOTAXI_HOST + '/order/api/v1/mini-program/order/creditPay/sign',
    method: 'POST',
  }, true);
};
/**
 * 设置支付分开通状态。
 */
export const setPayPwd = async (status: RobotaxiPaySettingStatus): Promise<SetPayPwdResponse> => {
src/robotaxi/api/types/begin-route.ts
@@ -605,6 +605,12 @@
/** 订单支付结果查询返回值。 */
export type QueryOrderPaymentResultResponse = {
    status: RobotaxiOrderStatus | number;
    orderId: string;
    payStatus: number;
    payType: number;
    payTime: string;
};
// 信用分授权结果查询响应
export type QueryLedgerCreditOrderResultResponse = {
    creditOrderStatus: 'DOING' | 'REVOKED' | 'EXPIRED' | string;
};
src/robotaxi/pages/begin-route/components/calling-car-modules/fare-detail-card.tsx
@@ -25,6 +25,12 @@
}: Props) {
    const [showDetail, setShowDetail] = useState(false); // 费用明细展开状态
    const canOpenCouponSelect = tripStatus === TripStatus.TripFinished && typeof onOpenCouponSelect === 'function'; // 仅待支付支持切换优惠券
    const isCreditPayProcessing = (
        tripStatus === TripStatus.TripFinished
        && !!orderId
        && String(Taro.getStorageSync('creditPayOrderId') || '') === String(orderId)
    ); // 信用下单结束后,展示免密支付中并阻止重复点击
    const payButtonText = isCreditPayProcessing ? '免密支付中' : '立即支付';
    // 外部重置版本变化时收起明细
    useEffect(() => {
@@ -219,7 +225,15 @@
                            :
                            <View className="pay-box-free">员工内部测试</View>
                    }{/*  */}
                    <View onClick={handlePay} className="pay-box-btn">立即支付</View>
                    <View className="pay-box-btn-wrapper">
                        <View
                            onClick={isCreditPayProcessing ? undefined : handlePay}
                            className="pay-box-btn"
                        >
                            {payButtonText}
                        </View>
                        {isCreditPayProcessing && <View className="pay-box-btn-mask" />}
                    </View>
                </View>
            }
        </View>
src/robotaxi/pages/begin-route/components/css-folder/calling-car.less
@@ -735,7 +735,46 @@
    font-size: 24px;
    line-height: 40px;
    letter-spacing: normal;
    color: @black-text-main;
    color: rgba(0, 0, 0, 0.9);
    display: flex;
    align-items: center;
    .unit {
      margin-left: 4px;
      font-size: 24px;
      line-height: 40px;
      letter-spacing: normal;
      color: rgba(0, 0, 0, 0.9);
    }
  }
  &-btn-wrapper {
    width: 296px;
    height: 94px;
    position: relative;
  }
  &-btn {
    width: 296px;
    height: 94px;
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 20px;
    background: linear-gradient(102deg, #8475FF 0%, #7DD6FF 102%);
    font-size: 28px;
    letter-spacing: normal;
    color: #FFFFFF;
  }
  &-btn-mask {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    border-radius: 20px;
    background: rgba(255, 255, 255, 0.4);
  }
}
.pay-box-btn {
src/robotaxi/pages/begin-route/components/css-folder/wechat-pay-score-loading.less
New file
@@ -0,0 +1,70 @@
.wechat-pay-score-loading-mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 999999;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 背景全透明,只有中间的盒子有颜色 */
  background: transparent;
}
.wechat-pay-score-loading-box {
  width: 260px;
  height: 260px;
  background-color: rgba(50, 50, 50, 0.95);
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #fff;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.wechat-pay-score-icon {
  width: 88px;
  height: 88px;
  margin-bottom: 24px;
}
.wechat-pay-score-text {
  font-size: 30px;
  font-weight: 400;
  margin-bottom: 32px;
  line-height: 1;
}
.wechat-pay-score-dots {
  display: flex;
  gap: 12px;
}
.wechat-pay-score-dot {
  width: 12px;
  height: 12px;
  background-color: #fff;
  border-radius: 50%;
  opacity: 0.3;
  animation: wechat-pay-score-dot-blink 1.4s infinite ease-in-out both;
}
.wechat-pay-score-dot:nth-child(1) {
  animation-delay: -0.32s;
}
.wechat-pay-score-dot:nth-child(2) {
  animation-delay: -0.16s;
}
@keyframes wechat-pay-score-dot-blink {
  0%, 80%, 100% {
    opacity: 0.3;
  }
  40% {
    opacity: 1;
  }
}
src/robotaxi/pages/begin-route/components/release-be-being.tsx
@@ -1,15 +1,18 @@
import Taro from "@tarojs/taro";
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Image, View } from "@tarojs/components";
import './css-folder/release-be-being.less'
type Props = {
    handleOpenCancelPopup?: () => void;
    onFinish?: () => void;
    finishBlocked?: boolean;
}
export default function ReleaseBeBeing({ handleOpenCancelPopup, onFinish }: Props) {
export default function ReleaseBeBeing({ handleOpenCancelPopup, onFinish, finishBlocked = false }: Props) {
    const [completedCount, setCompletedCount] = useState(0); // 已完成步骤数
    const [finishReady, setFinishReady] = useState(false); // 发布动画是否已走完
    const finishTriggeredRef = useRef(false); // 防止 onFinish 重复触发
    const startSite = Taro.getStorageSync('startSite') || {};
    const endSite = Taro.getStorageSync('endSite') || {};
    const startSiteName = startSite?.name || startSite?.address || '-';
@@ -24,9 +27,9 @@
        const step1 = setTimeout(() => setCompletedCount(1), 1000);
        const step2 = setTimeout(() => setCompletedCount(2), 2000);
        // 3秒后进入呼叫车中状态
        // 3 秒后标记动画完成;是否真正切页由 finishBlocked 控制
        const step3 = setTimeout(() => {
            onFinish?.();
            setFinishReady(true);
        }, 3000);
        return () => {
@@ -36,6 +39,16 @@
        };
    }, []);
    useEffect(() => {
        // 信用分下单时,需要等待用户从支付分确认页返回后再推进到“呼叫车中”。
        if (!finishReady || finishBlocked || finishTriggeredRef.current) {
            return;
        }
        finishTriggeredRef.current = true;
        onFinish?.();
    }, [finishBlocked, finishReady, onFinish]);
  const renderSiteItemMarker = (index, count) => {
  /* 根据 completedCount 动态显示完成图标 */
     if (index < count) {
src/robotaxi/pages/begin-route/components/wechat-pay-score-loading.tsx
New file
@@ -0,0 +1,30 @@
import { View, Image } from '@tarojs/components';
import React from 'react';
import './css-folder/wechat-pay-score-loading.less';
// 模拟微信支付分的图标 SVG Base64
const PAY_SCORE_ICON_BASE64 = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDQiIGhlaWdodD0iNDQiIHZpZXdCb3g9IjAgMCA0NCA0NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNMjIgM0wyNiA3SDMyVjEzTDM2IDE3TDI2IDIxVjI3SDI2TDIyIDMxTDE4IDI3SDEyVjIxTDggMTdMMTIgMTNWN0gxOEwyMiAzWiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIyLjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KICA8Y2lyY2xlIGN4PSIyMiIgY3k9IjE0IiByPSI0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIuNSIvPgogIDxwYXRoIGQ9Ik0xNSAyNUMxNSAyMS4xMzQgMTguMTM0IDE4IDIyIDE4QzI1Ljg2NiAxOCAyOSAyMS4xMzQgMjkgMjUiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMi41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KICA8cGF0aCBkPSJNMjQgMjdMMjggMzFMMzggMjEiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMyIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPg==';
type Props = {
  visible: boolean;
};
const WechatPayScoreLoading: React.FC<Props> = ({ visible }) => {
  if (!visible) return null;
  return (
    <View className="wechat-pay-score-loading-mask">
      <View className="wechat-pay-score-loading-box">
        <Image className="wechat-pay-score-icon" src={PAY_SCORE_ICON_BASE64} mode="aspectFit" />
        <View className="wechat-pay-score-text">微信支付分</View>
        <View className="wechat-pay-score-dots">
          <View className="wechat-pay-score-dot" />
          <View className="wechat-pay-score-dot" />
          <View className="wechat-pay-score-dot" />
        </View>
      </View>
    </View>
  );
};
export default React.memo(WechatPayScoreLoading);
src/robotaxi/pages/begin-route/hooks/use-credit-pay-return.ts
@@ -1,9 +1,16 @@
import { useCallback, useEffect, useRef } from 'react';
import Taro from '@tarojs/taro';
import { queryLedgerCreditOrderResult } from '../../../api/begin-route';
type Deps = {
  /** 支付分页面返回后的回调。 */
  onReturn: () => void;
  /** 当前正在下单的订单 ID,用于查询授权结果。 */
  orderId?: string;
  /** 支付分授权成功后的回调。 */
  onAuthorizedReturn: (queryId: string) => void | Promise<void>;
  /** 支付分未授权后的回调。 */
  onUnauthorizedReturn: (orderId?: string) => void | Promise<void>;
  /** 支付分页面拉起失败后的回调。 */
  onLaunchFailed: (orderId?: string) => void | Promise<void>;
};
const PAY_SCORE_REFERRER_APP_ID = 'wxd8f3793ea3b935b8';
@@ -28,22 +35,25 @@
  /**
   * 拉起微信支付分订单确认页。
   */
  const openCreditPayScoreUse = useCallback((packageValue: string) => {
  const openCreditPayScoreUse = useCallback((packageValue: string, targetOrderId?: string) => {
    const queryOrderId = targetOrderId || deps.orderId;
    if (!packageValue) {
      Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' });
      return false;
      void deps.onLaunchFailed(queryOrderId);
      return true;
    }
    // @ts-ignore
    if (!wx.openBusinessView) {
      Taro.showToast({ title: '请升级微信版本后重试', icon: 'none' });
      return false;
      void deps.onLaunchFailed(queryOrderId);
      return true;
    }
    clearAppShowListener();
    /** 支付分页面返回监听函数。 */
    const handleAppShow = (res) => {
    const handleAppShow = async (res) => {
      if (res?.scene !== 1038) {
        return;
      }
@@ -53,8 +63,90 @@
        return;
      }
      const queryId = String(res?.referrerInfo?.extraData?.query_id || '').trim();
      console.log('[信用分授权返回][AppShow]', {
        scene: res?.scene,
        referrerInfo: res?.referrerInfo,
        queryId
      });
      clearAppShowListener();
      deps.onReturn();
      if (!queryId) {
        void deps.onUnauthorizedReturn(queryOrderId);
        return;
      }
      // 根据 query_id 轮询查询真实的授权结果
      if (!queryOrderId) {
        console.log('[信用分授权查询失败] 缺失 orderId');
        void deps.onUnauthorizedReturn(queryOrderId);
        return;
      }
      Taro.showLoading({ title: '授权结果查询中', mask: true });
      const maxPollingCount = 120;
      let currentPollingCount = 0;
      const pollStatus = async () => {
        try {
          const result = await queryLedgerCreditOrderResult(queryOrderId);
          const status = result?.data?.creditOrderStatus;
          console.log(`[信用分授权查询结果] 第${currentPollingCount + 1}次`, result);
          if (status === 'REVOKED' || status === 'EXPIRED') {
            Taro.hideLoading();
            void deps.onUnauthorizedReturn(queryOrderId);
            return;
          }
          if (status === 'DOING' || result?.code === 20710) {
            Taro.hideLoading();
            Taro.showToast({ title: '授权成功', icon: 'none' });
            void deps.onAuthorizedReturn(queryId);
            return;
          }
          if (status === 'CREATED') {
            // CREATED 状态也当作中间状态继续轮询
            currentPollingCount++;
            if (currentPollingCount >= maxPollingCount) {
              Taro.hideLoading();
              console.log('[信用分授权查询超时] CREATED 状态持续2分钟');
              void deps.onUnauthorizedReturn(queryOrderId);
              return;
            }
            setTimeout(pollStatus, 1000);
            return;
          }
          // 其他状态,继续轮询
          currentPollingCount++;
          if (currentPollingCount >= maxPollingCount) {
            Taro.hideLoading();
            console.log('[信用分授权查询超时]');
            void deps.onUnauthorizedReturn(queryOrderId);
            return;
          }
          setTimeout(pollStatus, 1000);
        } catch (error: any) {
          const errorCode = String(error?.code || error?.data?.code || '');
          if (errorCode === '20710') {
            console.log(`[信用分授权查询遇到20710] 第${currentPollingCount + 1}次,当作授权成功处理`);
            Taro.hideLoading();
            Taro.showToast({ title: '授权成功', icon: 'none' });
            void deps.onAuthorizedReturn(queryId);
            return;
          }
          Taro.hideLoading();
          console.log('[信用分授权查询失败]', error);
          void deps.onUnauthorizedReturn(queryOrderId);
        }
      };
      await pollStatus();
    };
    appShowListenerRef.current = handleAppShow;
@@ -66,14 +158,19 @@
      extraData: {
        package: packageValue
      },
      success: (res) => {
        console.log('[信用分授权拉起][openBusinessView]', res);
      },
      fail: () => {
        clearAppShowListener();
        console.log('[信用分授权拉起失败][openBusinessView]');
        Taro.showToast({ title: '拉起支付分订单失败', icon: 'none' });
        void deps.onLaunchFailed(queryOrderId);
      }
    });
    return true;
  }, [clearAppShowListener, deps.onReturn]);
  }, [clearAppShowListener, deps.onAuthorizedReturn, deps.onLaunchFailed, deps.onUnauthorizedReturn, deps.orderId]);
  useEffect(() => clearAppShowListener, [clearAppShowListener]);
src/robotaxi/pages/begin-route/hooks/use-order-actions.shared.ts
@@ -126,7 +126,9 @@
  /** 未签约态支付处理。 */
  handleUnsignedPayment: (token: string) => void | Promise<void>;
  /** 已签约态支付处理。 */
  handleSignedCreditPayment: (value: SignedOrderPaymentValue) => void | Promise<void>;
  handleSignedCreditPayment: (value: SignedOrderPaymentValue, targetOrderId?: string) => boolean | Promise<boolean>;
  /** 结束行程后进入待支付态时,触发信用扣款结果查询。 */
  handleCreditPayTripFinished?: (targetOrderId: string) => void | Promise<void>;
  /** 同步上车点。 */
  setStartSite?: (site: any) => void;
};
src/robotaxi/pages/begin-route/hooks/use-order-cancel.ts
@@ -50,6 +50,7 @@
    deps.setVehicleInfo({});
    clearOrderTripStorage();
    Taro.removeStorageSync('pickupEndTime');
    Taro.removeStorageSync('creditPayOrderId');
    Taro.removeStorageSync(WAITING_TIMER_START_STORAGE_KEY);
    Taro.removeStorageSync(WAITING_TIMER_ELAPSED_STORAGE_KEY);
  };
src/robotaxi/pages/begin-route/hooks/use-order-payment.ts
@@ -92,7 +92,10 @@
    } else {
      syncTripFinishedStatus();
    }
    return orderDetail;
    return {
      orderDetail,
      nextTripStatus
    };
  };
  /**
@@ -145,7 +148,11 @@
    try {
      const endOrderResult: any = await endOrder(targetOrderId);
      const orderDetail = await syncPostEndOrderTripStatus(targetOrderId);
      const { orderDetail, nextTripStatus } = await syncPostEndOrderTripStatus(targetOrderId);
      if (nextTripStatus === TripStatus.TripFinished) {
        // 结束行程后若仍待支付,立即触发信用扣款结果查询,避免依赖页面 effect 才能进入。
        await deps.handleCreditPayTripFinished?.(targetOrderId);
      }
      reportTripPayStartEvent(orderDetail);
      if (endOrderResult) {
        Taro.showToast({
@@ -171,7 +178,11 @@
      await endOrder(targetOrderId);
    } catch (error) {
    }
    await syncPostEndOrderTripStatus(targetOrderId);
    const { nextTripStatus } = await syncPostEndOrderTripStatus(targetOrderId);
    if (nextTripStatus === TripStatus.TripFinished) {
      // 倒计时自动结束行程时也复用同一套信用扣款结果查询逻辑。
      await deps.handleCreditPayTripFinished?.(targetOrderId);
    }
  };
  /**
@@ -192,35 +203,34 @@
      mask: false
    });
    try {
      await getOrderPayment({ orderId: targetOrderId });
      syncPaymentSuccessStatus();
      // await getOrderPayment({ orderId: targetOrderId });
      // syncPaymentSuccessStatus();
      // const paymentResult = await getOrderPayment({ orderId: targetOrderId });
      // const paymentStatus = Number(paymentResult?.status);
      const paymentResult = await getOrderPayment({ orderId: targetOrderId });
      const paymentStatus = Number(paymentResult?.status);
      // if (paymentStatus === RobotaxiPaymentEntryStatus.Unsigned) {
      //   const cashierToken = paymentResult?.value?.token;
      //   if (!cashierToken) {
      //     Taro.showToast({ title: '支付凭证缺失,请稍后重试', icon: 'none' });
      //     return;
      //   }
      //   await deps.handleUnsignedPayment(cashierToken);
      //   return;
      // }
      if (paymentStatus === RobotaxiPaymentEntryStatus.Unsigned) {
        const cashierToken = paymentResult?.value?.token;
        if (!cashierToken) {
          Taro.showToast({ title: '支付凭证缺失,请稍后重试', icon: 'none' });
          return;
        }
        await deps.handleUnsignedPayment(cashierToken);
        return;
      }
      // if (paymentStatus === RobotaxiPaymentEntryStatus.Signed) {
      //   const signedPaymentValue = paymentResult?.value as SignedOrderPaymentValue;
      //   if (!signedPaymentValue?.packageValue) {
      //     Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' });
      //     return;
      //   }
      //   await deps.handleSignedCreditPayment(signedPaymentValue);
      //   return;
      // }
      if (paymentStatus === RobotaxiPaymentEntryStatus.Signed) {
        const signedPaymentValue = paymentResult?.value as SignedOrderPaymentValue;
        if (!signedPaymentValue?.packageValue) {
          Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' });
          return;
        }
        await deps.handleSignedCreditPayment(signedPaymentValue);
        return;
      }
      // Taro.showToast({ title: '暂不支持当前支付方式', icon: 'none' });
      Taro.showToast({ title: '暂不支持当前支付方式', icon: 'none' });
    } catch (error) {
      Taro.showToast({ title: '支付失败,请重试', icon: 'none' });
    } finally {
src/robotaxi/pages/begin-route/hooks/use-order-submit.ts
@@ -1,10 +1,9 @@
import Taro from '@tarojs/taro';
import { useCallback, useRef } from 'react';
import { formatDateTime, debounce, reportEvent } from '../../../../utils/Robotaxi-public-method';
import { createCreditPayOrder, saveOrder, routeStaleCheck } from '../../../api/begin-route';
import { createCreditPayOrder, saveOrder, routeStaleCheck, queryCreditPaySign } from '../../../api/begin-route';
import type { SaveOrderPayload } from '../../../api/types/begin-route';
import { getAgreement } from '../../../api/home';
import { getPayPwd, RobotaxiPaySettingStatus, setPayPwd } from '../../../api/payment';
import useCreditPaySign from '../../../hooks/use-credit-pay-sign';
import { checkOtp } from '../../../../utils/tools';
import { jumpTo } from '../../../../utils/jump';
@@ -51,6 +50,9 @@
  callImmediately: () => void;
};
/** 当前路线是否为载客测试免费。 */
const isOperateFreeRoute = (selectedRoute: any) => Number(selectedRoute?.operateFree) === 1;
/** 标准化文本,兼容普通空格与不可见空格差异。 */
const normalizeText = (value: any) => String(value || '').replace(/\s+/g, ' ').trim().toLowerCase();
@@ -94,9 +96,28 @@
  skipCreditPayCheck?: boolean;
  /** 是否跳过下单起始埋点。 */
  skipPayStartReport?: boolean;
  /** 跳过校验后强制指定信用下单模式。 */
  forcedCreditPaySubmitMode?: 'credit_pay_submit' | 'credit_pay_skip_auth_submit';
};
type CreditPayCheckResult = 'normal_submit' | 'credit_pay_submit' | 'blocked';
type CreditPayCheckResult =
  | 'normal_submit'
  | 'credit_pay_submit'
  | 'credit_pay_skip_auth_submit'
  | 'blocked';
/** 将签约状态转换为后续下单模式。 */
const resolveCreditPaySubmitMode = (
  status: any
): Extract<CreditPayCheckResult, 'credit_pay_submit' | 'credit_pay_skip_auth_submit'> | null => {
  if (Number(status) === 2) {
    return 'credit_pay_skip_auth_submit';
  }
  if (Number(status) === 3) {
    return 'credit_pay_submit';
  }
  return null;
};
/**
 * 管理下单前校验与提交动作。
@@ -104,6 +125,32 @@
export default function useOrderSubmit(deps: OrderActionsDeps): UseOrderSubmitResult {
  const waitingCreditPayReturnRef = useRef(false); // 是否正在等待微信支付分签约页返回
  const submitOrderFlowRef = useRef<(options?: SubmitOrderFlowOptions) => Promise<void>>(); // 始终指向最新的下单流程
  /**
   * 查询长期签约回跳后的最新支付分状态,并决定是否继续信用下单。
   */
  const resolveCreditPayModeAfterSignReturn = useCallback(async () => {
    Taro.showLoading({ title: '授权结果查询中', mask: true });
    let shouldHideLoading = true;
    try {
      const signResult = await queryCreditPaySign();
      const latestStatus = signResult?.data?.status ?? signResult?.status;
      const resolvedCreditPaySubmitMode = resolveCreditPaySubmitMode(latestStatus);
      if (resolvedCreditPaySubmitMode) {
        Taro.hideLoading();
        shouldHideLoading = false;
        Taro.showToast({ title: '授权成功', icon: 'none' });
        return resolvedCreditPaySubmitMode;
      }
      return resolvedCreditPaySubmitMode;
    } catch (error) {
      return null;
    } finally {
      if (shouldHideLoading) {
        Taro.hideLoading();
      }
    }
  }, []);
  /**
   * 结束下单中状态。
@@ -428,66 +475,61 @@
    }
  };
  /**
   * 继续执行签约页返回后的下单流程。
   */
  const continueSubmitAfterCreditPayReturn = useCallback(() => {
    if (!waitingCreditPayReturnRef.current) {
      return;
    }
    waitingCreditPayReturnRef.current = false;
    void submitOrderFlowRef.current?.({
      skipCreditPayCheck: true,
      skipPayStartReport: true
    });
  }, []);
  /**
   * 拉起签约失败后,降级为继续正常下单。
   */
  const continueSubmitAfterCreditPayLaunchFail = useCallback(() => {
    if (!waitingCreditPayReturnRef.current) {
      return;
    }
    waitingCreditPayReturnRef.current = false;
    // Taro.showToast({ title: '拉起免密开通失败,已继续下单', icon: 'none' });
    void submitOrderFlowRef.current?.({
      skipCreditPayCheck: true,
      skipPayStartReport: true
    });
  }, []);
  const { openCreditPaySign } = useCreditPaySign({
    onReturn: continueSubmitAfterCreditPayReturn,
    onLaunchFail: continueSubmitAfterCreditPayLaunchFail
    onReturn: async () => {
      // 长期签约页回跳后,先查询最新授权结果,再决定是否继续信用下单。
      waitingCreditPayReturnRef.current = false;
      const nextCreditPaySubmitMode = await resolveCreditPayModeAfterSignReturn();
      if (!nextCreditPaySubmitMode) {
        stopCallSubmitting();
        Taro.showToast({ title: '支付分授权未完成,请重试', icon: 'none' });
        return;
      }
      void submitOrderFlowRef.current?.({
        skipCreditPayCheck: true,
        forcedCreditPaySubmitMode: nextCreditPaySubmitMode
      });
    },
    onLaunchFail: () => {
      waitingCreditPayReturnRef.current = false;
      stopCallSubmitting();
      Taro.showToast({ title: '拉起签约失败', icon: 'none' });
    }
  });
  /**
   * 下单前校验免密状态。
   */
  const checkCreditPayReadyBeforeOrder = useCallback(async () => {
    // 免密状态校验,先默认返回正常下单,后续根据校验结果再处理。
    return 'normal_submit' as CreditPayCheckResult;
  const checkCreditPayReadyBeforeOrder = useCallback(async (selectedRoute: any) => {
    if (isOperateFreeRoute(selectedRoute)) {
      // 载客测试免费单直接走普通下单,不拉起微信信用下单弹窗。
      return 'normal_submit' as CreditPayCheckResult;
    }
    try {
      const hasEnabledCreditPay = await getPayPwd();
      if (hasEnabledCreditPay === true) {
        return 'normal_submit' as CreditPayCheckResult;
      const res = await queryCreditPaySign();
      const status = res?.data?.status ?? res?.status; // 兼容不同包裹层级
      const value = res?.data?.value ?? res?.value;
      const resolvedCreditPaySubmitMode = resolveCreditPaySubmitMode(status);
      if (resolvedCreditPaySubmitMode) {
        // status=2: 已确认签约,信用下单后无需再拉起微信支付分授权页。
        // status=3: 每次下单授权流程,信用下单后仍需拉起微信支付分确认页。
        return resolvedCreditPaySubmitMode;
      }
      const isCanceled = await deps.handleOpenCreditPayPopup();
      if (isCanceled) {
        // 用户取消开通,继续下单
        return 'normal_submit' as CreditPayCheckResult;
      }
      if (status === 1) {
        // 需要拉起长期授权
        const isCanceled = await deps.handleOpenCreditPayPopup();
        if (isCanceled) {
          // 用户取消开通,中断下单流程
          stopCallSubmitting();
          return 'blocked' as CreditPayCheckResult;
        }
      if (Number(deps.distanceConfig?.creditPayNeedConfirm) !== 0) {
        // 默认优先走信用下单,只有后端明确返回 0 时才继续旧的签约流程。
        return 'credit_pay_submit' as CreditPayCheckResult;
      }
      try {
        const response = await setPayPwd(RobotaxiPaySettingStatus.Enable);
        const signStr = String(response?.signStr || '').trim();
        // value 为支付中心签约响应对象(CreditPaySignVo),需取其中的 signStr 字段作为预授权 token。
        const signStr = String(value?.signStr ?? value ?? '').trim();
        if (!signStr) {
          // 获取免密签约信息失败,降级为继续下单
          return 'normal_submit' as CreditPayCheckResult;
@@ -505,11 +547,13 @@
        // 等待微信签约页返回后再恢复下单,避免当前流程继续往下执行。
        stopCallSubmitting();
        return 'blocked' as CreditPayCheckResult;
      } catch (e) {
        // 免密状态校验失败,降级为继续下单
        return 'normal_submit' as CreditPayCheckResult;
      }
      // 未知状态降级为继续下单
      return 'normal_submit' as CreditPayCheckResult;
    } catch (error) {
      // 接口失败,降级为继续下单
      return 'normal_submit' as CreditPayCheckResult;
    }
  }, [openCreditPaySign, deps]);
@@ -563,9 +607,9 @@
      return;
    }
    let submitMode: CreditPayCheckResult = 'normal_submit';
    let submitMode: CreditPayCheckResult = options.forcedCreditPaySubmitMode || 'normal_submit';
    if (!options.skipCreditPayCheck) {
      submitMode = await checkCreditPayReadyBeforeOrder();
      submitMode = await checkCreditPayReadyBeforeOrder(selectedRoute);
      if (submitMode === 'blocked') {
        return;
      }
@@ -575,34 +619,70 @@
    await waitForOrderSuccessSubscribePopup();
    try {
      const submitResult = submitMode === 'credit_pay_submit'
      const isCreditPaySubmit = submitMode === 'credit_pay_submit'
        || submitMode === 'credit_pay_skip_auth_submit';
      if (isCreditPaySubmit) {
        Taro.showLoading({ title: '加载中', mask: true });
      }
      const submitResult = isCreditPaySubmit
        ? await submitCreditPayOrder(startSite, openCityId, selectedRoute)
        : await submitOrder(startSite, openCityId);
      if (!submitResult?.orderId) {
        Taro.hideLoading();
        stopCallSubmitting();
        return;
      }
      deps.setOrderId(submitResult.orderId);
      deps.setTripStatus(TripStatus.Publishing);
      deps.bindSocketListeners();
      void subscribeAfterOrderSuccess(submitResult.orderId);
      if (submitMode === 'credit_pay_submit') {
      if (!isCreditPaySubmit) {
        Taro.removeStorageSync('creditPayOrderId');
      }
      if (isCreditPaySubmit) {
        Taro.setStorageSync('creditPayOrderId', submitResult.orderId);
        if (submitMode === 'credit_pay_skip_auth_submit') {
          Taro.hideLoading();
          // status=2 代表当前已是免确认模式,信用下单成功后直接进入发单态。
          deps.setTripStatus(TripStatus.Publishing);
          return;
        }
        const creditPayPackageValue = String(submitResult.creditPayPackageValue || '').trim();
        if (!creditPayPackageValue) {
          Taro.hideLoading();
          Taro.showToast({ title: '支付参数缺失,请稍后重试', icon: 'none' });
          return;
        }
        // 复用现有支付分确认页拉起与回跳查询链路。
        void deps.handleSignedCreditPayment({
        const launched = await deps.handleSignedCreditPayment({
          businessOrderId: '',
          authOrderId: '',
          channelOrderId: '',
          payType: 13,
          packageValue: creditPayPackageValue
        });
        }, submitResult.orderId);
        Taro.hideLoading();
        // 只有拉起失败才立刻进入发单状态(或者依靠 abort 逻辑终止)
        if (!launched) {
          deps.setTripStatus(TripStatus.Publishing);
        }
        return;
      }
      deps.setTripStatus(TripStatus.Publishing);
    } catch (error) {
      reportPayResultEvent(resolvePayResultStatus((error as any)?.msg || (error as any)?.message || error));
      Taro.hideLoading();
      const errorMessage = String((error as any)?.msg || (error as any)?.message || '下单失败,请重试');
      if (submitMode === 'credit_pay_submit' || submitMode === 'credit_pay_skip_auth_submit') {
        Taro.showToast({ title: errorMessage, icon: 'none' });
      }
      reportPayResultEvent(resolvePayResultStatus(errorMessage));
      stopCallSubmitting();
    }
  };
src/robotaxi/pages/begin-route/hooks/use-view-delegates.tsx
@@ -71,6 +71,8 @@
  endSite: any;
  /** 发单 */
  callImmediately: () => void;
  /** 是否正在等待信用分确认页返回结果 */
  isWaitingSignedCreditPayResult?: boolean;
  /** 预估态选择上下车点 */
  handleEstimateSelectSite: (pageType: "start" | "end") => void;
  /** 费用信息 */
@@ -94,6 +96,8 @@
  handlePickupTimeoutAutoCancel: () => void;
  /** 行程状态设置 */
  setTripStatus: (value: TripStatus) => void;
  /** 发布中动画结束 */
  handlePublishingAnimationFinish?: () => void;
  /** 完成倒计时启用 */
  finishCountdownEnabled: boolean;
  /** 订单 ID */
@@ -941,7 +945,12 @@
          handleOpenCancelPopup={() =>
            deps.handleOpenCancelPopupByTripStatus(TripStatus.Publishing)
          }
          finishBlocked={Boolean(deps.isWaitingSignedCreditPayResult)}
          onFinish={() => {
            if (deps.handlePublishingAnimationFinish) {
              deps.handlePublishingAnimationFinish();
              return;
            }
            deps.setTripStatus(TripStatus.CallingCar);
            Taro.setStorageSync("tripStatus", TripStatus.CallingCar);
          }}
src/robotaxi/pages/begin-route/hooks/use-web-socket-events.ts
@@ -17,6 +17,10 @@
export type UseWebSocketEventsDeps = {
    tripStatusRef: React.MutableRefObject<TripStatus>;
    isWaitingSignedCreditPayResultRef?: React.MutableRefObject<boolean>;
    pendingSignedCreditPayTripStatusRef?: React.MutableRefObject<TripStatus | null>;
    isHoldingTripStatusDuringPublishingAnimationRef?: React.MutableRefObject<boolean>;
    pendingTripStatusAfterPublishingAnimationRef?: React.MutableRefObject<TripStatus | null>;
    pickupArrivedReportedOrderIdRef: React.MutableRefObject<string>;
    tripInProgressReportedOrderIdRef: React.MutableRefObject<string>;
    arrivedDestinationReportedOrderIdRef: React.MutableRefObject<string>;
src/robotaxi/pages/begin-route/hooks/web-socket-handlers/create-order-status-socket-handler.ts
@@ -96,6 +96,14 @@
  if (currentTripStatus === mappedTripStatus) {
    return mappedTripStatus;
  }
  if (deps.isWaitingSignedCreditPayResultRef?.current) {
    deps.pendingSignedCreditPayTripStatusRef && (deps.pendingSignedCreditPayTripStatusRef.current = mappedTripStatus);
    return currentTripStatus;
  }
  if (deps.isHoldingTripStatusDuringPublishingAnimationRef?.current && currentTripStatus === TripStatus.Publishing) {
    deps.pendingTripStatusAfterPublishingAnimationRef && (deps.pendingTripStatusAfterPublishingAnimationRef.current = mappedTripStatus);
    return currentTripStatus;
  }
  deps.setTripStatus(mappedTripStatus);
  deps.tripStatusRef.current = mappedTripStatus;
  Taro.setStorageSync('tripStatus', mappedTripStatus);
src/robotaxi/pages/begin-route/index.tsx
@@ -60,11 +60,12 @@
import useBluetoothCarControl from './hooks/use-bluetooth-car-control';
import { toTripStatus, TripStatus } from '../../constants/trip-status';
import { MODIFY_DESTINATION_SOCKET_STATUS_KEY } from '../../constants/modify-destination';
import { selectNavigationRoute } from '../../api/begin-route';
import { cancelOrder, selectNavigationRoute } from '../../api/begin-route';
import { getAddressByLocation, getDistance, reportEvent } from '../../../utils/Robotaxi-public-method';
import { CurrentUserLocation, RouteDistanceValue } from './hooks/use-route-manager.shared';
import { MODIFY_DESTINATION_SESSION_KEY } from '../../constants/modify-destination';
import { getOrderId } from '../../utils/order';
import { getOrderDetail } from '../../api/order';
import { _ROBOTAXI_WS_HOST } from '../../../constants/api';
import { RobotaxiSignedPayType, SignedOrderPaymentValue } from '../../api/types/begin-route';
@@ -305,6 +306,7 @@
  const [pageDidShowVersion, setPageDidShowVersion] = useState(0); // 页面展示版本号,用于触发子模块重查
  const [resetDetailVersion, setResetDetailVersion] = useState(0); // 重置详情版本号,用于触发组件更新
  const [priceUpdateVersion, setPriceUpdateVersion] = useState(0); // 价格更新版本号
  const [, setCreditPayFallbackRefreshVersion] = useState(0); // 信用扣款回落普通支付后,触发页面重渲染
  const [evaluateSubmitVersion, setEvaluateSubmitVersion] = useState(0); // 评价提交版本号
  const [evaluateCloseVersion, setEvaluateCloseVersion] = useState(0); // 评价关闭版本号
  const [evaluateOrderRating, setEvaluateOrderRating] = useState(1); // 当前评价弹窗对应评分
@@ -315,7 +317,16 @@
  const [pickupExceptionInfo, setPickupExceptionInfo] = useState<PickupExceptionInfo | null>(null); // 接驾异常信息
  const [showPickupExceptionPopup, setShowPickupExceptionPopup] = useState(false); // 接驾异常弹窗展示状态
  const [showCreditPayPopup, setShowCreditPayPopup] = useState(false); // 免密支付开通弹窗
  const [isWaitingSignedCreditPayResult, setIsWaitingSignedCreditPayResult] = useState(false); // 信用分确认页已拉起,等待用户返回结果
  const creditPayResolverRef = useRef<((value: boolean) => void) | null>(null); // 免密弹窗的 Promise resolve 回调
  const isWaitingSignedCreditPayResultRef = useRef(false); // 信用分确认结果等待态引用,供 socket 状态同步拦截使用
  const pendingSignedCreditPayTripStatusRef = useRef<TripStatus | null>(null); // 等待信用分结果期间暂存的最新行程状态
  const pendingPublishingAfterSignedCreditPayRef = useRef(false); // 信用分下单成功后,等待结果返回再进入发布态
  const isHoldingTripStatusDuringPublishingAnimationRef = useRef(false); // 信用分返回后,发布中 3 秒动画期间暂存 socket 状态
  const pendingTripStatusAfterPublishingAnimationRef = useRef<TripStatus | null>(null); // 发布中动画结束后待恢复的最新状态
  const clearActiveOrderRuntimeRef = useRef<(() => void) | null>(null); // 信用分取消发单后复用订单清理逻辑
  const restartPlanningRouteRef = useRef<(() => void) | null>(null); // 信用分取消发单后复用发单前页面恢复逻辑
  const stopCallSubmittingRef = useRef<(() => void) | null>(null); // 信用分中止发单后复位“立即呼叫”提交态
  const [orderRouteMetrics, setOrderRouteMetrics] = useState<OrderRouteMetrics>({
    distanceMeters: null,
    durationSeconds: null
@@ -1357,6 +1368,52 @@
  }, []);
  /**
   * 统一维护“等待信用分结果”状态,同时同步 ref,供异步回调和 socket 读取。
   */
  const syncWaitingSignedCreditPayResult = useCallback((value: boolean) => {
    isWaitingSignedCreditPayResultRef.current = value;
    setIsWaitingSignedCreditPayResult(value);
  }, []);
  /**
   * 信用分结果返回后,恢复页面状态推进。
   */
  const resumeTripStatusAfterSignedCreditPayReturn = useCallback(() => {
    syncWaitingSignedCreditPayResult(false);
    const deferredTripStatus = pendingSignedCreditPayTripStatusRef.current;
    pendingSignedCreditPayTripStatusRef.current = null;
    if (pendingPublishingAfterSignedCreditPayRef.current) {
      pendingPublishingAfterSignedCreditPayRef.current = false;
      pendingTripStatusAfterPublishingAnimationRef.current = deferredTripStatus;
      isHoldingTripStatusDuringPublishingAnimationRef.current = true;
      setTripStatus(TripStatus.Publishing);
      tripStatusRef.current = TripStatus.Publishing;
      Taro.setStorageSync('tripStatus', TripStatus.Publishing);
      return;
    }
    if (deferredTripStatus !== null) {
      setTripStatus(deferredTripStatus);
      tripStatusRef.current = deferredTripStatus;
      Taro.setStorageSync('tripStatus', deferredTripStatus);
    }
  }, [syncWaitingSignedCreditPayResult]);
  /**
   * 发布中 3 秒动画结束后,再恢复信用分结果等待期间暂存的最新状态。
   */
  const handlePublishingAnimationFinish = useCallback(() => {
    const deferredTripStatus = pendingTripStatusAfterPublishingAnimationRef.current;
    pendingTripStatusAfterPublishingAnimationRef.current = null;
    isHoldingTripStatusDuringPublishingAnimationRef.current = false;
    const nextTripStatus = deferredTripStatus ?? TripStatus.CallingCar;
    setTripStatus(nextTripStatus);
    tripStatusRef.current = nextTripStatus;
    Taro.setStorageSync('tripStatus', nextTripStatus);
  }, []);
  /**
   * 确认开通免密支付。
   */
  const handleConfirmCreditPayPopup = useCallback(() => {
@@ -1390,6 +1447,10 @@
  const { bindSocketListeners, ensureSocketListeners } = useWebSocketEvents({
    tripStatusRef,
    isWaitingSignedCreditPayResultRef,
    pendingSignedCreditPayTripStatusRef,
    isHoldingTripStatusDuringPublishingAnimationRef,
    pendingTripStatusAfterPublishingAnimationRef,
    pickupArrivedReportedOrderIdRef,
    tripInProgressReportedOrderIdRef,
    arrivedDestinationReportedOrderIdRef,
@@ -1491,9 +1552,155 @@
    onPaySuccess: handlePaymentSuccess
  });
  // 信用分结束行程 loading 状态
  const creditPayLoadingTimerRef = useRef<any>(null);
  const creditPayFallbackCheckTimerRef = useRef<any>(null);
  const creditPayFallbackActiveOrderIdRef = useRef('');
  const clearCreditPayFallbackRuntime = useCallback((hideLoading = true) => {
    if (creditPayLoadingTimerRef.current) {
      clearTimeout(creditPayLoadingTimerRef.current);
      creditPayLoadingTimerRef.current = null;
    }
    if (creditPayFallbackCheckTimerRef.current) {
      clearInterval(creditPayFallbackCheckTimerRef.current);
      creditPayFallbackCheckTimerRef.current = null;
    }
    creditPayFallbackActiveOrderIdRef.current = '';
    if (hideLoading) {
      Taro.hideLoading();
    }
  }, []);
  const handleCreditPayTripFinished = useCallback(async (targetOrderId: string) => {
    const resolvedOrderId = String(targetOrderId || '').trim();
    const creditPayOrderId = String(Taro.getStorageSync('creditPayOrderId') || '').trim();
    if (!resolvedOrderId || !creditPayOrderId || creditPayOrderId !== resolvedOrderId) {
      return;
    }
    if (
      creditPayFallbackActiveOrderIdRef.current === resolvedOrderId
      && (creditPayLoadingTimerRef.current || creditPayFallbackCheckTimerRef.current)
    ) {
      return;
    }
    clearCreditPayFallbackRuntime(false);
    creditPayFallbackActiveOrderIdRef.current = resolvedOrderId;
    Taro.showLoading({ title: '扣款结果查询中,请勿重复支付', mask: true });
    const checkCreditPayFallback = async () => {
      try {
        const orderDetail: any = await getOrderDetail(resolvedOrderId);
        if (Number(orderDetail?.payType) === 1) {
          Taro.removeStorageSync('creditPayOrderId');
          clearCreditPayFallbackRuntime();
          setCreditPayFallbackRefreshVersion((prev) => prev + 1);
          Taro.showToast({ title: '免密扣款失败,请手动支付', icon: 'none' });
        }
      } catch (error) {
      }
    };
    // 先立即查一次,再轮询订单详情感知 payType 是否从信用支付回落到普通支付。
    await checkCreditPayFallback();
    if (creditPayFallbackActiveOrderIdRef.current !== resolvedOrderId) {
      return;
    }
    creditPayFallbackCheckTimerRef.current = setInterval(() => {
      void checkCreditPayFallback();
    }, 1000);
    creditPayLoadingTimerRef.current = setTimeout(() => {
      clearCreditPayFallbackRuntime();
      Taro.redirectTo({
        url: '/pages/robotaxi/home/index',
      });
    }, 10000);
  }, [clearCreditPayFallbackRuntime]);
  useEffect(() => {
    if (tripStatus === TripStatus.TripFinished) {
      void handleCreditPayTripFinished(orderId);
      return () => {
        clearCreditPayFallbackRuntime();
      };
    } else {
      clearCreditPayFallbackRuntime();
    }
    return () => {
      clearCreditPayFallbackRuntime();
    };
  }, [tripStatus, orderId, handleCreditPayTripFinished, clearCreditPayFallbackRuntime]);
  /**
   * 信用分流程未完成时,统一取消订单并终止发单。
   */
  const abortSignedCreditPayFlow = useCallback(async (reason: 'unauthorized' | 'launch_failed', fallbackOrderId?: string) => {
    const targetOrderId = getOrderId(orderId) || fallbackOrderId;
    console.log('[信用分流程中止]', {
      reason,
      orderId: targetOrderId
    });
    stopCallSubmittingRef.current?.();
    syncWaitingSignedCreditPayResult(false);
    pendingPublishingAfterSignedCreditPayRef.current = false;
    pendingSignedCreditPayTripStatusRef.current = null;
    pendingTripStatusAfterPublishingAnimationRef.current = null;
    isHoldingTripStatusDuringPublishingAnimationRef.current = false;
    if (!targetOrderId) {
      clearActiveOrderRuntimeRef.current?.();
      restartPlanningRouteRef.current?.();
      return;
    }
    try {
      await cancelOrder(targetOrderId);
      // Taro.showToast({
      //   title: reason === 'launch_failed' ? '拉起信用分失败,已取消发单' : '未完成信用分授权,已取消发单',
      //   icon: 'none'
      // });
      clearActiveOrderRuntimeRef.current?.();
      if (restartPlanningRouteRef.current) {
        restartPlanningRouteRef.current();
        return;
      }
      setTripStatus(TripStatus.PublishRoute);
      tripStatusRef.current = TripStatus.PublishRoute;
      Taro.setStorageSync('tripStatus', TripStatus.PublishRoute);
    } catch (error) {
      console.log('[信用分流程中止][取消订单失败]', {
        reason,
        error
      });
      Taro.showToast({ title: '取消订单失败,请重试', icon: 'none' });
    }
  }, [orderId, syncWaitingSignedCreditPayResult]);
  /**
   * 信用分页面返回但未拿到 query_id,视为未授权。
   */
  const handleSignedCreditPayUnauthorizedReturn = useCallback((targetOrderId?: string) => {
    return abortSignedCreditPayFlow('unauthorized', targetOrderId);
  }, [abortSignedCreditPayFlow]);
  /**
   * 信用分页面拉起失败,直接终止本次发单。
   */
  const handleSignedCreditPayLaunchFailed = useCallback((targetOrderId?: string) => {
    return abortSignedCreditPayFlow('launch_failed', targetOrderId);
  }, [abortSignedCreditPayFlow]);
  const { openCreditPayScoreUse } = useCreditPayReturn({
    // 信用下单链路回跳后仅关闭监听,不再主动查询支付结果。
    onReturn: () => {}
    orderId,
    // 信用下单链路回跳后,有 query_id 且结果为 DOING 或已确认时,继续正常发单。
    onAuthorizedReturn: () => resumeTripStatusAfterSignedCreditPayReturn(),
    // 授权结果为失败/撤销等,立即取消订单并终止发单。
    onUnauthorizedReturn: handleSignedCreditPayUnauthorizedReturn,
    // 拉起失败同样取消订单并终止发单。
    onLaunchFailed: handleSignedCreditPayLaunchFailed
  });
  useCashierPayListener({
@@ -1513,14 +1720,30 @@
  /**
   * 处理已签约态支付分支付。
   */
  const handleSignedCreditPayment = useCallback((value: SignedOrderPaymentValue) => {
  const handleSignedCreditPayment = useCallback((value: SignedOrderPaymentValue, targetOrderId?: string) => {
    if (Number(value?.payType) !== RobotaxiSignedPayType.WechatCredit) {
      Taro.showToast({ title: '暂不支持当前支付方式', icon: 'none' });
      return;
      return false;
    }
    openCreditPayScoreUse(value.packageValue);
  }, [openCreditPayScoreUse]);
    pendingPublishingAfterSignedCreditPayRef.current = true;
    pendingSignedCreditPayTripStatusRef.current = null;
    syncWaitingSignedCreditPayResult(true);
    const launched = openCreditPayScoreUse(value.packageValue, targetOrderId);
    if (!launched) {
      pendingPublishingAfterSignedCreditPayRef.current = false;
      syncWaitingSignedCreditPayResult(false);
    }
    return launched;
  }, [openCreditPayScoreUse, syncWaitingSignedCreditPayResult]);
  useEffect(() => {
    if (tripStatus !== TripStatus.Publishing) {
      syncWaitingSignedCreditPayResult(false);
      isHoldingTripStatusDuringPublishingAnimationRef.current = false;
      pendingTripStatusAfterPublishingAnimationRef.current = null;
    }
  }, [tripStatus, syncWaitingSignedCreditPayResult]);
  const orderActions = useOrderActions({
    tripStatus,
@@ -1559,6 +1782,7 @@
    routeRequestRouteAndFare,
    handleUnsignedPayment,
    handleSignedCreditPayment,
    handleCreditPayTripFinished,
    setStartSite
  });
@@ -1570,8 +1794,14 @@
    handlePickupTimeoutAutoCancel,
    handleConfirmOverRoute,
    handleFinishCountdownTimeout,
    handlePay
    handlePay,
    stopCallSubmitting,
    clearActiveOrderRuntime,
    restartPlanningRoute
  } = orderActions;
  stopCallSubmittingRef.current = stopCallSubmitting;
  clearActiveOrderRuntimeRef.current = clearActiveOrderRuntime;
  restartPlanningRouteRef.current = restartPlanningRoute;
  const {
    authSuccess: bluetoothAuthSuccess,
    startAuth: startBluetoothAuth,
@@ -1623,6 +1853,14 @@
  useDidShow(() => {
    setPageDidShowVersion((v) => v + 1);
    setDoorUnlockedInCurrentPage(false);
    // 信用分结果页返回后,需要先保留“发布中”动画,不要立刻走页面恢复链路把状态顶到最新。
    if (
      isWaitingSignedCreditPayResultRef.current
      || isHoldingTripStatusDuringPublishingAnimationRef.current
    ) {
      updateMapViewportPadding();
      return;
    }
    handleDidShow(router.params?.skipPageFlag, router.params?.fromModifyDestination);
    updateMapViewportPadding();
    // 完成态下隐藏小程序再恢复时,受控中心点可能与原生地图实际中心脱钩,主动触发一次
@@ -2135,6 +2373,7 @@
    elapsedSeconds,
    endSite: effectiveEndSite,
    callImmediately: handleCallImmediately,
    isWaitingSignedCreditPayResult,
    handleEstimateSelectSite,
    fareEstimate,
    handleOpenCouponSelect,
@@ -2145,6 +2384,7 @@
    handleOpenCancelPopupWithType,
    handlePickupTimeoutAutoCancel,
    setTripStatus,
    handlePublishingAnimationFinish,
    finishCountdownEnabled,
    orderId,
    vehicleInfo,
src/robotaxi/pages/order/index.tsx
@@ -225,6 +225,15 @@
    if (status == 10) {
      return "已完成";
    }
    if (status == 11) {
      return "退款中";
    }
    if (status == 12) {
      return "已退款";
    }
    if (status == 13) {
      return "退款失败";
    }
  };
  const getOrderStatusClassName = (status: number) => {
@@ -301,7 +310,7 @@
                        </View>
                      )}
                      {/* 发票功能暂未上线,屏蔽入口 */}
                      {false && ["已完成", "待评价"].includes(
                      { ["已完成", "待评价"].includes(
                        formatOrderStatus(item.status)
                      ) &&
                        item.invoiceStatus != 2 &&
@@ -320,7 +329,7 @@
                            </View>
                          </View>
                        )}
                      {["已完成", "已取消", "待评价"].includes(
                      {["已完成", "已取消", "待评价", "退款中", "已退款", "退款失败"].includes(
                        formatOrderStatus(item.status)
                      ) &&
                        canAgainOrder(item) && (
src/robotaxi/pages/payment-setting-detail/index.tsx
@@ -2,6 +2,7 @@
import { View, Text, Image } from '@tarojs/components';
import { useCallback, useState } from 'react';
import { getPayPwd, RobotaxiPaySettingStatus, setPayPwd } from '../../api/payment';
import { getOrderActive } from '../../api/home';
import { withRobotaxiWhitelist } from '../../../utils/whitelist';
import './index.less';
@@ -32,6 +33,29 @@
  }, []);
  /**
   * 关闭免密支付前,先确认当前没有进行中的订单。
   */
  const ensureNoActiveOrderBeforeClose = useCallback(async () => {
    try {
      const activeOrder = await getOrderActive();
      if (activeOrder) {
        Taro.showToast({
          title: '有进行中的订单,暂无法关闭服务',
          icon: 'none'
        });
        return false;
      }
      return true;
    } catch (error) {
      Taro.showToast({
        title: '查询订单状态失败,请稍后重试',
        icon: 'none'
      });
      return false;
    }
  }, []);
  /**
   * 关闭微信免密支付。
   */
  const handleClosePayment = async () => {
@@ -51,8 +75,14 @@
      return;
    }
    setIsOperating(true);
    const canClosePayment = await ensureNoActiveOrderBeforeClose();
    if (!canClosePayment) {
      setIsOperating(false);
      return;
    }
    try {
      setIsOperating(true);
      await setPayPwd(RobotaxiPaySettingStatus.Disable);
      Taro.showToast({
        title: '微信免密支付已关闭',
src/robotaxi/pages/personal-center/index.tsx
@@ -33,7 +33,7 @@
const toolMenuList: ToolMenuItem[] = [
  { key: "service", label: "联系客服" },
  // { key: "invoice", label: "开发票" }, // 暂未上线,屏蔽入口
  { key: "invoice", label: "开发票" }, // 暂未上线,屏蔽入口
  { key: "payment", label: "支付设置" },
  { key: "favorite", label: "收藏站点" }
];