hejianhao
2026-05-14 645ac85c2b59db924d34ae487eddf7d1afcb034d
feat(payment-setting): 新增微信支付分设置页面及相关功能

封装支付分相关后端调用接口,添加支付设置页面的组件、样式与静态资源,在个人中心新增跳转入口并注册页面到应用配置,完成支付分开通、关闭及状态查询的完整业务流程
6个文件已添加
2个文件已修改
324 ■■■■■ 已修改文件
src/app.config.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/api/payment.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/assets/pay-max-icon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/assets/pay-mini-icon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/payment-setting/index.config.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/payment-setting/index.less 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/payment-setting/index.tsx 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/personal-center/index.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/app.config.ts
@@ -39,6 +39,7 @@
        'pages/operating-area/index',
        'pages/share-trip/index',
        'pages/personal-center/index',
        'pages/payment-setting/index',
      ],
    },
    {
src/robotaxi/api/payment.ts
New file
@@ -0,0 +1,36 @@
import request from '../../utils/request';
import { _ROBOTAXI_HOST } from '../../constants/api';
/** 支付设置操作状态。 */
export enum RobotaxiPaySettingStatus {
  Enable = 1,
  Disable = 2
}
/** 支付设置查询结果。 */
export type GetPayPwdResponse = boolean;
/** 支付设置操作结果。 */
export type SetPayPwdResponse = {
  signStr?: string;
};
/**
 * 查询支付分开通状态。
 */
export const getPayPwd = async (): Promise<GetPayPwdResponse> => {
  return request({
    url: _ROBOTAXI_HOST + '/account/api/v1/mini-program/appUser/getPayPwd',
    method: 'POST',
  }, true);
};
/**
 * 设置支付分开通状态。
 */
export const setPayPwd = async (status: RobotaxiPaySettingStatus): Promise<SetPayPwdResponse> => {
  return request({
    url: _ROBOTAXI_HOST + `/account/api/v1/mini-program/appUser/setPayPwd?status=${status}`,
    method: 'POST',
  }, true);
};
src/robotaxi/assets/pay-max-icon.png
src/robotaxi/assets/pay-mini-icon.png
src/robotaxi/pages/payment-setting/index.config.ts
New file
@@ -0,0 +1,3 @@
export default {
  navigationBarTitleText: '免密支付',
}
src/robotaxi/pages/payment-setting/index.less
New file
@@ -0,0 +1,60 @@
.payment-setting-content {
    padding-top: 36px;
    box-sizing: border-box;
}
.payment-setting-item {
    padding: 32px 40px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    box-sizing: border-box;
    transition: background-color 0.2s;
}
.payment-setting-item-hover {
    background: rgba(0, 0, 0, 0.05);
}
.payment-setting-item-left {
    display: flex;
    align-items: center;
    min-width: 0;
    flex: 1;
}
.payment-setting-item-icon {
    width: 32px;
    height: 32px;
    margin-right: 12px;
    flex-shrink: 0;
}
.payment-setting-item-label {
    font-size: 30px;
    line-height: 30px;
    color: rgba(0, 0, 0, 0.9);
}
.payment-setting-item-right {
    display: flex;
    align-items: center;
    flex-shrink: 0;
}
.payment-setting-item-status {
    font-size: 30px;
    line-height: 30px;
    color: rgba(0, 0, 0, 0.4);
}
.payment-setting-item-status-signed {
    color: #07c160;
}
.payment-setting-item-arrow {
    width: 32px;
    height: 32px;
    margin-left: 8px;
}
src/robotaxi/pages/payment-setting/index.tsx
New file
@@ -0,0 +1,219 @@
import Taro, { useDidShow } from '@tarojs/taro';
import { View, Text, Image } from '@tarojs/components';
import { useCallback, useEffect, useRef, useState } from 'react';
import backIcon from '../../assets/mine-back.png';
import arrowIcon from '../../assets/mine-arrow.png';
import payMiniIcon from '../../assets/pay-mini-icon.png';
import { getPayPwd, RobotaxiPaySettingStatus, setPayPwd } from '../../api/payment';
import './index.less';
/** 支付分小程序回跳 appId。 */
const CREDIT_PAY_MINIPROGRAM_APP_ID = 'wxd8f3793ea3b935b8';
/** 页面显示状态文案。 */
const PAY_SETTING_STATUS_TEXT = {
  enabled: '已开通',
  disabled: '未开通'
};
/** AppShow 监听函数类型。 */
type AppShowHandler = (payload: any) => void;
export default function PaymentSetting() {
  const [isPayEnabled, setIsPayEnabled] = useState(false); // 当前是否已开通支付分
  const [isOperating, setIsOperating] = useState(false); // 当前是否正在执行操作
  const appShowListenerRef = useRef<AppShowHandler | null>(null); // 支付分回跳监听器
  const waitingCreditPayReturnRef = useRef(false); // 是否正在等待支付分签约页回跳
  /**
   * 清理支付分回跳监听。
   */
  const clearCreditPayReturnListener = useCallback(() => {
    if (!appShowListenerRef.current) {
      return;
    }
    Taro.offAppShow(appShowListenerRef.current);
    appShowListenerRef.current = null;
  }, []);
  /**
   * 请求支付分开通状态。
   */
  const requestPayStatus = useCallback(async () => {
    const response = await getPayPwd();
    return response === true;
  }, []);
  /**
   * 刷新支付分开通状态。
   */
  const refreshPayStatus = useCallback(async (showErrorToast = true) => {
    try {
      const nextStatus = await requestPayStatus();
      setIsPayEnabled(nextStatus);
      return nextStatus;
    } catch (error) {
      if (showErrorToast) {
        Taro.showToast({ title: '查询支付设置失败', icon: 'none' });
      }
      throw error;
    }
  }, [requestPayStatus]);
  /**
   * 返回上一页。
   */
  const handleBack = () => {
    Taro.navigateBack({
      fail: () => {
        Taro.reLaunch({ url: '/robotaxi/pages/personal-center/index' });
      }
    });
  };
  /**
   * 处理支付分签约回跳。
   */
  const handleCreditPayReturn = useCallback(async (payload: any) => {
    if (payload?.scene !== 1038 || payload?.referrerInfo?.appId !== CREDIT_PAY_MINIPROGRAM_APP_ID) {
      return;
    }
    waitingCreditPayReturnRef.current = false;
    clearCreditPayReturnListener();
    try {
      const nextStatus = await refreshPayStatus(false);
      Taro.showToast({
        title: nextStatus ? '开通成功' : '开通未完成',
        icon: nextStatus ? 'success' : 'none'
      });
    } catch (error) {
      Taro.showToast({ title: '查询签约结果失败', icon: 'none' });
    } finally {
      setIsOperating(false);
    }
  }, [clearCreditPayReturnListener, refreshPayStatus]);
  /**
   * 绑定支付分签约回跳监听。
   */
  const bindCreditPayReturnListener = useCallback(() => {
    clearCreditPayReturnListener();
    const handleAppShow: AppShowHandler = (payload) => {
      handleCreditPayReturn(payload);
    };
    appShowListenerRef.current = handleAppShow;
    Taro.onAppShow(handleAppShow);
  }, [clearCreditPayReturnListener, handleCreditPayReturn]);
  /**
   * 拉起微信支付分签约页。
   */
  const openCreditPaySign = useCallback((signStr: string) => {
    const weappGlobal = (globalThis as any).wx;
    if (typeof weappGlobal?.openBusinessView !== 'function') {
      Taro.showToast({ title: '请升级微信版本后重试', icon: 'none' });
      return false;
    }
    waitingCreditPayReturnRef.current = true;
    bindCreditPayReturnListener();
    weappGlobal.openBusinessView({
      businessType: 'wxpayScoreEnable',
      extraData: {
        apply_permissions_token: signStr,
      },
      fail: () => {
        waitingCreditPayReturnRef.current = false;
        clearCreditPayReturnListener();
        setIsOperating(false);
        Taro.showToast({ title: '拉起支付分签约失败', icon: 'none' });
      }
    });
    return true;
  }, [bindCreditPayReturnListener, clearCreditPayReturnListener]);
  /**
   * 处理点击支付设置项。
   */
  const handlePaySettingClick = async () => {
    if (isOperating) {
      return;
    }
    const nextOperateStatus = isPayEnabled
      ? RobotaxiPaySettingStatus.Disable
      : RobotaxiPaySettingStatus.Enable;
    setIsOperating(true);
    try {
      const response = await setPayPwd(nextOperateStatus);
      if (nextOperateStatus === RobotaxiPaySettingStatus.Disable) {
        const nextStatus = await refreshPayStatus(false);
        Taro.showToast({
          title: nextStatus ? '关闭未完成' : '已关闭',
          icon: nextStatus ? 'none' : 'success'
        });
        return;
      }
      const signStr = String(response?.signStr || '');
      if (!signStr) {
        Taro.showToast({ title: '获取签约信息失败', icon: 'none' });
        return;
      }
      const launched = openCreditPaySign(signStr);
      if (!launched) {
        return;
      }
    } catch (error) {
      Taro.showToast({
        title: isPayEnabled ? '关闭失败' : '开通失败',
        icon: 'none'
      });
    } finally {
      if (!waitingCreditPayReturnRef.current) {
        setIsOperating(false);
      }
    }
  };
  useDidShow(() => {
    if (waitingCreditPayReturnRef.current) {
      return;
    }
    refreshPayStatus(false).catch(() => { });
  });
  useEffect(() => {
    return () => {
      waitingCreditPayReturnRef.current = false;
      clearCreditPayReturnListener();
    };
  }, [clearCreditPayReturnListener]);
  return (
    <View className='payment-setting-content'>
      <View
        className='payment-setting-item'
        hoverClass='payment-setting-item-hover'
        onClick={handlePaySettingClick}
      >
        <View className='payment-setting-item-left'>
          <Image className='payment-setting-item-icon' src={payMiniIcon} />
          <Text className='payment-setting-item-label'>微信支付分 | 先乘后付</Text>
        </View>
        <View className='payment-setting-item-right'>
          <Text className={`payment-setting-item-status ${isPayEnabled ? 'payment-setting-item-status-signed' : ''}`}>
            {isPayEnabled ? PAY_SETTING_STATUS_TEXT.enabled : PAY_SETTING_STATUS_TEXT.disabled}
          </Text>
          <Image className='payment-setting-item-arrow' src={arrowIcon} />
        </View>
      </View>
    </View>
  );
}
src/robotaxi/pages/personal-center/index.tsx
@@ -122,6 +122,11 @@
      return;
    }
    if (key === "payment") {
      Taro.navigateTo({ url: "/robotaxi/pages/payment-setting/index" });
      return;
    }
    Taro.showToast({ title: "功能建设中", icon: "none" });
  };