import { useQueryClient } from '@tanstack/react-query' import { NewPaymentMethodElement, type PaymentMethodElementRef, } from 'components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement' import { organizationKeys } from 'data/organizations/keys' import { useOrganizationCustomerProfileQuery } from 'data/organizations/organization-customer-profile-query' import { useOrganizationCustomerProfileUpdateMutation } from 'data/organizations/organization-customer-profile-update-mutation' import { useOrganizationPaymentMethodMarkAsDefaultMutation } from 'data/organizations/organization-payment-method-default-mutation' import { useOrganizationTaxIdQuery } from 'data/organizations/organization-tax-id-query' import { useOrganizationTaxIdUpdateMutation } from 'data/organizations/organization-tax-id-update-mutation' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { isEqual } from 'lodash' import { useRef, useState } from 'react' import { toast } from 'sonner' import { Button, Checkbox_Shadcn_, Label_Shadcn_, Modal } from 'ui' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' interface AddPaymentMethodFormProps { returnUrl: string onCancel: () => void onConfirm: () => void } // Stripe docs recommend to use the new SetupIntent flow over // manually creating and attaching payment methods via the API // Small UX annoyance here, that the page will be refreshed const AddPaymentMethodForm = ({ onCancel, onConfirm }: AddPaymentMethodFormProps) => { const { data: selectedOrganization } = useSelectedOrganizationQuery() const { data: customerProfile, isPending: customerProfileLoading } = useOrganizationCustomerProfileQuery({ slug: selectedOrganization?.slug, }) const [isSaving, setIsSaving] = useState(false) const [isDefaultPaymentMethod, setIsDefaultPaymentMethod] = useState(true) const [isPrimaryBillingAddress, setIsPrimaryBillingAddress] = useState(true) const queryClient = useQueryClient() const { mutateAsync: markAsDefault } = useOrganizationPaymentMethodMarkAsDefaultMutation() const { mutateAsync: updateCustomerProfile } = useOrganizationCustomerProfileUpdateMutation() const { mutateAsync: updateTaxId } = useOrganizationTaxIdUpdateMutation() const { data: taxId, isPending: isCustomerTaxIdLoading } = useOrganizationTaxIdQuery({ slug: selectedOrganization?.slug, }) const paymentRef = useRef(null) const handleSubmit = async (event: any) => { event.preventDefault() setIsSaving(true) if (document !== undefined) { // [Joshen] This is to ensure that any 3DS popup from Stripe remains clickable document.body.classList.add('!pointer-events-auto') } const result = await paymentRef.current?.confirmSetup() if (!result) { setIsSaving(false) } else { if ( isDefaultPaymentMethod && selectedOrganization && typeof result.setupIntent?.payment_method === 'string' ) { try { await markAsDefault({ slug: selectedOrganization.slug, paymentMethodId: result.setupIntent.payment_method, }) await queryClient.invalidateQueries({ queryKey: organizationKeys.paymentMethods(selectedOrganization.slug), }) queryClient.setQueriesData( { queryKey: organizationKeys.paymentMethods(selectedOrganization.slug) }, (prev: any) => { if (!prev) return prev return { ...prev, defaultPaymentMethodId: result.setupIntent.payment_method, data: prev.data.map((pm: any) => ({ ...pm, is_default: pm.id === result.setupIntent.payment_method, })), } } ) } catch (error) { toast.error('Failed to set payment method as default') } } else { if (selectedOrganization) { await queryClient.invalidateQueries({ queryKey: organizationKeys.paymentMethods(selectedOrganization.slug), }) } } if (isPrimaryBillingAddress) { try { if ( result.address && (!isEqual(result.address, customerProfile?.address) || customerProfile?.billing_name !== result.customerName) ) { await updateCustomerProfile({ slug: selectedOrganization?.slug, billing_name: result.customerName, address: result.address, }) } if (result.taxId && !isEqual(result.taxId, taxId)) { await updateTaxId({ taxId: result.taxId, slug: selectedOrganization?.slug }) } } catch (error) { toast.error('Failed to update billing address') } } setIsSaving(false) onConfirm() } if (document !== undefined) { document.body.classList.remove('!pointer-events-auto') } } if (customerProfileLoading || isCustomerTaxIdLoading) { return (
) } return (
{ if (typeof checked === 'boolean') { setIsDefaultPaymentMethod(checked) } }} /> Save as default payment method
{ if (typeof checked === 'boolean') { setIsPrimaryBillingAddress(checked) } }} /> Use the billing address as my organization's primary address
) } export default AddPaymentMethodForm