247 lines
6.7 KiB
TypeScript
247 lines
6.7 KiB
TypeScript
|
|
/**
|
|||
|
|
* 支付网关工厂 (Payment Gateway Factory)
|
|||
|
|
* 统一管理所有支付网关,实现工厂模式
|
|||
|
|
*
|
|||
|
|
* 作者: 卡若
|
|||
|
|
* 版本: v4.0
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import {
|
|||
|
|
CreateTradeData,
|
|||
|
|
TradeResult,
|
|||
|
|
NotifyResult,
|
|||
|
|
PaymentPlatform,
|
|||
|
|
PaymentGateway,
|
|||
|
|
GatewayNotFoundError,
|
|||
|
|
PaymentMethod
|
|||
|
|
} from './types';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 抽象支付网关基类
|
|||
|
|
*/
|
|||
|
|
export abstract class AbstractGateway {
|
|||
|
|
protected config: Record<string, unknown>;
|
|||
|
|
|
|||
|
|
constructor(config: Record<string, unknown> = {}) {
|
|||
|
|
this.config = config;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建交易
|
|||
|
|
*/
|
|||
|
|
abstract createTrade(data: CreateTradeData): Promise<TradeResult>;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证签名
|
|||
|
|
*/
|
|||
|
|
abstract verifySign(data: Record<string, string>): boolean;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析回调数据
|
|||
|
|
*/
|
|||
|
|
abstract parseNotify(data: string | Record<string, string>): NotifyResult;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 关闭交易
|
|||
|
|
*/
|
|||
|
|
abstract closeTrade(tradeSn: string): Promise<boolean>;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询交易
|
|||
|
|
*/
|
|||
|
|
abstract queryTrade(tradeSn: string): Promise<NotifyResult | null>;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发起退款
|
|||
|
|
*/
|
|||
|
|
abstract refund(tradeSn: string, refundSn: string, amount: number, reason?: string): Promise<boolean>;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 回调成功响应
|
|||
|
|
*/
|
|||
|
|
successResponse(): string {
|
|||
|
|
return 'success';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 回调失败响应
|
|||
|
|
*/
|
|||
|
|
failResponse(): string {
|
|||
|
|
return 'fail';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 网关类型映射
|
|||
|
|
type GatewayClass = new (config: Record<string, unknown>) => AbstractGateway;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 支付网关工厂
|
|||
|
|
*/
|
|||
|
|
export class PaymentFactory {
|
|||
|
|
private static gateways: Map<string, GatewayClass> = new Map();
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 注册支付网关
|
|||
|
|
*/
|
|||
|
|
static register(name: string, gatewayClass: GatewayClass): void {
|
|||
|
|
this.gateways.set(name, gatewayClass);
|
|||
|
|
console.log(`[PaymentFactory] 注册支付网关: ${name}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建支付网关实例
|
|||
|
|
* @param gateway 网关名称,格式如 'wechat_jsapi',会取下划线前的部分
|
|||
|
|
*/
|
|||
|
|
static create(gateway: PaymentGateway | string): AbstractGateway {
|
|||
|
|
const gatewayName = gateway.split('_')[0] as PaymentPlatform;
|
|||
|
|
|
|||
|
|
const GatewayClass = this.gateways.get(gatewayName);
|
|||
|
|
if (!GatewayClass) {
|
|||
|
|
throw new GatewayNotFoundError(gateway);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const config = this.getGatewayConfig(gatewayName);
|
|||
|
|
return new GatewayClass(config);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取网关配置
|
|||
|
|
*/
|
|||
|
|
private static getGatewayConfig(gateway: PaymentPlatform): Record<string, unknown> {
|
|||
|
|
const configMap: Record<PaymentPlatform, () => Record<string, unknown>> = {
|
|||
|
|
alipay: () => ({
|
|||
|
|
// 支付宝新版接口需要 app_id,如果没有配置则使用 pid(旧版兼容)
|
|||
|
|
appId: process.env.ALIPAY_APP_ID || process.env.ALIPAY_PID || '2088511801157159',
|
|||
|
|
pid: process.env.ALIPAY_PID || '2088511801157159',
|
|||
|
|
sellerEmail: process.env.ALIPAY_SELLER_EMAIL || 'zhengzhiqun@vip.qq.com',
|
|||
|
|
privateKey: process.env.ALIPAY_PRIVATE_KEY || '',
|
|||
|
|
publicKey: process.env.ALIPAY_PUBLIC_KEY || '',
|
|||
|
|
md5Key: process.env.ALIPAY_MD5_KEY || 'lz6ey1h3kl9zqkgtjz3avb5gk37wzbrp',
|
|||
|
|
enabled: process.env.ALIPAY_ENABLED === 'true',
|
|||
|
|
mode: process.env.ALIPAY_MODE || 'production',
|
|||
|
|
}),
|
|||
|
|
wechat: () => ({
|
|||
|
|
// 微信支付需要使用绑定了支付功能的服务号AppID
|
|||
|
|
appId: process.env.WECHAT_APPID || 'wx7c0dbf34ddba300d', // 服务号AppID(已绑定商户号)
|
|||
|
|
appSecret: process.env.WECHAT_APP_SECRET || 'f865ef18c43dfea6cbe3b1f1aebdb82e',
|
|||
|
|
serviceAppId: process.env.WECHAT_SERVICE_APPID || 'wx7c0dbf34ddba300d',
|
|||
|
|
serviceSecret: process.env.WECHAT_SERVICE_SECRET || 'f865ef18c43dfea6cbe3b1f1aebdb82e',
|
|||
|
|
mchId: process.env.WECHAT_MCH_ID || '1318592501',
|
|||
|
|
mchKey: process.env.WECHAT_MCH_KEY || 'wx3e31b068be59ddc131b068be59ddc2',
|
|||
|
|
certPath: process.env.WECHAT_CERT_PATH || '',
|
|||
|
|
keyPath: process.env.WECHAT_KEY_PATH || '',
|
|||
|
|
enabled: process.env.WECHAT_ENABLED === 'true',
|
|||
|
|
mode: process.env.WECHAT_MODE || 'production',
|
|||
|
|
}),
|
|||
|
|
paypal: () => ({
|
|||
|
|
clientId: process.env.PAYPAL_CLIENT_ID || '',
|
|||
|
|
clientSecret: process.env.PAYPAL_CLIENT_SECRET || '',
|
|||
|
|
mode: process.env.PAYPAL_MODE || 'sandbox',
|
|||
|
|
enabled: process.env.PAYPAL_ENABLED === 'true',
|
|||
|
|
}),
|
|||
|
|
stripe: () => ({
|
|||
|
|
publicKey: process.env.STRIPE_PUBLIC_KEY || '',
|
|||
|
|
secretKey: process.env.STRIPE_SECRET_KEY || '',
|
|||
|
|
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET || '',
|
|||
|
|
mode: process.env.STRIPE_MODE || 'test',
|
|||
|
|
enabled: process.env.STRIPE_ENABLED === 'true',
|
|||
|
|
}),
|
|||
|
|
usdt: () => ({
|
|||
|
|
gatewayType: process.env.USDT_GATEWAY_TYPE || 'nowpayments',
|
|||
|
|
apiKey: process.env.NOWPAYMENTS_API_KEY || '',
|
|||
|
|
ipnSecret: process.env.NOWPAYMENTS_IPN_SECRET || '',
|
|||
|
|
enabled: process.env.USDT_ENABLED === 'true',
|
|||
|
|
}),
|
|||
|
|
coin: () => ({
|
|||
|
|
rate: parseInt(process.env.COIN_RATE || '100', 10),
|
|||
|
|
enabled: process.env.COIN_ENABLED === 'true',
|
|||
|
|
}),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return configMap[gateway]?.() || {};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取已启用的支付网关列表
|
|||
|
|
*/
|
|||
|
|
static getEnabledGateways(): PaymentMethod[] {
|
|||
|
|
const methods: PaymentMethod[] = [];
|
|||
|
|
|
|||
|
|
// 支付宝
|
|||
|
|
if (process.env.ALIPAY_ENABLED === 'true' || true) { // 默认启用
|
|||
|
|
methods.push({
|
|||
|
|
gateway: 'alipay_wap',
|
|||
|
|
name: '支付宝',
|
|||
|
|
icon: '/icons/alipay.png',
|
|||
|
|
enabled: true,
|
|||
|
|
available: true,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 微信支付
|
|||
|
|
if (process.env.WECHAT_ENABLED === 'true' || true) { // 默认启用
|
|||
|
|
methods.push({
|
|||
|
|
gateway: 'wechat_native',
|
|||
|
|
name: '微信支付',
|
|||
|
|
icon: '/icons/wechat.png',
|
|||
|
|
enabled: true,
|
|||
|
|
available: true,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PayPal
|
|||
|
|
if (process.env.PAYPAL_ENABLED === 'true') {
|
|||
|
|
methods.push({
|
|||
|
|
gateway: 'paypal',
|
|||
|
|
name: 'PayPal',
|
|||
|
|
icon: '/icons/paypal.png',
|
|||
|
|
enabled: true,
|
|||
|
|
available: true,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Stripe
|
|||
|
|
if (process.env.STRIPE_ENABLED === 'true') {
|
|||
|
|
methods.push({
|
|||
|
|
gateway: 'stripe',
|
|||
|
|
name: 'Stripe',
|
|||
|
|
icon: '/icons/stripe.png',
|
|||
|
|
enabled: true,
|
|||
|
|
available: true,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// USDT
|
|||
|
|
if (process.env.USDT_ENABLED === 'true') {
|
|||
|
|
methods.push({
|
|||
|
|
gateway: 'usdt',
|
|||
|
|
name: 'USDT (TRC20)',
|
|||
|
|
icon: '/icons/usdt.png',
|
|||
|
|
enabled: true,
|
|||
|
|
available: true,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return methods;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查网关是否已注册
|
|||
|
|
*/
|
|||
|
|
static hasGateway(name: string): boolean {
|
|||
|
|
return this.gateways.has(name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有已注册的网关名称
|
|||
|
|
*/
|
|||
|
|
static getRegisteredGateways(): string[] {
|
|||
|
|
return Array.from(this.gateways.keys());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出便捷函数
|
|||
|
|
export function createPaymentGateway(gateway: PaymentGateway | string): AbstractGateway {
|
|||
|
|
return PaymentFactory.create(gateway);
|
|||
|
|
}
|