feat: integrate Stripe payment with bugfixes and active timeout cancellation

- Add Stripe payment provider with Checkout Session flow
- Payment provider abstraction layer (EasyPay + Stripe unified interface)
- Stripe webhook with proper raw body handling and signature verification
- Frontend: Stripe button with URL validation, anti-duplicate click, noopener
- Active timeout cancellation: query platform before expiring, recover paid orders
- Singleton Stripe client, idempotency keys, Math.round for amounts
- Handle async_payment events, return null for unknown webhook events
- Set Checkout Session expires_at aligned with order timeout
- Add cancelPayment to provider interface (Stripe: sessions.expire, EasyPay: no-op)
- Enable stripe in frontend payment type list
This commit is contained in:
erio
2026-03-01 17:58:08 +08:00
parent 2f45044073
commit d9ab65ecf2
59 changed files with 1571 additions and 432 deletions

View File

@@ -1,7 +1,7 @@
'use client';
import { useSearchParams } from 'next/navigation';
import { useState, useEffect, Suspense, useMemo } from 'react';
import { useState, useEffect, Suspense } from 'react';
import PaymentForm from '@/components/PaymentForm';
import PaymentQRCode from '@/components/PaymentQRCode';
import OrderStatus from '@/components/OrderStatus';
@@ -13,9 +13,10 @@ interface OrderResult {
orderId: string;
amount: number;
status: string;
paymentType: 'alipay' | 'wxpay';
paymentType: 'alipay' | 'wxpay' | 'stripe';
payUrl?: string | null;
qrCode?: string | null;
checkoutUrl?: string | null;
expiresAt: string;
}
@@ -47,7 +48,7 @@ function PayContent() {
const [activeMobileTab, setActiveMobileTab] = useState<'pay' | 'orders'>('pay');
const [config] = useState<AppConfig>({
enabledPaymentTypes: ['alipay', 'wxpay'],
enabledPaymentTypes: ['alipay', 'wxpay', 'stripe'],
minAmount: 1,
maxAmount: 10000,
});
@@ -185,6 +186,7 @@ function PayContent() {
paymentType: data.paymentType || paymentType,
payUrl: data.payUrl,
qrCode: data.qrCode,
checkoutUrl: data.checkoutUrl,
expiresAt: data.expiresAt,
});
@@ -385,6 +387,7 @@ function PayContent() {
orderId={orderResult.orderId}
payUrl={orderResult.payUrl}
qrCode={orderResult.qrCode}
checkoutUrl={orderResult.checkoutUrl}
paymentType={orderResult.paymentType}
amount={orderResult.amount}
expiresAt={orderResult.expiresAt}