woocommerce/plugins/woocommerce-blocks/packages/prices/utils/price.ts

154 lines
3.6 KiB
TypeScript

/**
* External dependencies
*/
import { SITE_CURRENCY } from '@woocommerce/settings';
import type {
Currency,
CurrencyResponse,
CartShippingPackageShippingRate,
} from '@woocommerce/types';
/**
* Gets currency information in normalized format from an API response or the server.
*
* If no currency was provided, or currency_code is empty, the default store currency will be used.
*/
export const getCurrencyFromPriceResponse = (
// Currency data object, for example an API response containing currency formatting data.
currencyData?:
| CurrencyResponse
| Record< string, never >
| CartShippingPackageShippingRate
): Currency => {
if ( ! currencyData?.currency_code ) {
return SITE_CURRENCY;
}
const {
currency_code: code,
currency_symbol: symbol,
currency_thousand_separator: thousandSeparator,
currency_decimal_separator: decimalSeparator,
currency_minor_unit: minorUnit,
currency_prefix: prefix,
currency_suffix: suffix,
} = currencyData;
return {
code: code || SITE_CURRENCY.code,
symbol: symbol || SITE_CURRENCY.symbol,
thousandSeparator:
typeof thousandSeparator === 'string'
? thousandSeparator
: SITE_CURRENCY.thousandSeparator,
decimalSeparator:
typeof decimalSeparator === 'string'
? decimalSeparator
: SITE_CURRENCY.decimalSeparator,
minorUnit: Number.isFinite( minorUnit )
? minorUnit
: SITE_CURRENCY.minorUnit,
prefix: typeof prefix === 'string' ? prefix : SITE_CURRENCY.prefix,
suffix: typeof suffix === 'string' ? suffix : SITE_CURRENCY.suffix,
};
};
/**
* Gets currency information in normalized format, allowing overrides.
*/
export const getCurrency = (
currencyData: Partial< Currency > = {}
): Currency => {
return {
...SITE_CURRENCY,
...currencyData,
};
};
const applyThousandSeparator = (
numberString: string,
thousandSeparator: string
): string => {
return numberString.replace( /\B(?=(\d{3})+(?!\d))/g, thousandSeparator );
};
const splitDecimal = (
numberString: string
): {
beforeDecimal: string;
afterDecimal: string;
} => {
const parts = numberString.split( '.' );
const beforeDecimal = parts[ 0 ];
const afterDecimal = parts[ 1 ] || '';
return {
beforeDecimal,
afterDecimal,
};
};
const applyDecimal = (
afterDecimal: string,
decimalSeparator: string,
minorUnit: number
): string => {
if ( afterDecimal ) {
return `${ decimalSeparator }${ afterDecimal.padEnd(
minorUnit,
'0'
) }`;
}
if ( minorUnit > 0 ) {
return `${ decimalSeparator }${ '0'.repeat( minorUnit ) }`;
}
return '';
};
/**
* Format a price, provided using the smallest unit of the currency, as a
* decimal complete with currency symbols using current store settings.
*/
export const formatPrice = (
// Price in minor unit, e.g. cents.
price: number | string,
currencyData?: Currency
): string => {
if ( price === '' || price === undefined ) {
return '';
}
const priceInt: number =
typeof price === 'number' ? price : parseInt( price, 10 );
if ( ! Number.isFinite( priceInt ) ) {
return '';
}
const currency: Currency = getCurrency( currencyData );
const { minorUnit, prefix, suffix, decimalSeparator, thousandSeparator } =
currency;
const formattedPrice: number = priceInt / 10 ** minorUnit;
const { beforeDecimal, afterDecimal } = splitDecimal(
formattedPrice.toString()
);
const formattedValue = `${ prefix }${ applyThousandSeparator(
beforeDecimal,
thousandSeparator
) }${ applyDecimal(
afterDecimal,
decimalSeparator,
minorUnit
) }${ suffix }`;
// This uses a textarea to magically decode HTML currency symbols.
const txt = document.createElement( 'textarea' );
txt.innerHTML = formattedValue;
return txt.value;
};