diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx index 75c66312a5..459a17e6d8 100644 --- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/BillingDetails.tsx @@ -30,7 +30,7 @@ function BillingDetails() { diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx index 4def60fbd4..c5b2e9f14d 100644 --- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import { useState } from 'react' -import { subscriptionDetailType } from 'services/account' +import { accountDetailsPropType } from 'services/account' import { formatTimestampToCalendarDate } from 'shared/utils/billing' import A from 'ui/A' import Button from 'ui/Button' @@ -10,8 +10,9 @@ import Icon from 'ui/Icon' import BankInformation from './BankInformation' import CardInformation from './CardInformation' import PaymentMethodForm from './PaymentMethodForm' -function PaymentCard({ subscriptionDetail, provider, owner }) { +function PaymentCard({ accountDetails, provider, owner }) { const [isFormOpen, setIsFormOpen] = useState(false) + const subscriptionDetail = accountDetails?.subscriptionDetail const card = subscriptionDetail?.defaultPaymentMethod?.card const usBankAccount = subscriptionDetail?.defaultPaymentMethod?.usBankAccount @@ -41,7 +42,7 @@ function PaymentCard({ subscriptionDetail, provider, owner }) { provider={provider} owner={owner} closeForm={() => setIsFormOpen(false)} - subscriptionDetail={subscriptionDetail} + accountDetails={accountDetails} /> ) : card ? ( @@ -72,7 +73,7 @@ function PaymentCard({ subscriptionDetail, provider, owner }) { } PaymentCard.propTypes = { - subscriptionDetail: subscriptionDetailType, + accountDetails: accountDetailsPropType, provider: PropTypes.string.isRequired, owner: PropTypes.string.isRequired, } diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx index 6173a5af87..fd48e5e014 100644 --- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.test.jsx @@ -55,6 +55,10 @@ const subscriptionDetail = { cancelAtPeriodEnd: false, } +const accountDetails = { + subscriptionDetail, +} + const usBankSubscriptionDetail = { defaultPaymentMethod: { usBankAccount: { @@ -100,12 +104,10 @@ describe('PaymentCard', () => { return { user } } - describe(`when the user doesn't have any subscriptionDetail`, () => { - // NOTE: This test is misleading because we hide this component from a higher level in - // BillingDetails.tsx if there is no subscriptionDetail + describe(`when the user doesn't have any accountDetails`, () => { it('renders the set payment method message', () => { render( - + ) expect( @@ -120,9 +122,12 @@ describe('PaymentCard', () => { it('renders an error message', () => { render( { const { user } = setup() render( { const { user } = setup() render( { it('renders the card', () => { render( , @@ -204,7 +215,7 @@ describe('PaymentCard', () => { it('renders the next billing', () => { render( , @@ -217,9 +228,15 @@ describe('PaymentCard', () => { describe('when the user has a US bank account', () => { it('renders the bank account details', () => { + const testAccountDetails = { + ...accountDetails, + subscriptionDetail: { + ...usBankSubscriptionDetail, + }, + } render( , @@ -235,9 +252,12 @@ describe('PaymentCard', () => { it(`doesn't render the next billing`, () => { render( { render( , @@ -280,7 +300,7 @@ describe('PaymentCard', () => { }) render( , @@ -305,7 +325,7 @@ describe('PaymentCard', () => { render( , @@ -327,7 +347,7 @@ describe('PaymentCard', () => { }) render( , @@ -354,7 +374,7 @@ describe('PaymentCard', () => { }) render( , @@ -376,7 +396,7 @@ describe('PaymentCard', () => { }) render( , diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.test.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.test.tsx index 20331e0f06..8d824d2282 100644 --- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.test.tsx +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.test.tsx @@ -6,9 +6,12 @@ import { MemoryRouter, Route } from 'react-router-dom' import { vi } from 'vitest' import { z } from 'zod' -import { SubscriptionDetailSchema } from 'services/account/useAccountDetails' +import { + AccountDetailsSchema, + SubscriptionDetailSchema, +} from 'services/account/useAccountDetails' -import PaymentMethodForm from './PaymentMethodForm' +import PaymentMethodForm, { getEmail, getName } from './PaymentMethodForm' const queryClient = new QueryClient() @@ -66,6 +69,26 @@ const subscriptionDetail: z.infer = { trialEnd: null, } +const accountDetails: z.infer = { + name: 'John Doe', + email: 'test@example.com', + subscriptionDetail: subscriptionDetail, + activatedStudentCount: 0, + activatedUserCount: 0, + checkoutSessionId: null, + delinquent: null, + inactiveUserCount: 0, + integrationId: null, + nbActivePrivateRepos: null, + planAutoActivate: null, + planProvider: null, + repoTotalCredits: 0, + rootOrganization: null, + scheduleDetail: null, + studentCount: 0, + usesInvoice: false, +} + const mocks = { useUpdatePaymentMethod: vi.fn(), } @@ -90,7 +113,7 @@ describe('PaymentMethodForm', () => { render( {}} @@ -111,7 +134,7 @@ describe('PaymentMethodForm', () => { }) render( {}} @@ -133,7 +156,7 @@ describe('PaymentMethodForm', () => { }) render( {}} @@ -155,7 +178,7 @@ describe('PaymentMethodForm', () => { }) render( { }) render( {}} @@ -203,7 +226,7 @@ describe('PaymentMethodForm', () => { }) render( {}} @@ -215,4 +238,62 @@ describe('PaymentMethodForm', () => { expect(screen.queryByRole('button', { name: /Cancel/i })).toBeDisabled() }) }) + + describe('when the email is missing from billing details', () => { + it('infers one from the rest of the data', () => { + const accountDetailsWithoutBillingEmail = { + email: 'customer@email.com', + subscriptionDetail: { + defaultPaymentMethod: { + billingDetails: { + email: null, + }, + }, + }, + } as z.infer + + const email = getEmail(accountDetailsWithoutBillingEmail) + expect(email).toBe('customer@email.com') + }) + }) + + describe('when the name is missing from billing details', () => { + it('uses latestInvoice customerName when billing name is missing', () => { + const accountDetailsWithoutBillingName = { + subscriptionDetail: { + defaultPaymentMethod: { + billingDetails: { + name: undefined, + }, + }, + latestInvoice: { + customerName: 'Customer Name', + }, + }, + } as unknown as z.infer + + const name = getName(accountDetailsWithoutBillingName) + expect(name).toBe('Customer Name') + }) + + it('uses account name when billing name and invoice name are missing', () => { + const accountDetailsWithoutBillingName = { + name: 'Account Name', + subscriptionDetail: { + defaultPaymentMethod: { + billingDetails: { + name: undefined, + }, + }, + latestInvoice: { + customerName: undefined, + }, + }, + } as unknown as z.infer + + const name = getName(accountDetailsWithoutBillingName) + + expect(name).toBe('Account Name') + }) + }) }) diff --git a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.tsx b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.tsx index 85c3141d09..5804b9ad77 100644 --- a/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.tsx +++ b/src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.tsx @@ -3,10 +3,7 @@ import { StripePaymentElement } from '@stripe/stripe-js' import cs from 'classnames' import { z } from 'zod' -import { - BillingDetailsSchema, - SubscriptionDetailSchema, -} from 'services/account' +import { AccountDetailsSchema, BillingDetailsSchema } from 'services/account' import { useUpdatePaymentMethod } from 'services/account/useUpdatePaymentMethod' import { Provider } from 'shared/api/helpers' import Button from 'ui/Button' @@ -15,20 +12,24 @@ interface PaymentMethodFormProps { closeForm: () => void provider: Provider owner: string - subscriptionDetail: z.infer + accountDetails: z.infer } const PaymentMethodForm = ({ closeForm, provider, owner, - subscriptionDetail, + accountDetails, }: PaymentMethodFormProps) => { const elements = useElements() + const subscriptionDetail = accountDetails?.subscriptionDetail const billingDetails = subscriptionDetail?.defaultPaymentMethod?.billingDetails + const email = getEmail(accountDetails) + const name = getName(accountDetails) + const { mutate: updatePaymentMethod, isLoading, @@ -37,8 +38,8 @@ const PaymentMethodForm = ({ } = useUpdatePaymentMethod({ provider, owner, - name: billingDetails?.name || undefined, - email: billingDetails?.email || undefined, + name, + email, address: stripeAddress(billingDetails) || undefined, }) @@ -124,4 +125,29 @@ export const stripeAddress = ( } } +export const getEmail = ( + accountDetails: z.infer +) => { + return ( + accountDetails?.subscriptionDetail?.defaultPaymentMethod?.billingDetails + ?.email || + accountDetails?.subscriptionDetail?.latestInvoice?.customerEmail || + accountDetails?.subscriptionDetail?.customer?.email || + accountDetails?.email || + undefined + ) +} + +export const getName = ( + accountDetails: z.infer +) => { + return ( + accountDetails?.subscriptionDetail?.defaultPaymentMethod?.billingDetails + ?.name || + accountDetails?.subscriptionDetail?.latestInvoice?.customerName || + accountDetails?.name || + undefined + ) +} + export default PaymentMethodForm