import crypto from 'crypto'; import { SystemConfig } from '@/models'; import dbConnect from './dbConnect'; interface AlipayConfig { appId: string; privateKey: string; alipayPublicKey: string; gateway: string; notifyUrl: string; returnUrl?: string; } export class AlipayService { private config: AlipayConfig | null = null; async init() { if (this.config) return; await dbConnect(); const systemConfig = await SystemConfig.findOne({ 配置标识: 'default' }).select('+支付宝设置.AppID +支付宝设置.应用私钥 +支付宝设置.公钥 +支付宝设置.网关地址 +支付宝设置.回调URL'); if (!systemConfig || !systemConfig.支付宝设置) { throw new Error('Alipay configuration not found'); } const { AppID, 应用私钥, 公钥, 网关地址, 回调URL } = systemConfig.支付宝设置; this.config = { appId: AppID, privateKey: this.convertBase64ToPem(应用私钥, 'PRIVATE KEY'), alipayPublicKey: this.convertBase64ToPem(公钥, 'PUBLIC KEY'), gateway: 网关地址 || 'https://openapi.alipay.com/gateway.do', notifyUrl: 回调URL }; } /** * 将 Base64 编码的密钥转换为 PEM 格式 */ private convertBase64ToPem(base64Key: string, keyType: 'PRIVATE KEY' | 'PUBLIC KEY'): string { // 如果已经是 PEM 格式(包含 BEGIN),直接返回 if (base64Key.includes('-----BEGIN')) { return base64Key; } // 移除所有空白字符 const cleanKey = base64Key.replace(/\s/g, ''); // 每 64 个字符插入一个换行符 const formattedKey = cleanKey.match(/.{1,64}/g)?.join('\n') || cleanKey; return `-----BEGIN ${keyType}-----\n${formattedKey}\n-----END ${keyType}-----`; } /** * Generate Alipay Page Pay URL */ async generatePagePayUrl(order: { outTradeNo: string; totalAmount: string; subject: string; body?: string; returnUrl?: string }) { await this.init(); if (!this.config) throw new Error('Alipay not initialized'); const params: Record = { app_id: this.config.appId, method: 'alipay.trade.page.pay', format: 'JSON', charset: 'utf-8', sign_type: 'RSA2', timestamp: new Date().toISOString().replace('T', ' ').split('.')[0], version: '1.0', notify_url: `${this.config.notifyUrl}/api/payment/notify`, return_url: order.returnUrl || `${this.config.notifyUrl}/api/payment/return`, // 优先使用传入的跳转地址 biz_content: JSON.stringify({ out_trade_no: order.outTradeNo, product_code: 'FAST_INSTANT_TRADE_PAY', total_amount: order.totalAmount, subject: order.subject, body: order.body, }), }; params.sign = this.sign(params); const query = Object.keys(params) .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join('&'); return `${this.config.gateway}?${query}`; } /** * Verify Alipay Signature */ async verifySignature(params: Record): Promise { await this.init(); if (!this.config) throw new Error('Alipay not initialized'); const { sign, sign_type, ...rest } = params; if (!sign) return false; // Sort and stringify params const content = Object.keys(rest) .sort() .map((key) => { const value = rest[key]; return value !== '' && value !== undefined && value !== null ? `${key}=${value}` : null; }) .filter(Boolean) .join('&'); const verify = crypto.createVerify('RSA-SHA256'); verify.update(content); return verify.verify(this.config.alipayPublicKey, sign, 'base64'); } /** * Internal Sign Method */ private sign(params: Record): string { if (!this.config) throw new Error('Alipay not initialized'); const content = Object.keys(params) .sort() .map((key) => { const value = params[key]; return value !== '' && value !== undefined && value !== null ? `${key}=${value}` : null; }) .filter(Boolean) .join('&'); const sign = crypto.createSign('RSA-SHA256'); sign.update(content); return sign.sign(this.config.privateKey, 'base64'); } } export const alipayService = new AlipayService();