| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- /**
- * 支付页面
- * 处理微信支付流程和状态管理
- */
- import Taro from '@tarojs/taro'
- import { useState } from 'react'
- import { View, Text } from '@tarojs/components'
- import { useQuery } from '@tanstack/react-query'
- import { Button } from '@/components/ui/button'
- import {
- requestWechatPayment,
- PaymentStatus,
- PaymentStateManager,
- PaymentRateLimiter,
- retryPayment
- } from '@/utils/payment'
- import { paymentClient } from '@/api'
- interface PaymentData {
- timeStamp: string
- nonceStr: string
- package: string
- signType: string
- paySign: string
- }
- const PaymentPage = () => {
- const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.PENDING)
- const [isProcessing, setIsProcessing] = useState(false)
- const [errorMessage, setErrorMessage] = useState('')
- // 获取页面参数 - 参照 goods-detail 页面的写法
- const routerParams = Taro.getCurrentInstance().router?.params
- const orderId = routerParams?.orderId ? parseInt(routerParams.orderId) : 0
- const amount = routerParams?.amount ? parseFloat(routerParams.amount) : 0
- const orderNo = routerParams?.orderNo
- // 获取支付参数
- const { data: paymentData, isLoading: paymentLoading } = useQuery({
- queryKey: ['payment-params', orderId],
- queryFn: async () => {
- if (!orderId) throw new Error('订单ID无效')
- // 调用后端API获取微信支付参数
- const response = await paymentClient.payment.$post({
- json: {
- orderId: orderId,
- totalAmount: Math.round(amount * 100), // 转换为分
- description: `订单支付 - ${orderNo || `ORD${orderId}`}`
- }
- })
- if (response.status !== 200) {
- throw new Error(`获取支付参数失败: ${response.status}`)
- }
- const responseData = await response.json()
- // 转换响应数据格式
- const paymentData: PaymentData = {
- timeStamp: responseData.timeStamp,
- nonceStr: responseData.nonceStr,
- package: responseData.package,
- signType: responseData.signType,
- paySign: responseData.paySign
- }
- return paymentData
- },
- enabled: !!orderId && paymentStatus === PaymentStatus.PENDING
- })
- // 支付状态管理
- const paymentStateManager = PaymentStateManager.getInstance()
- const rateLimiter = PaymentRateLimiter.getInstance()
- // 处理支付
- const handlePayment = async () => {
- if (!paymentData || !orderId) {
- setErrorMessage('支付参数不完整')
- return
- }
- // 检查频率限制
- const rateLimit = rateLimiter.isRateLimited(orderId)
- if (rateLimit.limited) {
- setErrorMessage(`支付频率过高,请${Math.ceil(rateLimit.remainingTime! / 1000)}秒后重试`)
- return
- }
- setIsProcessing(true)
- setErrorMessage('')
- setPaymentStatus(PaymentStatus.PROCESSING)
- paymentStateManager.setPaymentState(orderId, PaymentStatus.PROCESSING)
- try {
- // 记录支付尝试
- rateLimiter.recordAttempt(orderId)
- // 调用微信支付
- const paymentResult = await requestWechatPayment(paymentData)
- if (paymentResult.success) {
- // 支付成功
- setPaymentStatus(PaymentStatus.SUCCESS)
- paymentStateManager.setPaymentState(orderId, PaymentStatus.SUCCESS)
- // 清除频率限制记录
- rateLimiter.clearAttempts(orderId)
- // 跳转到支付成功页面
- setTimeout(() => {
- Taro.redirectTo({
- url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}`
- })
- }, 1500)
- } else {
- // 支付失败
- setPaymentStatus(PaymentStatus.FAILED)
- paymentStateManager.setPaymentState(orderId, PaymentStatus.FAILED)
- if (paymentResult.type === 'cancel') {
- setErrorMessage('用户取消支付')
- } else {
- setErrorMessage(paymentResult.message || '支付失败')
- }
- }
- } catch (error: any) {
- console.error('支付处理异常:', error)
- setPaymentStatus(PaymentStatus.FAILED)
- paymentStateManager.setPaymentState(orderId, PaymentStatus.FAILED)
- setErrorMessage(error.message || '支付异常')
- } finally {
- setIsProcessing(false)
- }
- }
- // 重试支付
- const handleRetryPayment = async () => {
- if (!paymentData || !orderId) return
- setIsProcessing(true)
- setErrorMessage('')
- try {
- const retryResult = await retryPayment(
- () => requestWechatPayment(paymentData),
- 3,
- 1000
- )
- if (retryResult.success) {
- setPaymentStatus(PaymentStatus.SUCCESS)
- paymentStateManager.setPaymentState(orderId, PaymentStatus.SUCCESS)
- // 跳转到支付成功页面
- setTimeout(() => {
- Taro.redirectTo({
- url: `/pages/payment-success/index?orderId=${orderId}&amount=${amount}`
- })
- }, 1500)
- } else {
- setPaymentStatus(PaymentStatus.FAILED)
- setErrorMessage(retryResult.message || '支付重试失败')
- }
- } catch (error: any) {
- console.error('支付重试异常:', error)
- setPaymentStatus(PaymentStatus.FAILED)
- setErrorMessage(error.message || '支付重试异常')
- } finally {
- setIsProcessing(false)
- }
- }
- // 取消支付
- const handleCancelPayment = () => {
- if (orderId) {
- paymentStateManager.clearPaymentState(orderId)
- rateLimiter.clearAttempts(orderId)
- }
- // 返回上一页
- Taro.navigateBack()
- }
- // 渲染支付状态
- const renderPaymentStatus = () => {
- switch (paymentStatus) {
- case PaymentStatus.PENDING:
- return (
- <View>
- <Text className="text-xl font-bold text-orange-500 block mb-2">待支付</Text>
- <Text className="text-sm text-gray-600 block">请确认支付信息</Text>
- </View>
- )
- case PaymentStatus.PROCESSING:
- return (
- <View>
- <Text className="text-xl font-bold text-blue-500 block mb-2">支付中...</Text>
- <Text className="text-sm text-gray-600 block">请稍候</Text>
- </View>
- )
- case PaymentStatus.SUCCESS:
- return (
- <View>
- <Text className="text-xl font-bold text-green-500 block mb-2">支付成功</Text>
- <Text className="text-sm text-gray-600 block">正在跳转...</Text>
- </View>
- )
- case PaymentStatus.FAILED:
- return (
- <View>
- <Text className="text-xl font-bold text-red-500 block mb-2">支付失败</Text>
- <Text className="text-sm text-gray-600 block">{errorMessage}</Text>
- </View>
- )
- default:
- return null
- }
- }
- if (!orderId || !amount) {
- return (
- <View className="min-h-screen bg-gray-50 flex flex-col items-center justify-center">
- <Text className="text-xl text-red-500 mb-8">参数错误</Text>
- <Button onClick={() => Taro.navigateBack()} className="w-48 h-18 bg-blue-500 text-white rounded-full text-sm">
- 返回
- </Button>
- </View>
- )
- }
- return (
- <View className="min-h-screen bg-gray-50 p-5">
- {/* 头部 */}
- <View className="text-center py-6 bg-white rounded-2xl mb-5">
- <Text className="text-2xl font-bold text-gray-800">支付订单</Text>
- </View>
- {/* 订单信息 */}
- <View className="bg-white rounded-2xl p-6 mb-5">
- <View className="flex justify-between items-center mb-4">
- <Text className="text-sm text-gray-600">订单号:</Text>
- <Text className="text-sm text-gray-800">{orderNo || `ORD${orderId}`}</Text>
- </View>
- <View className="flex justify-between items-center">
- <Text className="text-sm text-gray-600">支付金额:</Text>
- <Text className="text-2xl font-bold text-orange-500">¥{amount.toFixed(2)}</Text>
- </View>
- </View>
- {/* 支付状态 */}
- <View className="bg-white rounded-2xl p-8 mb-5 text-center">
- {renderPaymentStatus()}
- </View>
- {/* 支付按钮 */}
- <View className="mb-5">
- {paymentStatus === PaymentStatus.PENDING && (
- <Button
- onClick={handlePayment}
- disabled={isProcessing || paymentLoading}
- className={`w-full h-22 bg-gradient-to-r from-orange-500 to-orange-400 text-white rounded-full text-lg font-bold ${
- isProcessing ? 'bg-gray-400' : ''
- }`}
- >
- {isProcessing ? '支付中...' : `确认支付 ¥${amount.toFixed(2)}`}
- </Button>
- )}
- {paymentStatus === PaymentStatus.FAILED && (
- <View className="flex gap-4">
- <Button onClick={handleRetryPayment} className="flex-1 h-22 bg-blue-500 text-white rounded-full text-sm">
- 重试支付
- </Button>
- <Button onClick={handleCancelPayment} className="flex-1 h-22 bg-gray-100 text-gray-600 border border-gray-300 rounded-full text-sm">
- 取消支付
- </Button>
- </View>
- )}
- {paymentStatus === PaymentStatus.PROCESSING && (
- <Button disabled className="w-full h-22 bg-gray-100 text-gray-500 rounded-full text-sm">
- 支付处理中...
- </Button>
- )}
- {paymentStatus === PaymentStatus.SUCCESS && (
- <Button disabled className="w-full h-22 bg-gray-100 text-gray-500 rounded-full text-sm">
- 支付成功
- </Button>
- )}
- </View>
- {/* 支付说明 */}
- <View className="bg-white rounded-2xl p-6">
- <Text className="text-sm font-bold text-gray-800 block mb-4">支付说明</Text>
- <Text className="text-xs text-gray-600 leading-relaxed whitespace-pre-line">
- • 请确保网络连接正常
- {'\n'}
- • 支付过程中请勿关闭页面
- {'\n'}
- • 如遇支付问题,请尝试重新支付
- {'\n'}
- • 支付成功后会自动跳转
- </Text>
- </View>
- </View>
- )
- }
- export default PaymentPage
|