CreditTopUp() — supabase Function Reference
Architecture documentation for the CreditTopUp() function in CreditTopUp.tsx from the supabase codebase.
Entity Profile
Relationship Graph
Source Code
apps/studio/components/interfaces/Organization/BillingSettings/CreditTopUp.tsx lines 60–349
export const CreditTopUp = ({ slug }: { slug: string | undefined }) => {
const { resolvedTheme } = useTheme()
const queryClient = useQueryClient()
const paymentMethodSelectionRef = useRef<{
createPaymentMethod: PaymentMethodElementRef['createPaymentMethod']
}>(null)
const { can: canTopUpCredits, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
PermissionAction.BILLING_WRITE,
'stripe.subscriptions'
)
const {
mutateAsync: topUpCredits,
isPending: executingTopUp,
error: errorInitiatingTopUp,
} = useOrganizationCreditTopUpMutation({})
const form = useForm<CreditTopUpForm>({
resolver: zodResolver(FormSchema),
defaultValues: {
amount: 100,
paymentMethod: '',
},
})
const [topUpModalVisible, setTopUpModalVisible] = useState(false)
const [paymentConfirmationLoading, setPaymentConfirmationLoading] = useState(false)
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
const [captchaRef, setCaptchaRef] = useState<HCaptcha | null>(null)
const captchaRefCallback = useCallback((node: any) => {
setCaptchaRef(node)
}, [])
const resetCaptcha = () => {
setCaptchaToken(null)
captchaRef?.resetCaptcha()
}
const initHcaptcha = async () => {
if (topUpModalVisible && captchaRef) {
let token = captchaToken
try {
if (!token) {
const captchaResponse = await captchaRef.execute({ async: true })
token = captchaResponse?.response ?? null
setCaptchaToken(token)
return token
}
} catch (error) {
return token
}
return token
}
}
useEffect(() => {
initHcaptcha()
}, [topUpModalVisible, captchaRef])
const [paymentIntentSecret, setPaymentIntentSecret] = useState('')
const [paymentIntentConfirmation, setPaymentIntentConfirmation] = useState<PaymentIntentResult>()
const onSubmit: SubmitHandler<CreditTopUpForm> = async ({ amount, paymentMethod }) => {
setPaymentIntentConfirmation(undefined)
const token = await initHcaptcha()
const paymentMethodResult = await paymentMethodSelectionRef.current?.createPaymentMethod()
if (!paymentMethodResult) {
return
}
await topUpCredits(
{
slug,
amount,
payment_method_id: paymentMethodResult.paymentMethod.id,
hcaptchaToken: token,
address: paymentMethodResult.address,
tax_id: paymentMethodResult.taxId ?? undefined,
billing_name: paymentMethodResult.customerName,
},
{
onSuccess: (data) => {
if (data.status === 'succeeded') {
onSuccessfulPayment()
} else {
setPaymentIntentSecret(data.payment_intent_secret || '')
}
resetCaptcha()
},
}
)
}
const options = useMemo(() => {
return {
clientSecret: paymentIntentSecret,
appearance: getStripeElementsAppearanceOptions(resolvedTheme),
} as any
}, [paymentIntentSecret, resolvedTheme])
const onTopUpDialogVisibilityChange = (visible: boolean) => {
setTopUpModalVisible(visible)
if (!visible) {
setCaptchaRef(null)
setPaymentIntentConfirmation(undefined)
setPaymentIntentSecret('')
}
}
const paymentIntentConfirmed = (paymentIntentConfirmation: PaymentIntentResult) => {
// Reset payment intent secret to ensure another attempt works as expected
setPaymentIntentSecret('')
setPaymentIntentConfirmation(paymentIntentConfirmation)
if (paymentIntentConfirmation.paymentIntent?.status === 'succeeded') {
onSuccessfulPayment()
}
}
const onSuccessfulPayment = async () => {
onTopUpDialogVisibilityChange(false)
await queryClient.invalidateQueries({ queryKey: subscriptionKeys.orgSubscription(slug) })
toast.success(
'Successfully topped up balance. It may take a minute to reflect in your account.'
)
}
return (
<Dialog open={topUpModalVisible} onOpenChange={(open) => onTopUpDialogVisibilityChange(open)}>
<DialogTrigger asChild>
<ButtonTooltip
type="default"
className="pointer-events-auto"
disabled={!canTopUpCredits || !isPermissionsLoaded}
tooltip={{
content: {
side: 'bottom',
text:
isPermissionsLoaded && !canTopUpCredits
? 'You need additional permissions to top up credits'
: undefined,
},
}}
>
Top Up
</ButtonTooltip>
</DialogTrigger>
<DialogContent onInteractOutside={(e) => e.preventDefault()}>
<HCaptcha
ref={captchaRefCallback}
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
size="invisible"
onOpen={() => {
// [Joshen] This is to ensure that hCaptcha popup remains clickable
if (document !== undefined) document.body.classList.add('!pointer-events-auto')
}}
onClose={() => {
if (document !== undefined) document.body.classList.remove('!pointer-events-auto')
}}
onVerify={(token) => {
setCaptchaToken(token)
if (document !== undefined) document.body.classList.remove('!pointer-events-auto')
}}
onExpire={() => {
setCaptchaToken(null)
}}
/>
<DialogHeader>
<DialogTitle>Top Up Credits</DialogTitle>
<DialogDescription className="space-y-2">
<p className="prose text-sm">
On successful payment, an invoice will be issued and you'll be granted credits.
Credits will be applied to future invoices only and are not refundable. The topped up
credits do not expire.
</p>
<p className="prose text-sm">
For larger discounted credit packages, please reach out to us via{' '}
<SupportLink
queryParams={{
orgSlug: slug,
projectRef: NO_PROJECT_MARKER,
subject: 'I would like to inquire about larger credit packages',
category: SupportCategories.SALES_ENQUIRY,
}}
>
support
</SupportLink>
.
</p>
</DialogDescription>
</DialogHeader>
<DialogSectionSeparator />
<Form_Shadcn_ {...form}>
<form id={FORM_ID} onSubmit={form.handleSubmit(onSubmit)}>
<DialogSection className="flex flex-col gap-2">
<FormField_Shadcn_
control={form.control}
name="amount"
render={({ field }) => (
<FormItemLayout label="Amount (USD)" className="gap-1">
<Input_Shadcn_ {...field} type="number" placeholder="100" />
</FormItemLayout>
)}
/>
<FormField_Shadcn_
control={form.control}
name="paymentMethod"
render={() => (
<PaymentMethodSelection
ref={paymentMethodSelectionRef}
onSelectPaymentMethod={(pm) => form.setValue('paymentMethod', pm)}
selectedPaymentMethod={form.getValues('paymentMethod')}
readOnly={executingTopUp || paymentConfirmationLoading}
/>
)}
/>
{paymentIntentConfirmation && paymentIntentConfirmation.error && (
<Alert_Shadcn_ variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle_Shadcn_>Error confirming payment</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
{paymentIntentConfirmation.error.message}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{paymentIntentConfirmation?.paymentIntent &&
paymentIntentConfirmation.paymentIntent.status === 'processing' && (
<Alert_Shadcn_ variant="default">
<Info className="h-4 w-4" />
<AlertTitle_Shadcn_>Payment processing</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
Your payment is processing and we are waiting for a confirmation from your
card issuer. If the payment goes through you'll automatically be credited.
Please check back later.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{errorInitiatingTopUp && (
<Alert_Shadcn_ variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle_Shadcn_>Error topping up balance</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
{errorInitiatingTopUp.message}
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</DialogSection>
{!paymentIntentConfirmation?.paymentIntent && (
<DialogFooter>
<Button
htmlType="submit"
type="primary"
loading={executingTopUp || paymentConfirmationLoading}
>
Top Up
</Button>
</DialogFooter>
)}
</form>
</Form_Shadcn_>
{stripePromise && paymentIntentSecret && (
<Elements stripe={stripePromise} options={options}>
<PaymentConfirmation
paymentIntentSecret={paymentIntentSecret}
onPaymentIntentConfirm={(paymentIntentConfirmation) =>
paymentIntentConfirmed(paymentIntentConfirmation)
}
onLoadingChange={(loading) => setPaymentConfirmationLoading(loading)}
/>
</Elements>
)}
</DialogContent>
</Dialog>
)
}
Domain
Subdomains
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free