From 1eeef975bcdbb0cb6feff1682f925760b4f8a3b1 Mon Sep 17 00:00:00 2001 From: Sushma Yadupathi Date: Fri, 21 Nov 2025 12:13:28 -0500 Subject: [PATCH 1/3] transfer basket initial changes --- .../app/pages/checkout-one-click/index.jsx | 10 +++-- .../partials/one-click-contact-info.jsx | 15 ++++---- .../partials/one-click-shipping-address.jsx | 38 ++++++++++--------- .../partials/one-click-shipping-options.jsx | 20 +++++----- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx index 4287637621..a0b213e82a 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx @@ -155,10 +155,12 @@ const CheckoutOneClick = () => { } } - return addPaymentInstrumentToBasket({ - parameters: {basketId: basket?.basketId}, - body: paymentInstrument - }) + if (basket?.paymentInstruments?.length > 0) { + return addPaymentInstrumentToBasket({ + parameters: {basketId: basket?.basketId}, + body: paymentInstrument + }) + } } // Reset guest checkout flag when step changes (user goes back to edit) diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx index ba8bfa2d74..78d55131ed 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx @@ -65,7 +65,7 @@ const ContactInfo = ({isSocialEnabled = false, idps = [], onRegisteredUserChoseG const logout = useAuthHelper(AuthHelpers.Logout) const updateCustomerForBasket = useShopperBasketsMutation('updateCustomerForBasket') - const mergeBasket = useShopperBasketsMutation('mergeBasket') + const transferBasket = useShopperBasketsMutation('transferBasket') const updateCustomer = useShopperCustomersMutation('updateCustomer') const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless) const loginPasswordless = useAuthHelper(AuthHelpers.LoginPasswordlessUser) @@ -270,14 +270,13 @@ const ContactInfo = ({isSocialEnabled = false, idps = [], onRegisteredUserChoseG let basketId if (hasBasketItem) { // Mirror legacy checkout flow header and await completion - const merged = await mergeBasket.mutateAsync({ + const merged = await transferBasket.mutateAsync({ headers: { 'Content-Type': 'application/json' }, parameters: { - createDestinationBasket: true - }, - body: {sourceBasketId: basket.basketId} + merge: true + } }) basketId = merged?.basketId || basket.basketId // Ensure we hydrate the latest basket after merge @@ -364,15 +363,17 @@ const ContactInfo = ({isSocialEnabled = false, idps = [], onRegisteredUserChoseG return } try { - await mergeBasket.mutateAsync({ + await transferBasket.mutateAsync({ headers: { 'Content-Type': 'application/json' }, parameters: { - createDestinationBasket: true + merge: true } }) await currentBasketQuery.refetch() + // Jump straight to payment if basket is already complete after transfer + goToStep(STEPS.PAYMENT) } catch (_e) { // no-op } finally { diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx index e9962ad9f2..b57dce38e0 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx @@ -64,7 +64,7 @@ export default function ShippingAddress() { ) const productItemsCount = basket?.productItems?.length || 0 const hasMultipleProductItems = productItemsCount > 1 - const multishipEnabled = getConfig()?.app?.multishipEnabled ?? true + const multishipEnabled = false const hasMultipleDeliveryShipments = deliveryShipments.length > 1 @@ -129,23 +129,25 @@ export default function ShippingAddress() { const refreshed = await currentBasketQuery.refetch() const latestBasketId = refreshed?.data?.basketId || basket.basketId - await updateShippingAddressForShipment.mutateAsync({ - parameters: { - basketId: latestBasketId, - shipmentId: targetDeliveryShipmentId, - useAsBilling: false - }, - body: { - address1, - city, - countryCode, - firstName, - lastName, - phone: phoneValue, - postalCode, - stateCode - } - }) + if (refreshed?.data?.shipments?.shippingAddress?.length > 0) { + await updateShippingAddressForShipment.mutateAsync({ + parameters: { + basketId: latestBasketId, + shipmentId: targetDeliveryShipmentId, + useAsBilling: false + }, + body: { + address1, + city, + countryCode, + firstName, + lastName, + phone: phoneValue, + postalCode, + stateCode + } + }) + } if (customer.isRegistered && !addressId) { const body = { diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx index 686d6daa05..e9d5fe7367 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx @@ -180,15 +180,17 @@ export default function ShippingOptions() { ]) const submitForm = async ({shippingMethodId}) => { - await updateShippingMethod.mutateAsync({ - parameters: { - basketId: basket.basketId, - shipmentId: targetDeliveryShipment?.shipmentId || 'me' - }, - body: { - id: shippingMethodId - } - }) + if (basket?.shipments?.shippingMethod?.length > 0) { + await updateShippingMethod.mutateAsync({ + parameters: { + basketId: basket.basketId, + shipmentId: targetDeliveryShipment?.shipmentId || 'me' + }, + body: { + id: shippingMethodId + } + }) + } goToNextStep() } From 10300aa2c021aa45853a58225d2d28bdca7b6ef9 Mon Sep 17 00:00:00 2001 From: Sushma Yadupathi Date: Fri, 5 Dec 2025 12:34:45 -0500 Subject: [PATCH 2/3] removed some of the checks --- .../app/pages/checkout-one-click/index.jsx | 10 ++--- .../partials/one-click-contact-info.jsx | 2 - .../partials/one-click-shipping-address.jsx | 38 +++++++++---------- .../partials/one-click-shipping-options.jsx | 20 +++++----- 4 files changed, 31 insertions(+), 39 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx index a0b213e82a..4287637621 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx @@ -155,12 +155,10 @@ const CheckoutOneClick = () => { } } - if (basket?.paymentInstruments?.length > 0) { - return addPaymentInstrumentToBasket({ - parameters: {basketId: basket?.basketId}, - body: paymentInstrument - }) - } + return addPaymentInstrumentToBasket({ + parameters: {basketId: basket?.basketId}, + body: paymentInstrument + }) } // Reset guest checkout flag when step changes (user goes back to edit) diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx index 78d55131ed..fcbd0e6a9c 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx @@ -372,8 +372,6 @@ const ContactInfo = ({isSocialEnabled = false, idps = [], onRegisteredUserChoseG } }) await currentBasketQuery.refetch() - // Jump straight to payment if basket is already complete after transfer - goToStep(STEPS.PAYMENT) } catch (_e) { // no-op } finally { diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx index b57dce38e0..e9962ad9f2 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx @@ -64,7 +64,7 @@ export default function ShippingAddress() { ) const productItemsCount = basket?.productItems?.length || 0 const hasMultipleProductItems = productItemsCount > 1 - const multishipEnabled = false + const multishipEnabled = getConfig()?.app?.multishipEnabled ?? true const hasMultipleDeliveryShipments = deliveryShipments.length > 1 @@ -129,25 +129,23 @@ export default function ShippingAddress() { const refreshed = await currentBasketQuery.refetch() const latestBasketId = refreshed?.data?.basketId || basket.basketId - if (refreshed?.data?.shipments?.shippingAddress?.length > 0) { - await updateShippingAddressForShipment.mutateAsync({ - parameters: { - basketId: latestBasketId, - shipmentId: targetDeliveryShipmentId, - useAsBilling: false - }, - body: { - address1, - city, - countryCode, - firstName, - lastName, - phone: phoneValue, - postalCode, - stateCode - } - }) - } + await updateShippingAddressForShipment.mutateAsync({ + parameters: { + basketId: latestBasketId, + shipmentId: targetDeliveryShipmentId, + useAsBilling: false + }, + body: { + address1, + city, + countryCode, + firstName, + lastName, + phone: phoneValue, + postalCode, + stateCode + } + }) if (customer.isRegistered && !addressId) { const body = { diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx index e9d5fe7367..686d6daa05 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx @@ -180,17 +180,15 @@ export default function ShippingOptions() { ]) const submitForm = async ({shippingMethodId}) => { - if (basket?.shipments?.shippingMethod?.length > 0) { - await updateShippingMethod.mutateAsync({ - parameters: { - basketId: basket.basketId, - shipmentId: targetDeliveryShipment?.shipmentId || 'me' - }, - body: { - id: shippingMethodId - } - }) - } + await updateShippingMethod.mutateAsync({ + parameters: { + basketId: basket.basketId, + shipmentId: targetDeliveryShipment?.shipmentId || 'me' + }, + body: { + id: shippingMethodId + } + }) goToNextStep() } From a073eb0072a537a0a878e535be7b1b041699f372 Mon Sep 17 00:00:00 2001 From: Sushma Yadupathi Date: Fri, 5 Dec 2025 13:54:39 -0500 Subject: [PATCH 3/3] fix tests --- .../partials/one-click-contact-info.test.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js index b6aab465ff..c0bbca8649 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js @@ -21,7 +21,7 @@ const mockAuthHelperFunctions = { } const mockUpdateCustomerForBasket = {mutateAsync: jest.fn()} -const mockMergeBasket = {mutate: jest.fn(), mutateAsync: jest.fn()} +const mockTransferBasket = {mutate: jest.fn(), mutateAsync: jest.fn()} jest.mock('@salesforce/commerce-sdk-react', () => { const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') @@ -33,7 +33,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { .mockImplementation((helperType) => mockAuthHelperFunctions[helperType]), useShopperBasketsMutation: jest.fn().mockImplementation((mutationType) => { if (mutationType === 'updateCustomerForBasket') return mockUpdateCustomerForBasket - if (mutationType === 'mergeBasket') return mockMergeBasket + if (mutationType === 'transferBasket') return mockTransferBasket return {mutate: jest.fn()} }), useShopperCustomersMutation: jest.fn().mockImplementation((mutationType) => { @@ -155,8 +155,9 @@ describe('ContactInfo Component', () => { test('updates checkout contact phone when user types phone (guest)', async () => { const {user} = renderWithProviders() const phoneInput = screen.getByLabelText('Phone') - await user.type(phoneInput, '7275551234') - // Formatting can be applied incrementally; assert value is being updated + await act(async () => { + await user.type(phoneInput, '7275551234') + }) expect(phoneInput.value.length).toBeGreaterThan(0) }) @@ -521,13 +522,13 @@ describe('ContactInfo Component', () => { expect(screen.getByText(/Resend Code/i)).toBeInTheDocument() }) - test('OTP verification merges and updates basket email using merged id', async () => { + test('OTP verification transfers and updates basket email using transferred id', async () => { // Arrange mocks mockAuthHelperFunctions[AuthHelpers.LoginPasswordlessUser].mutateAsync.mockResolvedValue({}) mockAuthHelperFunctions[AuthHelpers.AuthorizePasswordless].mutateAsync.mockResolvedValue({}) const mergedId = 'merged-123' - mockMergeBasket.mutateAsync.mockResolvedValue({basketId: mergedId}) + mockTransferBasket.mutateAsync.mockResolvedValue({basketId: mergedId}) // Make refetch return merged id to simulate hydration const refetchSpy = jest.fn().mockResolvedValue({data: {basketId: mergedId}}) mockUseCurrentBasket.mockReturnValue({ @@ -560,11 +561,10 @@ describe('ContactInfo Component', () => { useCustomerType.mockReturnValue({isRegistered: true}) await waitFor(() => { - expect(mockMergeBasket.mutateAsync).toHaveBeenCalled() - // Validate merge called with sourceBasketId in body and createDestinationBasket param - const mergeArgs = mockMergeBasket.mutateAsync.mock.calls[0]?.[0] - expect(mergeArgs?.parameters).toMatchObject({createDestinationBasket: true}) - expect(mergeArgs?.body).toMatchObject({sourceBasketId: 'guest-1'}) + expect(mockTransferBasket.mutateAsync).toHaveBeenCalled() + // Validate transferBasket called with merge=true parameter + const transferArgs = mockTransferBasket.mutateAsync.mock.calls[0]?.[0] + expect(transferArgs?.parameters).toMatchObject({merge: true}) }) // Updating basket email may occur asynchronously or be skipped if unchanged; don't hard-require it here })