/* 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 (
);
}