2019-11-15 14:41:23 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2024-07-12 13:24:22 +00:00
|
|
|
import { SITE_CURRENCY } from '@woocommerce/settings';
|
2021-05-25 11:49:13 +00:00
|
|
|
import type {
|
|
|
|
Currency,
|
|
|
|
CurrencyResponse,
|
|
|
|
CartShippingPackageShippingRate,
|
|
|
|
} from '@woocommerce/types';
|
2019-11-15 14:41:23 +00:00
|
|
|
|
2019-12-18 11:29:20 +00:00
|
|
|
/**
|
|
|
|
* Gets currency information in normalized format from an API response or the server.
|
2021-11-19 12:32:37 +00:00
|
|
|
*
|
|
|
|
* If no currency was provided, or currency_code is empty, the default store currency will be used.
|
2019-12-18 11:29:20 +00:00
|
|
|
*/
|
2021-03-05 14:03:48 +00:00
|
|
|
export const getCurrencyFromPriceResponse = (
|
|
|
|
// Currency data object, for example an API response containing currency formatting data.
|
2022-06-14 08:48:49 +00:00
|
|
|
currencyData?:
|
2021-05-25 11:49:13 +00:00
|
|
|
| CurrencyResponse
|
2021-05-10 09:03:30 +00:00
|
|
|
| Record< string, never >
|
|
|
|
| CartShippingPackageShippingRate
|
2021-03-05 14:03:48 +00:00
|
|
|
): Currency => {
|
2021-11-19 12:32:37 +00:00
|
|
|
if ( ! currencyData?.currency_code ) {
|
2024-07-12 13:24:22 +00:00
|
|
|
return SITE_CURRENCY;
|
2019-12-18 11:29:20 +00:00
|
|
|
}
|
2020-01-10 10:22:09 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-12-18 11:29:20 +00:00
|
|
|
return {
|
2024-07-12 13:24:22 +00:00
|
|
|
code: code || SITE_CURRENCY.code,
|
|
|
|
symbol: symbol || SITE_CURRENCY.symbol,
|
2020-01-10 10:22:09 +00:00
|
|
|
thousandSeparator:
|
2024-07-12 13:24:22 +00:00
|
|
|
typeof thousandSeparator === 'string'
|
|
|
|
? thousandSeparator
|
|
|
|
: SITE_CURRENCY.thousandSeparator,
|
2020-01-10 10:22:09 +00:00
|
|
|
decimalSeparator:
|
2024-07-12 13:24:22 +00:00
|
|
|
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,
|
2019-12-18 11:29:20 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets currency information in normalized format, allowing overrides.
|
|
|
|
*/
|
2021-03-05 14:03:48 +00:00
|
|
|
export const getCurrency = (
|
|
|
|
currencyData: Partial< Currency > = {}
|
|
|
|
): Currency => {
|
2019-12-18 11:29:20 +00:00
|
|
|
return {
|
2024-07-12 13:24:22 +00:00
|
|
|
...SITE_CURRENCY,
|
2019-12-18 11:29:20 +00:00
|
|
|
...currencyData,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-11-19 12:32:37 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-11-24 13:21:40 +00:00
|
|
|
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 '';
|
|
|
|
};
|
|
|
|
|
2019-12-18 11:29:20 +00:00
|
|
|
/**
|
|
|
|
* Format a price, provided using the smallest unit of the currency, as a
|
|
|
|
* decimal complete with currency symbols using current store settings.
|
|
|
|
*/
|
2021-03-05 14:03:48 +00:00
|
|
|
export const formatPrice = (
|
|
|
|
// Price in minor unit, e.g. cents.
|
|
|
|
price: number | string,
|
2021-10-04 03:54:28 +00:00
|
|
|
currencyData?: Currency
|
2021-03-05 14:03:48 +00:00
|
|
|
): string => {
|
2019-12-18 11:29:20 +00:00
|
|
|
if ( price === '' || price === undefined ) {
|
2019-11-15 14:41:23 +00:00
|
|
|
return '';
|
|
|
|
}
|
2019-12-18 11:29:20 +00:00
|
|
|
|
2021-03-05 14:03:48 +00:00
|
|
|
const priceInt: number =
|
|
|
|
typeof price === 'number' ? price : parseInt( price, 10 );
|
2019-12-18 11:29:20 +00:00
|
|
|
|
|
|
|
if ( ! Number.isFinite( priceInt ) ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2021-03-05 14:03:48 +00:00
|
|
|
const currency: Currency = getCurrency( currencyData );
|
2021-11-19 12:32:37 +00:00
|
|
|
|
2022-06-15 09:56:52 +00:00
|
|
|
const { minorUnit, prefix, suffix, decimalSeparator, thousandSeparator } =
|
|
|
|
currency;
|
2021-11-19 12:32:37 +00:00
|
|
|
|
|
|
|
const formattedPrice: number = priceInt / 10 ** minorUnit;
|
|
|
|
|
|
|
|
const { beforeDecimal, afterDecimal } = splitDecimal(
|
|
|
|
formattedPrice.toString()
|
|
|
|
);
|
|
|
|
|
|
|
|
const formattedValue = `${ prefix }${ applyThousandSeparator(
|
|
|
|
beforeDecimal,
|
|
|
|
thousandSeparator
|
2021-11-24 13:21:40 +00:00
|
|
|
) }${ applyDecimal(
|
|
|
|
afterDecimal,
|
|
|
|
decimalSeparator,
|
|
|
|
minorUnit
|
|
|
|
) }${ suffix }`;
|
2019-11-15 14:41:23 +00:00
|
|
|
|
|
|
|
// This uses a textarea to magically decode HTML currency symbols.
|
|
|
|
const txt = document.createElement( 'textarea' );
|
|
|
|
txt.innerHTML = formattedValue;
|
|
|
|
return txt.value;
|
|
|
|
};
|