Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,11 @@ import {
getPaymentInstrumentCardType,
getMaskCreditCardNumber
} from '@salesforce/retail-react-app/app/utils/cc-utils'
import {nanoid} from 'nanoid'

const CheckoutOneClick = () => {
const {formatMessage} = useIntl()
const navigate = useNavigation()
const {step, STEPS, contactPhone} = useCheckout()
const {step, STEPS} = useCheckout()
const showToast = useToast()
const [isLoading, setIsLoading] = useState(false)
const [enableUserRegistration, setEnableUserRegistration] = useState(false)
Expand Down Expand Up @@ -119,8 +118,6 @@ const CheckoutOneClick = () => {
ShopperBasketsMutations.UpdateBillingAddressForBasket
)
const {mutateAsync: createOrder} = useShopperOrdersMutation(ShopperOrdersMutations.CreateOrder)
const createCustomerAddress = useShopperCustomersMutation('createCustomerAddress')
const updateCustomer = useShopperCustomersMutation('updateCustomer')

const handleSavePreferenceChange = (shouldSave) => {
setShouldSavePaymentMethod(shouldSave)
Expand Down Expand Up @@ -382,85 +379,6 @@ const CheckoutOneClick = () => {
fullCardDetails
)
}

// For newly registered guests only, persist shipping address when billing same as shipping
// Skip saving pickup/store addresses - only save delivery addresses
// For multi-shipment orders, save all delivery addresses with the first one as default
if (
enableUserRegistration &&
currentCustomer?.isRegistered &&
!registeredUserChoseGuest
) {
try {
const customerId = order.customerInfo?.customerId
if (!customerId) return

// Get all delivery shipments (not pickup) from the order
// This handles both single delivery and multi-shipment orders
// For BOPIS orders, pickup shipments are filtered out
const deliveryShipments =
order?.shipments?.filter(
(shipment) =>
!isPickupShipment(shipment) && shipment.shippingAddress
) || []

if (deliveryShipments.length > 0) {
// Save all delivery addresses, with the first one as preferred
for (let i = 0; i < deliveryShipments.length; i++) {
const shipment = deliveryShipments[i]
const shipping = shipment.shippingAddress
if (!shipping) continue

// Whitelist fields and strip non-customer fields (e.g., id, _type)
const {
address1,
address2,
city,
countryCode,
firstName,
lastName,
phone,
postalCode,
stateCode
} = shipping || {}

await createCustomerAddress.mutateAsync({
parameters: {customerId},
body: {
addressId: nanoid(),
preferred: i === 0, // First address is preferred
address1,
address2,
city,
countryCode,
firstName,
lastName,
phone,
postalCode,
stateCode
}
})
}
}

// Persist phone number as phoneHome for newly registered guest shoppers
const phoneHome = basket?.billingAddress?.phone || contactPhone
if (phoneHome) {
await updateCustomer.mutateAsync({
parameters: {customerId},
body: {phoneHome}
})
}
} catch (_e) {
// Only surface error if shopper opted to register/save details; otherwise fail silently
showError(
formatMessage({
id: 'checkout.error.cannot_save_address',
defaultMessage: 'Could not save shipping address.'
})
)
}
}
}

