From 3829d0e52e6769870471a4ab542bdce8d8409cad Mon Sep 17 00:00:00 2001
From: erio
Date: Fri, 6 Mar 2026 16:46:36 +0800
Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=E6=94=AF=E4=BB=98?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=A1=AC=E7=BC=96=E7=A0=81=E6=8A=BD=E5=8F=96?=
=?UTF-8?q?=E5=88=B0=20pay-utils=20=E7=BB=9F=E4=B8=80=E7=AE=A1=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- PaymentTypeMeta 新增 iconSrc、chartBar、buttonClass 字段
- 新增工具函数: getPaymentMeta、getPaymentIconSrc、
getPaymentChannelLabel、isStripeType、isRedirectPayment 等
- PaymentQRCode: 用 meta/工具函数替换散落的颜色和类型判断
- PaymentForm: 提交按钮颜色改用 meta.buttonClass
- PaymentMethodChart: 删除重复的 TYPE_CONFIG,改用 getPaymentMeta
- stripe-popup: 按钮颜色改用 meta.buttonClass
Co-Authored-By: Claude Opus 4.6
---
src/app/pay/stripe-popup/page.tsx | 3 +-
src/components/PaymentForm.tsx | 6 +--
src/components/PaymentQRCode.tsx | 34 +++++++------
src/components/admin/PaymentMethodChart.tsx | 21 ++------
src/lib/pay-utils.ts | 54 +++++++++++++++++++++
5 files changed, 83 insertions(+), 35 deletions(-)
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 ? (
<>
-
- 前往支付宝收银台
+ {iconSrc &&
}
+ {`前往${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';