2025.11.27.17.50
This commit is contained in:
138
src/lib/alipay.ts
Normal file
138
src/lib/alipay.ts
Normal 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();
|
||||
Reference in New Issue
Block a user