From 41d0f3786095b516764df09a398376e071e482bf Mon Sep 17 00:00:00 2001 From: Wesley Rosa Date: Thu, 23 Jan 2025 08:48:59 -0300 Subject: [PATCH] Shipping fields file --- client/express-checkout/utils/index.js | 1 + client/express-checkout/utils/normalize.js | 48 +++++++ .../express-checkout/utils/shipping-fields.js | 130 ++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 client/express-checkout/utils/normalize.js create mode 100644 client/express-checkout/utils/shipping-fields.js diff --git a/client/express-checkout/utils/index.js b/client/express-checkout/utils/index.js index c809449a4..f377f09a9 100644 --- a/client/express-checkout/utils/index.js +++ b/client/express-checkout/utils/index.js @@ -7,6 +7,7 @@ import { PAYMENT_METHOD_CARD, PAYMENT_METHOD_LINK, } from 'wcstripe/stripe-utils/constants'; +export * from './normalize'; /** * Get error messages from WooCommerce notice. diff --git a/client/express-checkout/utils/normalize.js b/client/express-checkout/utils/normalize.js new file mode 100644 index 000000000..c02e66079 --- /dev/null +++ b/client/express-checkout/utils/normalize.js @@ -0,0 +1,48 @@ +/** + * Normalizes incoming cart total items for use as a displayItems with the Stripe api. + * + * @param {Array} displayItems Items to normalize. + * + * @return {Array} An array of PaymentItems + */ +export const normalizeLineItems = ( displayItems ) => { + return displayItems.map( ( displayItem ) => { + let amount = displayItem?.amount ?? displayItem?.value; + if ( displayItem.key === 'total_discount' ) { + amount = -amount; + } + + return { + name: displayItem.label, + amount, + }; + } ); +}; + +/** + * Normalize shipping address information from Stripe's address object to + * the cart shipping address object shape. + * + * @param {Object} shippingAddress Stripe's shipping address item + * + * @return {Object} The shipping address in the shape expected by the cart. + */ +export const normalizeShippingAddress = ( shippingAddress ) => { + return { + first_name: + shippingAddress?.recipient + ?.split( ' ' ) + ?.slice( 0, 1 ) + ?.join( ' ' ) ?? '', + last_name: + shippingAddress?.recipient?.split( ' ' )?.slice( 1 )?.join( ' ' ) ?? + '', + company: '', + address_1: shippingAddress?.addressLine?.[ 0 ] ?? '', + address_2: shippingAddress?.addressLine?.[ 1 ] ?? '', + city: shippingAddress?.city ?? '', + state: shippingAddress?.state ?? '', + country: shippingAddress?.country ?? '', + postcode: shippingAddress?.postal_code ?? '', + }; +}; diff --git a/client/express-checkout/utils/shipping-fields.js b/client/express-checkout/utils/shipping-fields.js new file mode 100644 index 000000000..beedf2b10 --- /dev/null +++ b/client/express-checkout/utils/shipping-fields.js @@ -0,0 +1,130 @@ +/** + * Internal dependencies + */ +import { normalizeShippingAddress, getExpressCheckoutData } from '.'; + +/** + * Checks if the intermediate address is redacted for the given country. + * CA and GB addresses are redacted and are causing errors until WooCommerce is able to + * handle redacted addresses. + * https://developers.google.com/pay/api/web/reference/response-objects#IntermediateAddress + * + * @param {string} country - The country code. + * + * @return {boolean} True if the postcode is redacted for the country, false otherwise. + */ +const isPostcodeRedactedForCountry = ( country ) => { + return [ 'CA', 'GB' ].includes( country ); +}; + +/* + * Updates a field in a form with a new value. + * + * @param {String} formSelector - The selector for the form containing the field. + * @param {Object} fieldName - The name of the field to update. + * @param {Object} value - The new value for the field. + */ +const updateShortcodeField = ( formSelector, fieldName, value ) => { + const field = document.querySelector( + `${ formSelector } [name="${ fieldName }"]` + ); + + if ( ! field ) return; + + // Check if the field is a dropdown (country/state). + if ( field.tagName === 'SELECT' && /country|state/.test( fieldName ) ) { + const options = Array.from( field.options ); + const match = options.find( + ( opt ) => + opt.value === value || + opt.textContent.trim().toLowerCase() === value.toLowerCase() + ); + + if ( match ) { + field.value = match.value; + jQuery( field ).trigger( 'change' ).trigger( 'close' ); + } + } else { + // Default behavior for text inputs. + field.value = value; + jQuery( field ).trigger( 'change' ); + } +}; + +/** + * Updates the WooCommerce Blocks shipping UI to reflect a new shipping address. + * + * @param {Object} eventAddress - The shipping address returned by the payment event. + */ +const updateBlocksShippingUI = ( eventAddress ) => { + wp?.data + ?.dispatch( 'wc/store/cart' ) + ?.setShippingAddress( normalizeShippingAddress( eventAddress ) ); +}; + +/** + * Updates the WooCommerce shortcode cart/checkout shipping UI to reflect a new shipping address. + * + * @param {Object} eventAddress - The shipping address returned by the payment event. + */ +const updateShortcodeShippingUI = ( eventAddress ) => { + const context = getExpressCheckoutData( 'button_context' ); + const address = normalizeShippingAddress( eventAddress ); + + const keys = [ 'country', 'state', 'city', 'postcode' ]; + + if ( context === 'cart' ) { + keys.forEach( ( key ) => { + if ( address[ key ] ) { + updateShortcodeField( + 'form.woocommerce-shipping-calculator', + `calc_shipping_${ key }`, + address[ key ] + ); + } + } ); + document + .querySelector( + 'form.woocommerce-shipping-calculator [name="calc_shipping"]' + ) + ?.click(); + } else if ( context === 'checkout' ) { + keys.forEach( ( key ) => { + if ( address[ key ] ) { + updateShortcodeField( + 'form.woocommerce-checkout', + `billing_${ key }`, + address[ key ] + ); + } + } ); + } +}; + +/** + * Updates the WooCommerce shipping UI to reflect a new shipping address. + * + * Determines the current context (cart or checkout) and updates either + * WooCommerce Blocks or shortcode-based shipping forms, if applicable. + * + * @param {Object} newAddress - The new shipping address object returned by the payment event. + * @param {string} newAddress.country - The country code of the shipping address. + * @param {string} [newAddress.state] - The state/province of the shipping address. + * @param {string} [newAddress.city] - The city of the shipping address. + * @param {string} [newAddress.postcode] - The postal/ZIP code of the shipping address. + */ +export const updateShippingAddressUI = ( newAddress ) => { + const context = getExpressCheckoutData( 'button_context' ); + const isBlocks = getExpressCheckoutData( 'has_block' ); + + if ( + [ 'cart', 'checkout' ].includes( context ) && + ! isPostcodeRedactedForCountry( newAddress.country ) + ) { + if ( isBlocks ) { + updateBlocksShippingUI( newAddress ); + } else { + updateShortcodeShippingUI( newAddress ); + } + } +};