From f50a180ec4e49b3064b904b91776dabea5eea885 Mon Sep 17 00:00:00 2001 From: erio Date: Sat, 7 Mar 2026 04:27:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E9=AA=8C=E7=AD=BE=20PEM=20=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=A1=A5=E5=85=A8=EF=BC=8CStripe=20webhook?= =?UTF-8?q?=20=E5=A4=B1=E8=B4=A5=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wxpay client: 添加 formatPublicKey() 自动包裹 PEM 头尾,修复裸 base64 公钥导致的 DECODER routines::unsupported 错误 - stripe webhook: 处理失败时返回 500 让 Stripe 重试 - 修正支付宝测试用例与实际代码对齐 Co-Authored-By: Claude Opus 4.6 --- src/__tests__/lib/alipay/provider.test.ts | 3 +++ src/__tests__/lib/alipay/sign.test.ts | 6 +++--- src/app/api/stripe/webhook/route.ts | 6 +++++- src/lib/wxpay/client.ts | 10 ++++++++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/__tests__/lib/alipay/provider.test.ts b/src/__tests__/lib/alipay/provider.test.ts index f22ddb6..f372ae1 100644 --- a/src/__tests__/lib/alipay/provider.test.ts +++ b/src/__tests__/lib/alipay/provider.test.ts @@ -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', }); }); diff --git a/src/__tests__/lib/alipay/sign.test.ts b/src/__tests__/lib/alipay/sign.test.ts index 5be2995..a7b033c 100644 --- a/src/__tests__/lib/alipay/sign.test.ts +++ b/src/__tests__/lib/alipay/sign.test.ts @@ -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', () => { diff --git a/src/app/api/stripe/webhook/route.ts b/src/app/api/stripe/webhook/route.ts index 7dec1e5..d440b46 100644 --- a/src/app/api/stripe/webhook/route.ts +++ b/src/app/api/stripe/webhook/route.ts @@ -19,8 +19,12 @@ export async function POST(request: NextRequest): Promise { // 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); diff --git a/src/lib/wxpay/client.ts b/src/lib/wxpay/client.ts index 7c4db5d..7382ba6 100644 --- a/src/lib/wxpay/client.ts +++ b/src/lib/wxpay/client.ts @@ -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) { @@ -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'); }