Skip to content

Commit 0a20a68

Browse files
PR comments fixed and Test cases updated
1 parent b801042 commit 0a20a68

15 files changed

Lines changed: 349 additions & 66 deletions

packages/react/src/components/checkout/__tests__/checkout-address.test.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,28 +263,30 @@ describe('Checkout address behavior', () => {
263263
});
264264
});
265265

266-
it('syncs only billing names in onlyNames mode without stale address fields', async () => {
266+
it('syncs only billing names in onlyNames mode without clearing billing address', async () => {
267267
const draftOrder = buildDraftOrder({
268268
totals: {
269-
subTotal: { value: 0, currencyCode: 'USD' },
269+
subTotal: { value: 2500, currencyCode: 'USD' },
270270
discountTotal: { value: 0, currencyCode: 'USD' },
271271
shippingTotal: { value: 0, currencyCode: 'USD' },
272272
taxTotal: { value: 0, currencyCode: 'USD' },
273273
feeTotal: { value: 0, currencyCode: 'USD' },
274-
total: { value: 0, currencyCode: 'USD' },
274+
total: { value: 2500, currencyCode: 'USD' },
275275
},
276276
lineItems: [
277277
{
278278
fulfillmentMode: 'PICKUP',
279-
unitAmount: { value: 0, currencyCode: 'USD' },
279+
unitAmount: { value: 2500, currencyCode: 'USD' },
280280
},
281281
],
282282
billing: {
283283
firstName: '',
284284
lastName: '',
285285
phone: '',
286286
email: 'jane@example.com',
287-
address: buildBillingAddress({ addressLine1: 'Stale Billing St' }),
287+
address: buildBillingAddress({
288+
addressLine1: 'Paid Pickup Billing St',
289+
}),
288290
},
289291
});
290292
const { user } = renderCheckout({
@@ -310,9 +312,9 @@ describe('Checkout address behavior', () => {
310312
lastName: 'Buyer',
311313
},
312314
});
313-
expect(getLastUpdateInput()?.billing ?? {}).not.toMatchObject({
314-
address: expect.objectContaining({ addressLine1: 'Stale Billing St' }),
315-
});
315+
// Paid pickup collects a billing address in Payment; names-only sync must
316+
// omit address so an existing billing.address is not cleared to null.
317+
expect(getLastUpdateInput()?.billing).not.toHaveProperty('address');
316318
});
317319

318320
it('does not sync the address until country, state, city, and postal-code are valid', async () => {

packages/react/src/components/checkout/__tests__/checkout-draft-order-sync.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,8 +563,9 @@ describe('Checkout draft-order field sync', () => {
563563
await waitForOperation('UpdateCheckoutSessionDraftOrder');
564564

565565
expect(getLastUpdateInput()).toMatchObject({
566-
billing: { firstName: 'Only', lastName: 'Names', address: null },
566+
billing: { firstName: 'Only', lastName: 'Names' },
567567
});
568+
expect(getLastUpdateInput()?.billing).not.toHaveProperty('address');
568569
expect(getLastUpdateInput()?.billing).not.toMatchObject({
569570
addressLine1: expect.anything(),
570571
postalCode: expect.anything(),

packages/react/src/components/checkout/__tests__/checkout-form-validation.test.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { enUs } from '@godaddy/localizations';
22
import { screen, waitFor } from '@testing-library/react';
33
import { useFormContext } from 'react-hook-form';
44
import { describe, expect, it, vi } from 'vitest';
5-
import { PaymentMethodType, PaymentProvider } from '@/types';
5+
import { PaymentProvider } from '@/types';
66
import {
77
buildDraftOrder,
88
buildLineItem,
@@ -42,7 +42,6 @@ function _offlinePaymentMethods() {
4242
mercadopago: null,
4343
ccavenue: null,
4444
offline: {
45-
type: PaymentMethodType.OFFLINE,
4645
processor: PaymentProvider.OFFLINE,
4746
checkoutTypes: ['standard'],
4847
},
@@ -52,7 +51,6 @@ function _offlinePaymentMethods() {
5251
function stripeOnlyPaymentMethods() {
5352
return {
5453
card: {
55-
type: PaymentMethodType.CREDIT_CARD,
5654
processor: PaymentProvider.STRIPE,
5755
checkoutTypes: ['standard'],
5856
},
@@ -179,7 +177,6 @@ describe('Checkout form validation', () => {
179177
...stripeOnlyPaymentMethods(),
180178
card: null as never,
181179
offline: {
182-
type: PaymentMethodType.OFFLINE,
183180
processor: PaymentProvider.OFFLINE,
184181
checkoutTypes: ['standard'],
185182
},

packages/react/src/components/checkout/__tests__/checkout-free-payment-form.test.tsx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
import { screen, waitFor } from '@testing-library/react';
1+
import { screen } from '@testing-library/react';
22
import { describe, expect, it } from 'vitest';
33
import {
44
advanceCheckoutDebounce,
55
buildCheckoutSession,
66
buildDraftOrder,
77
buildShippingRates,
88
clearOperations,
9+
getOperationOrder,
910
getOperations,
1011
renderCheckout,
12+
typeIntoNamedField,
1113
waitForCheckoutReady,
1214
waitForOperation,
1315
} from './checkout-test-env';
14-
import { getLastConfirmInput } from './checkout-test-fixtures';
16+
import {
17+
getLastConfirmInput,
18+
getLastUpdateInput,
19+
} from './checkout-test-fixtures';
1520

1621
function buildFreeDraftOrder(
1722
overrides: Parameters<typeof buildDraftOrder>[0] = {}
@@ -182,4 +187,51 @@ describe('Checkout FreePaymentForm integration', () => {
182187
document.querySelector('input[name="shippingAddressLine1"]')
183188
).not.toBeInTheDocument();
184189
});
190+
191+
it('persists pickup billing names before free-order confirmation without waiting for debounce', async () => {
192+
const draftOrder = buildFreeDraftOrder({
193+
lineItems: [{ fulfillmentMode: 'PICKUP' }],
194+
billing: {
195+
firstName: '',
196+
lastName: '',
197+
phone: '',
198+
email: 'jane@example.com',
199+
address: null,
200+
},
201+
});
202+
const session = buildCheckoutSession({
203+
draftOrder,
204+
enableShipping: false,
205+
enableLocalPickup: true,
206+
enableTaxCollection: false,
207+
});
208+
209+
const { user } = renderCheckout({ session, draftOrder });
210+
await waitForCheckoutReady();
211+
212+
await typeIntoNamedField(user, 'billingFirstName', 'Immediate');
213+
await typeIntoNamedField(user, 'billingLastName', 'Pickup');
214+
215+
// Intentionally do NOT advance AddressForm debounce — confirm must still
216+
// queue and flush the current form names before ConfirmCheckoutSession.
217+
clearOperations();
218+
await user.click(
219+
await screen.findByRole('button', { name: /complete your free order/i })
220+
);
221+
await waitForOperation('ConfirmCheckoutSession');
222+
223+
const [updateIdx, confirmIdx] = getOperationOrder([
224+
'UpdateCheckoutSessionDraftOrder',
225+
'ConfirmCheckoutSession',
226+
]);
227+
expect(updateIdx).toBeGreaterThanOrEqual(0);
228+
expect(confirmIdx).toBeGreaterThan(updateIdx);
229+
expect(getLastUpdateInput()).toMatchObject({
230+
billing: {
231+
firstName: 'Immediate',
232+
lastName: 'Pickup',
233+
},
234+
});
235+
expect(getLastUpdateInput()?.billing).not.toHaveProperty('address');
236+
});
185237
});

packages/react/src/components/checkout/__tests__/checkout-pickup.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,32 @@ describe('Checkout pickup behavior', () => {
2525
).toBeInTheDocument();
2626
});
2727

28+
it('keeps a single set of name fields for paid pickup with credit-card billing', async () => {
29+
renderCheckout({
30+
draftOrderOverrides: {
31+
lineItems: [{ fulfillmentMode: 'PICKUP' }],
32+
},
33+
sessionOverrides: {
34+
enableShipping: false,
35+
enableLocalPickup: true,
36+
enableBillingAddressCollection: true,
37+
},
38+
});
39+
await waitForCheckoutReady();
40+
41+
// Names live in Pickup only; CreditCardContainer/ACH billing forms hide
42+
// duplicate name inputs while still collecting the billing address.
43+
expect(
44+
document.querySelectorAll('input[name="billingFirstName"]')
45+
).toHaveLength(1);
46+
expect(
47+
document.querySelectorAll('input[name="billingLastName"]')
48+
).toHaveLength(1);
49+
expect(
50+
document.querySelector('input[name="billingAddressLine1"]')
51+
).toBeInTheDocument();
52+
});
53+
2854
it('switches from shipping to pickup and calculates taxes with pickup location', async () => {
2955
const { user } = renderCheckout();
3056
await waitForCheckoutReady();

packages/react/src/components/checkout/address/address-form.tsx

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ export function AddressForm({
169169
!!lastName?.trim() &&
170170
debouncedContact === serializedContact;
171171

172+
// Names-only sync must omit `address`. Sending `address: null` clears any
173+
// billing address already collected for paid pickup (Payment section).
172174
useDraftOrderFieldSync({
173175
key: 'name',
174176
data: contact,
@@ -180,7 +182,6 @@ export function AddressForm({
180182
const fields = {
181183
firstName: data.firstName.trim(),
182184
lastName: data.lastName.trim(),
183-
address: null,
184185
};
185186

186187
return mapAddressFieldsToInput(
@@ -522,46 +523,48 @@ export function AddressForm({
522523
)}
523524

524525
{showNames ? (
525-
<div className='grid grid-cols-1 sm:grid-cols-2 gap-2'>
526-
<FormField
527-
control={form.control}
528-
name={`${sectionKey}FirstName`}
529-
render={({ field, fieldState }) => (
530-
<FormItem className='space-y-1'>
531-
<FormLabel className='sr-only'>{t.shipping.firstName}</FormLabel>
532-
<FormControl>
533-
<Input
534-
placeholder={t.shipping.firstName}
535-
hasError={!!fieldState.error}
536-
aria-required={requiredFields?.[`${sectionKey}FirstName`]}
537-
{...field}
538-
disabled={isConfirmingCheckout}
539-
/>
540-
</FormControl>
541-
<FormMessage />
542-
</FormItem>
543-
)}
544-
/>
545-
<FormField
546-
control={form.control}
547-
name={`${sectionKey}LastName`}
548-
render={({ field, fieldState }) => (
549-
<FormItem className='space-y-1'>
550-
<FormLabel className='sr-only'>{t.shipping.lastName}</FormLabel>
551-
<FormControl>
552-
<Input
553-
placeholder={t.shipping.lastName}
554-
hasError={!!fieldState.error}
555-
aria-required={requiredFields?.[`${sectionKey}LastName`]}
556-
{...field}
557-
disabled={isConfirmingCheckout}
558-
/>
559-
</FormControl>
560-
<FormMessage />
561-
</FormItem>
562-
)}
563-
/>
564-
</div>
526+
<div className='grid grid-cols-1 sm:grid-cols-2 gap-2'>
527+
<FormField
528+
control={form.control}
529+
name={`${sectionKey}FirstName`}
530+
render={({ field, fieldState }) => (
531+
<FormItem className='space-y-1'>
532+
<FormLabel className='sr-only'>
533+
{t.shipping.firstName}
534+
</FormLabel>
535+
<FormControl>
536+
<Input
537+
placeholder={t.shipping.firstName}
538+
hasError={!!fieldState.error}
539+
aria-required={requiredFields?.[`${sectionKey}FirstName`]}
540+
{...field}
541+
disabled={isConfirmingCheckout}
542+
/>
543+
</FormControl>
544+
<FormMessage />
545+
</FormItem>
546+
)}
547+
/>
548+
<FormField
549+
control={form.control}
550+
name={`${sectionKey}LastName`}
551+
render={({ field, fieldState }) => (
552+
<FormItem className='space-y-1'>
553+
<FormLabel className='sr-only'>{t.shipping.lastName}</FormLabel>
554+
<FormControl>
555+
<Input
556+
placeholder={t.shipping.lastName}
557+
hasError={!!fieldState.error}
558+
aria-required={requiredFields?.[`${sectionKey}LastName`]}
559+
{...field}
560+
disabled={isConfirmingCheckout}
561+
/>
562+
</FormControl>
563+
<FormMessage />
564+
</FormItem>
565+
)}
566+
/>
567+
</div>
565568
) : null}
566569

567570
{showAddressFields ? (

packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
22
import { useFormContext } from 'react-hook-form';
3-
import { useCheckoutContext } from '@/components/checkout/checkout';
43
import type { CheckoutFormData } from '@/components/checkout/checkout';
4+
import { useCheckoutContext } from '@/components/checkout/checkout';
55
import { useGetPriceAdjustments } from '@/components/checkout/discount/utils/use-get-price-adjustments';
66
import {
77
useDraftOrder,
@@ -29,11 +29,12 @@ import { useLoadPoyntCollect } from '@/components/checkout/payment/utils/use-loa
2929
import { filterAndSortShippingMethods } from '@/components/checkout/shipping/utils/filter-shipping-methods';
3030
import { useGetShippingMethodByAddress } from '@/components/checkout/shipping/utils/use-get-shipping-methods';
3131
import { useGetTaxes } from '@/components/checkout/taxes/utils/use-get-taxes';
32-
import { validatePickupPrerequisites } from '@/components/checkout/utils/use-validate-pickup-prerequisites';
3332
import {
3433
useConvertMajorToMinorUnits,
3534
useFormatCurrency,
3635
} from '@/components/checkout/utils/format-currency';
36+
import { useSyncPickupBillingNames } from '@/components/checkout/utils/use-sync-pickup-billing-names';
37+
import { validatePickupPrerequisites } from '@/components/checkout/utils/use-validate-pickup-prerequisites';
3738
import { Skeleton } from '@/components/ui/skeleton';
3839
import { useGoDaddyContext } from '@/godaddy-provider';
3940
import { GraphQLErrorWithCodes } from '@/lib/graphql-with-errors';
@@ -53,6 +54,7 @@ export function ExpressCheckoutButton() {
5354
useCheckoutContext();
5455
const isPaymentDisabled = useIsPaymentDisabled();
5556
const { isPoyntLoaded } = useLoadPoyntCollect();
57+
const syncPickupBillingNames = useSyncPickupBillingNames();
5658

5759
const isDisabled = isConfirmingCheckout || isPaymentDisabled;
5860
const { godaddyPaymentsConfig } = useCheckoutContext();
@@ -195,6 +197,10 @@ export function ExpressCheckoutButton() {
195197
return;
196198
}
197199

200+
// Persist pickup names before the wallet opens (AddressForm debounce may
201+
// not have enqueued a draft-order patch yet).
202+
await syncPickupBillingNames();
203+
198204
// Read from refs to get current values (avoid stale closure)
199205
const currentCouponCode = appliedCouponCodeRef.current;
200206
const currentAdjustments = calculatedAdjustmentsRef.current;
@@ -307,6 +313,7 @@ export function ExpressCheckoutButton() {
307313
isDisabled,
308314
form,
309315
session,
316+
syncPickupBillingNames,
310317
]
311318
);
312319

0 commit comments

Comments
 (0)