Skip to content

Commit c0d7536

Browse files
@W-21005976 Save newly registered user's info when they leave checkout… (#3632)
* W-21005976 Save newly registered user's info when they leave checkout and return * code review comments
1 parent 5c2942e commit c0d7536

File tree

3 files changed

+106
-86
lines changed

3 files changed

+106
-86
lines changed

packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx

Lines changed: 1 addition & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,11 @@ import {
5757
getPaymentInstrumentCardType,
5858
getMaskCreditCardNumber
5959
} from '@salesforce/retail-react-app/app/utils/cc-utils'
60-
import {nanoid} from 'nanoid'
6160

6261
const CheckoutOneClick = () => {
6362
const {formatMessage} = useIntl()
6463
const navigate = useNavigation()
65-
const {step, STEPS, contactPhone} = useCheckout()
64+
const {step, STEPS} = useCheckout()
6665
const showToast = useToast()
6766
const [isLoading, setIsLoading] = useState(false)
6867
const [enableUserRegistration, setEnableUserRegistration] = useState(false)
@@ -123,8 +122,6 @@ const CheckoutOneClick = () => {
123122
ShopperBasketsMutations.UpdateBillingAddressForBasket
124123
)
125124
const {mutateAsync: createOrder} = useShopperOrdersMutation(ShopperOrdersMutations.CreateOrder)
126-
const createCustomerAddress = useShopperCustomersMutation('createCustomerAddress')
127-
const updateCustomer = useShopperCustomersMutation('updateCustomer')
128125

129126
const handleSavePreferenceChange = (shouldSave) => {
130127
setShouldSavePaymentMethod(shouldSave)
@@ -386,85 +383,6 @@ const CheckoutOneClick = () => {
386383
fullCardDetails
387384
)
388385
}
389-
390-
// For newly registered guests only, persist shipping address when billing same as shipping
391-
// Skip saving pickup/store addresses - only save delivery addresses
392-
// For multi-shipment orders, save all delivery addresses with the first one as default
393-
if (
394-
enableUserRegistration &&
395-
currentCustomer?.isRegistered &&
396-
!registeredUserChoseGuest
397-
) {
398-
try {
399-
const customerId = order.customerInfo?.customerId
400-
if (!customerId) return
401-
402-
// Get all delivery shipments (not pickup) from the order
403-
// This handles both single delivery and multi-shipment orders
404-
// For BOPIS orders, pickup shipments are filtered out
405-
const deliveryShipments =
406-
order?.shipments?.filter(
407-
(shipment) =>
408-
!isPickupShipment(shipment) && shipment.shippingAddress
409-
) || []
410-
411-
if (deliveryShipments.length > 0) {
412-
// Save all delivery addresses, with the first one as preferred
413-
for (let i = 0; i < deliveryShipments.length; i++) {
414-
const shipment = deliveryShipments[i]
415-
const shipping = shipment.shippingAddress
416-
if (!shipping) continue
417-
418-
// Whitelist fields and strip non-customer fields (e.g., id, _type)
419-
const {
420-
address1,
421-
address2,
422-
city,
423-
countryCode,
424-
firstName,
425-
lastName,
426-
phone,
427-
postalCode,
428-
stateCode
429-
} = shipping || {}
430-
431-
await createCustomerAddress.mutateAsync({
432-
parameters: {customerId},
433-
body: {
434-
addressId: nanoid(),
435-
preferred: i === 0, // First address is preferred
436-
address1,
437-
address2,
438-
city,
439-
countryCode,
440-
firstName,
441-
lastName,
442-
phone,
443-
postalCode,
444-
stateCode
445-
}
446-
})
447-
}
448-
}
449-
450-
// Persist phone number as phoneHome for newly registered guest shoppers
451-
const phoneHome = basket?.billingAddress?.phone || contactPhone
452-
if (phoneHome) {
453-
await updateCustomer.mutateAsync({
454-
parameters: {customerId},
455-
body: {phoneHome}
456-
})
457-
}
458-
} catch (_e) {
459-
// Only surface error if shopper opted to register/save details; otherwise fail silently
460-
showError(
461-
formatMessage({
462-
id: 'checkout.error.cannot_save_address',
463-
defaultMessage: 'Could not save shipping address.'
464-
})
465-
)
466-
}
467-
}
468386
}
469387

