2025.11.27.17.50

This commit is contained in:
RUI
2025-11-27 17:50:44 +08:00
commit 5dbb30b32c
111 changed files with 18320 additions and 0 deletions

138
src/lib/alipay.ts Normal file
View File

@@ -0,0 +1,138 @@
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();