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'); }