diff --git a/src/app/pay/stripe-popup/page.tsx b/src/app/pay/stripe-popup/page.tsx index ecb709d..87fbff3 100644 --- a/src/app/pay/stripe-popup/page.tsx +++ b/src/app/pay/stripe-popup/page.tsx @@ -2,6 +2,7 @@ import { useSearchParams } from 'next/navigation'; import { useEffect, useState, useCallback, Suspense } from 'react'; +import { getPaymentMeta } from '@/lib/pay-utils'; function StripePopupContent() { const searchParams = useSearchParams(); @@ -254,7 +255,7 @@ function StripePopupContent() { 'w-full rounded-lg py-3 font-medium text-white shadow-md transition-colors', stripeSubmitting ? 'bg-gray-400 cursor-not-allowed' - : 'bg-[#635bff] hover:bg-[#5249d9] active:bg-[#4840c4]', + : getPaymentMeta('stripe').buttonClass, ].join(' ')} > {stripeSubmitting ? ( diff --git a/src/components/PaymentForm.tsx b/src/components/PaymentForm.tsx index f0def70..3e733ea 100644 --- a/src/components/PaymentForm.tsx +++ b/src/components/PaymentForm.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState } from 'react'; -import { PAYMENT_TYPE_META, getPaymentIconType } from '@/lib/pay-utils'; +import { PAYMENT_TYPE_META, getPaymentIconType, getPaymentMeta } from '@/lib/pay-utils'; export interface MethodLimitInfo { available: boolean; @@ -327,9 +327,7 @@ export default function PaymentForm({ disabled={!isValid || loading} className={`w-full rounded-lg py-3 text-center font-medium text-white transition-colors ${ isValid && !loading - ? effectivePaymentType.startsWith('stripe') - ? 'bg-[#635bff] hover:bg-[#5851db] active:bg-[#4b44c7]' - : 'bg-blue-600 hover:bg-blue-700 active:bg-blue-800' + ? getPaymentMeta(effectivePaymentType).buttonClass : dark ? 'cursor-not-allowed bg-slate-700 text-slate-300' : 'cursor-not-allowed bg-gray-300' diff --git a/src/components/PaymentQRCode.tsx b/src/components/PaymentQRCode.tsx index 6517605..d1f0cca 100644 --- a/src/components/PaymentQRCode.tsx +++ b/src/components/PaymentQRCode.tsx @@ -2,6 +2,13 @@ import { useEffect, useMemo, useState, useCallback, useRef } from 'react'; import QRCode from 'qrcode'; +import { + isStripeType, + isRedirectPayment, + getPaymentMeta, + getPaymentIconSrc, + getPaymentChannelLabel, +} from '@/lib/pay-utils'; interface PaymentQRCodeProps { orderId: string; @@ -71,14 +78,13 @@ export default function PaymentQRCode({ const paymentMethodListenerAdded = useRef(false); // alipay_direct 使用电脑网站支付,payUrl 是跳转链接不是二维码内容 - const isAlipayDirect = paymentType === 'alipay_direct'; + const isRedirect = isRedirectPayment(paymentType); const qrPayload = useMemo(() => { - // alipay_direct 的 payUrl 是跳转链接,不应生成二维码 - if (isAlipayDirect && !qrCode) return ''; + if (isRedirect && !qrCode) return ''; const value = (qrCode || payUrl || '').trim(); return value; - }, [qrCode, payUrl, isAlipayDirect]); + }, [qrCode, payUrl, isRedirect]); useEffect(() => { let cancelled = false; @@ -115,7 +121,7 @@ export default function PaymentQRCode({ }, [qrPayload]); // Initialize Stripe Payment Element - const isStripe = paymentType?.startsWith('stripe'); + const isStripe = isStripeType(paymentType); useEffect(() => { if (!isStripe || !clientSecret || !stripePublishableKey) return; @@ -318,10 +324,10 @@ export default function PaymentQRCode({ } }; - const isWx = paymentType?.startsWith('wxpay'); - const iconSrc = isStripe ? '' : isWx ? '/icons/wxpay.svg' : '/icons/alipay.svg'; - const channelLabel = isStripe ? 'Stripe' : isWx ? '\u5FAE\u4FE1' : '\u652F\u4ED8\u5B9D'; - const iconBgClass = isStripe ? 'bg-[#635bff]' : isWx ? 'bg-[#07C160]' : 'bg-[#1677FF]'; + const meta = getPaymentMeta(paymentType || 'alipay'); + const iconSrc = getPaymentIconSrc(paymentType || 'alipay'); + const channelLabel = getPaymentChannelLabel(paymentType || 'alipay'); + const iconBgClass = meta.iconBg; if (cancelBlocked) { return ( @@ -414,7 +420,7 @@ export default function PaymentQRCode({ 'w-full rounded-lg py-3 font-medium text-white shadow-md transition-colors', stripeSubmitting ? 'bg-gray-400 cursor-not-allowed' - : 'bg-[#635bff] hover:bg-[#5249d9] active:bg-[#4840c4]', + : meta.buttonClass, ].join(' ')} > {stripeSubmitting ? ( @@ -457,16 +463,16 @@ export default function PaymentQRCode({ {TEXT_H5_HINT}

- ) : isAlipayDirect && payUrl ? ( + ) : isRedirect && payUrl ? ( <> - {channelLabel} - 前往支付宝收银台 + {iconSrc && {channelLabel}} + {`前往${channelLabel}收银台`}

{TEXT_H5_HINT} diff --git a/src/components/admin/PaymentMethodChart.tsx b/src/components/admin/PaymentMethodChart.tsx index df21ef1..3d16fab 100644 --- a/src/components/admin/PaymentMethodChart.tsx +++ b/src/components/admin/PaymentMethodChart.tsx @@ -1,6 +1,6 @@ 'use client'; -import { getPaymentTypeLabel } from '@/lib/pay-utils'; +import { getPaymentTypeLabel, getPaymentMeta } from '@/lib/pay-utils'; interface PaymentMethod { paymentType: string; @@ -14,14 +14,6 @@ interface PaymentMethodChartProps { dark?: boolean; } -const TYPE_CONFIG: Record = { - alipay: { label: '支付宝(易支付)', light: 'bg-cyan-500', dark: 'bg-cyan-400' }, - alipay_direct: { label: '支付宝(官方)', light: 'bg-blue-500', dark: 'bg-blue-400' }, - wxpay: { label: '微信支付(易支付)', light: 'bg-green-500', dark: 'bg-green-400' }, - wxpay_direct: { label: '微信支付(官方)', light: 'bg-emerald-500', dark: 'bg-emerald-400' }, - stripe: { label: 'Stripe', light: 'bg-purple-500', dark: 'bg-purple-400' }, -}; - export default function PaymentMethodChart({ data, dark }: PaymentMethodChartProps) { if (data.length === 0) { return ( @@ -51,15 +43,12 @@ export default function PaymentMethodChart({ data, dark }: PaymentMethodChartPro

{data.map((method) => { - const config = TYPE_CONFIG[method.paymentType] || { - label: getPaymentTypeLabel(method.paymentType), - light: 'bg-gray-500', - dark: 'bg-gray-400', - }; + const meta = getPaymentMeta(method.paymentType); + const label = getPaymentTypeLabel(method.paymentType); return (
- {config.label} + {label} ¥{method.amount.toLocaleString()} · {method.percentage}% @@ -70,7 +59,7 @@ export default function PaymentMethodChart({ data, dark }: PaymentMethodChartPro )} >
diff --git a/src/lib/pay-utils.ts b/src/lib/pay-utils.ts index ac6b0e3..52bcac0 100644 --- a/src/lib/pay-utils.ts +++ b/src/lib/pay-utils.ts @@ -70,6 +70,12 @@ export interface PaymentTypeMeta { selectedBorder: string; selectedBg: string; iconBg: string; + /** 图标路径(Stripe 不使用外部图标) */ + iconSrc?: string; + /** 图表条形颜色 class */ + chartBar: { light: string; dark: string }; + /** 按钮颜色 class(含 hover/active 状态) */ + buttonClass: string; } export const PAYMENT_TYPE_META: Record = { @@ -80,6 +86,9 @@ export const PAYMENT_TYPE_META: Record = { selectedBorder: 'border-cyan-400', selectedBg: 'bg-cyan-50', iconBg: 'bg-[#00AEEF]', + iconSrc: '/icons/alipay.svg', + chartBar: { light: 'bg-cyan-500', dark: 'bg-cyan-400' }, + buttonClass: 'bg-[#00AEEF] hover:bg-[#009dd6] active:bg-[#008cbe]', }, alipay_direct: { label: '支付宝', @@ -88,6 +97,9 @@ export const PAYMENT_TYPE_META: Record = { selectedBorder: 'border-blue-500', selectedBg: 'bg-blue-50', iconBg: 'bg-[#1677FF]', + iconSrc: '/icons/alipay.svg', + chartBar: { light: 'bg-blue-500', dark: 'bg-blue-400' }, + buttonClass: 'bg-[#1677FF] hover:bg-[#0958d9] active:bg-[#003eb3]', }, wxpay: { label: '微信支付', @@ -96,6 +108,9 @@ export const PAYMENT_TYPE_META: Record = { selectedBorder: 'border-green-500', selectedBg: 'bg-green-50', iconBg: 'bg-[#2BB741]', + iconSrc: '/icons/wxpay.svg', + chartBar: { light: 'bg-green-500', dark: 'bg-green-400' }, + buttonClass: 'bg-[#2BB741] hover:bg-[#24a038] active:bg-[#1d8a2f]', }, wxpay_direct: { label: '微信支付', @@ -104,6 +119,9 @@ export const PAYMENT_TYPE_META: Record = { selectedBorder: 'border-green-600', selectedBg: 'bg-green-50', iconBg: 'bg-[#07C160]', + iconSrc: '/icons/wxpay.svg', + chartBar: { light: 'bg-emerald-500', dark: 'bg-emerald-400' }, + buttonClass: 'bg-[#07C160] hover:bg-[#06ad56] active:bg-[#05994c]', }, stripe: { label: 'Stripe', @@ -112,6 +130,8 @@ export const PAYMENT_TYPE_META: Record = { selectedBorder: 'border-[#635bff]', selectedBg: 'bg-[#635bff]/10', iconBg: 'bg-[#635bff]', + chartBar: { light: 'bg-purple-500', dark: 'bg-purple-400' }, + buttonClass: 'bg-[#635bff] hover:bg-[#5249d9] active:bg-[#4840c4]', }, }; @@ -130,6 +150,40 @@ export function getPaymentIconType(type: string): string { return type; } +/** 获取支付方式的元数据,带合理的 fallback */ +export function getPaymentMeta(type: string): PaymentTypeMeta { + const base = getPaymentIconType(type); + return PAYMENT_TYPE_META[type] || PAYMENT_TYPE_META[base] || PAYMENT_TYPE_META.alipay; +} + +/** 获取支付方式图标路径 */ +export function getPaymentIconSrc(type: string): string { + return getPaymentMeta(type).iconSrc || ''; +} + +/** 获取支付方式简短标签(如 '支付宝'、'微信'、'Stripe') */ +export function getPaymentChannelLabel(type: string): string { + return getPaymentMeta(type).label; +} + +/** 支付类型谓词函数 */ +export function isStripeType(type: string | undefined | null): boolean { + return !!type?.startsWith('stripe'); +} + +export function isWxpayType(type: string | undefined | null): boolean { + return !!type?.startsWith('wxpay'); +} + +export function isAlipayType(type: string | undefined | null): boolean { + return !!type?.startsWith('alipay'); +} + +/** alipay_direct 使用页面跳转而非二维码 */ +export function isRedirectPayment(type: string | undefined | null): boolean { + return type === 'alipay_direct'; +} + export function getStatusBadgeClass(status: string, isDark: boolean): string { if (['COMPLETED', 'PAID'].includes(status)) { return isDark ? 'bg-emerald-500/20 text-emerald-200' : 'bg-emerald-100 text-emerald-700';