对接微信支付
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user