navigate(`/checkout/confirmation/${order.orderNo}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {useRef, useState, useEffect} from 'react'
import {FormattedMessage} from 'react-intl'
import {FormattedMessage, useIntl} from 'react-intl'
import PropTypes from 'prop-types'
import {
Box,
Expand All @@ -23,7 +23,12 @@ import {
import OtpAuth from '@salesforce/retail-react-app/app/components/otp-auth'
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
import {useCustomerType, useAuthHelper, AuthHelpers} from '@salesforce/commerce-sdk-react'
import {useShopperCustomersMutation} from '@salesforce/commerce-sdk-react'
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout-one-click/util/checkout-context'
import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
import {isPickupShipment} from '@salesforce/retail-react-app/app/utils/shipment-utils'
import {nanoid} from 'nanoid'

export default function UserRegistration({
enableUserRegistration,
Expand All @@ -36,15 +41,28 @@ export default function UserRegistration({
onLoadingChange
}) {
const {data: basket} = useCurrentBasket()
const {contactPhone} = useCheckout()
const {isGuest} = useCustomerType()
const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless)
const loginPasswordless = useAuthHelper(AuthHelpers.LoginPasswordlessUser)
const {locale} = useMultiSite()
const {formatMessage} = useIntl()
const showToast = useToast()
const createCustomerAddress = useShopperCustomersMutation('createCustomerAddress')
const updateCustomer = useShopperCustomersMutation('updateCustomer')

const {isOpen: isOtpOpen, onOpen: onOtpOpen, onClose: onOtpClose} = useDisclosure()
const otpSentRef = useRef(false)
const [registrationSucceeded, setRegistrationSucceeded] = useState(false)
const [isLoadingOtp, setIsLoadingOtp] = useState(false)

const showError = (message) => {
showToast({
title: message,
status: 'error'
})
}

const handleOtpClose = () => {
otpSentRef.current = false
onOtpClose()
Expand Down Expand Up @@ -92,13 +110,79 @@ export default function UserRegistration({
}
}, [isOtpOpen, isLoadingOtp, onLoadingChange])

const saveAddressesAndPhoneToProfile = async (customerId) => {
if (!basket || !customerId) return
const deliveryShipments =
basket.shipments?.filter(
(shipment) => !isPickupShipment(shipment) && shipment.shippingAddress
) || []
try {
if (deliveryShipments.length > 0) {
for (let i = 0; i < deliveryShipments.length; i++) {
const shipment = deliveryShipments[i]
const shipping = shipment.shippingAddress
if (!shipping) continue

const {
address1,
address2,
city,
countryCode,
firstName,
lastName,
phone,
postalCode,
stateCode
} = shipping || {}

await createCustomerAddress.mutateAsync({
parameters: {customerId},
body: {
addressId: nanoid(),
preferred: i === 0,
address1,
address2,
city,
countryCode,
firstName,
lastName,
phone,
postalCode,
stateCode
}
})
}
}

const phoneHome = basket.billingAddress?.phone || contactPhone
if (phoneHome) {
await updateCustomer.mutateAsync({
parameters: {customerId},
body: {phoneHome}
})
}
} catch (_e) {
showError(
formatMessage({
id: 'checkout.error.cannot_save_address',
defaultMessage: 'Could not save shipping address.'
})
)
}
}

const handleOtpVerification = async (otpCode) => {
try {
await loginPasswordless.mutateAsync({
const token = await loginPasswordless.mutateAsync({
pwdlessLoginToken: otpCode,
register_customer: true
})

const customerId = token?.customer_id || token?.customerId
if (customerId && basket) {
await saveAddressesAndPhoneToProfile(customerId)
}

if (onRegistered) {
await onRegistered(basket?.basketId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ import useAuthContext from '@salesforce/commerce-sdk-react/hooks/useAuthContext'

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

jest.mock(
'@salesforce/retail-react-app/app/pages/checkout-one-click/util/checkout-context',
() => ({
useCheckout: () => ({contactPhone: ''})
})
)

const {AuthHelpers} = jest.requireActual('@salesforce/commerce-sdk-react')

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

const mockCreateCustomerAddress = {mutateAsync: jest.fn().mockResolvedValue({})}
const mockUpdateCustomer = {mutateAsync: jest.fn().mockResolvedValue({})}
const mockCreateCustomerPaymentInstrument = {mutateAsync: jest.fn().mockResolvedValue({})}

jest.mock('@salesforce/commerce-sdk-react', () => {
const original = jest.requireActual('@salesforce/commerce-sdk-react')
return {
...original,
useCustomerType: jest.fn(),
useAuthHelper: jest.fn((helper) => mockAuthHelperFunctions[helper])
useAuthHelper: jest.fn((helper) => mockAuthHelperFunctions[helper]),
useShopperCustomersMutation: jest.fn((mutationType) => {
if (mutationType === 'createCustomerAddress') return mockCreateCustomerAddress
if (mutationType === 'updateCustomer') return mockUpdateCustomer
if (mutationType === 'createCustomerPaymentInstrument')
return mockCreateCustomerPaymentInstrument
return {mutateAsync: jest.fn()}
})
}
})
jest.mock('@salesforce/commerce-sdk-react/hooks/useAuthContext', () =>
Expand Down
Loading