470388
setCheckoutGuestChoiceInStorage(false)

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.jsx

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77
import React, {useRef, useState, useEffect} from 'react'
8-
import {FormattedMessage} from 'react-intl'
8+
import {FormattedMessage, useIntl} from 'react-intl'
99
import PropTypes from 'prop-types'
1010
import {
1111
Box,
@@ -23,7 +23,12 @@ import {
2323
import OtpAuth from '@salesforce/retail-react-app/app/components/otp-auth'
2424
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
2525
import {useCustomerType, useAuthHelper, AuthHelpers} from '@salesforce/commerce-sdk-react'
26+
import {useShopperCustomersMutation} from '@salesforce/commerce-sdk-react'
2627
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
28+
import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout-one-click/util/checkout-context'
29+
import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
30+
import {isPickupShipment} from '@salesforce/retail-react-app/app/utils/shipment-utils'
31+
import {nanoid} from 'nanoid'
2732

2833
export default function UserRegistration({
2934
enableUserRegistration,
@@ -36,15 +41,28 @@ export default function UserRegistration({
3641
onLoadingChange
3742
}) {
3843
const {data: basket} = useCurrentBasket()
44+
const {contactPhone} = useCheckout()
3945
const {isGuest} = useCustomerType()
4046
const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless)
4147
const loginPasswordless = useAuthHelper(AuthHelpers.LoginPasswordlessUser)
4248
const {locale} = useMultiSite()
49+
const {formatMessage} = useIntl()
50+
const showToast = useToast()
51+
const createCustomerAddress = useShopperCustomersMutation('createCustomerAddress')
52+
const updateCustomer = useShopperCustomersMutation('updateCustomer')
53+
4354
const {isOpen: isOtpOpen, onOpen: onOtpOpen, onClose: onOtpClose} = useDisclosure()
4455
const otpSentRef = useRef(false)
4556
const [registrationSucceeded, setRegistrationSucceeded] = useState(false)
4657
const [isLoadingOtp, setIsLoadingOtp] = useState(false)
4758

59+
const showError = (message) => {
60+
showToast({
61+
title: message,
62+
status: 'error'
63+
})
64+
}
65+
4866
const handleOtpClose = () => {
4967
otpSentRef.current = false
5068
onOtpClose()
@@ -92,13 +110,79 @@ export default function UserRegistration({
92110
}
93111
}, [isOtpOpen, isLoadingOtp, onLoadingChange])
94112

113+
const saveAddressesAndPhoneToProfile = async (customerId) => {
114+
if (!basket || !customerId) return
115+
const deliveryShipments =
116+
basket.shipments?.filter(
117+
(shipment) => !isPickupShipment(shipment) && shipment.shippingAddress
118+
) || []
119+
try {
120+
if (deliveryShipments.length > 0) {
121+
for (let i = 0; i < deliveryShipments.length; i++) {
122+
const shipment = deliveryShipments[i]
123+
const shipping = shipment.shippingAddress
124+
if (!shipping) continue
125+
126+
const {
127+
address1,
128+
address2,
129+
city,
130+
countryCode,
131+
firstName,
132+
lastName,
133+
phone,
134+
postalCode,
135+
stateCode
136+
} = shipping || {}
137+
138+
await createCustomerAddress.mutateAsync({
139+
parameters: {customerId},
140+
body: {
141+
addressId: nanoid(),
142+
preferred: i === 0,
143+
address1,
144+
address2,
145+
city,
146+
countryCode,
147+
firstName,
148+
lastName,
149+
phone,
150+
postalCode,
151+
stateCode
152+
}
153+
})
154+
}
155+
}
156+
157+
const phoneHome = basket.billingAddress?.phone || contactPhone
158+
if (phoneHome) {
159+
await updateCustomer.mutateAsync({
160+
parameters: {customerId},
161+
body: {phoneHome}
162+
})
163+
}
164+
} catch (_e) {
165+
showError(
166+
formatMessage({
167+
id: 'checkout.error.cannot_save_address',
168+
defaultMessage: 'Could not save shipping address.'
169+
})
170+
)
171+
}
172+
}
173+
95174
const handleOtpVerification = async (otpCode) => {
96175
try {
97-
await loginPasswordless.mutateAsync({
176+
const token = await loginPasswordless.mutateAsync({
98177
pwdlessLoginToken: otpCode,
99178
register_customer: true
100179
})
101180

181+
const customerId = token?.customer_id || token?.customerId
182+
if (customerId && basket) {
183+
await saveAddressesAndPhoneToProfile(customerId)
184+
}
185+
102186
if (onRegistered) {
103187
await onRegistered(basket?.basketId)
104188
}

packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.test.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ import useAuthContext from '@salesforce/commerce-sdk-react/hooks/useAuthContext'
1515

1616
jest.mock('@salesforce/retail-react-app/app/hooks/use-current-basket')
1717

18+
jest.mock(
19+
'@salesforce/retail-react-app/app/pages/checkout-one-click/util/checkout-context',
20+
() => ({
21+
useCheckout: () => ({contactPhone: ''})
22+
})
23+
)
24+
1825
const {AuthHelpers} = jest.requireActual('@salesforce/commerce-sdk-react')
1926

2027
const TEST_MESSAGES = {
@@ -29,12 +36,23 @@ const mockAuthHelperFunctions = {
2936
[AuthHelpers.LoginPasswordlessUser]: {mutateAsync: jest.fn()}
3037
}
3138

39+
const mockCreateCustomerAddress = {mutateAsync: jest.fn().mockResolvedValue({})}
40+
const mockUpdateCustomer = {mutateAsync: jest.fn().mockResolvedValue({})}
41+
const mockCreateCustomerPaymentInstrument = {mutateAsync: jest.fn().mockResolvedValue({})}
42+
3243
jest.mock('@salesforce/commerce-sdk-react', () => {
3344
const original = jest.requireActual('@salesforce/commerce-sdk-react')
3445
return {
3546
...original,
3647
useCustomerType: jest.fn(),
37-
useAuthHelper: jest.fn((helper) => mockAuthHelperFunctions[helper])
48+
useAuthHelper: jest.fn((helper) => mockAuthHelperFunctions[helper]),
49+
useShopperCustomersMutation: jest.fn((mutationType) => {
50+
if (mutationType === 'createCustomerAddress') return mockCreateCustomerAddress
51+
if (mutationType === 'updateCustomer') return mockUpdateCustomer
52+
if (mutationType === 'createCustomerPaymentInstrument')
53+
return mockCreateCustomerPaymentInstrument
54+
return {mutateAsync: jest.fn()}
55+
})
3856
}
3957
})
4058
jest.mock('@salesforce/commerce-sdk-react/hooks/useAuthContext', () =>

0 commit comments

Comments
 (0)