diff --git a/src/shared/assets/icons/channel/stripe.svg b/src/shared/assets/icons/channel/stripe.svg new file mode 100644 index 0000000..938c491 --- /dev/null +++ b/src/shared/assets/icons/channel/stripe.svg @@ -0,0 +1 @@ + diff --git a/src/shared/components/pay/PayMethodIcon.vue b/src/shared/components/pay/PayMethodIcon.vue index 5c2158d..641c2ef 100644 --- a/src/shared/components/pay/PayMethodIcon.vue +++ b/src/shared/components/pay/PayMethodIcon.vue @@ -10,12 +10,13 @@ import alipaySvg from '@/shared/assets/icons/channel/alipay.svg' import douyinSvg from '@/shared/assets/icons/channel/douyin.svg' import mastercardSvg from '@/shared/assets/icons/channel/mastercard.svg' import otherSvg from '@/shared/assets/icons/channel/other.svg' +import stripeSvg from '@/shared/assets/icons/channel/stripe.svg' import unionPaySvg from '@/shared/assets/icons/channel/union_pay.svg' import visaSvg from '@/shared/assets/icons/channel/visa.svg' import wechatSvg from '@/shared/assets/icons/channel/wechat.svg' const props = withDefaults(defineProps<{ - /** 图标编码: wechat / alipay / union_pay / douyin / aggregate_pay / visa / mastercard */ + /** 图标编码: wechat / alipay / union_pay / douyin / aggregate_pay / visa / mastercard / stripe */ icon?: string /** 图标显示边长(px),容器略大以留内边距 */ size?: number @@ -33,6 +34,7 @@ const ICON_MAP: Record = { aggregate_pay: aggregatePaySvg, visa: visaSvg, mastercard: mastercardSvg, + stripe: stripeSvg, } const src = computed(() => { diff --git a/src/shared/components/pay/StripeIntentPay.vue b/src/shared/components/pay/StripeIntentPay.vue new file mode 100644 index 0000000..de3fac5 --- /dev/null +++ b/src/shared/components/pay/StripeIntentPay.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/src/shared/enums/pay-body-type.ts b/src/shared/enums/pay-body-type.ts index 5197b56..1f9e88d 100644 --- a/src/shared/enums/pay-body-type.ts +++ b/src/shared/enums/pay-body-type.ts @@ -3,6 +3,7 @@ * * 后端枚举: link / jsapi / from / identifier / qr_code / json * 历史误写 url 在分发层按 link 兼容 + * Stripe 通道扩展: stripe_checkout(Checkout Session 跳转) / stripe_intent(PaymentIntent client_secret) */ export const PayBodyType = { /** 支付链接 */ @@ -19,6 +20,10 @@ export const PayBodyType = { JSON: 'json', /** 历史误写, 按 link 处理 */ URL_LEGACY: 'url', + /** Stripe Checkout Session 跳转 URL(前端直接跳转, 与 link 行为一致) */ + STRIPE_CHECKOUT: 'stripe_checkout', + /** Stripe PaymentIntent client_secret(前端用 Stripe.js Elements 调 confirmCardPayment) */ + STRIPE_INTENT: 'stripe_intent', } as const export type PayBodyTypeCode = (typeof PayBodyType)[keyof typeof PayBodyType] diff --git a/src/shared/locales/en-US/cashier.json b/src/shared/locales/en-US/cashier.json index 678a4ca..a7dca4d 100644 --- a/src/shared/locales/en-US/cashier.json +++ b/src/shared/locales/en-US/cashier.json @@ -18,7 +18,8 @@ "douyin": "Douyin Pay", "aggregate_pay": "Aggregate Pay", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "Failed to load order", "payFail": "Failed to start payment", @@ -42,6 +43,12 @@ "autoRedirectTip": "Auto-return to merchant in {n}s", "backToMerchant": "Return to merchant", "payTime": "Payment time", + "stripeIntentTitle": "Stripe Card Payment", + "stripeCardTip": "Enter your card details to complete the payment", + "stripePayNow": "Pay", + "stripePayloadInvalid": "Invalid payment payload, please try again", + "stripeKeyMissing": "Payment configuration is missing, unable to pay", + "stripeLoadFail": "Failed to load Stripe, please try again", "miniScanTitle": "Scan to open the cashier mini-program", "miniScanDesc": "Scan the QR code with WeChat to open the cashier and complete payment", "miniInWalletTitle": "Cashier mini-program unavailable", diff --git a/src/shared/locales/id-ID/cashier.json b/src/shared/locales/id-ID/cashier.json index efe06ab..5db7e27 100644 --- a/src/shared/locales/id-ID/cashier.json +++ b/src/shared/locales/id-ID/cashier.json @@ -18,7 +18,8 @@ "douyin": "Douyin Pay", "aggregate_pay": "Pembayaran agregat", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "Gagal memuat pesanan", "payFail": "Gagal memulai pembayaran", @@ -42,6 +43,12 @@ "autoRedirectTip": "Kembali ke merchant otomatis dalam {n} detik", "backToMerchant": "Kembali ke merchant", "payTime": "Waktu pembayaran", + "stripeIntentTitle": "Pembayaran Kartu Stripe", + "stripeCardTip": "Masukkan detail kartu untuk menyelesaikan pembayaran", + "stripePayNow": "Bayar", + "stripePayloadInvalid": "Parameter pembayaran tidak valid, coba lagi", + "stripeKeyMissing": "Konfigurasi pembayaran tidak ada", + "stripeLoadFail": "Gagal memuat Stripe, coba lagi", "miniScanTitle": "Pindai untuk membuka mini-program kasir", "miniScanDesc": "Pindai kode QR dengan WeChat untuk membuka kasir dan menyelesaikan pembayaran", "miniInWalletTitle": "Mini-program kasir tidak tersedia", diff --git a/src/shared/locales/ja-JP/cashier.json b/src/shared/locales/ja-JP/cashier.json index 87773ef..1760104 100644 --- a/src/shared/locales/ja-JP/cashier.json +++ b/src/shared/locales/ja-JP/cashier.json @@ -18,7 +18,8 @@ "douyin": "Douyin Pay", "aggregate_pay": "統合決済", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "注文の読み込みに失敗しました", "payFail": "支払いの開始に失敗しました", @@ -42,6 +43,12 @@ "autoRedirectTip": "{n} 秒後に自動的に店舗に戻ります", "backToMerchant": "店舗に戻る", "payTime": "支払い日時", + "stripeIntentTitle": "Stripe カード支払い", + "stripeCardTip": "カード情報を入力して支払いを完了してください", + "stripePayNow": "支払いを確定", + "stripePayloadInvalid": "支払いパラメータが無効です。もう一度お試しください", + "stripeKeyMissing": "支払い設定がありません", + "stripeLoadFail": "Stripe の読み込みに失敗しました。もう一度お試しください", "miniScanTitle": "スキャンして決済ミニプログラムを開く", "miniScanDesc": "WeChatでQRコードをスキャンして決済ミニプログラムを開き、支払いを完了してください", "miniInWalletTitle": "決済ミニプログラムは現在利用できません", diff --git a/src/shared/locales/ko-KR/cashier.json b/src/shared/locales/ko-KR/cashier.json index c780bec..fba86ee 100644 --- a/src/shared/locales/ko-KR/cashier.json +++ b/src/shared/locales/ko-KR/cashier.json @@ -18,7 +18,8 @@ "douyin": "Douyin Pay", "aggregate_pay": "통합 결제", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "주문을 불러오지 못했습니다", "payFail": "결제를 시작하지 못했습니다", @@ -42,6 +43,12 @@ "autoRedirectTip": "{n}초 후 자동으로 가맹점으로 돌아갑니다", "backToMerchant": "가맹점으로 돌아가기", "payTime": "결제 시간", + "stripeIntentTitle": "Stripe 카드 결제", + "stripeCardTip": "카드 정보를 입력하여 결제를 완료하세요", + "stripePayNow": "결제하기", + "stripePayloadInvalid": "결제 파라미터가 올바르지 않습니다. 다시 시도해 주세요", + "stripeKeyMissing": "결제 설정이 없습니다", + "stripeLoadFail": "Stripe 로드에 실패했습니다. 다시 시도해 주세요", "miniScanTitle": "스캔하여 결제 미니프로그램 열기", "miniScanDesc": "WeChat으로 QR 코드를 스캔하여 결제 미니프로그램을 열고 결제를 완료하세요", "miniInWalletTitle": "결제 미니프로그램을 사용할 수 없습니다", diff --git a/src/shared/locales/ms-MY/cashier.json b/src/shared/locales/ms-MY/cashier.json index 0dc9275..390e698 100644 --- a/src/shared/locales/ms-MY/cashier.json +++ b/src/shared/locales/ms-MY/cashier.json @@ -18,7 +18,8 @@ "douyin": "Douyin Pay", "aggregate_pay": "Bayaran agregat", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "Gagal memuatkan pesanan", "payFail": "Gagal memulakan pembayaran", @@ -42,6 +43,12 @@ "autoRedirectTip": "Kembali ke merchant automatik dalam {n} saat", "backToMerchant": "Kembali ke merchant", "payTime": "Masa pembayaran", + "stripeIntentTitle": "Pembayaran Kad Stripe", + "stripeCardTip": "Masukkan maklumat kad untuk melengkapkan pembayaran", + "stripePayNow": "Bayar", + "stripePayloadInvalid": "Parameter pembayaran tidak sah, sila cuba lagi", + "stripeKeyMissing": "Konfigurasi pembayaran tiada", + "stripeLoadFail": "Gagal memuat Stripe, sila cuba lagi", "miniScanTitle": "Imbas untuk membuka mini-program kaunter", "miniScanDesc": "Sila imbas kod QR dengan WeChat untuk membuka kaunter dan melengkapkan bayaran", "miniInWalletTitle": "Mini-program kaunter tidak tersedia", diff --git a/src/shared/locales/th-TH/cashier.json b/src/shared/locales/th-TH/cashier.json index 74e25c1..9cf4dff 100644 --- a/src/shared/locales/th-TH/cashier.json +++ b/src/shared/locales/th-TH/cashier.json @@ -18,7 +18,8 @@ "douyin": "Douyin Pay", "aggregate_pay": "ชำระแบบรวม", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "โหลดคำสั่งซื้อไม่สำเร็จ", "payFail": "เริ่มชำระเงินไม่สำเร็จ", @@ -42,6 +43,12 @@ "autoRedirectTip": "กลับสู่ร้านค้าอัตโนมัติใน {n} วินาที", "backToMerchant": "กลับสู่ร้านค้า", "payTime": "เวลาที่ชำระ", + "stripeIntentTitle": "ชำระเงินด้วยบัตร Stripe", + "stripeCardTip": "กรุณากรอกข้อมูลบัตรเพื่อชำระเงิน", + "stripePayNow": "ชำระเงิน", + "stripePayloadInvalid": "พารามิเตอร์การชำระเงินไม่ถูกต้อง โปรดลองอีกครั้ง", + "stripeKeyMissing": "ไม่มีการกำหนดค่าการชำระเงิน", + "stripeLoadFail": "โหลด Stripe ไม่สำเร็จ โปรดลองอีกครั้ง", "miniScanTitle": "สแกนเพื่อเปิดมินิโปรแกรมแคชเชียร์", "miniScanDesc": "โปรดสแกนคิวอาร์โค้ดด้วย WeChat เพื่อเปิดแคชเชียร์และชำระเงินให้เสร็จสิ้น", "miniInWalletTitle": "มินิโปรแกรมแคชเชียร์ไม่พร้อมใช้งาน", diff --git a/src/shared/locales/vi-VN/cashier.json b/src/shared/locales/vi-VN/cashier.json index 7c03dc5..00d2ba4 100644 --- a/src/shared/locales/vi-VN/cashier.json +++ b/src/shared/locales/vi-VN/cashier.json @@ -18,7 +18,8 @@ "douyin": "Douyin Pay", "aggregate_pay": "Thanh toán tổng hợp", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "Không tải được đơn hàng", "payFail": "Không thể bắt đầu thanh toán", @@ -42,6 +43,12 @@ "autoRedirectTip": "Tự động quay lại merchant sau {n} giây", "backToMerchant": "Quay lại merchant", "payTime": "Thời gian thanh toán", + "stripeIntentTitle": "Thanh toán thẻ Stripe", + "stripeCardTip": "Nhập thông tin thẻ để hoàn tất thanh toán", + "stripePayNow": "Thanh toán", + "stripePayloadInvalid": "Tham số thanh toán không hợp lệ, vui lòng thử lại", + "stripeKeyMissing": "Thiếu cấu hình thanh toán", + "stripeLoadFail": "Không tải được Stripe, vui lòng thử lại", "miniScanTitle": "Quét để mở mini-program thu ngân", "miniScanDesc": "Vui lòng quét mã QR bằng WeChat để mở thu ngân và hoàn tất thanh toán", "miniInWalletTitle": "Mini-program thu ngân không khả dụng", diff --git a/src/shared/locales/zh-CN/cashier.json b/src/shared/locales/zh-CN/cashier.json index db800c0..dc9ecf0 100644 --- a/src/shared/locales/zh-CN/cashier.json +++ b/src/shared/locales/zh-CN/cashier.json @@ -18,7 +18,8 @@ "douyin": "抖音支付", "aggregate_pay": "聚合支付", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "加载订单失败", "payFail": "支付发起失败", @@ -42,6 +43,12 @@ "autoRedirectTip": "{n} 秒后自动返回商户", "backToMerchant": "返回商户", "payTime": "支付时间", + "stripeIntentTitle": "Stripe 卡支付", + "stripeCardTip": "请输入银行卡信息完成支付", + "stripePayNow": "确认支付", + "stripePayloadInvalid": "支付参数无效,请重试", + "stripeKeyMissing": "支付配置缺失,无法完成支付", + "stripeLoadFail": "Stripe 加载失败,请重试", "miniScanTitle": "请扫码打开收银台小程序", "miniScanDesc": "请使用微信扫描二维码进入统一收银台并完成支付", "miniInWalletTitle": "收银台小程序暂不可用", diff --git a/src/shared/locales/zh-HK/cashier.json b/src/shared/locales/zh-HK/cashier.json index a63f41e..29701c0 100644 --- a/src/shared/locales/zh-HK/cashier.json +++ b/src/shared/locales/zh-HK/cashier.json @@ -18,7 +18,8 @@ "douyin": "抖音支付", "aggregate_pay": "聚合支付", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "載入訂單失敗", "payFail": "支付發起失敗", @@ -42,6 +43,12 @@ "autoRedirectTip": "{n} 秒後自動返回商戶", "backToMerchant": "返回商戶", "payTime": "支付時間", + "stripeIntentTitle": "Stripe 卡支付", + "stripeCardTip": "請輸入信用卡資料完成付款", + "stripePayNow": "確認付款", + "stripePayloadInvalid": "付款參數無效,請重試", + "stripeKeyMissing": "付款設定遺失,無法完成付款", + "stripeLoadFail": "Stripe 載入失敗,請重試", "miniScanTitle": "請掃碼開啟收銀台小程式", "miniScanDesc": "請使用微信掃描二維碼進入統一收銀台並完成付款", "miniInWalletTitle": "收銀台小程式暫不可用", diff --git a/src/shared/locales/zh-TW/cashier.json b/src/shared/locales/zh-TW/cashier.json index f94baa2..0b07656 100644 --- a/src/shared/locales/zh-TW/cashier.json +++ b/src/shared/locales/zh-TW/cashier.json @@ -18,7 +18,8 @@ "douyin": "抖音支付", "aggregate_pay": "聚合支付", "visa": "Visa", - "mastercard": "Mastercard" + "mastercard": "Mastercard", + "stripe": "Stripe" }, "loadFail": "載入訂單失敗", "payFail": "支付發起失敗", @@ -42,6 +43,12 @@ "autoRedirectTip": "{n} 秒後自動返回商戶", "backToMerchant": "返回商戶", "payTime": "支付時間", + "stripeIntentTitle": "Stripe 卡支付", + "stripeCardTip": "請輸入信用卡資訊完成付款", + "stripePayNow": "確認付款", + "stripePayloadInvalid": "付款參數無效,請重試", + "stripeKeyMissing": "付款設定遺失,無法完成付款", + "stripeLoadFail": "Stripe 載入失敗,請重試", "miniScanTitle": "請掃碼開啟收銀台小程式", "miniScanDesc": "請使用微信掃描二維碼進入統一收銀台並完成付款", "miniInWalletTitle": "收銀台小程式暫不可用", diff --git a/src/shared/pay/stripe.ts b/src/shared/pay/stripe.ts new file mode 100644 index 0000000..c9ce6bd --- /dev/null +++ b/src/shared/pay/stripe.ts @@ -0,0 +1,202 @@ +/** + * Stripe 国际卡支付(PaymentIntent + Elements 模式) + * + * 项目不引入 @stripe/stripe-js npm 包:运行时动态加载 https://js.stripe.com/v3/, + * 以 Card Element 收集卡信息,调 confirmCardPayment 完成支付(3DS 由 Stripe.js 自动处理)。 + * 与后端 stripe_intent 支付参数体类型对应(Checkout Session 跳转走 stripe_checkout/link 分支)。 + */ + +import { loadScript } from '@/shared/utils/load-script' + +/** Stripe.js 官方 CDN 地址 */ +const STRIPE_JS_URL = 'https://js.stripe.com/v3/' + +/** Stripe Card Element(最小化声明,仅用到的成员) */ +export interface StripeJsCardElement { + mount: (el: HTMLElement | string) => void + unmount: () => void + destroy: () => void + on: (event: 'change', handler: (event: StripeJsCardChangeEvent) => void) => void +} + +/** Card Element change 事件负载 */ +export interface StripeJsCardChangeEvent { + /** 卡信息是否完整(可提交支付) */ + complete?: boolean + /** 输入错误(卡号/有效期/CVC 校验失败) */ + error?: { message?: string, type?: string } | null +} + +/** confirmCardPayment 错误(卡被拒/3DS 未完成等) */ +export interface StripeJsConfirmError { + message?: string + code?: string + decline_code?: string + type?: string +} + +/** confirmCardPayment 结果 */ +export interface StripeJsConfirmResult { + paymentIntent?: { id?: string, status?: string } + error?: StripeJsConfirmError +} + +/** Stripe Elements 实例(仅声明 card 元素创建) */ +export interface StripeJsElements { + create: (type: 'card', options?: Record) => StripeJsCardElement +} + +/** Stripe 客户端(最小化声明) */ +export interface StripeJsClient { + elements: (options?: { locale?: string }) => StripeJsElements + confirmCardPayment: ( + clientSecret: string, + options?: { + payment_method?: { card: StripeJsCardElement } + return_url?: string + }, + ) => Promise +} + +/** js.stripe.com/v3 注入的全局构造函数 */ +type StripeJsGlobal = (publishableKey: string) => StripeJsClient + +/** 已加载的 Stripe.js 全局对象(单例) */ +let stripeJsPromise: Promise | null = null + +/** + * 动态加载 Stripe.js(全局只加载一次;失败清除缓存允许重试) + */ +export function loadStripeJs(): Promise { + if (stripeJsPromise) { + return stripeJsPromise + } + stripeJsPromise = loadScript(STRIPE_JS_URL) + .then(() => { + const Stripe = (window as unknown as { Stripe?: StripeJsGlobal }).Stripe + if (!Stripe) { + // 脚本加载成功但全局对象缺失,按失败处理 + throw new Error('Stripe.js global not found') + } + return Stripe + }) + .catch((err) => { + // 失败后允许下次重新加载 + stripeJsPromise = null + throw err + }) + return stripeJsPromise +} + +/** stripe_intent 参数体(后端 payBody JSON 字段) */ +export interface StripeIntentPayload { + /** PaymentIntent client_secret(pi_xxx_secret_xxx) */ + clientSecret: string + /** Stripe publishableKey(pk_xxx),用于初始化 Stripe.js */ + publishableKey: string + /** 3DS 认证完成后的回跳地址(可选) */ + returnUrl?: string +} + +/** + * 解析 stripe_intent 的 payBody + * + * 优先按 JSON 解析(clientSecret / publishableKey / returnUrl); + * 兼容后端直接返回纯 client_secret 字符串的场景(此时缺 publishableKey 由上层提示)。 + */ +export function parseStripeIntentPayload(payBody: string): StripeIntentPayload | null { + if (!payBody) { + return null + } + let json: unknown = null + try { + json = JSON.parse(payBody) + } + catch { + // 非 JSON:视为纯 client_secret 字符串 + return { clientSecret: payBody, publishableKey: '' } + } + if (json && typeof json === 'object') { + const data = json as Record + return { + clientSecret: String(data.clientSecret ?? data.client_secret ?? ''), + publishableKey: String(data.publishableKey ?? data.publishable_key ?? ''), + returnUrl: typeof data.returnUrl === 'string' && data.returnUrl ? data.returnUrl : undefined, + } + } + // JSON 标量(如带引号的字符串字面量):按纯 client_secret 处理 + return { clientSecret: payBody, publishableKey: '' } +} + +/** BCP47 语言码 → Stripe Elements locale */ +const STRIPE_LOCALE_MAP: Record = { + 'zh-CN': 'zh', + 'zh-TW': 'zh-Hant', + 'zh-HK': 'zh-Hant', + 'en-US': 'en', + 'ja-JP': 'ja', + 'ko-KR': 'ko', + 'id-ID': 'id', + 'vi-VN': 'vi', + 'th-TH': 'th', + 'ms-MY': 'ms', +} + +/** + * 当前界面语言转 Stripe locale(未命中返回 undefined,由 Stripe 按浏览器语言自动) + */ +export function toStripeLocale(locale?: string): string | undefined { + if (!locale) { + return undefined + } + return STRIPE_LOCALE_MAP[locale.replace('_', '-')] +} + +/** + * 初始化 Stripe 客户端(publishableKey 必填,页面语言在 elements() 时传入) + */ +export function createStripeClient(publishableKey: string): Promise { + return loadStripeJs().then(Stripe => Stripe(publishableKey)) +} + +/** 确认支付结果 */ +export type ConfirmStripePaymentResult + = | { ok: true, status: 'succeeded' } + | { ok: true, status: 'pending' } + | { ok: false, message: string } + +/** + * 提交卡信息确认支付 + * + * - succeeded:Stripe 已确认扣款成功 + * - pending:已受理但未终态(processing / 3DS 处理中),由上层轮询订单兜底 + * - ok=false:支付被拒/参数错误,message 为 Stripe 已按 locale 本地化的错误文案 + */ +export async function confirmStripePayment( + client: StripeJsClient, + clientSecret: string, + cardElement: StripeJsCardElement, + returnUrl?: string, +): Promise { + let result: StripeJsConfirmResult + try { + result = await client.confirmCardPayment(clientSecret, { + payment_method: { card: cardElement }, + ...(returnUrl ? { return_url: returnUrl } : {}), + }) + } + catch (e: any) { + // Stripe.js 内部异常(网络/SDK),非卡信息错误 + return { ok: false, message: e?.message || 'stripe confirm failed' } + } + if (result.error) { + // 卡被拒 / 3DS 未完成等,message 已按 locale 本地化 + return { ok: false, message: result.error.message || 'stripe confirm failed' } + } + const status = result.paymentIntent?.status + if (status === 'succeeded') { + return { ok: true, status: 'succeeded' } + } + // processing / requires_action 等:已受理,等待最终结果 + return { ok: true, status: 'pending' } +} diff --git a/src/shared/utils/pay-result.ts b/src/shared/utils/pay-result.ts index c8a83f1..f9f772e 100644 --- a/src/shared/utils/pay-result.ts +++ b/src/shared/utils/pay-result.ts @@ -19,6 +19,7 @@ export type PayResultAction | { type: 'qrcode', content: string } | { type: 'form', html: string } | { type: 'jsapi', payload: string } + | { type: 'stripe_intent', payload: string } | { type: 'poll' } | { type: 'unsupported', payBodyType?: string, payBody?: string } @@ -63,6 +64,16 @@ export function resolvePayResult( return { type: 'jsapi', payload: body } } + // Stripe Checkout Session: 跳转 URL(与 link 分支行为一致) + if (body && bodyType === PayBodyType.STRIPE_CHECKOUT) { + return { type: 'redirect', url: body } + } + + // Stripe PaymentIntent: client_secret(JSON 或纯字符串), 由调用方弹 Elements 卡输入面板 + if (body && bodyType === PayBodyType.STRIPE_INTENT) { + return { type: 'stripe_intent', payload: body } + } + // json / identifier / 未知:有 body 时先轮询并交给上层展示;无 body 纯轮询 if (body && (bodyType === PayBodyType.JSON || bodyType === PayBodyType.IDENTIFIER)) { return { type: 'unsupported', payBodyType: bodyType, payBody: body }