feat: multi-module iteration

This commit is contained in:
2026-08-04 21:38:49 +08:00
parent 9d96c73246
commit 71f508e02b
1366 changed files with 202004 additions and 0 deletions
@@ -0,0 +1,97 @@
import { createDecipheriv, createVerify, timingSafeEqual } from 'crypto';
export type WechatPayNotifyResource = {
transaction_id: string;
out_trade_no: string;
trade_state: string;
trade_state_desc?: string;
amount?: { total?: number; payer_total?: number };
};
export type WechatRefundNotifyResource = {
refund_id: string;
out_refund_no: string;
transaction_id?: string;
out_trade_no?: string;
refund_status: 'SUCCESS' | 'PROCESSING' | 'ABNORMAL' | 'CLOSED';
amount?: { refund?: number; total?: number; payer_refund?: number; payer_total?: number };
};
export type WechatPayNotifyEnvelope = {
id: string;
create_time: string;
event_type: string;
resource_type: string;
summary: string;
resource: {
algorithm: string;
ciphertext: string;
associated_data?: string;
nonce: string;
original_type?: string;
};
};
export function decryptPayResource(
apiV3Key: string,
associatedData: string,
nonce: string,
ciphertext: string,
): WechatPayNotifyResource {
const key = Buffer.from(apiV3Key, 'utf8');
const buf = Buffer.from(ciphertext, 'base64');
const authTag = buf.subarray(buf.length - 16);
const data = buf.subarray(0, buf.length - 16);
const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(nonce, 'utf8'));
if (associatedData) {
decipher.setAAD(Buffer.from(associatedData, 'utf8'));
}
decipher.setAuthTag(authTag);
const decoded = Buffer.concat([decipher.update(data), decipher.final()]);
return JSON.parse(decoded.toString('utf8')) as WechatPayNotifyResource;
}
export function verifyPaySignature(params: {
platformPublicKeyPem: string;
timestamp: string;
nonce: string;
body: string;
signature: string;
}): boolean {
const message = `${params.timestamp}\n${params.nonce}\n${params.body}\n`;
const verifier = createVerify('RSA-SHA256');
verifier.update(message);
verifier.end();
const ok = verifier.verify(params.platformPublicKeyPem, params.signature, 'base64');
if (!ok) return false;
const ts = Number(params.timestamp);
if (!Number.isFinite(ts)) return false;
const skewMs = Math.abs(Date.now() - ts * 1000);
return skewMs <= 5 * 60 * 1000;
}
export function safeEqual(a: string, b: string): boolean {
const ba = Buffer.from(a);
const bb = Buffer.from(b);
if (ba.length !== bb.length) return false;
return timingSafeEqual(ba, bb);
}
/**
* 规范化 .env / system_config 中的 PEM
* - 去掉外层引号(DB/表单常把整段含引号写入)
* - 把字面量 \\n 转成真实换行
* OpenSSL 报 1E08010C DECODER unsupported 时多半是这两类污染。
*/
export function normalizePemEnv(raw: string | undefined | null): string {
if (!raw) return '';
let value = String(raw).trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1).trim();
}
value = value.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').replace(/\r\n/g, '\n');
return value.trim();
}