refactor: extract pay page components and migrate zpay → easypay
Components: - Add PayPageLayout, OrderFilterBar, MobileOrderList, OrderTable, OrderSummaryCards - Extract shared pay-utils (types, constants, helper functions) - Simplify pay/page.tsx and orders/page.tsx EasyPay migration: - Remove src/lib/zpay/, api/zpay/notify, zpay test, zpay.md - Simplify config.ts: single envSchema, no ZPAY_* fallback - Rename DB field zpay_trade_no → payment_trade_no (migration added) - Update OrderDetail label: ZPAY订单号 → 支付单号 - Update CLAUDE.md project structure
This commit is contained in:
@@ -6,30 +6,21 @@ const optionalTrimmedString = z.preprocess((value) => {
|
||||
return trimmed === '' ? undefined : trimmed;
|
||||
}, z.string().optional());
|
||||
|
||||
const rawEnvSchema = z.object({
|
||||
const envSchema = z.object({
|
||||
DATABASE_URL: z.string().min(1),
|
||||
|
||||
SUB2API_BASE_URL: z.string().url(),
|
||||
SUB2API_ADMIN_API_KEY: z.string().min(1),
|
||||
|
||||
EASY_PAY_PID: optionalTrimmedString,
|
||||
EASY_PAY_PKEY: optionalTrimmedString,
|
||||
EASY_PAY_API_BASE: optionalTrimmedString,
|
||||
EASY_PAY_NOTIFY_URL: optionalTrimmedString,
|
||||
EASY_PAY_RETURN_URL: optionalTrimmedString,
|
||||
EASY_PAY_PID: z.string().min(1),
|
||||
EASY_PAY_PKEY: z.string().min(1),
|
||||
EASY_PAY_API_BASE: z.string().url(),
|
||||
EASY_PAY_NOTIFY_URL: z.string().url(),
|
||||
EASY_PAY_RETURN_URL: z.string().url(),
|
||||
EASY_PAY_CID: optionalTrimmedString,
|
||||
EASY_PAY_CID_ALIPAY: optionalTrimmedString,
|
||||
EASY_PAY_CID_WXPAY: optionalTrimmedString,
|
||||
|
||||
ZPAY_PID: optionalTrimmedString,
|
||||
ZPAY_PKEY: optionalTrimmedString,
|
||||
ZPAY_API_BASE: optionalTrimmedString,
|
||||
ZPAY_NOTIFY_URL: optionalTrimmedString,
|
||||
ZPAY_RETURN_URL: optionalTrimmedString,
|
||||
ZPAY_CID: optionalTrimmedString,
|
||||
ZPAY_CID_ALIPAY: optionalTrimmedString,
|
||||
ZPAY_CID_WXPAY: optionalTrimmedString,
|
||||
|
||||
ENABLED_PAYMENT_TYPES: z.string().default('alipay,wxpay').transform(v => v.split(',').map(s => s.trim())),
|
||||
|
||||
ORDER_TIMEOUT_MINUTES: z.string().default('5').transform(Number).pipe(z.number().int().positive()),
|
||||
@@ -44,96 +35,19 @@ const rawEnvSchema = z.object({
|
||||
NEXT_PUBLIC_PAY_HELP_TEXT: optionalTrimmedString,
|
||||
});
|
||||
|
||||
const resolvedEnvSchema = z.object({
|
||||
DATABASE_URL: z.string().min(1),
|
||||
SUB2API_BASE_URL: z.string().url(),
|
||||
SUB2API_ADMIN_API_KEY: z.string().min(1),
|
||||
|
||||
EASY_PAY_PID: z.string().min(1),
|
||||
EASY_PAY_PKEY: z.string().min(1),
|
||||
EASY_PAY_API_BASE: z.string().url(),
|
||||
EASY_PAY_NOTIFY_URL: z.string().url(),
|
||||
EASY_PAY_RETURN_URL: z.string().url(),
|
||||
EASY_PAY_CID: optionalTrimmedString,
|
||||
EASY_PAY_CID_ALIPAY: optionalTrimmedString,
|
||||
EASY_PAY_CID_WXPAY: optionalTrimmedString,
|
||||
|
||||
ENABLED_PAYMENT_TYPES: z.array(z.string()),
|
||||
|
||||
ORDER_TIMEOUT_MINUTES: z.number().int().positive(),
|
||||
MIN_RECHARGE_AMOUNT: z.number().positive(),
|
||||
MAX_RECHARGE_AMOUNT: z.number().positive(),
|
||||
PRODUCT_NAME: z.string(),
|
||||
|
||||
ADMIN_TOKEN: z.string().min(1),
|
||||
|
||||
NEXT_PUBLIC_APP_URL: z.string().url(),
|
||||
NEXT_PUBLIC_PAY_HELP_IMAGE_URL: optionalTrimmedString,
|
||||
NEXT_PUBLIC_PAY_HELP_TEXT: optionalTrimmedString,
|
||||
});
|
||||
|
||||
export type Env = z.infer<typeof resolvedEnvSchema>;
|
||||
|
||||
type RawEnv = z.infer<typeof rawEnvSchema>;
|
||||
|
||||
function pickRequired(raw: RawEnv, key: keyof RawEnv, fallbackKey: keyof RawEnv): string {
|
||||
const value = raw[key] ?? raw[fallbackKey];
|
||||
if (!value) {
|
||||
throw new Error(`Missing required env: ${String(key)} (fallback: ${String(fallbackKey)})`);
|
||||
}
|
||||
return value as string;
|
||||
}
|
||||
|
||||
function pickOptional(raw: RawEnv, key: keyof RawEnv, fallbackKey: keyof RawEnv): string | undefined {
|
||||
return (raw[key] ?? raw[fallbackKey] ?? undefined) as string | undefined;
|
||||
}
|
||||
export type Env = z.infer<typeof envSchema>;
|
||||
|
||||
let cachedEnv: Env | null = null;
|
||||
|
||||
export function getEnv(): Env {
|
||||
if (cachedEnv) return cachedEnv;
|
||||
|
||||
const parsed = rawEnvSchema.safeParse(process.env);
|
||||
const parsed = envSchema.safeParse(process.env);
|
||||
if (!parsed.success) {
|
||||
console.error('Invalid environment variables:', parsed.error.flatten().fieldErrors);
|
||||
throw new Error('Invalid environment variables');
|
||||
}
|
||||
|
||||
const raw = parsed.data;
|
||||
const resolved = {
|
||||
DATABASE_URL: raw.DATABASE_URL,
|
||||
SUB2API_BASE_URL: raw.SUB2API_BASE_URL,
|
||||
SUB2API_ADMIN_API_KEY: raw.SUB2API_ADMIN_API_KEY,
|
||||
|
||||
EASY_PAY_PID: pickRequired(raw, 'EASY_PAY_PID', 'ZPAY_PID'),
|
||||
EASY_PAY_PKEY: pickRequired(raw, 'EASY_PAY_PKEY', 'ZPAY_PKEY'),
|
||||
EASY_PAY_API_BASE: pickRequired(raw, 'EASY_PAY_API_BASE', 'ZPAY_API_BASE'),
|
||||
EASY_PAY_NOTIFY_URL: pickRequired(raw, 'EASY_PAY_NOTIFY_URL', 'ZPAY_NOTIFY_URL'),
|
||||
EASY_PAY_RETURN_URL: pickRequired(raw, 'EASY_PAY_RETURN_URL', 'ZPAY_RETURN_URL'),
|
||||
EASY_PAY_CID: pickOptional(raw, 'EASY_PAY_CID', 'ZPAY_CID'),
|
||||
EASY_PAY_CID_ALIPAY: pickOptional(raw, 'EASY_PAY_CID_ALIPAY', 'ZPAY_CID_ALIPAY'),
|
||||
EASY_PAY_CID_WXPAY: pickOptional(raw, 'EASY_PAY_CID_WXPAY', 'ZPAY_CID_WXPAY'),
|
||||
|
||||
ENABLED_PAYMENT_TYPES: raw.ENABLED_PAYMENT_TYPES,
|
||||
|
||||
ORDER_TIMEOUT_MINUTES: raw.ORDER_TIMEOUT_MINUTES,
|
||||
MIN_RECHARGE_AMOUNT: raw.MIN_RECHARGE_AMOUNT,
|
||||
MAX_RECHARGE_AMOUNT: raw.MAX_RECHARGE_AMOUNT,
|
||||
PRODUCT_NAME: raw.PRODUCT_NAME,
|
||||
|
||||
ADMIN_TOKEN: raw.ADMIN_TOKEN,
|
||||
|
||||
NEXT_PUBLIC_APP_URL: raw.NEXT_PUBLIC_APP_URL,
|
||||
NEXT_PUBLIC_PAY_HELP_IMAGE_URL: raw.NEXT_PUBLIC_PAY_HELP_IMAGE_URL,
|
||||
NEXT_PUBLIC_PAY_HELP_TEXT: raw.NEXT_PUBLIC_PAY_HELP_TEXT,
|
||||
};
|
||||
|
||||
const resolvedParsed = resolvedEnvSchema.safeParse(resolved);
|
||||
if (!resolvedParsed.success) {
|
||||
console.error('Invalid resolved env variables:', resolvedParsed.error.flatten().fieldErrors);
|
||||
throw new Error('Invalid resolved env variables');
|
||||
}
|
||||
|
||||
cachedEnv = resolvedParsed.data;
|
||||
cachedEnv = parsed.data;
|
||||
return cachedEnv;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user