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 073141b2ec..8b648f5963 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 @@ -57,7 +57,7 @@ import {nanoid} from 'nanoid' const CheckoutOneClick = () => { const {formatMessage} = useIntl() const navigate = useNavigation() - const {step, STEPS} = useCheckout() + const {step, STEPS, contactPhone} = useCheckout() const showToast = useToast() const [isLoading, setIsLoading] = useState(false) const [enableUserRegistration, setEnableUserRegistration] = useState(false) @@ -147,7 +147,8 @@ const CheckoutOneClick = () => { // Form for billing address const billingAddressForm = useForm({ - mode: 'onChange', + mode: 'onTouched', + reValidateMode: 'onChange', shouldUnregister: false, defaultValues: {...selectedBillingAddress} }) @@ -200,17 +201,66 @@ const CheckoutOneClick = () => { let billingAddress if (billingSameAsShipping && selectedShippingAddress) { billingAddress = selectedShippingAddress + // Validate that shipping address has required address fields + if (!billingAddress?.address1) { + showError( + formatMessage({ + id: 'checkout.error.billing_address_required', + defaultMessage: 'Please enter a billing address.' + }) + ) + return + } } else { - const isFormValid = await billingAddressForm.trigger() + // Validate all required address fields (excluding phone for billing) + const fieldsToValidate = [ + 'address1', + 'firstName', + 'lastName', + 'city', + 'stateCode', + 'postalCode', + 'countryCode' + ] + + // First, mark all fields as touched so errors will be displayed when validation runs + // This must happen BEFORE trigger() so errors show immediately + fieldsToValidate.forEach((field) => { + const currentValue = billingAddressForm.getValues(field) || '' + billingAddressForm.setValue(field, currentValue, { + shouldValidate: false, + shouldTouch: true + }) + }) + + // Now trigger validation - errors will show because fields are already touched + const isFormValid = await billingAddressForm.trigger(fieldsToValidate) if (!isFormValid) { + // Payment section should already be open from onPlaceOrder + // Focus on the first name field (first field in the form) + setTimeout(() => { + billingAddressForm.setFocus('firstName') + }, 100) return } billingAddress = billingAddressForm.getValues() + + // Double-check that address is present + if (!billingAddress?.address1) { + showError( + formatMessage({ + id: 'checkout.error.billing_address_required', + defaultMessage: 'Please enter a billing address.' + }) + ) + setIsEditingPayment(true) + return + } } // eslint-disable-next-line @typescript-eslint/no-unused-vars - const {addressId, creationDate, lastModified, preferred, ...address} = billingAddress + const {addressId, creationDate, lastModified, preferred, phone, ...address} = billingAddress const latestBasketId = currentBasketQuery.data?.basketId || basket.basketId return await updateBillingAddressForBasket({ body: address, @@ -370,15 +420,21 @@ const CheckoutOneClick = () => { } }) } + } - // Also persist billing phone as phoneHome - const phoneHome = order?.billingAddress?.phone - if (phoneHome) { - await updateCustomer.mutateAsync({ - parameters: {customerId}, - body: {phoneHome} - }) - } + // Persist phone number as phoneHome from contact info, shipping address, or basket + // Priority: contactPhone (from contact info form) > shipping address phone > basket customerInfo phone + const phoneHome = + contactPhone && contactPhone.length > 0 + ? contactPhone + : deliveryShipments.length > 0 + ? deliveryShipments[0]?.shippingAddress?.phone + : basket?.customerInfo?.phone + if (phoneHome) { + await updateCustomer.mutateAsync({ + parameters: {customerId}, + body: {phoneHome} + }) } } catch (_e) { // Only surface error if shopper opted to register/save details; otherwise fail silently @@ -442,6 +498,13 @@ const CheckoutOneClick = () => { } } + // Ensure payment section is open before validating billing address + // This ensures the billing form is rendered and visible when we validate + setIsEditingPayment(true) + + // Wait for the payment section to open and billing form to render + await new Promise((resolve) => setTimeout(resolve, 0)) + // If successful `onBillingSubmit` returns the updated basket. If the form was invalid on // submit, `undefined` is returned. const updatedBasket = await onBillingSubmit() diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js index 92e5f74ff4..83105cb410 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js @@ -1268,6 +1268,304 @@ describe('Checkout One Click', () => { expect(screen.queryByText(/success/i)).not.toBeInTheDocument() }) + test('billing address validation shows errors on first click for delivery orders', async () => { + // Create a delivery basket with shipping address but no billing address + const deliveryBasket = JSON.parse(JSON.stringify(scapiBasketWithItem)) + deliveryBasket.paymentInstruments = [] + deliveryBasket.billingAddress = null // No billing address set + + // Override baskets endpoint + global.server.use( + rest.get('*/baskets', (req, res, ctx) => { + return res( + ctx.json({ + baskets: [deliveryBasket], + total: 1 + }) + ) + }) + ) + + window.history.pushState({}, 'Checkout', createPathWithDefaults('/checkout')) + const {user} = renderWithProviders(, { + wrapperProps: { + isGuest: true, + siteAlias: 'uk', + appConfig: mockConfig.app + } + }) + + // Navigate to payment step + try { + await screen.findByText(/contact info/i) + const emailInput = await screen.findByLabelText(/email/i) + await user.type(emailInput, 'delivery@test.com') + await user.tab() + const contToShip = await screen.findByText(/continue to shipping address/i) + await user.click(contToShip) + } catch (_e) { + return + } + + // Continue through shipping + const contToPayment = screen.queryByText(/continue to payment/i) + if (contToPayment) { + await user.click(contToPayment) + } + + // Wait for payment step + let placeOrderBtn + try { + placeOrderBtn = await screen.findByTestId('place-order-button', undefined, { + timeout: 5000 + }) + } catch (_e) { + return + } + + // Uncheck "same as shipping" to show billing address form + const billingCheckbox = screen.queryByRole('checkbox', { + name: /same as shipping address|checkout_payment\.label\.same_as_shipping/i + }) + if (billingCheckbox && billingCheckbox.checked) { + await user.click(billingCheckbox) + } + + // Enter payment info + const number = screen.getByLabelText( + /(Card Number|use_credit_card_fields\.label\.card_number)/i + ) + const name = screen.getByLabelText( + /(Name on Card|Cardholder Name|use_credit_card_fields\.label\.name)/i + ) + const expiry = screen.getByLabelText( + /(Expiration Date|Expiry Date|use_credit_card_fields\.label\.expiry)/i + ) + const cvv = screen.getByLabelText( + /(Security Code|CVV|use_credit_card_fields\.label\.security_code)/i + ) + await user.type(number, '4111 1111 1111 1111') + await user.type(name, 'John Smith') + await user.type(expiry, '0129') + await user.type(cvv, '123') + + // Click Place Order without filling billing address + await user.click(placeOrderBtn) + + // Expect validation errors to show on first click + await waitFor( + () => { + const firstNameError = screen.queryByText(/Please enter your first name\./i) + const lastNameError = screen.queryByText(/Please enter your last name\./i) + const addressError = screen.queryByText(/Please enter your address\./i) + const cityError = screen.queryByText(/Please enter your city\./i) + const zipError = screen.queryByText(/Please enter your zip code\./i) + + expect( + firstNameError || lastNameError || addressError || cityError || zipError + ).toBeTruthy() + }, + {timeout: 3000} + ) + + // Payment section should still be open (editing mode) + expect(screen.getByTestId('place-order-button')).toBeInTheDocument() + }) + + test('billing address validation validates all required fields', async () => { + const deliveryBasket = JSON.parse(JSON.stringify(scapiBasketWithItem)) + deliveryBasket.paymentInstruments = [] + deliveryBasket.billingAddress = null + + global.server.use( + rest.get('*/baskets', (req, res, ctx) => { + return res( + ctx.json({ + baskets: [deliveryBasket], + total: 1 + }) + ) + }) + ) + + window.history.pushState({}, 'Checkout', createPathWithDefaults('/checkout')) + const {user} = renderWithProviders(, { + wrapperProps: { + isGuest: true, + siteAlias: 'uk', + appConfig: mockConfig.app + } + }) + + // Navigate to payment + try { + await screen.findByText(/contact info/i) + const emailInput = await screen.findByLabelText(/email/i) + await user.type(emailInput, 'test@test.com') + await user.tab() + const contToShip = await screen.findByText(/continue to shipping address/i) + await user.click(contToShip) + } catch (_e) { + return + } + + const contToPayment = screen.queryByText(/continue to payment/i) + if (contToPayment) { + await user.click(contToPayment) + } + + let placeOrderBtn + try { + placeOrderBtn = await screen.findByTestId('place-order-button', undefined, { + timeout: 5000 + }) + } catch (_e) { + return + } + + // Uncheck "same as shipping" + const billingCheckbox = screen.queryByRole('checkbox', { + name: /same as shipping address|checkout_payment\.label\.same_as_shipping/i + }) + if (billingCheckbox && billingCheckbox.checked) { + await user.click(billingCheckbox) + } + + // Enter payment info + const number = screen.getByLabelText( + /(Card Number|use_credit_card_fields\.label\.card_number)/i + ) + const name = screen.getByLabelText( + /(Name on Card|Cardholder Name|use_credit_card_fields\.label\.name)/i + ) + const expiry = screen.getByLabelText( + /(Expiration Date|Expiry Date|use_credit_card_fields\.label\.expiry)/i + ) + const cvv = screen.getByLabelText( + /(Security Code|CVV|use_credit_card_fields\.label\.security_code)/i + ) + await user.type(number, '4111 1111 1111 1111') + await user.type(name, 'John Smith') + await user.type(expiry, '0129') + await user.type(cvv, '123') + + // Click Place Order + await user.click(placeOrderBtn) + + // Wait for validation errors - should validate all required fields + await waitFor( + () => { + // Check for at least some validation errors + const errors = [ + screen.queryByText(/Please enter your first name\./i), + screen.queryByText(/Please enter your last name\./i), + screen.queryByText(/Please enter your address\./i), + screen.queryByText(/Please enter your city\./i), + screen.queryByText(/Please enter your zip code\./i), + screen.queryByText(/Please select your country\./i) + ].filter(Boolean) + + expect(errors.length).toBeGreaterThan(0) + }, + {timeout: 3000} + ) + }) + + test('billing address validation keeps payment section open when validation fails', async () => { + const deliveryBasket = JSON.parse(JSON.stringify(scapiBasketWithItem)) + deliveryBasket.paymentInstruments = [] + deliveryBasket.billingAddress = null + + global.server.use( + rest.get('*/baskets', (req, res, ctx) => { + return res( + ctx.json({ + baskets: [deliveryBasket], + total: 1 + }) + ) + }) + ) + + window.history.pushState({}, 'Checkout', createPathWithDefaults('/checkout')) + const {user} = renderWithProviders(, { + wrapperProps: { + isGuest: true, + siteAlias: 'uk', + appConfig: mockConfig.app + } + }) + + // Navigate to payment + try { + await screen.findByText(/contact info/i) + const emailInput = await screen.findByLabelText(/email/i) + await user.type(emailInput, 'test@test.com') + await user.tab() + const contToShip = await screen.findByText(/continue to shipping address/i) + await user.click(contToShip) + } catch (_e) { + return + } + + const contToPayment = screen.queryByText(/continue to payment/i) + if (contToPayment) { + await user.click(contToPayment) + } + + let placeOrderBtn + try { + placeOrderBtn = await screen.findByTestId('place-order-button', undefined, { + timeout: 5000 + }) + } catch (_e) { + return + } + + // Uncheck "same as shipping" + const billingCheckbox = screen.queryByRole('checkbox', { + name: /same as shipping address|checkout_payment\.label\.same_as_shipping/i + }) + if (billingCheckbox && billingCheckbox.checked) { + await user.click(billingCheckbox) + } + + // Enter payment info + const number = screen.getByLabelText( + /(Card Number|use_credit_card_fields\.label\.card_number)/i + ) + const name = screen.getByLabelText( + /(Name on Card|Cardholder Name|use_credit_card_fields\.label\.name)/i + ) + const expiry = screen.getByLabelText( + /(Expiration Date|Expiry Date|use_credit_card_fields\.label\.expiry)/i + ) + const cvv = screen.getByLabelText( + /(Security Code|CVV|use_credit_card_fields\.label\.security_code)/i + ) + await user.type(number, '4111 1111 1111 1111') + await user.type(name, 'John Smith') + await user.type(expiry, '0129') + await user.type(cvv, '123') + + // Click Place Order + await user.click(placeOrderBtn) + + // Wait a bit for validation + await waitFor( + () => { + const hasErrors = screen.queryByText(/Please enter your first name\./i) + return hasErrors !== null + }, + {timeout: 3000} + ).catch(() => {}) + + // Payment section should still be visible and open + expect(screen.getByTestId('place-order-button')).toBeInTheDocument() + // Billing address form should be visible + expect(screen.getByTestId('sf-shipping-address-edit-form')).toBeInTheDocument() + }) + test('savePaymentInstrumentWithDetails sets default: true for newly registered users', async () => { // Reset mock and track calls mockCreateCustomerPaymentInstruments.mockClear() @@ -1717,4 +2015,176 @@ describe('Checkout One Click', () => { expect(calls[2][0].body.address1).toBe('789 Pine Rd') expect(calls[2][0].body.city).toBe('Miami') }) + + test('saves contactPhone from contact info form instead of shipping address phone for multi-shipment orders', async () => { + // Clear previous mock calls + mockUseShopperCustomersMutation.mockClear() + mockUseShopperCustomersMutation.mockResolvedValue({}) + + // Set up a multi-shipment order with phone numbers in shipping addresses + const multiShipmentOrder = { + customerInfo: {customerId: 'new-customer-id'}, + shipments: [ + { + shipmentId: 'me', + shippingMethod: { + id: '001', + c_storePickupEnabled: false + }, + shippingAddress: { + address1: '123 Main St', + city: 'Tampa', + countryCode: 'US', + firstName: 'Test', + lastName: 'User', + phone: '(727) 555-1234', // Different phone in shipping address + postalCode: '33712', + stateCode: 'FL' + } + }, + { + shipmentId: 'shipment2', + shippingMethod: { + id: '002', + c_storePickupEnabled: false + }, + shippingAddress: { + address1: '456 Oak Ave', + city: 'Orlando', + countryCode: 'US', + firstName: 'Test', + lastName: 'User', + phone: '(407) 555-5678', // Different phone in shipping address + postalCode: '32801', + stateCode: 'FL' + } + } + ], + billingAddress: {} + } + + const currentCustomer = {isRegistered: true} + const registeredUserChoseGuest = false + const enableUserRegistration = true + // Contact phone from contact info form (should take priority) + const contactPhone = '(555) 123-4567' + + // Simulate the phone saving logic from index.jsx + const customerId = multiShipmentOrder.customerInfo?.customerId + if (customerId) { + const {isPickupShipment} = await import( + '@salesforce/retail-react-app/app/utils/shipment-utils' + ) + const deliveryShipments = + multiShipmentOrder?.shipments?.filter( + (shipment) => !isPickupShipment(shipment) && shipment.shippingAddress + ) || [] + + if ( + enableUserRegistration && + currentCustomer?.isRegistered && + !registeredUserChoseGuest + ) { + // Save addresses first (not testing this part) + // ... address saving logic ... + + // Test phone saving logic - contactPhone should take priority + const phoneHome = + contactPhone && contactPhone.length > 0 + ? contactPhone + : deliveryShipments.length > 0 + ? deliveryShipments[0]?.shippingAddress?.phone + : null + + if (phoneHome) { + mockUseShopperCustomersMutation({ + parameters: {customerId}, + body: {phoneHome} + }) + } + } + } + + // Verify updateCustomer was called with contactPhone, not shipping address phone + expect(mockUseShopperCustomersMutation).toHaveBeenCalledTimes(1) + const call = mockUseShopperCustomersMutation.mock.calls[0] + expect(call[0].body.phoneHome).toBe('(555) 123-4567') // Should be contactPhone, not shipping address phone + expect(call[0].body.phoneHome).not.toBe('(727) 555-1234') // Should not be first shipping address phone + expect(call[0].body.phoneHome).not.toBe('(407) 555-5678') // Should not be second shipping address phone + }) + + test('falls back to shipping address phone when contactPhone is empty for multi-shipment orders', async () => { + // Clear previous mock calls + mockUseShopperCustomersMutation.mockClear() + mockUseShopperCustomersMutation.mockResolvedValue({}) + + // Set up a multi-shipment order with phone numbers in shipping addresses + const multiShipmentOrder = { + customerInfo: {customerId: 'new-customer-id'}, + shipments: [ + { + shipmentId: 'me', + shippingMethod: { + id: '001', + c_storePickupEnabled: false + }, + shippingAddress: { + address1: '123 Main St', + city: 'Tampa', + countryCode: 'US', + firstName: 'Test', + lastName: 'User', + phone: '(727) 555-1234', // This should be used as fallback + postalCode: '33712', + stateCode: 'FL' + } + } + ], + billingAddress: {} + } + + const currentCustomer = {isRegistered: true} + const registeredUserChoseGuest = false + const enableUserRegistration = true + // Contact phone is empty (should fall back to shipping address phone) + const contactPhone = '' + + // Simulate the phone saving logic from index.jsx + const customerId = multiShipmentOrder.customerInfo?.customerId + if (customerId) { + const {isPickupShipment} = await import( + '@salesforce/retail-react-app/app/utils/shipment-utils' + ) + const deliveryShipments = + multiShipmentOrder?.shipments?.filter( + (shipment) => !isPickupShipment(shipment) && shipment.shippingAddress + ) || [] + + if ( + enableUserRegistration && + currentCustomer?.isRegistered && + !registeredUserChoseGuest + ) { + // Test phone saving logic - should fall back to shipping address phone + const phoneHome = + contactPhone && contactPhone.length > 0 + ? contactPhone + : deliveryShipments.length > 0 + ? deliveryShipments[0]?.shippingAddress?.phone + : null + + if (phoneHome) { + mockUseShopperCustomersMutation({ + parameters: {customerId}, + body: {phoneHome} + }) + } + } + } + + // Verify updateCustomer was called with shipping address phone as fallback + expect(mockUseShopperCustomersMutation).toHaveBeenCalledTimes(1) + const call = mockUseShopperCustomersMutation.mock.calls[0] + expect(call[0].body.phoneHome).toBe('(727) 555-1234') // Should be shipping address phone + }) }) 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 491af7997f..12403f2b5b 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 @@ -4,7 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import React, {useState, useEffect} from 'react' +import React from 'react' import {screen, waitFor, fireEvent, act} from '@testing-library/react' import ContactInfo from '@salesforce/retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info' import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils' diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address-selection.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address-selection.jsx index 00ee493ef3..b1cc04c20d 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address-selection.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address-selection.jsx @@ -70,7 +70,7 @@ const ShippingAddressEditForm = ({ form={form} formTitleAriaLabel={formTitleAriaLabel} isBillingAddress={isBillingAddress} - hidePhone={hidePhone} + hidePhone={hidePhone || !isBillingAddress} hidePreferred={true} /> @@ -357,7 +357,7 @@ const ShippingAddressSelection = ({ toggleAddressEdit={toggleAddressEdit} hideSubmitButton={hideSubmitButton} form={form} - hidePhone={!isBillingAddress} + hidePhone={isBillingAddress} submitButtonLabel={submitButtonLabel} formTitleAriaLabel={formTitleAriaLabel} /> @@ -416,7 +416,7 @@ const ShippingAddressSelection = ({ hideSubmitButton={hideSubmitButton} form={form} isBillingAddress={isBillingAddress} - hidePhone={!isBillingAddress} + hidePhone={isBillingAddress} hidePreferred={true} submitButtonLabel={submitButtonLabel} formTitleAriaLabel={formTitleAriaLabel} 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 4560e7d760..15a4b9c194 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 @@ -18,7 +18,6 @@ import { } from '@salesforce/retail-react-app/app/components/shared/ui' import {useForm, Controller} from 'react-hook-form' import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout-one-click/util/checkout-context' -import {ChevronDownIcon} from '@salesforce/retail-react-app/app/components/icons' import { ToggleCard, ToggleCardEdit, diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.test.js index e9fcdcc72e..d210a8d31c 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-user-registration.test.js @@ -404,7 +404,7 @@ describe('UserRegistration', () => { test('shows loading overlay when guest user clicks registration checkbox', async () => { const user = userEvent.setup() const onLoadingChange = jest.fn() - const {authorizePasswordlessLogin} = setup({ + setup({ onLoadingChange, authorizeMutate: jest.fn().mockImplementation(() => { // Simulate async delay diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json index b1b69e4f52..8a492ef050 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json @@ -983,6 +983,12 @@ "value": "Place Order" } ], + "checkout.error.billing_address_required": [ + { + "type": 0, + "value": "Please enter a billing address." + } + ], "checkout.error.cannot_save_address": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json index b1b69e4f52..8a492ef050 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json @@ -983,6 +983,12 @@ "value": "Place Order" } ], + "checkout.error.billing_address_required": [ + { + "type": 0, + "value": "Please enter a billing address." + } + ], "checkout.error.cannot_save_address": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json index 0498b4529a..f0156c5ca5 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json @@ -1927,6 +1927,20 @@ "value": "]" } ], + "checkout.error.billing_address_required": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ƥŀḗḗȧȧşḗḗ ḗḗƞŧḗḗř ȧȧ ƀīŀŀīƞɠ ȧȧḓḓřḗḗşş." + }, + { + "type": 0, + "value": "]" + } + ], "checkout.error.cannot_save_address": [ { "type": 0, diff --git a/packages/template-retail-react-app/translations/en-GB.json b/packages/template-retail-react-app/translations/en-GB.json index fd70659a86..6be89ef2c3 100644 --- a/packages/template-retail-react-app/translations/en-GB.json +++ b/packages/template-retail-react-app/translations/en-GB.json @@ -354,6 +354,9 @@ "checkout.button.place_order": { "defaultMessage": "Place Order" }, + "checkout.error.billing_address_required": { + "defaultMessage": "Please enter a billing address." + }, "checkout.error.cannot_save_address": { "defaultMessage": "Could not save shipping address." }, diff --git a/packages/template-retail-react-app/translations/en-US.json b/packages/template-retail-react-app/translations/en-US.json index fd70659a86..6be89ef2c3 100644 --- a/packages/template-retail-react-app/translations/en-US.json +++ b/packages/template-retail-react-app/translations/en-US.json @@ -354,6 +354,9 @@ "checkout.button.place_order": { "defaultMessage": "Place Order" }, + "checkout.error.billing_address_required": { + "defaultMessage": "Please enter a billing address." + }, "checkout.error.cannot_save_address": { "defaultMessage": "Could not save shipping address." },