2021-04-23 09:42:36 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2024-01-05 17:35:28 +00:00
|
|
|
import prepareFormFields from '@woocommerce/base-components/cart-checkout/form/prepare-form-fields';
|
2021-05-13 10:20:37 +00:00
|
|
|
import { isEmail } from '@wordpress/url';
|
2024-03-22 14:14:14 +00:00
|
|
|
import {
|
|
|
|
isString,
|
|
|
|
type CartResponseBillingAddress,
|
|
|
|
type CartResponseShippingAddress,
|
|
|
|
isObject,
|
2021-05-13 10:20:37 +00:00
|
|
|
} from '@woocommerce/types';
|
2024-09-09 13:19:57 +00:00
|
|
|
import {
|
|
|
|
ShippingAddress,
|
|
|
|
BillingAddress,
|
|
|
|
CoreAddress,
|
|
|
|
KeyedFormField,
|
|
|
|
} from '@woocommerce/settings';
|
2023-03-22 07:15:13 +00:00
|
|
|
import { decodeEntities } from '@wordpress/html-entities';
|
2023-05-31 09:30:44 +00:00
|
|
|
import {
|
|
|
|
SHIPPING_COUNTRIES,
|
|
|
|
SHIPPING_STATES,
|
2024-01-05 17:35:28 +00:00
|
|
|
ADDRESS_FORM_KEYS,
|
2023-05-31 09:30:44 +00:00
|
|
|
} from '@woocommerce/block-settings';
|
2022-02-22 17:45:01 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Compare two addresses and see if they are the same.
|
|
|
|
*/
|
2022-10-06 14:48:52 +00:00
|
|
|
export const isSameAddress = < T extends ShippingAddress | BillingAddress >(
|
|
|
|
address1: T,
|
|
|
|
address2: T
|
2022-02-22 17:45:01 +00:00
|
|
|
): boolean => {
|
2024-01-05 17:35:28 +00:00
|
|
|
return ADDRESS_FORM_KEYS.every( ( field: string ) => {
|
2023-12-15 12:45:38 +00:00
|
|
|
return address1[ field as keyof T ] === address2[ field as keyof T ];
|
|
|
|
} );
|
2022-02-22 17:45:01 +00:00
|
|
|
};
|
2021-04-23 09:42:36 +00:00
|
|
|
|
2020-03-10 10:55:19 +00:00
|
|
|
/**
|
|
|
|
* pluckAddress takes a full address object and returns relevant fields for calculating
|
|
|
|
* shipping, so we can track when one of them change to update rates.
|
|
|
|
*
|
|
|
|
* @param {Object} address An object containing all address information
|
2020-09-20 23:54:08 +00:00
|
|
|
* @param {string} address.country The country.
|
|
|
|
* @param {string} address.state The state.
|
|
|
|
* @param {string} address.city The city.
|
|
|
|
* @param {string} address.postcode The postal code.
|
2020-03-10 10:55:19 +00:00
|
|
|
*
|
|
|
|
* @return {Object} pluckedAddress An object containing shipping address that are needed to fetch an address.
|
|
|
|
*/
|
2020-11-20 15:13:35 +00:00
|
|
|
export const pluckAddress = ( {
|
|
|
|
country = '',
|
|
|
|
state = '',
|
|
|
|
city = '',
|
|
|
|
postcode = '',
|
2021-05-13 10:20:37 +00:00
|
|
|
}: CartResponseBillingAddress | CartResponseShippingAddress ): {
|
|
|
|
country: string;
|
|
|
|
state: string;
|
|
|
|
city: string;
|
|
|
|
postcode: string;
|
|
|
|
} => ( {
|
2020-12-17 14:52:44 +00:00
|
|
|
country: country.trim(),
|
|
|
|
state: state.trim(),
|
|
|
|
city: city.trim(),
|
2020-11-20 15:13:35 +00:00
|
|
|
postcode: postcode ? postcode.replace( ' ', '' ).toUpperCase() : '',
|
2020-03-10 10:55:19 +00:00
|
|
|
} );
|
2021-04-23 09:42:36 +00:00
|
|
|
|
2021-05-13 10:20:37 +00:00
|
|
|
/**
|
|
|
|
* pluckEmail takes a full address object and returns only the email address, if set and valid. Otherwise returns an empty string.
|
|
|
|
*
|
|
|
|
* @param {Object} address An object containing all address information
|
|
|
|
* @param {string} address.email The email address.
|
|
|
|
* @return {string} The email address.
|
|
|
|
*/
|
|
|
|
export const pluckEmail = ( {
|
|
|
|
email = '',
|
|
|
|
}: CartResponseBillingAddress ): string =>
|
|
|
|
isEmail( email ) ? email.trim() : '';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Type-guard.
|
|
|
|
*/
|
|
|
|
const isValidAddressKey = (
|
|
|
|
key: string,
|
|
|
|
address: CartResponseBillingAddress | CartResponseShippingAddress
|
|
|
|
): key is keyof typeof address => {
|
|
|
|
return key in address;
|
|
|
|
};
|
|
|
|
|
2021-04-23 09:42:36 +00:00
|
|
|
/**
|
|
|
|
* Sets fields to an empty string in an address if they are hidden by the settings in countryLocale.
|
|
|
|
*
|
|
|
|
* @param {Object} address The address to empty fields from.
|
|
|
|
* @return {Object} The address with hidden fields values removed.
|
|
|
|
*/
|
2021-05-13 10:20:37 +00:00
|
|
|
export const emptyHiddenAddressFields = <
|
|
|
|
T extends CartResponseBillingAddress | CartResponseShippingAddress
|
|
|
|
>(
|
|
|
|
address: T
|
|
|
|
): T => {
|
2024-01-05 17:35:28 +00:00
|
|
|
const addressForm = prepareFormFields(
|
|
|
|
ADDRESS_FORM_KEYS,
|
2023-12-15 12:45:38 +00:00
|
|
|
{},
|
|
|
|
address.country
|
|
|
|
);
|
2021-05-13 10:20:37 +00:00
|
|
|
const newAddress = Object.assign( {}, address ) as T;
|
|
|
|
|
2024-01-05 17:35:28 +00:00
|
|
|
addressForm.forEach( ( { key = '', hidden = false } ) => {
|
2021-05-13 10:20:37 +00:00
|
|
|
if ( hidden && isValidAddressKey( key, address ) ) {
|
|
|
|
newAddress[ key ] = '';
|
2021-04-23 09:42:36 +00:00
|
|
|
}
|
|
|
|
} );
|
2021-05-13 10:20:37 +00:00
|
|
|
|
2021-04-23 09:42:36 +00:00
|
|
|
return newAddress;
|
|
|
|
};
|
2023-03-22 07:15:13 +00:00
|
|
|
|
2024-02-16 12:10:51 +00:00
|
|
|
/**
|
|
|
|
* Sets fields to an empty string in an address.
|
|
|
|
*
|
|
|
|
* @param {Object} address The address to empty fields from.
|
|
|
|
* @return {Object} The address with all fields values removed.
|
|
|
|
*/
|
|
|
|
export const emptyAddressFields = <
|
|
|
|
T extends CartResponseBillingAddress | CartResponseShippingAddress
|
|
|
|
>(
|
|
|
|
address: T
|
|
|
|
): T => {
|
|
|
|
const addressForm = prepareFormFields(
|
|
|
|
ADDRESS_FORM_KEYS,
|
|
|
|
{},
|
|
|
|
address.country
|
|
|
|
);
|
|
|
|
const newAddress = Object.assign( {}, address ) as T;
|
|
|
|
|
|
|
|
addressForm.forEach( ( { key = '' } ) => {
|
|
|
|
// Clear address fields except country and state to keep consistency with shortcode Checkout.
|
|
|
|
if (
|
|
|
|
key !== 'country' &&
|
|
|
|
key !== 'state' &&
|
|
|
|
isValidAddressKey( key, address )
|
|
|
|
) {
|
|
|
|
newAddress[ key ] = '';
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
return newAddress;
|
|
|
|
};
|
2023-03-22 07:15:13 +00:00
|
|
|
/*
|
|
|
|
* Formats a shipping address for display.
|
|
|
|
*
|
|
|
|
* @param {Object} address The address to format.
|
|
|
|
* @return {string | null} The formatted address or null if no address is provided.
|
|
|
|
*/
|
|
|
|
export const formatShippingAddress = (
|
|
|
|
address: ShippingAddress | BillingAddress
|
|
|
|
): string | null => {
|
|
|
|
// We bail early if we don't have an address.
|
|
|
|
if ( Object.values( address ).length === 0 ) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-03-22 14:14:14 +00:00
|
|
|
const formattedCountry = isString( SHIPPING_COUNTRIES[ address.country ] )
|
|
|
|
? decodeEntities( SHIPPING_COUNTRIES[ address.country ] )
|
|
|
|
: '';
|
2023-03-22 07:15:13 +00:00
|
|
|
|
|
|
|
const formattedState =
|
2024-03-22 14:14:14 +00:00
|
|
|
isObject( SHIPPING_STATES[ address.country ] ) &&
|
|
|
|
isString( SHIPPING_STATES[ address.country ][ address.state ] )
|
2023-03-22 07:15:13 +00:00
|
|
|
? decodeEntities(
|
2023-05-31 09:30:44 +00:00
|
|
|
SHIPPING_STATES[ address.country ][ address.state ]
|
2023-03-22 07:15:13 +00:00
|
|
|
)
|
|
|
|
: address.state;
|
|
|
|
|
|
|
|
const addressParts = [];
|
|
|
|
|
|
|
|
addressParts.push( address.postcode.toUpperCase() );
|
|
|
|
addressParts.push( address.city );
|
|
|
|
addressParts.push( formattedState );
|
|
|
|
addressParts.push( formattedCountry );
|
|
|
|
|
|
|
|
const formattedLocation = addressParts.filter( Boolean ).join( ', ' );
|
|
|
|
|
|
|
|
if ( ! formattedLocation ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return formattedLocation;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2023-12-06 16:33:15 +00:00
|
|
|
* Checks that all required fields in an address are completed based on the settings in countryLocale.
|
2024-09-09 13:19:57 +00:00
|
|
|
*
|
|
|
|
* @param {Object} address The address to check.
|
|
|
|
* @param {Array} keysToCheck Optional override to include only specific keys for checking.
|
|
|
|
* If there are other required fields in the address, but not specified in this arg then
|
|
|
|
* they will be ignored.
|
2023-03-22 07:15:13 +00:00
|
|
|
*/
|
|
|
|
export const isAddressComplete = (
|
2024-09-09 13:19:57 +00:00
|
|
|
address: ShippingAddress | BillingAddress,
|
|
|
|
keysToCheck: ( keyof CoreAddress )[] = []
|
2023-03-22 07:15:13 +00:00
|
|
|
): boolean => {
|
2023-12-06 16:33:15 +00:00
|
|
|
if ( ! address.country ) {
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-05 17:35:28 +00:00
|
|
|
const addressForm = prepareFormFields(
|
|
|
|
ADDRESS_FORM_KEYS,
|
2023-12-15 12:45:38 +00:00
|
|
|
{},
|
|
|
|
address.country
|
|
|
|
);
|
2023-12-06 16:33:15 +00:00
|
|
|
|
2024-09-09 13:19:57 +00:00
|
|
|
// Filter the address form so only fields from the keysToCheck arg remain, if that arg is empty, then default to the
|
|
|
|
// full address form.
|
|
|
|
const filteredAddressForm =
|
|
|
|
keysToCheck.length > 0
|
|
|
|
? ( Object.values( addressForm ) as KeyedFormField[] ).filter(
|
|
|
|
( { key } ) =>
|
|
|
|
keysToCheck.includes( key as keyof CoreAddress )
|
|
|
|
)
|
|
|
|
: addressForm;
|
|
|
|
|
|
|
|
return filteredAddressForm.every(
|
2023-12-06 16:33:15 +00:00
|
|
|
( { key = '', hidden = false, required = false } ) => {
|
|
|
|
if ( hidden || ! required ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return isValidAddressKey( key, address ) && address[ key ] !== '';
|
|
|
|
}
|
|
|
|
);
|
2023-03-22 07:15:13 +00:00
|
|
|
};
|