feat: Stripe 改用 PaymentIntent + Payment Element,iframe 嵌入支付宝弹窗支付
Stripe 集成重构: - 从 Checkout Session 改为 PaymentIntent + Payment Element 模式 - 前端内联渲染 Stripe 支付表单,支持信用卡、支付宝等多种方式 - Webhook 事件改为 payment_intent.succeeded / payment_intent.payment_failed - provider/test 同步更新 iframe 嵌入模式 (ui_mode=embedded): - 支付宝等需跳转的方式改为弹出新窗口处理,避免 X-Frame-Options 冲破 iframe - 信用卡等无跳转方式仍在 iframe 内联完成 - 弹窗使用 confirmAlipayPayment 直接跳转,无需二次操作 - result 页面检测弹窗模式,支付成功后自动关闭窗口 Bug 修复: - 修复配置加载前支付方式闪烁(初始值改为空数组 + loading) - 修复桌面端 PaymentForm 缺少 methodLimits prop - 修复 stripeError 隐藏表单导致无法重试 - 快捷金额增加 1000/2000 选项,过滤低于 minAmount 的选项 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ interface OrderResult {
|
||||
paymentType: 'alipay' | 'wxpay' | 'stripe';
|
||||
payUrl?: string | null;
|
||||
qrCode?: string | null;
|
||||
checkoutUrl?: string | null;
|
||||
clientSecret?: string | null;
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ interface AppConfig {
|
||||
methodLimits?: Record<string, MethodLimitInfo>;
|
||||
helpImageUrl?: string | null;
|
||||
helpText?: string | null;
|
||||
stripePublishableKey?: string | null;
|
||||
}
|
||||
|
||||
function PayContent() {
|
||||
@@ -59,7 +60,7 @@ function PayContent() {
|
||||
const [activeMobileTab, setActiveMobileTab] = useState<'pay' | 'orders'>('pay');
|
||||
|
||||
const [config, setConfig] = useState<AppConfig>({
|
||||
enabledPaymentTypes: ['alipay', 'wxpay', 'stripe'],
|
||||
enabledPaymentTypes: [],
|
||||
minAmount: 1,
|
||||
maxAmount: 1000,
|
||||
maxDailyAmount: 0,
|
||||
@@ -108,6 +109,7 @@ function PayContent() {
|
||||
methodLimits: cfgData.config.methodLimits,
|
||||
helpImageUrl: cfgData.config.helpImageUrl ?? null,
|
||||
helpText: cfgData.config.helpText ?? null,
|
||||
stripePublishableKey: cfgData.config.stripePublishableKey ?? null,
|
||||
});
|
||||
}
|
||||
} else if (cfgRes.status === 404) {
|
||||
@@ -261,7 +263,7 @@ function PayContent() {
|
||||
paymentType: data.paymentType || paymentType,
|
||||
payUrl: data.payUrl,
|
||||
qrCode: data.qrCode,
|
||||
checkoutUrl: data.checkoutUrl,
|
||||
clientSecret: data.clientSecret,
|
||||
expiresAt: data.expiresAt,
|
||||
});
|
||||
|
||||
@@ -377,7 +379,16 @@ function PayContent() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 'form' && (
|
||||
{step === 'form' && config.enabledPaymentTypes.length === 0 && (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-2 border-blue-500 border-t-transparent" />
|
||||
<span className={['ml-3 text-sm', isDark ? 'text-slate-400' : 'text-gray-500'].join(' ')}>
|
||||
加载中...
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === 'form' && config.enabledPaymentTypes.length > 0 && (
|
||||
<>
|
||||
{isMobile ? (
|
||||
activeMobileTab === 'pay' ? (
|
||||
@@ -465,7 +476,8 @@ function PayContent() {
|
||||
token={token || undefined}
|
||||
payUrl={orderResult.payUrl}
|
||||
qrCode={orderResult.qrCode}
|
||||
checkoutUrl={orderResult.checkoutUrl}
|
||||
clientSecret={orderResult.clientSecret}
|
||||
stripePublishableKey={config.stripePublishableKey}
|
||||
paymentType={orderResult.paymentType}
|
||||
amount={orderResult.amount}
|
||||
payAmount={orderResult.payAmount}
|
||||
@@ -473,6 +485,7 @@ function PayContent() {
|
||||
onStatusChange={handleStatusChange}
|
||||
onBack={handleBack}
|
||||
dark={isDark}
|
||||
isEmbedded={isEmbedded}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user