/* eslint-disable @typescript-eslint/indent */ import Center from '@arcblock/ux/lib/Center'; import Dialog from '@arcblock/ux/lib/Dialog'; import { useLocaleContext } from '@arcblock/ux/lib/Locale/context'; import type { TCustomer } from '@blocklet/payment-types'; import { CircularProgress, Typography, useTheme } from '@mui/material'; import { styled } from '@mui/system'; import { useSetState } from 'ahooks'; import { useEffect, useCallback } from 'react'; import { useMobile } from '../../../hooks/mobile'; import LoadingButton from '../../../components/loading-button'; const { Elements, PaymentElement, useElements, useStripe, loadStripe, LinkAuthenticationElement } = (globalThis as any) .__STRIPE_COMPONENTS__; export type StripeCheckoutFormProps = { clientSecret: string; intentType: string; customer: TCustomer; mode: string; onConfirm: Function; onSkip?: Function | null; returnUrl?: string; submitButtonText?: string; }; const PaymentElementContainer = styled('div')` width: 100%; opacity: 0; transition: opacity 300ms ease; &.visible { opacity: 1; } `; // @doc https://stripe.com/docs/js/elements_object/create_payment_element function StripeCheckoutForm({ clientSecret, intentType, customer, mode, onConfirm, onSkip = null, returnUrl = '', submitButtonText = '', }: StripeCheckoutFormProps) { const stripe = useStripe(); const elements = useElements(); const { t } = useLocaleContext(); const theme = useTheme(); const [state, setState] = useSetState({ message: '', confirming: false, loaded: false, showBillingForm: false, isTransitioning: false, paymentMethod: 'card', }); const handlePaymentMethodChange = (event: any) => { const method = event.value?.type; const needsBillingInfo = method === 'google_pay' || method === 'apple_pay'; const shouldShowForm = needsBillingInfo && !isCompleteBillingAddress(customer.address); if (shouldShowForm && !state.showBillingForm) { setState({ isTransitioning: true }); setTimeout(() => { setState({ isTransitioning: false, paymentMethod: method, showBillingForm: true, }); }, 300); } else { // if shouldShowForm is false, set showBillingForm to false immediately setState({ showBillingForm: false, paymentMethod: method, isTransitioning: false, }); } }; const isCompleteBillingAddress = (address: any) => { return address && address.line1 && address.city && address.state && address.postal_code && address.country; }; useEffect(() => { if (!stripe) { return; } if (!clientSecret) { return; } const method = intentType === 'payment_intent' ? 'retrievePaymentIntent' : 'retrieveSetupIntent'; stripe[method](clientSecret).then(({ paymentIntent, setupIntent }: any) => { const intent = paymentIntent || setupIntent; switch (intent?.status) { case 'succeeded': setState({ message: t('paymentCredit.preparePayMessage.succeeded') }); break; case 'processing': setState({ message: t('paymentCredit.preparePayMessage.processing') }); break; case 'requires_payment_method': // 忽略该状态 default: break; } }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [stripe, clientSecret]); const handleSubmit = useCallback( async (e: any) => { e.preventDefault(); if (!stripe || !elements) { return; } try { setState({ confirming: true, message: '' }); const method = intentType === 'payment_intent' ? 'confirmPayment' : 'confirmSetup'; const { error: submitError } = await elements.submit(); if (submitError) { setState({ confirming: false }); return; } const { error, paymentIntent, setupIntent } = await stripe[method]({ elements, redirect: 'if_required', confirmParams: { return_url: returnUrl || window.location.href, ...(!state.showBillingForm ? { payment_method_data: { billing_details: { name: customer.name, phone: customer.phone, email: customer.email, address: { ...(customer.address || {}), country: customer.address?.country || 'us', line1: customer.address?.line1 || '', line2: customer.address?.line2 || '', city: customer.address?.city || '', state: customer.address?.state || '', postal_code: customer.address?.postal_code || '00000', }, }, }, } : {}), }, }); const intent = paymentIntent || setupIntent; if (intent?.status === 'canceled' || intent?.status === 'requires_payment_method') { setState({ confirming: false }); return; } setState({ confirming: false }); if (error) { if (error.type === 'validation_error') { return; } setState({ message: error.message as string }); return; } onConfirm(); } catch (err: any) { console.error(err); setState({ confirming: false, message: err.message as string }); } }, [customer, intentType, stripe, state.showBillingForm, returnUrl] // eslint-disable-line ); return ( {(!state.paymentMethod || ['link', 'card'].includes(state.paymentMethod)) && ( )} setState({ loaded: true })} /> {(!stripe || !elements || !state.loaded) && (
)} {stripe && elements && state.loaded && ( <> {submitButtonText || t('payment.checkout.continue', { action: t(`payment.checkout.${mode}`) })} {onSkip && ( onSkip()}> {t('payment.checkout.skipPaymentMethod', { defaultValue: 'Skip, bind later' })} )} )} {state.message && {state.message}}
); } const Content = styled('form')` display: flex; flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; min-height: 320px; `; export type StripeCheckoutProps = { clientSecret: string; intentType: string; publicKey: string; mode: string; customer: TCustomer; onConfirm: Function; onCancel: Function; onSkip?: Function | null; returnUrl?: string; title?: string; submitButtonText?: string; }; export default function StripeCheckout({ clientSecret, intentType, publicKey, mode, customer, onConfirm, onCancel, onSkip = null, returnUrl = '', title = '', submitButtonText = '', }: StripeCheckoutProps) { const stripePromise = loadStripe(publicKey); const { isMobile } = useMobile(); const { t, locale } = useLocaleContext(); const theme = useTheme(); const [state, setState] = useSetState({ open: true, closable: true, }); const handleClose = (_: any, reason: string) => { if (reason === 'backdropClick') { return; } setState({ open: false }); onCancel(); }; return ( ); }