@@ -3,7 +3,27 @@ import { LRUCache } from 'lru-cache'
33import { injectI18n } from '../providers/i18n'
44
55const formatterCache = new LRUCache < string , Intl . NumberFormat > ( { max : 10 } )
6- const maxDigitsCache = new LRUCache < string , number > ( { max : 10 } )
6+ // Billing amounts use Stripe's minor units, which can differ from Intl's display defaults
7+ // (for example, COP displays without decimals in some runtimes but stores centavos).
8+ // ISK and UGX also use two-decimal amounts for Stripe compatibility.
9+ // https://docs.stripe.com/currencies#zero-decimal
10+ const zeroDecimalCurrencies = new Set ( [
11+ 'BIF' ,
12+ 'CLP' ,
13+ 'DJF' ,
14+ 'GNF' ,
15+ 'JPY' ,
16+ 'KMF' ,
17+ 'KRW' ,
18+ 'MGA' ,
19+ 'PYG' ,
20+ 'RWF' ,
21+ 'VND' ,
22+ 'VUV' ,
23+ 'XAF' ,
24+ 'XOF' ,
25+ 'XPF' ,
26+ ] )
727
828// `formatMoney(1234.56, 'USD')` → `$1,234.56`
929export function useFormatMoney ( ) {
@@ -26,13 +46,13 @@ export function useFormatPrice() {
2646 const { locale } = injectI18n ( )
2747
2848 function format ( price : number , currency : string , trimZeros = false ) : string {
29- const maxDigits = getMaxDigits ( currency )
49+ const maxDigits = zeroDecimalCurrencies . has ( currency . toUpperCase ( ) ) ? 0 : 2
3050 const convertedPrice = price / Math . pow ( 10 , maxDigits )
3151
32- const minimumFractionDigits = trimZeros && Number . isInteger ( convertedPrice ) ? 0 : undefined
52+ const minimumFractionDigits = trimZeros && Number . isInteger ( convertedPrice ) ? 0 : maxDigits
3353
3454 try {
35- const formatter = getFormatter ( locale . value , currency , minimumFractionDigits )
55+ const formatter = getFormatter ( locale . value , currency , minimumFractionDigits , maxDigits )
3656 return formatter . format ( convertedPrice )
3757 } catch {
3858 return `${ currency } ${ convertedPrice } `
@@ -46,33 +66,18 @@ function getFormatter(
4666 locale : string ,
4767 currency : string ,
4868 minimumFractionDigits ?: number ,
69+ maximumFractionDigits ?: number ,
4970) : Intl . NumberFormat {
50- const cacheKey = `${ locale } :${ currency } :${ minimumFractionDigits } `
71+ const cacheKey = `${ locale } :${ currency } :${ minimumFractionDigits } : ${ maximumFractionDigits } `
5172 let formatter = formatterCache . get ( cacheKey )
5273 if ( ! formatter ) {
5374 formatter = new Intl . NumberFormat ( locale , {
5475 style : 'currency' ,
5576 currency,
5677 minimumFractionDigits,
78+ maximumFractionDigits,
5779 } )
5880 formatterCache . set ( cacheKey , formatter )
5981 }
6082 return formatter
6183}
62-
63- function getMaxDigits ( currency : string ) : number {
64- let maxDigits = maxDigitsCache . get ( currency )
65- if ( ! maxDigits ) {
66- try {
67- const formatter = new Intl . NumberFormat ( undefined , {
68- style : 'currency' ,
69- currency,
70- } )
71- maxDigits = formatter . resolvedOptions ( ) . maximumFractionDigits ?? 2
72- } catch {
73- maxDigits = 2
74- }
75- maxDigitsCache . set ( currency , maxDigits )
76- }
77- return maxDigits
78- }
0 commit comments