-
Notifications
You must be signed in to change notification settings - Fork 6
ENT-11429: Add billing address capture and fix tracking event issue #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b77c24f
073f871
8020061
15644db
b4cc66a
f38a527
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,18 @@ | ||
| import { FormattedMessage } from '@edx/frontend-platform/i18n'; | ||
| import { Form } from '@openedx/paragon'; | ||
| import { AddressElement, PaymentElement } from '@stripe/react-stripe-js'; | ||
| import { StripeAddressElementOptions } from '@stripe/stripe-js'; | ||
| import { StripeAddressElementChangeEvent, StripeAddressElementOptions } from '@stripe/stripe-js'; | ||
| import { useCallback } from 'react'; | ||
|
|
||
| import { FieldContainer } from '@/components/FieldContainer'; | ||
| import { DataStoreKey } from '@/constants/checkout'; | ||
| import { useCheckoutFormStore } from '@/hooks/useCheckoutFormStore'; | ||
|
|
||
| import type { UseFormReturn } from 'react-hook-form'; | ||
|
|
||
| interface BillingFormFieldsProps { | ||
| form: UseFormReturn<BillingDetailsData>; | ||
| } | ||
|
|
||
| const BillingAddressTitle = () => ( | ||
| <h3 className="mb-3"> | ||
|
|
@@ -33,31 +43,72 @@ const BillingPaymentTitle = () => ( | |
| </> | ||
| ); | ||
|
|
||
| const BillingFormFields = () => { | ||
| /** | ||
| * Stripe AddressElement configured for collecting the cardholder’s billing address. | ||
| * | ||
| * The `mode: "billing"` option ensures the address is tied to the payment method | ||
| * and used for fraud checks and payment authorization. | ||
| * | ||
| * Docs: https://docs.stripe.com/elements/address-element | ||
| */ | ||
| /** | ||
| * BillingFormFields component | ||
| * | ||
| * Renders the billing address form and payment element for the billing details page. | ||
| * Uses Stripe AddressElement and PaymentElement. | ||
| * AddressElement provides autocomplete/manual entry and returns | ||
| * normalized address fields (line1, line2, city, state, postal code). | ||
| */ | ||
| const BillingFormFields = ({ form }: BillingFormFieldsProps) => { | ||
| const billingDetailsData = useCheckoutFormStore( | ||
| (state) => state.formData[DataStoreKey.BillingDetails], | ||
| ); | ||
| const setFormData = useCheckoutFormStore((state) => state.setFormData); | ||
|
|
||
| const onAddressChange = useCallback((event: StripeAddressElementChangeEvent) => { | ||
| const name = event.value?.name || ''; | ||
| const country = event.value?.address?.country || ''; | ||
| const line1 = event.value?.address?.line1 || ''; | ||
| const line2 = event.value?.address?.line2 || ''; | ||
| const city = event.value?.address?.city || ''; | ||
| const state = event.value?.address?.state || ''; | ||
| const zip = event.value?.address?.postal_code || ''; | ||
|
|
||
| form.setValue('fullName', name, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); | ||
| form.setValue('country', country, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); | ||
| form.setValue('line1', line1, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); | ||
| form.setValue('line2', line2, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); | ||
| form.setValue('city', city, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); | ||
| form.setValue('state', state, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); | ||
| form.setValue('zip', zip, { shouldValidate: true, shouldDirty: true, shouldTouch: true }); | ||
|
|
||
| setFormData(DataStoreKey.BillingDetails, { | ||
| ...billingDetailsData, | ||
| fullName: name, | ||
| country, | ||
| line1, | ||
| line2, | ||
| city, | ||
| state, | ||
| zip, | ||
| }); | ||
| }, [billingDetailsData, form, setFormData]); | ||
|
|
||
| const addressElementOptions: StripeAddressElementOptions = { | ||
| mode: 'billing', | ||
| }; | ||
|
|
||
| return ( | ||
| <> | ||
| <FieldContainer> | ||
| <BillingAddressTitle /> | ||
| <AddressElement | ||
| options={addressElementOptions} | ||
| onChange={onAddressChange} | ||
| /> | ||
| {form.formState.errors?.fullName?.message && ( | ||
| <Form.Control.Feedback type="invalid" hasIcon={false} className="d-block mt-2"> | ||
| {form.formState.errors.fullName.message} | ||
| </Form.Control.Feedback> | ||
| )} | ||
|
Comment on lines
+101
to
+105
|
||
| </FieldContainer> | ||
| <FieldContainer> | ||
| <BillingPaymentTitle /> | ||
| <PaymentElement | ||
| options={{ | ||
| layout: 'tabs', // or 'accordion' | ||
| layout: 'tabs', | ||
| }} | ||
| /> | ||
| </FieldContainer> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getErrorMessagefalls back to pretty-printing the entire thrown object viaJSON.stringify(errObj, null, 2)and rendering it to end users. Depending on what is thrown (e.g., API/Stripe error payloads), this can expose sensitive details; prefer returning a safe generic message (or only stringify in non-production environments), and keep full details in logs/telemetry instead.