From fd1cb2d39182b10f488ebf3d694c57c8a7cbfc8a Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Mon, 10 Nov 2025 16:03:43 -0500 Subject: [PATCH 1/5] feat(sub v3): Surface account balance and card brand --- static/gsAdmin/views/customerDetails.spec.tsx | 5 ++++ .../gsApp/components/creditCardEdit/panel.tsx | 3 ++- static/gsApp/types/index.tsx | 11 ++++---- .../steps/addPaymentMethod.spec.tsx | 2 ++ .../headerCards/billingInfoCard.tsx | 25 ++++++++++++++++--- .../getsentry-test/fixtures/subscription.ts | 1 + 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/static/gsAdmin/views/customerDetails.spec.tsx b/static/gsAdmin/views/customerDetails.spec.tsx index 0a14fe02ba5581..a10c06dc902871 100644 --- a/static/gsAdmin/views/customerDetails.spec.tsx +++ b/static/gsAdmin/views/customerDetails.spec.tsx @@ -2529,6 +2529,7 @@ describe('Customer Details', () => { countryCode: 'US', expMonth: 12, expYear: 2028, + brand: 'Visa', }, }); @@ -2564,6 +2565,7 @@ describe('Customer Details', () => { countryCode: 'US', expMonth: 12, expYear: 2028, + brand: 'Visa', }, }); @@ -2599,6 +2601,7 @@ describe('Customer Details', () => { countryCode: 'US', expMonth: 12, expYear: 2028, + brand: 'Visa', }, }); @@ -2633,6 +2636,7 @@ describe('Customer Details', () => { countryCode: 'US', expMonth: 12, expYear: 2028, + brand: 'Visa', }, }); @@ -2689,6 +2693,7 @@ describe('Customer Details', () => { countryCode: 'US', expMonth: 12, expYear: 2028, + brand: 'Visa', }, }); diff --git a/static/gsApp/components/creditCardEdit/panel.tsx b/static/gsApp/components/creditCardEdit/panel.tsx index 1a0484568ae6ea..edea4a8be20213 100644 --- a/static/gsApp/components/creditCardEdit/panel.tsx +++ b/static/gsApp/components/creditCardEdit/panel.tsx @@ -12,6 +12,7 @@ import PanelHeader from 'sentry/components/panels/panelHeader'; import {t} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; import {decodeScalar} from 'sentry/utils/queryString'; +import {toTitleCase} from 'sentry/utils/string/toTitleCase'; import {openEditCreditCard} from 'getsentry/actionCreators/modal'; import CreditCardSetup from 'getsentry/components/creditCardEdit/setup'; @@ -189,7 +190,7 @@ function CreditCardPanel({ /> ) : subscription.paymentSource ? ( - {`****${subscription.paymentSource.last4} ${moment(new Date(subscription.paymentSource.expYear, subscription.paymentSource.expMonth - 1)).format('MM/YY')}`} + {`${subscription.paymentSource.brand ? toTitleCase(subscription.paymentSource.brand, {allowInnerUpperCase: true}) + ' ' : ''}****${subscription.paymentSource.last4} ${subscription.paymentSource.expYear && subscription.paymentSource.expMonth ? moment(new Date(subscription.paymentSource.expYear, subscription.paymentSource.expMonth - 1)).format('MM/YY') : ''}`} {`${countryName ? `${countryName} ` : ''} ${subscription.paymentSource.zipCode}`} ) : ( diff --git a/static/gsApp/types/index.tsx b/static/gsApp/types/index.tsx index 03fca9888578b6..1707c62a993132 100644 --- a/static/gsApp/types/index.tsx +++ b/static/gsApp/types/index.tsx @@ -378,11 +378,12 @@ export type Subscription = { onDemandSpendUsed: number; partner: Partner | null; paymentSource: { - countryCode: string; - expMonth: number; - expYear: number; - last4: string; - zipCode: string; + brand: string | null; + countryCode: string | null; + expMonth: number | null; + expYear: number | null; + last4: string | null; + zipCode: string | null; } | null; pendingChanges: PendingChanges | null; // Subscription details diff --git a/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx b/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx index 1b1ea187e53293..aef270958bfb47 100644 --- a/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx +++ b/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx @@ -109,6 +109,7 @@ describe('AddPaymentMethod', () => { countryCode: 'US', expMonth: 12, expYear: 2028, + brand: 'Visa', }, }; @@ -141,6 +142,7 @@ describe('AddPaymentMethod', () => { countryCode: 'US', expMonth: 12, expYear: 2028, + brand: 'Visa', }, }; diff --git a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx index 5b69b833d99ed3..22c2c8f0fd931b 100644 --- a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx +++ b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx @@ -4,12 +4,14 @@ import {Text} from 'sentry/components/core/text'; import Placeholder from 'sentry/components/placeholder'; import {t, tct} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; +import {toTitleCase} from 'sentry/utils/string/toTitleCase'; import {useNavContext} from 'sentry/views/nav/context'; import {NavLayout} from 'sentry/views/nav/types'; import {useBillingDetails} from 'getsentry/hooks/useBillingDetails'; import type {Subscription} from 'getsentry/types'; import {hasSomeBillingDetails} from 'getsentry/utils/billing'; +import formatCurrency from 'getsentry/utils/formatCurrency'; import {countryHasSalesTax, getTaxFieldInfo} from 'getsentry/utils/salesTax'; import SubscriptionHeaderCard from 'getsentry/views/subscriptionPage/headerCards/subscriptionHeaderCard'; @@ -33,7 +35,7 @@ function BillingInfoCard({ align="start" maxWidth="100%" > - + , + {!!subscription.accountBalance && ( + + {tct('Account balance: [balance]', { + balance, + })} + + )} {primaryDetails.length > 0 ? primaryDetails.join(', ') @@ -129,7 +145,10 @@ function PaymentSourceInfo({subscription}: {subscription: Subscription}) { return ( - {tct('Card ending in [last4]', { + {tct('[cardBrand] ending in [last4]', { + cardBrand: paymentSource.brand + ? toTitleCase(paymentSource.brand, {allowInnerUpperCase: true}) + : t('Card'), last4: paymentSource.last4, })} diff --git a/tests/js/getsentry-test/fixtures/subscription.ts b/tests/js/getsentry-test/fixtures/subscription.ts index cbf630ec141e24..9d3584bf245f3a 100644 --- a/tests/js/getsentry-test/fixtures/subscription.ts +++ b/tests/js/getsentry-test/fixtures/subscription.ts @@ -97,6 +97,7 @@ export function SubscriptionFixture(props: Props): TSubscription { zipCode: '94242', expMonth: 12, expYear: 2077, + brand: 'Visa', }, billingPeriodEnd: '2018-10-24', onDemandSpendUsed: 0, From 91edfd3f24a8cf67300cf33fc2ff5f8292a311f9 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Mon, 10 Nov 2025 16:15:41 -0500 Subject: [PATCH 2/5] fix tests --- .../subscriptionPage/headerCards/billingInfoCard.spec.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx index 17592c93e43b68..e673c93fc96113 100644 --- a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx +++ b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx @@ -23,13 +23,14 @@ describe('BillingInfoCard', () => { method: 'GET', body: BillingDetailsFixture(), }); - const subscription = SubscriptionFixture({organization}); + const subscription = SubscriptionFixture({organization, accountBalance: 10_00}); render(); expect(screen.getByText('Billing information')).toBeInTheDocument(); await screen.findByText('Test company, Display Address'); expect(screen.getByText('Billing email: test@gmail.com')).toBeInTheDocument(); - expect(screen.getByText('Card ending in 4242')).toBeInTheDocument(); + expect(screen.getByText('Visa ending in 4242')).toBeInTheDocument(); + expect(screen.getByText('Account balance: $10')).toBeInTheDocument(); }); it('renders with some pre-existing info', async () => { @@ -48,7 +49,7 @@ describe('BillingInfoCard', () => { expect( screen.getByText('No billing email or tax number on file') ).toBeInTheDocument(); - expect(screen.getByText('Card ending in 4242')).toBeInTheDocument(); + expect(screen.getByText('Visa ending in 4242')).toBeInTheDocument(); }); it('renders without pre-existing info', async () => { From b9f8ae6a371c888ac3e65446a74bd643e5cfdcc5 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Mon, 10 Nov 2025 16:17:55 -0500 Subject: [PATCH 3/5] add test --- .../subscriptionPage/headerCards/billingInfoCard.spec.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx index e673c93fc96113..98bdf46a30c968 100644 --- a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx +++ b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.spec.tsx @@ -39,7 +39,7 @@ describe('BillingInfoCard', () => { method: 'GET', body: BillingDetailsFixture({billingEmail: null, companyName: null}), }); - const subscription = SubscriptionFixture({organization}); + const subscription = SubscriptionFixture({organization, accountBalance: -10_00}); render(); expect(screen.getByText('Billing information')).toBeInTheDocument(); @@ -50,6 +50,7 @@ describe('BillingInfoCard', () => { screen.getByText('No billing email or tax number on file') ).toBeInTheDocument(); expect(screen.getByText('Visa ending in 4242')).toBeInTheDocument(); + expect(screen.getByText('Account balance: $10 credit')).toBeInTheDocument(); }); it('renders without pre-existing info', async () => { From cd83c5af30e7ade25e7a3ccbcdd89e4182cc757d Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Wed, 12 Nov 2025 10:56:40 -0500 Subject: [PATCH 4/5] correct types --- static/gsApp/components/creditCardEdit/panel.tsx | 4 ++-- static/gsApp/types/index.tsx | 8 ++++---- .../subscriptionPage/headerCards/billingInfoCard.tsx | 4 +--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/static/gsApp/components/creditCardEdit/panel.tsx b/static/gsApp/components/creditCardEdit/panel.tsx index edea4a8be20213..f73cbecb57f68f 100644 --- a/static/gsApp/components/creditCardEdit/panel.tsx +++ b/static/gsApp/components/creditCardEdit/panel.tsx @@ -190,8 +190,8 @@ function CreditCardPanel({ /> ) : subscription.paymentSource ? ( - {`${subscription.paymentSource.brand ? toTitleCase(subscription.paymentSource.brand, {allowInnerUpperCase: true}) + ' ' : ''}****${subscription.paymentSource.last4} ${subscription.paymentSource.expYear && subscription.paymentSource.expMonth ? moment(new Date(subscription.paymentSource.expYear, subscription.paymentSource.expMonth - 1)).format('MM/YY') : ''}`} - {`${countryName ? `${countryName} ` : ''} ${subscription.paymentSource.zipCode}`} + {`${toTitleCase(subscription.paymentSource.brand, {allowInnerUpperCase: true})} ****${subscription.paymentSource.last4} ${moment(new Date(subscription.paymentSource.expYear, subscription.paymentSource.expMonth - 1)).format('MM/YY')}`} + {`${countryName ? `${countryName} ` : ''} ${subscription.paymentSource.zipCode ? subscription.paymentSource.zipCode : ''}`} ) : ( {t('No payment method on file')} diff --git a/static/gsApp/types/index.tsx b/static/gsApp/types/index.tsx index 1707c62a993132..5a22e78ab6e53d 100644 --- a/static/gsApp/types/index.tsx +++ b/static/gsApp/types/index.tsx @@ -378,11 +378,11 @@ export type Subscription = { onDemandSpendUsed: number; partner: Partner | null; paymentSource: { - brand: string | null; + brand: string; countryCode: string | null; - expMonth: number | null; - expYear: number | null; - last4: string | null; + expMonth: number; + expYear: number; + last4: string; zipCode: string | null; } | null; pendingChanges: PendingChanges | null; diff --git a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx index 22c2c8f0fd931b..6307c996a2c0b6 100644 --- a/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx +++ b/static/gsApp/views/subscriptionPage/headerCards/billingInfoCard.tsx @@ -146,9 +146,7 @@ function PaymentSourceInfo({subscription}: {subscription: Subscription}) { return ( {tct('[cardBrand] ending in [last4]', { - cardBrand: paymentSource.brand - ? toTitleCase(paymentSource.brand, {allowInnerUpperCase: true}) - : t('Card'), + cardBrand: toTitleCase(paymentSource.brand, {allowInnerUpperCase: true}), last4: paymentSource.last4, })} From 08f69a57d0839eb7bfe8b83c869fe8cbedd613bf Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Thu, 13 Nov 2025 11:40:19 -0500 Subject: [PATCH 5/5] fix test --- static/gsApp/views/subscriptionPage/billingInformation.spec.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/static/gsApp/views/subscriptionPage/billingInformation.spec.tsx b/static/gsApp/views/subscriptionPage/billingInformation.spec.tsx index 236227e5217293..1f993ca666c8e5 100644 --- a/static/gsApp/views/subscriptionPage/billingInformation.spec.tsx +++ b/static/gsApp/views/subscriptionPage/billingInformation.spec.tsx @@ -627,6 +627,7 @@ describe('Billing details form', () => { zipCode: '94242', expMonth: 8, expYear: 2030, + brand: 'Visa', }, });