Files
AOUN/src/lib/alipay.ts
2025-11-27 17:50:44 +08:00

139 lines
4.6 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.
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<string, string> = {
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<string, string>): Promise<boolean> {
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, string>): 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();