Skip to content

Commit 9d2807e

Browse files
feat(billing): include prepaid credits in credit balance (supabase#45177)
### Summary This PR updates the logic to include `prepaid_credits_balance` while showing the existing customer balance. This changes the credit balance shown in: - Billing Settings > Credit Balance - Credit code redemption modal The displayed amount now reflects the total credit balance across prepaid credits and the customer balance. ### Testing - Open an org billing page with prepaid credits and verify Credit Balance includes both sources. - Open the credit redemption modal and verify Current Balance matches the combined credit amount. - Verify an org with only customer balance still shows the same credit amount as before. - Verify an org with only prepaid credits balance and no customer balance now shows credits correctly. - Verify an org with no credits shows 0.00 and does not show /credits. - Verify an org where net balance is debt still shows a negative amount without the /credits suffix. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Improvements** * Credit balance display now includes purchased and prorated credits for a complete account view. * Credit redemption and current-balance screens now show combined credit totals (prepaid + existing) for clearer availability. * UI descriptive text clarified to explain how credits are applied and how charges occur once credits are exhausted. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 9743da0 commit 9d2807e

4 files changed

Lines changed: 37 additions & 13 deletions

File tree

apps/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useParams } from 'common'
33

44
import { CreditCodeRedemption } from './CreditCodeRedemption'
55
import { CreditTopUp } from './CreditTopUp'
6+
import { getTotalCreditBalanceCents } from './helpers'
67
import {
78
ScaffoldSection,
89
ScaffoldSectionContent,
@@ -31,13 +32,14 @@ const CreditBalance = () => {
3132
isSuccess,
3233
} = useOrgSubscriptionQuery({ orgSlug: slug }, { enabled: canReadSubscriptions })
3334

34-
const customerBalance = (subscription?.customer_balance ?? 0) / 100
35-
const isCredit = customerBalance < 0
36-
const isDebt = customerBalance > 0
37-
const balance =
38-
isCredit && customerBalance !== 0
39-
? customerBalance.toFixed(2).toString().replace('-', '')
40-
: customerBalance.toFixed(2)
35+
const combinedCreditBalanceCents = getTotalCreditBalanceCents({
36+
customerBalance: subscription?.customer_balance,
37+
prepaidCreditsBalance: subscription?.prepaid_credits_balance,
38+
})
39+
const combinedCreditBalance = combinedCreditBalanceCents / 100
40+
const hasCredits = combinedCreditBalanceCents > 0
41+
const hasDebt = combinedCreditBalanceCents < 0
42+
const balance = Math.abs(combinedCreditBalance).toFixed(2)
4143

4244
return (
4345
<ScaffoldSection>
@@ -47,8 +49,11 @@ const CreditBalance = () => {
4749
<p className="text-foreground text-base m-0">Credit Balance</p>
4850
</div>
4951
<p className="text-sm text-foreground-light m-0">
50-
Credits will be applied to future invoices, before charging your payment method. If your
51-
credit balance runs out, your default payment method will be charged.
52+
Credits will be applied to future invoices, before charging your payment method. This
53+
balance includes purchased credits and any prorated credits from plan changes.
54+
</p>
55+
<p className="text-sm text-foreground-light m-0">
56+
If your credits run out, your default payment method will be charged.
5257
</p>
5358
</div>
5459
</ScaffoldSectionDetail>
@@ -79,10 +84,10 @@ const CreditBalance = () => {
7984
<div className="flex w-full justify-between items-center">
8085
<span>Balance</span>
8186
<div className="flex items-center space-x-1">
82-
{isDebt && <h4 className="opacity-50">-</h4>}
87+
{hasDebt && <h4 className="opacity-50">-</h4>}
8388
<h4 className="opacity-50">$</h4>
8489
<h1 className="relative">{balance}</h1>
85-
{isCredit && <h4 className="opacity-50">/credits</h4>}
90+
{hasCredits && <h4 className="opacity-50">/credits</h4>}
8691
</div>
8792
</div>
8893
)}

apps/studio/components/interfaces/Organization/BillingSettings/CreditCodeRedemption.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { Admonition, ShimmeringLoader, TimestampInfo } from 'ui-patterns'
2626
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
2727
import { z } from 'zod'
2828

29+
import { getTotalCreditBalanceCents } from './helpers'
2930
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
3031
import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton'
3132
import { useOrganizationCreditCodeRedemptionMutation } from '@/data/organizations/organization-credit-code-redemption-mutation'
@@ -59,6 +60,12 @@ export const CreditCodeRedemption = ({
5960
const { data: org, isLoading: isOrgLoading } = useOrganizationQuery({ slug })
6061
const { data: customerProfile, isLoading: isCustomerProfileLoading } =
6162
useOrganizationCustomerProfileQuery({ slug })
63+
const combinedCreditBalanceCents = customerProfile
64+
? getTotalCreditBalanceCents({
65+
customerBalance: customerProfile.balance,
66+
prepaidCreditsBalance: customerProfile.prepaid_credits_balance,
67+
})
68+
: undefined
6269

6370
const { can: canRedeemCode, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
6471
PermissionAction.BILLING_WRITE,
@@ -282,12 +289,12 @@ export const CreditCodeRedemption = ({
282289
)}
283290
/>
284291

285-
{customerProfile && customerProfile.balance < 0 && (
292+
{combinedCreditBalanceCents !== undefined && combinedCreditBalanceCents > 0 && (
286293
<div className="flex w-full justify-between items-center">
287294
<span className="text-sm">Current Balance</span>
288295
<div className="flex items-center gap-x-1">
289296
<p className="opacity-50 text-sm">$</p>
290-
<p className="text-2xl">{customerProfile.balance / -100}</p>
297+
<p className="text-2xl">{combinedCreditBalanceCents / 100}</p>
291298
<p className="opacity-50 text-sm">/credits</p>
292299
</div>
293300
</div>

apps/studio/components/interfaces/Organization/BillingSettings/helpers.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,13 @@ export const generateUpgradeReasons = (originalPlan?: string, upgradedPlan?: str
9494

9595
return reasons
9696
}
97+
98+
// For `customerBalance`, negative sign means credit.
99+
// Negate it first so both sources contribute as positive credit amounts before combining.
100+
export const getTotalCreditBalanceCents = ({
101+
customerBalance = 0,
102+
prepaidCreditsBalance = 0,
103+
}: {
104+
customerBalance?: number
105+
prepaidCreditsBalance?: number
106+
}) => -customerBalance + prepaidCreditsBalance

packages/api-types/types/platform.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6071,6 +6071,7 @@ export interface components {
60716071
billing_name?: string
60726072
billing_via_partner: boolean
60736073
email: string
6074+
prepaid_credits_balance?: number
60746075
tax_id: {
60756076
country: string
60766077
type: string
@@ -6767,6 +6768,7 @@ export interface components {
67676768
id: 'free' | 'pro' | 'team' | 'enterprise' | 'platform'
67686769
name: string
67696770
}
6771+
prepaid_credits_balance?: number
67706772
project_addons: {
67716773
addons: {
67726774
/** @enum {string} */

0 commit comments

Comments
 (0)