Skip to content

Commit 9c51adb

Browse files
wip
1 parent 7f4797b commit 9c51adb

File tree

6 files changed

+97
-12
lines changed

6 files changed

+97
-12
lines changed

src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ function PaymentCard({ subscriptionDetail, provider, owner }) {
1414
const [isFormOpen, setIsFormOpen] = useState(false)
1515
const card = subscriptionDetail?.defaultPaymentMethod?.card
1616
const usBankAccount = subscriptionDetail?.defaultPaymentMethod?.usBankAccount
17+
// const isAwaitingDelayedPaymentMethodVerification = true
1718

1819
let nextBillingDisplayDate = null
1920
if (!subscriptionDetail?.cancelAtPeriodEnd) {

src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ import {
77
BillingDetailsSchema,
88
SubscriptionDetailSchema,
99
} from 'services/account'
10-
import { useUpdatePaymentMethod } from 'services/account/useUpdatePaymentMethod'
10+
import {
11+
MissingAddressError,
12+
MissingEmailError,
13+
MissingNameError,
14+
useUpdatePaymentMethod,
15+
} from 'services/account/useUpdatePaymentMethod'
1116
import { Provider } from 'shared/api/helpers'
17+
import A from 'ui/A'
1218
import Button from 'ui/Button'
1319

1420
interface PaymentMethodFormProps {
@@ -33,15 +39,13 @@ const PaymentMethodForm = ({
3339
mutate: updatePaymentMethod,
3440
isLoading,
3541
error,
36-
reset,
3742
} = useUpdatePaymentMethod({
3843
provider,
3944
owner,
4045
name: billingDetails?.name || undefined,
4146
email: billingDetails?.email || undefined,
4247
address: stripeAddress(billingDetails) || undefined,
4348
})
44-
4549
async function submit(e: React.FormEvent) {
4650
e.preventDefault()
4751

@@ -62,8 +66,6 @@ const PaymentMethodForm = ({
6266
})
6367
}
6468

65-
const showError = error && !reset
66-
6769
return (
6870
<form onSubmit={submit} aria-label="form">
6971
<div className={cs('flex flex-col gap-3')}>
@@ -80,7 +82,7 @@ const PaymentMethodForm = ({
8082
}}
8183
/>
8284
<p className="mt-1 text-ds-primary-red">
83-
{showError && error?.message}
85+
{error ? getErrorMessage(error) : null}
8486
</p>
8587
<div className="mb-8 mt-4 flex gap-1">
8688
<Button
@@ -124,4 +126,27 @@ export const stripeAddress = (
124126
}
125127
}
126128

129+
export const getErrorMessage = (error: Error): JSX.Element => {
130+
switch (error.message) {
131+
case MissingNameError:
132+
return <span>Missing name, please edit Full Name</span>
133+
case MissingEmailError:
134+
return <span>Missing email, please edit Email</span>
135+
case MissingAddressError:
136+
return <span>Missing address, please edit Address</span>
137+
default:
138+
return (
139+
<span>
140+
There&apos;s been an error. Please try refreshing your browser, if
141+
this error persists please{' '}
142+
{/* @ts-expect-error ignore until we can convert A component to ts */}
143+
<A to={{ pageName: 'support' }} variant="link">
144+
contact support
145+
</A>
146+
.
147+
</span>
148+
)
149+
}
150+
}
151+
127152
export default PaymentMethodForm

src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ describe('CurrentOrgPlan', () => {
373373
)
374374
expect(paymentFailed).toBeInTheDocument()
375375
const contactSupport = await screen.findByText(
376-
'Please try a different card or contact support at [email protected].'
376+
'Please try a different payment method or contact support at [email protected].'
377377
)
378378
expect(contactSupport).toBeInTheDocument()
379379
})

src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.tsx

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,51 @@ function CurrentOrgPlan() {
4040
})
4141
)
4242

43+
const isAwaitingFirstPaymentMethodVerification = false
44+
const isAwaitingVerification =
45+
accountDetails?.unverifiedPaymentMethods?.length
46+
4347
const scheduledPhase = accountDetails?.scheduleDetail?.scheduledPhase
44-
const isDelinquent = accountDetails?.delinquent
48+
// customer is delinquent until their first payment method is verified
49+
const isDelinquent =
50+
accountDetails?.delinquent && !isAwaitingFirstPaymentMethodVerification
4551
const scheduleStart = scheduledPhase
4652
? getScheduleStart(scheduledPhase)
4753
: undefined
4854

4955
const shouldRenderBillingDetails =
50-
(accountDetails?.planProvider !== 'github' &&
56+
!isAwaitingFirstPaymentMethodVerification &&
57+
((accountDetails?.planProvider !== 'github' &&
5158
!accountDetails?.rootOrganization) ||
52-
accountDetails?.usesInvoice
59+
accountDetails?.usesInvoice)
5360

5461
const planUpdatedNotification = usePlanUpdatedNotification()
5562

5663
const account = enterpriseDetails?.owner?.account
5764

65+
const hasSuccessfulDefaultPaymentMethod =
66+
accountDetails?.subscriptionDetail?.defaultPaymentMethod
67+
68+
const hideSuccessBanner = isAwaitingVerification
69+
? hasSuccessfulDefaultPaymentMethod
70+
: true
71+
5872
return (
5973
<div className="w-full lg:w-4/5">
74+
{isAwaitingVerification ? (
75+
<UnverifiedPaymentMethodAlert
76+
url={
77+
accountDetails?.unverifiedPaymentMethods?.[0]
78+
?.hostedVerificationLink
79+
}
80+
/>
81+
) : null}
6082
{planUpdatedNotification.isCancellation ? (
6183
<InfoAlertCancellation
6284
subscriptionDetail={accountDetails?.subscriptionDetail}
6385
/>
6486
) : null}
65-
<InfoMessageStripeCallback />
87+
{hideSuccessBanner ? <InfoMessageStripeCallback /> : null}
6688
{isDelinquent ? <DelinquentAlert /> : null}
6789
{planData?.plan ? (
6890
<div className="flex flex-col gap-4 sm:mr-4 sm:flex-initial md:w-2/3 lg:w-3/4">
@@ -147,7 +169,31 @@ const DelinquentAlert = () => {
147169
<Alert variant={'error'}>
148170
<Alert.Title>Your most recent payment failed</Alert.Title>
149171
<Alert.Description>
150-
Please try a different card or contact support at [email protected].
172+
Please try a different payment method or contact support at
173+
174+
</Alert.Description>
175+
</Alert>
176+
<br />
177+
</>
178+
)
179+
}
180+
181+
const UnverifiedPaymentMethodAlert = ({ url }: { url: string | undefined }) => {
182+
return (
183+
<>
184+
<Alert variant={'warning'}>
185+
<Alert.Title>New Payment Method Awaiting Verification</Alert.Title>
186+
<Alert.Description>
187+
Your new payment method requires verification. Click{' '}
188+
<A
189+
href={url}
190+
isExternal={true}
191+
hook="stripe-payment-method-verification"
192+
to={undefined}
193+
>
194+
here
195+
</A>{' '}
196+
to complete the verification process.
151197
</Alert.Description>
152198
</Alert>
153199
<br />

src/services/account/useAccountDetails.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,14 @@ export const AccountDetailsSchema = z.object({
150150
studentCount: z.number(),
151151
subscriptionDetail: SubscriptionDetailSchema,
152152
usesInvoice: z.boolean(),
153+
unverifiedPaymentMethods: z
154+
.array(
155+
z.object({
156+
paymentMethodId: z.string(),
157+
hostedVerificationLink: z.string(),
158+
})
159+
)
160+
.nullable(),
153161
})
154162

155163
export interface UseAccountDetailsArgs {

src/services/account/useUpdatePaymentMethod.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,8 @@ export function useUpdatePaymentMethod({
9494
},
9595
})
9696
}
97+
98+
// Errors from Stripe api confirmSetup() - unfortunately seems to just be by text, no error codes
99+
export const MissingNameError = `You specified "never" for fields.billing_details.name when creating the payment Element, but did not pass confirmParams.payment_method_data.billing_details.name when calling stripe.confirmSetup(). If you opt out of collecting data via the payment Element using the fields option, the data must be passed in when calling stripe.confirmSetup().`
100+
export const MissingEmailError = `You specified "never" for fields.billing_details.email when creating the payment Element, but did not pass confirmParams.payment_method_data.billing_details.email when calling stripe.confirmSetup(). If you opt out of collecting data via the payment Element using the fields option, the data must be passed in when calling stripe.confirmSetup().`
101+
export const MissingAddressError = `You specified "never" for fields.billing_details.address when creating the payment Element, but did not pass confirmParams.payment_method_data.billing_details.address when calling stripe.confirmSetup(). If you opt out of collecting data via the payment Element using the fields option, the data must be passed in when calling stripe.confirmSetup().`

0 commit comments

Comments
 (0)