fix: 支付安全审核修复(支付宝+微信)
支付宝: - 回调增加 app_id 校验,防止跨商户通知 - 回调增加 sign_type 过滤,仅接受 RSA2 - 退款增加 out_request_no 保证幂等 - 金额解析增加精度保护 - timestamp 改用 CST 时区 微信: - 自行实现 AES-GCM 解密替代库的 decipher_gcm(修复 AuthTag 未验证) - WXPAY_PUBLIC_KEY_ID 改为必填 - serial 匹配检查改为强制 - 时间戳校验移到签名验证之前 - nonce 改用 crypto.randomBytes - publicKey 不允许空 Buffer fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,10 @@ function getPayInstance(): WxPay {
|
||||
const env = assertWxpayEnv(getEnv());
|
||||
|
||||
const privateKey = Buffer.from(env.WXPAY_PRIVATE_KEY);
|
||||
const publicKey = env.WXPAY_PUBLIC_KEY ? Buffer.from(env.WXPAY_PUBLIC_KEY) : Buffer.alloc(0);
|
||||
if (!env.WXPAY_PUBLIC_KEY) {
|
||||
throw new Error('WXPAY_PUBLIC_KEY is required');
|
||||
}
|
||||
const publicKey = Buffer.from(env.WXPAY_PUBLIC_KEY);
|
||||
|
||||
payInstance = new WxPay({
|
||||
appid: env.WXPAY_APP_ID,
|
||||
@@ -45,7 +48,7 @@ function yuanToFen(yuan: number): number {
|
||||
|
||||
async function request<T>(method: string, url: string, body?: Record<string, unknown>): Promise<T> {
|
||||
const pay = getPayInstance();
|
||||
const nonce_str = Math.random().toString(36).substring(2, 15);
|
||||
const nonce_str = crypto.randomBytes(16).toString('hex');
|
||||
const timestamp = Math.floor(Date.now() / 1000).toString();
|
||||
|
||||
const signature = pay.getSignature(method, nonce_str, timestamp, url, body ? JSON.stringify(body) : '');
|
||||
@@ -134,8 +137,17 @@ export async function createRefund(params: WxpayRefundParams): Promise<Record<st
|
||||
}
|
||||
|
||||
export function decipherNotify<T>(ciphertext: string, associatedData: string, nonce: string): T {
|
||||
const pay = getPayInstance();
|
||||
return pay.decipher_gcm<T>(ciphertext, associatedData, nonce);
|
||||
const env = assertWxpayEnv(getEnv());
|
||||
const key = env.WXPAY_API_V3_KEY;
|
||||
const ciphertextBuf = Buffer.from(ciphertext, 'base64');
|
||||
// AES-GCM 最后 16 字节是 AuthTag
|
||||
const authTag = ciphertextBuf.subarray(ciphertextBuf.length - 16);
|
||||
const data = ciphertextBuf.subarray(0, ciphertextBuf.length - 16);
|
||||
const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
|
||||
decipher.setAuthTag(authTag);
|
||||
decipher.setAAD(Buffer.from(associatedData));
|
||||
const decoded = Buffer.concat([decipher.update(data), decipher.final()]);
|
||||
return JSON.parse(decoded.toString('utf-8')) as T;
|
||||
}
|
||||
|
||||
export async function verifyNotifySign(params: {
|
||||
|
||||
Reference in New Issue
Block a user