fix: 微信支付回调验签 PEM 格式自动补全,Stripe webhook 失败重试
- wxpay client: 添加 formatPublicKey() 自动包裹 PEM 头尾,修复裸 base64 公钥导致的 DECODER routines::unsupported 错误 - stripe webhook: 处理失败时返回 500 让 Stripe 重试 - 修正支付宝测试用例与实际代码对齐 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,7 @@ describe('AlipayProvider', () => {
|
||||
total_amount: '50.00',
|
||||
sign: 'test_sign',
|
||||
sign_type: 'RSA2',
|
||||
app_id: '2021000000000000',
|
||||
}).toString();
|
||||
|
||||
const result = await provider.verifyNotification(body, {});
|
||||
@@ -190,6 +191,7 @@ describe('AlipayProvider', () => {
|
||||
total_amount: '30.00',
|
||||
sign: 'test_sign',
|
||||
sign_type: 'RSA2',
|
||||
app_id: '2021000000000000',
|
||||
}).toString();
|
||||
|
||||
const result = await provider.verifyNotification(body, {});
|
||||
@@ -237,6 +239,7 @@ describe('AlipayProvider', () => {
|
||||
out_trade_no: 'order-001',
|
||||
refund_amount: '100.00',
|
||||
refund_reason: 'customer request',
|
||||
out_request_no: 'order-001-refund',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -44,16 +44,16 @@ describe('Alipay RSA2 Sign', () => {
|
||||
expect(sign1).toBe(sign2);
|
||||
});
|
||||
|
||||
it('should filter out sign field but keep sign_type', () => {
|
||||
it('should filter out sign and sign_type fields', () => {
|
||||
const paramsWithSign = { ...testParams, sign: 'old_sign' };
|
||||
const sign1 = generateSign(testParams, privateKey);
|
||||
const sign2 = generateSign(paramsWithSign, privateKey);
|
||||
expect(sign1).toBe(sign2);
|
||||
|
||||
// sign_type should be included in signing
|
||||
// sign_type should also be excluded from signing (per Alipay spec)
|
||||
const paramsWithSignType = { ...testParams, sign_type: 'RSA2' };
|
||||
const sign3 = generateSign(paramsWithSignType, privateKey);
|
||||
expect(sign3).not.toBe(sign1);
|
||||
expect(sign3).toBe(sign1);
|
||||
});
|
||||
|
||||
it('should filter out empty values', () => {
|
||||
|
||||
@@ -19,8 +19,12 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||
// Unknown event type — acknowledge receipt
|
||||
return NextResponse.json({ received: true });
|
||||
}
|
||||
await handlePaymentNotify(notification, provider.name);
|
||||
const success = await handlePaymentNotify(notification, provider.name);
|
||||
|
||||
if (!success) {
|
||||
// 处理失败(充值未完成等),返回 500 让 Stripe 重试
|
||||
return NextResponse.json({ error: 'Processing failed, will retry' }, { status: 500 });
|
||||
}
|
||||
return NextResponse.json({ received: true });
|
||||
} catch (error) {
|
||||
console.error('Stripe webhook error:', error);
|
||||
|
||||
@@ -3,6 +3,12 @@ import crypto from 'crypto';
|
||||
import { getEnv } from '@/lib/config';
|
||||
import type { WxpayPcOrderParams, WxpayH5OrderParams, WxpayRefundParams } from './types';
|
||||
|
||||
/** 自动补全 PEM 格式(公钥) */
|
||||
function formatPublicKey(key: string): string {
|
||||
if (key.includes('-----BEGIN')) return key;
|
||||
return `-----BEGIN PUBLIC KEY-----\n${key}\n-----END PUBLIC KEY-----`;
|
||||
}
|
||||
|
||||
const BASE_URL = 'https://api.mch.weixin.qq.com';
|
||||
|
||||
function assertWxpayEnv(env: ReturnType<typeof getEnv>) {
|
||||
@@ -29,7 +35,7 @@ function getPayInstance(): WxPay {
|
||||
if (!env.WXPAY_PUBLIC_KEY) {
|
||||
throw new Error('WXPAY_PUBLIC_KEY is required');
|
||||
}
|
||||
const publicKey = Buffer.from(env.WXPAY_PUBLIC_KEY);
|
||||
const publicKey = Buffer.from(formatPublicKey(env.WXPAY_PUBLIC_KEY));
|
||||
|
||||
payInstance = new WxPay({
|
||||
appid: env.WXPAY_APP_ID,
|
||||
@@ -166,5 +172,5 @@ export async function verifyNotifySign(params: {
|
||||
const message = `${params.timestamp}\n${params.nonce}\n${params.body}\n`;
|
||||
const verify = crypto.createVerify('RSA-SHA256');
|
||||
verify.update(message);
|
||||
return verify.verify(env.WXPAY_PUBLIC_KEY, params.signature, 'base64');
|
||||
return verify.verify(formatPublicKey(env.WXPAY_PUBLIC_KEY), params.signature, 'base64');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user