NewPaymentMethodElement() — supabase Function Reference
Architecture documentation for the NewPaymentMethodElement() function in NewPaymentMethodElement.tsx from the supabase codebase.
Entity Profile
Relationship Graph
Source Code
apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx lines 77–363
(
{
email,
readOnly,
currentAddress,
currentTaxId,
customerName,
}: {
email?: string | null | undefined
readOnly: boolean
currentAddress?: CustomerAddress | null
currentTaxId?: CustomerTaxId | null
customerName?: string | undefined
},
ref
) => {
const stripe = useStripe()
const elements = useElements()
const form = useForm<BillingCustomerDataFormValues>({
resolver: zodResolver(BillingCustomerDataSchema),
defaultValues: {
tax_id_name: currentTaxId
? TAX_IDS.find(
(option) =>
option.type === currentTaxId.type && option.countryIso2 === currentTaxId.country
)?.name || ''
: '',
tax_id_type: currentTaxId ? currentTaxId.type : '',
tax_id_value: currentTaxId ? currentTaxId.value : '',
},
})
// To avoid rendering the business checkbox prematurely and causing weird layout shifts, we wait until the address element is fully loaded
const [fullyLoaded, setFullyLoaded] = useState(false)
const [showTaxIDsPopover, setShowTaxIDsPopover] = useState(false)
const onSelectTaxIdType = (name: string) => {
const selectedTaxIdOption = TAX_IDS.find((option) => option.name === name)
if (!selectedTaxIdOption) return
form.setValue('tax_id_type', selectedTaxIdOption.type)
form.setValue('tax_id_value', '')
form.setValue('tax_id_name', name)
}
const { tax_id_name } = form.watch()
const selectedTaxId = TAX_IDS.find((option) => option.name === tax_id_name)
const [purchasingAsBusiness, setPurchasingAsBusiness] = useState(currentTaxId != null)
const [stripeAddress, setStripeAddress] = useState<
StripeAddressElementChangeEvent['value'] | undefined
>(undefined)
const availableTaxIds = useMemo(() => {
const country = stripeAddress?.address.country || null
return TAX_IDS.filter((taxId) => country == null || taxId.countryIso2 === country).sort(
(a, b) => a.country.localeCompare(b.country)
)
}, [stripeAddress])
const createPaymentMethod = async (): ReturnType<
PaymentMethodElementRef['createPaymentMethod']
> => {
if (!stripe || !elements) return
await form.trigger()
if (purchasingAsBusiness && availableTaxIds.length > 0 && !form.getValues('tax_id_value')) {
return
}
await elements.submit()
// To avoid double 3DS confirmation, we just create the payment method here, as there might be a confirmation step while doing the actual payment
const { error, paymentMethod } = await stripe.createPaymentMethod({
elements,
})
if (error || paymentMethod == null) {
toast.error(error?.message ?? ' Failed to process card details')
return
}
const addressElement = await elements.getElement('address')!.getValue()
return {
paymentMethod,
address: {
...addressElement.value.address,
line2: addressElement.value.address.line2 || undefined,
},
customerName: addressElement.value.name,
taxId: getConfiguredTaxId(),
}
}
function getConfiguredTaxId(): CustomerTaxId | null {
return purchasingAsBusiness && selectedTaxId
? {
country: selectedTaxId.countryIso2,
type: selectedTaxId.type,
value: form.getValues('tax_id_value'),
}
: null
}
const confirmSetup = async (): ReturnType<PaymentMethodElementRef['confirmSetup']> => {
if (!stripe || !elements) return
await elements.submit()
const { error, setupIntent } = await stripe.confirmSetup({
elements,
redirect: 'if_required',
confirmParams: { return_url: `${getURL()}/org/_/billing` },
})
if (error || setupIntent == null) {
toast.error(error?.message ?? ' Failed to process card details')
return
}
const addressElement = await elements.getElement('address')!.getValue()
return {
setupIntent,
address: {
...addressElement.value.address,
line2: addressElement.value.address.line2 || undefined,
},
customerName: addressElement.value.name,
taxId: getConfiguredTaxId(),
}
}
useImperativeHandle(ref, () => ({
createPaymentMethod,
confirmSetup,
}))
const addressOptions: StripeAddressElementOptions = useMemo(
() => ({
mode: 'billing',
autocomplete: {
apiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_KEY!,
mode: 'google_maps_api',
},
display: { name: purchasingAsBusiness ? 'organization' : 'full' },
defaultValues: {
address: currentAddress ?? undefined,
name: customerName,
},
}),
[purchasingAsBusiness]
)
// Preselect tax id if there is no more than 2 available tax ids (even if there are two options, first one in the list is likely to be it)
useEffect(() => {
if (availableTaxIds.length && stripeAddress?.address.country && !currentTaxId) {
const taxIdOption = availableTaxIds[0]
form.setValue('tax_id_type', taxIdOption.type)
form.setValue('tax_id_value', '')
form.setValue('tax_id_name', taxIdOption.name)
}
}, [availableTaxIds, stripeAddress])
return (
<div className="space-y-2">
<p className="text-sm text-foreground-lighter">
Please ensure CVC and postal codes match what’s on file for your card.
</p>
<PaymentElement
options={{
layout: 'tabs',
defaultValues: { billingDetails: { email: email ?? undefined } },
readOnly,
}}
/>
{fullyLoaded && (
<div className="flex items-center space-x-2 py-4">
<Checkbox_Shadcn_
id="business"
checked={purchasingAsBusiness}
onCheckedChange={() => setPurchasingAsBusiness(!purchasingAsBusiness)}
/>
<label htmlFor="business" className="text-foreground text-sm leading-none">
I’m purchasing as a business
</label>
</div>
)}
<AddressElement
options={addressOptions}
// Force reload after changing purchasingAsBusiness setting, it seems like the element does not reload otherwise
key={`address-elements-${purchasingAsBusiness}`}
onChange={(evt) => setStripeAddress(evt.value)}
onReady={() => setFullyLoaded(true)}
/>
{purchasingAsBusiness && availableTaxIds.length > 0 && (
<Form {...form}>
<div className="grid grid-cols-2 gap-x-2 w-full">
<FormField
name="tax_id_name"
control={form.control}
render={({ field }) => (
<FormItemLayout hideMessage layout="vertical">
<Popover open={showTaxIDsPopover} onOpenChange={setShowTaxIDsPopover}>
<PopoverTrigger asChild>
<FormControl>
<Button
type="default"
role="combobox"
size="medium"
className={cn(
'w-full justify-between h-[34px]',
!selectedTaxId && 'text-muted'
)}
iconRight={
<ChevronsUpDown
className="ml-2 h-4 w-4 shrink-0 opacity-50"
strokeWidth={1.5}
/>
}
>
{selectedTaxId
? `${selectedTaxId.country} - ${selectedTaxId.name}`
: 'Select tax ID'}
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent sameWidthAsTrigger className="p-0" align="start">
<Command>
<CommandInput placeholder="Search tax ID..." />
<CommandList>
<CommandEmpty>No tax ID found.</CommandEmpty>
<CommandGroup>
{availableTaxIds.map((option) => (
<CommandItem
key={option.name}
value={`${option.country} - ${option.name}`}
onSelect={() => {
onSelectTaxIdType(option.name)
setShowTaxIDsPopover(false)
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
selectedTaxId?.name === option.name
? 'opacity-100'
: 'opacity-0'
)}
/>
{option.country} - {option.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormMessage />
</FormItemLayout>
)}
/>
{selectedTaxId && (
<FormField
name="tax_id_value"
control={form.control}
render={({ field }) => (
<FormItem_Shadcn_>
<FormControl>
<Input {...field} placeholder={selectedTaxId?.placeholder} />
</FormControl>
<FormMessage />
</FormItem_Shadcn_>
)}
/>
)}
</div>
</Form>
)}
</div>
)
}
Domain
Subdomains
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free