对接微信支付

This commit is contained in:
2026-07-01 22:41:49 +08:00
parent b78ef8798c
commit ff4622ff6c
14 changed files with 389 additions and 19 deletions
@@ -0,0 +1,69 @@
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 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);
}