import { SendButton } from '@/components/SendButton' import { createSignal, onMount, Show } from 'solid-js' import type { Stripe, StripeElements } from '@stripe/stripe-js' import { BotContext } from '@/types' import type { PaymentInputBlock, RuntimeOptions } from '@indite.io/schemas' import { loadStripe } from '@/lib/stripe' import { removePaymentInProgressFromStorage, setPaymentInProgressInStorage, } from '../helpers/paymentInProgressStorage' import { defaultPaymentInputOptions } from '@indite.io/schemas/features/blocks/inputs/payment/constants' type Props = { context: BotContext options: PaymentInputBlock['options'] & RuntimeOptions onSuccess: () => void onTransitionEnd: () => void } const slotName = 'stripe-payment-form' let paymentElementSlot: HTMLSlotElement | null = null let stripe: Stripe | null = null let elements: StripeElements | null = null export const StripePaymentForm = (props: Props) => { const [message, setMessage] = createSignal() const [isMounted, setIsMounted] = createSignal(false) const [isLoading, setIsLoading] = createSignal(false) onMount(async () => { if (paymentElementSlot) { initShadowMountPoint(paymentElementSlot) } if (!props.options?.publicKey) return setMessage('Missing Stripe public key') stripe = await loadStripe(props.options?.publicKey) if (!stripe) return elements = stripe.elements({ appearance: { theme: 'stripe', variables: { colorPrimary: paymentElementSlot ? getComputedStyle(paymentElementSlot).getPropertyValue( '--bot-button-bg-color' ) : '', }, }, clientSecret: props.options.paymentIntentSecret, }) const paymentElement = elements.create('payment', { layout: 'tabs', }) paymentElement.mount('#payment-element') setTimeout(() => { setIsMounted(true) props.onTransitionEnd() }, 1000) }) const handleSubmit = async (event: SubmitEvent) => { event.preventDefault() if (!stripe || !elements) return setIsLoading(true) setPaymentInProgressInStorage({ sessionId: props.context.sessionId, resultId: props.context.resultId, bot: props.context.bot, }) const { postalCode, ...address } = props.options?.additionalInformation?.address ?? {} const { error, paymentIntent } = await stripe.confirmPayment({ elements, confirmParams: { return_url: window.location.href, payment_method_data: { billing_details: { name: props.options?.additionalInformation?.name, email: props.options?.additionalInformation?.email, phone: props.options?.additionalInformation?.phoneNumber, address: { ...address, postal_code: postalCode, }, }, }, }, redirect: 'if_required', }) removePaymentInProgressFromStorage() setIsLoading(false) if (error?.type === 'validation_error') return if (error?.type === 'card_error') return setMessage(error.message) if (!error && paymentIntent.status === 'succeeded') return props.onSuccess() } return (
(paymentElementSlot = el)} /> {props.options?.labels?.button ?? defaultPaymentInputOptions.labels.button}{' '} {props.options?.amountLabel}
{message()}
) } const initShadowMountPoint = (element: HTMLElement) => { const rootNode = element.getRootNode() as ShadowRoot const host = rootNode.host const slotPlaceholder = document.createElement('div') slotPlaceholder.style.width = '100%' slotPlaceholder.slot = slotName host.appendChild(slotPlaceholder) const paymentElementContainer = document.createElement('div') paymentElementContainer.id = 'payment-element' slotPlaceholder.appendChild(paymentElementContainer) }