Files
sub2apipay/src/lib/pay-utils.ts
erio 937f54dec2 feat: 集成微信支付直连(Native + H5)及金融级安全修复
- 新增 wxpay provider(wechatpay-node-v3 SDK),支持 Native 扫码和 H5 跳转
- 新增 /api/wxpay/notify 回调路由,AES-256-GCM 解密 + RSA 签名验证
- 修复 confirmPayment count=0 静默成功、充值失败返回 true 等 P0 问题
- 修复 notifyUrl 硬编码 easypay、回调金额覆盖订单金额等 P1 问题
- 手续费计算改用 Prisma.Decimal 精确运算,消除浮点误差
- 支付宝 provider 移除冗余 paramsForVerify,fetch 添加超时
- 补充 .env.example 配置文档和 CLAUDE.md 支付渠道说明
2026-03-06 13:57:52 +08:00

114 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface UserInfo {
id?: number;
username: string;
balance?: number;
}
export interface MyOrder {
id: string;
amount: number;
status: string;
paymentType: string;
createdAt: string;
}
export type OrderStatusFilter = 'ALL' | 'PENDING' | 'PAID' | 'COMPLETED' | 'CANCELLED' | 'EXPIRED' | 'FAILED';
export const STATUS_TEXT_MAP: Record<string, string> = {
PENDING: '待支付',
PAID: '已支付',
RECHARGING: '充值中',
COMPLETED: '已完成',
EXPIRED: '已超时',
CANCELLED: '已取消',
FAILED: '失败',
REFUNDING: '退款中',
REFUNDED: '已退款',
REFUND_FAILED: '退款失败',
};
export const FILTER_OPTIONS: { key: OrderStatusFilter; label: string }[] = [
{ key: 'ALL', label: '全部' },
{ key: 'PENDING', label: '待支付' },
{ key: 'COMPLETED', label: '已完成' },
{ key: 'CANCELLED', label: '已取消' },
{ key: 'EXPIRED', label: '已超时' },
];
export function detectDeviceIsMobile(): boolean {
if (typeof window === 'undefined') return false;
// 1. 现代 APIChromium 系浏览器,最准确)
const uad = (navigator as Navigator & { userAgentData?: { mobile: boolean } }).userAgentData;
if (uad !== undefined) return uad.mobile;
// 2. UA 正则兜底Safari / Firefox 等)
const ua = navigator.userAgent || '';
const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Windows Phone|Mobile/i.test(ua);
if (mobileUA) return true;
// 3. 触控 + 小屏兜底(新版 iPad UA 伪装成 Mac 的情况)
const smallPhysicalScreen = Math.min(window.screen.width, window.screen.height) <= 768;
const touchCapable = navigator.maxTouchPoints > 1;
return touchCapable && smallPhysicalScreen;
}
export function formatStatus(status: string): string {
return STATUS_TEXT_MAP[status] || status;
}
export function formatCreatedAt(value: string): string {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
return date.toLocaleString();
}
export interface PaymentTypeMeta {
label: string;
sublabel?: string;
color: string;
selectedBorder: string;
selectedBg: string;
iconBg: string;
}
export const PAYMENT_TYPE_META: Record<string, PaymentTypeMeta> = {
alipay: {
label: '支付宝',
sublabel: 'ALIPAY',
color: '#00AEEF',
selectedBorder: 'border-cyan-400',
selectedBg: 'bg-cyan-50',
iconBg: 'bg-[#00AEEF]',
},
wxpay: {
label: '微信支付',
sublabel: 'WECHAT PAY',
color: '#07C160',
selectedBorder: 'border-green-500',
selectedBg: 'bg-green-50',
iconBg: 'bg-[#07C160]',
},
stripe: {
label: 'Stripe',
sublabel: '信用卡 / 借记卡',
color: '#635bff',
selectedBorder: 'border-[#635bff]',
selectedBg: 'bg-[#635bff]/10',
iconBg: 'bg-[#635bff]',
},
};
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';
}
if (status === 'PENDING') {
return isDark ? 'bg-blue-500/20 text-blue-200' : 'bg-blue-100 text-blue-700';
}
if (['CANCELLED', 'EXPIRED', 'FAILED'].includes(status)) {
return isDark ? 'bg-slate-600 text-slate-200' : 'bg-slate-100 text-slate-700';
}
return isDark ? 'bg-slate-700 text-slate-200' : 'bg-slate-100 text-slate-700';
}