feat: 技术支持工单/企微权限/开发版本管理/消息推送等迭代
This commit is contained in:
@@ -1,97 +0,0 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user