dinero.js 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/dinero.js ADDED
@@ -0,0 +1,970 @@
1
+ import { Defaults, Globals } from './services/settings'
2
+ import Static from './services/static'
3
+ import Format from './services/format'
4
+ import Calculator from './services/calculator'
5
+ import CurrencyConverter from './services/currency-converter'
6
+ import {
7
+ assert,
8
+ assertPercentage,
9
+ assertValidRatios,
10
+ assertInteger
11
+ } from './services/assert'
12
+ import { isUndefined } from './services/helpers'
13
+
14
+ const calculator = Calculator()
15
+
16
+ /**
17
+ * A Dinero object is an immutable data structure representing a specific monetary value.
18
+ * It comes with methods for creating, parsing, manipulating, testing, transforming and formatting them.
19
+ *
20
+ * A Dinero object has:
21
+ *
22
+ * * An `amount`, expressed in minor currency units, as an integer.
23
+ * * A `currency`, expressed as an {@link https://en.wikipedia.org/wiki/ISO_4217#Active_codes ISO 4217 currency code}.
24
+ * * A `precision`, expressed as an integer, to represent the number of decimal places in the `amount`.
25
+ * This is helpful when you want to represent fractional minor currency units (e.g.: $10.4545).
26
+ * You can also use it to represent a currency with a different [exponent](https://en.wikipedia.org/wiki/ISO_4217#Treatment_of_minor_currency_units_.28the_.22exponent.22.29) than `2` (e.g.: Iraqi dinar with 1000 fils in 1 dinar (exponent of `3`), Japanese yen with no sub-units (exponent of `0`)).
27
+ * * An optional `locale` property that affects how output strings are formatted.
28
+ *
29
+ * Here's an overview of the public API:
30
+ *
31
+ * * **Access:** {@link module:Dinero~getAmount getAmount}, {@link module:Dinero~getCurrency getCurrency}, {@link module:Dinero~getLocale getLocale} and {@link module:Dinero~getPrecision getPrecision}.
32
+ * * **Manipulation:** {@link module:Dinero~add add}, {@link module:Dinero~subtract subtract}, {@link module:Dinero~multiply multiply}, {@link module:Dinero~divide divide}, {@link module:Dinero~percentage percentage}, {@link module:Dinero~allocate allocate} and {@link module:Dinero~convert convert}.
33
+ * * **Testing:** {@link module:Dinero~equalsTo equalsTo}, {@link module:Dinero~lessThan lessThan}, {@link module:Dinero~lessThanOrEqual lessThanOrEqual}, {@link module:Dinero~greaterThan greaterThan}, {@link module:Dinero~greaterThanOrEqual greaterThanOrEqual}, {@link module:Dinero~isZero isZero}, {@link module:Dinero~isPositive isPositive}, {@link module:Dinero~isNegative isNegative}, {@link module:Dinero~hasSubUnits hasSubUnits}, {@link module:Dinero~hasSameCurrency hasSameCurrency} and {@link module:Dinero~hasSameAmount hasSameAmount}.
34
+ * * **Configuration:** {@link module:Dinero~setLocale setLocale}.
35
+ * * **Conversion & formatting:** {@link module:Dinero~toFormat toFormat}, {@link module:Dinero~toUnit toUnit}, {@link module:Dinero~toRoundedUnit toRoundedUnit}, {@link module:Dinero~toObject toObject}, {@link module:Dinero~toJSON toJSON}, {@link module:Dinero~convertPrecision convertPrecision} and {@link module:Dinero.normalizePrecision normalizePrecision}.
36
+ *
37
+ * Dinero.js uses `number`s under the hood, so it's constrained by the [double-precision floating-point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). Using values over [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MAX_SAFE_INTEGER) or below [`Number.MIN_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MIN_SAFE_INTEGER) will yield unpredictable results.
38
+ * Same goes with performing calculations: once the internal `amount` value exceeds those limits, precision is no longer guaranteed.
39
+ *
40
+ * @module Dinero
41
+ * @param {Number} [options.amount=0] - The amount in minor currency units (as an integer).
42
+ * @param {String} [options.currency='USD'] - An ISO 4217 currency code.
43
+ * @param {String} [options.precision=2] - The number of decimal places to represent.
44
+ *
45
+ * @throws {TypeError} If `amount` or `precision` is invalid. Integers over [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MAX_SAFE_INTEGER) or below [`Number.MIN_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MIN_SAFE_INTEGER) are considered valid, even though they can lead to imprecise amounts.
46
+ *
47
+ * @return {Object}
48
+ */
49
+ const Dinero = options => {
50
+ const { amount, currency, precision } = Object.assign(
51
+ {},
52
+ {
53
+ amount: Dinero.defaultAmount,
54
+ currency: Dinero.defaultCurrency,
55
+ precision: Dinero.defaultPrecision
56
+ },
57
+ options
58
+ )
59
+
60
+ assertInteger(amount)
61
+ assertInteger(precision)
62
+
63
+ const {
64
+ globalLocale,
65
+ globalFormat,
66
+ globalRoundingMode,
67
+ globalFormatRoundingMode
68
+ } = Dinero
69
+
70
+ const globalExchangeRatesApi = Object.assign(
71
+ {},
72
+ Dinero.globalExchangeRatesApi
73
+ )
74
+
75
+ /**
76
+ * Uses ES5 function notation so `this` can be passed through call, apply and bind
77
+ * @ignore
78
+ */
79
+ const create = function(options) {
80
+ const obj = Object.assign(
81
+ {},
82
+ Object.assign({}, { amount, currency, precision }, options),
83
+ Object.assign({}, { locale: this.locale }, options)
84
+ )
85
+ return Object.assign(
86
+ Dinero({
87
+ amount: obj.amount,
88
+ currency: obj.currency,
89
+ precision: obj.precision
90
+ }),
91
+ {
92
+ locale: obj.locale
93
+ }
94
+ )
95
+ }
96
+
97
+ /**
98
+ * Uses ES5 function notation so `this` can be passed through call, apply and bind
99
+ * @ignore
100
+ */
101
+ const assertSameCurrency = function(comparator) {
102
+ assert(
103
+ this.hasSameCurrency(comparator),
104
+ 'You must provide a Dinero instance with the same currency.',
105
+ TypeError
106
+ )
107
+ }
108
+
109
+ return {
110
+ /**
111
+ * Returns the amount.
112
+ *
113
+ * @example
114
+ * // returns 500
115
+ * Dinero({ amount: 500 }).getAmount()
116
+ *
117
+ * @return {Number}
118
+ */
119
+ getAmount() {
120
+ return amount
121
+ },
122
+ /**
123
+ * Returns the currency.
124
+ *
125
+ * @example
126
+ * // returns 'EUR'
127
+ * Dinero({ currency: 'EUR' }).getCurrency()
128
+ *
129
+ * @return {String}
130
+ */
131
+ getCurrency() {
132
+ return currency
133
+ },
134
+ /**
135
+ * Returns the locale.
136
+ *
137
+ * @example
138
+ * // returns 'fr-FR'
139
+ * Dinero().setLocale('fr-FR').getLocale()
140
+ *
141
+ * @return {String}
142
+ */
143
+ getLocale() {
144
+ return this.locale || globalLocale
145
+ },
146
+ /**
147
+ * Returns a new Dinero object with an embedded locale.
148
+ *
149
+ * @param {String} newLocale - The new locale as an {@link http://tools.ietf.org/html/rfc5646 BCP 47 language tag}.
150
+ *
151
+ * @example
152
+ * // Returns a Dinero object with locale 'ja-JP'
153
+ * Dinero().setLocale('ja-JP')
154
+ *
155
+ * @return {Dinero}
156
+ */
157
+ setLocale(newLocale) {
158
+ return create.call(this, { locale: newLocale })
159
+ },
160
+ /**
161
+ * Returns the precision.
162
+ *
163
+ * @example
164
+ * // returns 3
165
+ * Dinero({ precision: 3 }).getPrecision()
166
+ *
167
+ * @return {Number}
168
+ */
169
+ getPrecision() {
170
+ return precision
171
+ },
172
+ /**
173
+ * Returns a new Dinero object with a new precision and a converted amount.
174
+ *
175
+ * By default, fractional minor currency units are rounded using the **half to even** rule ([banker's rounding](http://wiki.c2.com/?BankersRounding)).
176
+ * This can be necessary when you need to convert objects to a smaller precision.
177
+ *
178
+ * Rounding *can* lead to accuracy issues as you chain many times. Consider a minimal amount of subsequent conversions for safer results.
179
+ * You can also specify a different `roundingMode` to better fit your needs.
180
+ *
181
+ * @param {Number} newPrecision - The new precision.
182
+ * @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
183
+ *
184
+ * @example
185
+ * // Returns a Dinero object with precision 3 and amount 1000
186
+ * Dinero({ amount: 100, precision: 2 }).convertPrecision(3)
187
+ *
188
+ * @throws {TypeError} If `newPrecision` is invalid.
189
+ *
190
+ * @return {Dinero}
191
+ */
192
+ convertPrecision(newPrecision, roundingMode = globalFormatRoundingMode) {
193
+ assertInteger(newPrecision)
194
+ const precision = this.getPrecision()
195
+ const isNewPrecisionLarger = newPrecision > precision
196
+ const operation = isNewPrecisionLarger
197
+ ? calculator.multiply
198
+ : calculator.divide
199
+ const terms = isNewPrecisionLarger
200
+ ? [newPrecision, precision]
201
+ : [precision, newPrecision]
202
+ const factor = Math.pow(10, calculator.subtract(...terms))
203
+ return create.call(this, {
204
+ amount: calculator.round(
205
+ operation(this.getAmount(), factor),
206
+ roundingMode
207
+ ),
208
+ precision: newPrecision
209
+ })
210
+ },
211
+ /**
212
+ * Returns a new Dinero object that represents the sum of this and an other Dinero object.
213
+ *
214
+ * If Dinero objects have a different `precision`, they will be first converted to the highest.
215
+ *
216
+ * @param {Dinero} addend - The Dinero object to add.
217
+ *
218
+ * @example
219
+ * // returns a Dinero object with amount 600
220
+ * Dinero({ amount: 400 }).add(Dinero({ amount: 200 }))
221
+ * @example
222
+ * // returns a Dinero object with amount 144545 and precision 4
223
+ * Dinero({ amount: 400 }).add(Dinero({ amount: 104545, precision: 4 }))
224
+ *
225
+ * @throws {TypeError} If `addend` has a different currency.
226
+ *
227
+ * @return {Dinero}
228
+ */
229
+ add(addend) {
230
+ assertSameCurrency.call(this, addend)
231
+ const addends = Dinero.normalizePrecision([this, addend])
232
+ return create.call(this, {
233
+ amount: calculator.add(addends[0].getAmount(), addends[1].getAmount()),
234
+ precision: addends[0].getPrecision()
235
+ })
236
+ },
237
+ /**
238
+ * Returns a new Dinero object that represents the difference of this and an other Dinero object.
239
+ *
240
+ * If Dinero objects have a different `precision`, they will be first converted to the highest.
241
+ *
242
+ * @param {Dinero} subtrahend - The Dinero object to subtract.
243
+ *
244
+ * @example
245
+ * // returns a Dinero object with amount 200
246
+ * Dinero({ amount: 400 }).subtract(Dinero({ amount: 200 }))
247
+ * @example
248
+ * // returns a Dinero object with amount 64545 and precision 4
249
+ * Dinero({ amount: 104545, precision: 4 }).subtract(Dinero({ amount: 400 }))
250
+ *
251
+ * @throws {TypeError} If `subtrahend` has a different currency.
252
+ *
253
+ * @return {Dinero}
254
+ */
255
+ subtract(subtrahend) {
256
+ assertSameCurrency.call(this, subtrahend)
257
+ const subtrahends = Dinero.normalizePrecision([this, subtrahend])
258
+ return create.call(this, {
259
+ amount: calculator.subtract(
260
+ subtrahends[0].getAmount(),
261
+ subtrahends[1].getAmount()
262
+ ),
263
+ precision: subtrahends[0].getPrecision()
264
+ })
265
+ },
266
+ /**
267
+ * Returns a new Dinero object that represents the multiplied value by the given factor.
268
+ *
269
+ * By default, fractional minor currency units are rounded using the **half to even** rule ([banker's rounding](http://wiki.c2.com/?BankersRounding)).
270
+ *
271
+ * Rounding *can* lead to accuracy issues as you chain many times. Consider a minimal amount of subsequent calculations for safer results.
272
+ * You can also specify a different `roundingMode` to better fit your needs.
273
+ *
274
+ * @param {Number} multiplier - The factor to multiply by.
275
+ * @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
276
+ *
277
+ * @example
278
+ * // returns a Dinero object with amount 1600
279
+ * Dinero({ amount: 400 }).multiply(4)
280
+ * @example
281
+ * // returns a Dinero object with amount 800
282
+ * Dinero({ amount: 400 }).multiply(2.001)
283
+ * @example
284
+ * // returns a Dinero object with amount 801
285
+ * Dinero({ amount: 400 }).multiply(2.00125, 'HALF_UP')
286
+ *
287
+ * @return {Dinero}
288
+ */
289
+ multiply(multiplier, roundingMode = globalRoundingMode) {
290
+ return create.call(this, {
291
+ amount: calculator.round(
292
+ calculator.multiply(this.getAmount(), multiplier),
293
+ roundingMode
294
+ )
295
+ })
296
+ },
297
+ /**
298
+ * Returns a new Dinero object that represents the divided value by the given factor.
299
+ *
300
+ * By default, fractional minor currency units are rounded using the **half to even** rule ([banker's rounding](http://wiki.c2.com/?BankersRounding)).
301
+ *
302
+ * Rounding *can* lead to accuracy issues as you chain many times. Consider a minimal amount of subsequent calculations for safer results.
303
+ * You can also specify a different `roundingMode` to better fit your needs.
304
+ *
305
+ * As rounding is applied, precision may be lost in the process. If you want to accurately split a Dinero object, use {@link module:Dinero~allocate allocate} instead.
306
+ *
307
+ * @param {Number} divisor - The factor to divide by.
308
+ * @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
309
+ *
310
+ * @example
311
+ * // returns a Dinero object with amount 100
312
+ * Dinero({ amount: 400 }).divide(4)
313
+ * @example
314
+ * // returns a Dinero object with amount 52
315
+ * Dinero({ amount: 105 }).divide(2)
316
+ * @example
317
+ * // returns a Dinero object with amount 53
318
+ * Dinero({ amount: 105 }).divide(2, 'HALF_UP')
319
+ *
320
+ * @return {Dinero}
321
+ */
322
+ divide(divisor, roundingMode = globalRoundingMode) {
323
+ return create.call(this, {
324
+ amount: calculator.round(
325
+ calculator.divide(this.getAmount(), divisor),
326
+ roundingMode
327
+ )
328
+ })
329
+ },
330
+ /**
331
+ * Returns a new Dinero object that represents a percentage of this.
332
+ *
333
+ * As rounding is applied, precision may be lost in the process. If you want to accurately split a Dinero object, use {@link module:Dinero~allocate allocate} instead.
334
+ *
335
+ * @param {Number} percentage - The percentage to extract (between 0 and 100).
336
+ * @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
337
+ *
338
+ * @example
339
+ * // returns a Dinero object with amount 5000
340
+ * Dinero({ amount: 10000 }).percentage(50)
341
+ * @example
342
+ * // returns a Dinero object with amount 29
343
+ * Dinero({ amount: 57 }).percentage(50, "HALF_ODD")
344
+ *
345
+ * @throws {RangeError} If `percentage` is out of range.
346
+ *
347
+ * @return {Dinero}
348
+ */
349
+ percentage(percentage, roundingMode = globalRoundingMode) {
350
+ assertPercentage(percentage)
351
+ return this.multiply(calculator.divide(percentage, 100), roundingMode)
352
+ },
353
+ /**
354
+ * Allocates the amount of a Dinero object according to a list of ratios.
355
+ *
356
+ * Sometimes you need to split monetary values but percentages can't cut it without adding or losing pennies.
357
+ * A good example is invoicing: let's say you need to bill $1,000.03 and you want a 50% downpayment.
358
+ * If you use {@link module:Dinero~percentage percentage}, you'll get an accurate Dinero object but the amount won't be billable: you can't split a penny.
359
+ * If you round it, you'll bill a penny extra.
360
+ * With {@link module:Dinero~allocate allocate}, you can split a monetary amount then distribute the remainder as evenly as possible.
361
+ *
362
+ * You can use percentage style or ratio style for `ratios`: `[25, 75]` and `[1, 3]` will do the same thing.
363
+ *
364
+ * Since v1.8.0, you can use zero ratios (such as [0, 50, 50]). If there's a remainder to distribute, zero ratios are skipped and return a Dinero object with amount zero.
365
+ *
366
+ * @param {Number[]} ratios - The ratios to allocate the money to.
367
+ *
368
+ * @example
369
+ * // returns an array of two Dinero objects
370
+ * // the first one with an amount of 502
371
+ * // the second one with an amount of 501
372
+ * Dinero({ amount: 1003 }).allocate([50, 50])
373
+ * @example
374
+ * // returns an array of two Dinero objects
375
+ * // the first one with an amount of 25
376
+ * // the second one with an amount of 75
377
+ * Dinero({ amount: 100 }).allocate([1, 3])
378
+ * @example
379
+ * // since version 1.8.0
380
+ * // returns an array of three Dinero objects
381
+ * // the first one with an amount of 0
382
+ * // the second one with an amount of 502
383
+ * // the third one with an amount of 501
384
+ * Dinero({ amount: 1003 }).allocate([0, 50, 50])
385
+ *
386
+ * @throws {TypeError} If ratios are invalid.
387
+ *
388
+ * @return {Dinero[]}
389
+ */
390
+ allocate(ratios) {
391
+ assertValidRatios(ratios)
392
+
393
+ const total = ratios.reduce((a, b) => calculator.add(a, b))
394
+ let remainder = this.getAmount()
395
+
396
+ const shares = ratios.map(ratio => {
397
+ const share = Math.floor(
398
+ calculator.divide(calculator.multiply(this.getAmount(), ratio), total)
399
+ )
400
+ remainder = calculator.subtract(remainder, share)
401
+ return create.call(this, { amount: share })
402
+ })
403
+
404
+ let i = 0
405
+ while (remainder > 0) {
406
+ if (ratios[i] > 0) {
407
+ shares[i] = shares[i].add(create.call(this, { amount: 1 }))
408
+ remainder = calculator.subtract(remainder, 1)
409
+ }
410
+ i += 1
411
+ }
412
+
413
+ return shares
414
+ },
415
+ /**
416
+ * Returns a Promise containing a new Dinero object converted to another currency.
417
+ *
418
+ * You have two options to provide the exchange rates:
419
+ *
420
+ * 1. **Use an exchange rate REST API, and let Dinero handle the fetching and conversion.**
421
+ * This is a simple option if you have access to an exchange rate REST API and want Dinero to do the rest.
422
+ * 2. **Fetch the exchange rates on your own and provide them directly.**
423
+ * This is useful if you're fetching your rates from somewhere else (a file, a database), use a different protocol or query language than REST (SOAP, GraphQL) or want to fetch rates once and cache them instead of making new requests every time.
424
+ *
425
+ * **If you want to use a REST API**, you must provide a third-party endpoint yourself. Dinero doesn't come bundled with an exchange rates endpoint.
426
+ *
427
+ * Here are some exchange rate APIs you can use:
428
+ *
429
+ * * [Fixer](https://fixer.io)
430
+ * * [Open Exchange Rates](https://openexchangerates.org)
431
+ * * [Coinbase](https://api.coinbase.com/v2/exchange-rates)
432
+ * * More [foreign](https://github.com/toddmotto/public-apis#currency-exchange) and [crypto](https://github.com/toddmotto/public-apis#cryptocurrency) exchange rate APIs.
433
+ *
434
+ * **If you want to fetch your own rates and provide them directly**, you need to pass a promise that resolves to the exchanges rates.
435
+ *
436
+ * In both cases, you need to specify at least:
437
+ *
438
+ * * a **destination currency**: the currency in which you want to convert your Dinero object. You can specify it with `currency`.
439
+ * * an **endpoint**: the API URL to query exchange rates, with parameters, or a promise that resolves to the exchange rates. You can specify it with `options.endpoint`.
440
+ * * a **property path**: the path to access the wanted rate in your API's JSON response (or the custom promise's payload). For example, with a response of:
441
+ * ```json
442
+ * {
443
+ * "data": {
444
+ * "base": "USD",
445
+ * "destination": "EUR",
446
+ * "rate": "0.827728919"
447
+ * }
448
+ * }
449
+ * ```
450
+ * Then the property path is `'data.rate'`. You can specify it with `options.propertyPath`.
451
+ *
452
+ * The base currency (the one of your Dinero object) and the destination currency can be used as "merge tags" with the mustache syntax, respectively `{{from}}` and `{{to}}`.
453
+ * You can use these tags to refer to these values in `options.endpoint` and `options.propertyPath`.
454
+ *
455
+ * For example, if you need to specify the base currency as a query parameter, you can do the following:
456
+ *
457
+ * ```js
458
+ * {
459
+ * endpoint: 'https://yourexchangerates.api/latest?base={{from}}'
460
+ * }
461
+ * ```
462
+ *
463
+ * @param {String} currency - The destination currency, expressed as an {@link https://en.wikipedia.org/wiki/ISO_4217#Active_codes ISO 4217 currency code}.
464
+ * @param {(String|Promise)} options.endpoint - The API endpoint to retrieve exchange rates. You can substitute this with a promise that resolves to the exchanges rates if you already have them.
465
+ * @param {String} [options.propertyPath='rates.{{to}}'] - The property path to the rate.
466
+ * @param {Object} [options.headers] - The HTTP headers to provide, if needed.
467
+ * @param {String} [options.roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
468
+ *
469
+ * @example
470
+ * // your global API parameters
471
+ * Dinero.globalExchangeRatesApi = { ... }
472
+ *
473
+ * // returns a Promise containing a Dinero object with the destination currency
474
+ * // and the initial amount converted to the new currency.
475
+ * Dinero({ amount: 500 }).convert('EUR')
476
+ * @example
477
+ * // returns a Promise containing a Dinero object,
478
+ * // with specific API parameters and rounding mode for this specific instance.
479
+ * Dinero({ amount: 500 })
480
+ * .convert('XBT', {
481
+ * endpoint: 'https://yourexchangerates.api/latest?base={{from}}',
482
+ * propertyPath: 'data.rates.{{to}}',
483
+ * headers: {
484
+ * 'user-key': 'xxxxxxxxx'
485
+ * },
486
+ * roundingMode: 'HALF_UP'
487
+ * })
488
+ * @example
489
+ * // usage with exchange rates provided as a custom promise
490
+ * // using the default `propertyPath` format (so it doesn't have to be specified)
491
+ * const rates = {
492
+ * rates: {
493
+ * EUR: 0.81162
494
+ * }
495
+ * }
496
+ *
497
+ * Dinero({ amount: 500 })
498
+ * .convert('EUR', {
499
+ * endpoint: new Promise(resolve => resolve(rates))
500
+ * })
501
+ * @example
502
+ * // usage with Promise.prototype.then and Promise.prototype.catch
503
+ * Dinero({ amount: 500 })
504
+ * .convert('EUR')
505
+ * .then(dinero => {
506
+ * dinero.getCurrency() // returns 'EUR'
507
+ * })
508
+ * .catch(err => {
509
+ * // handle errors
510
+ * })
511
+ * @example
512
+ * // usage with async/await
513
+ * (async () => {
514
+ * const price = await Dinero({ amount: 500 }).convert('EUR')
515
+ * price.getCurrency() // returns 'EUR'
516
+ * })()
517
+ *
518
+ * @return {Promise}
519
+ */
520
+ convert(
521
+ currency,
522
+ {
523
+ endpoint = globalExchangeRatesApi.endpoint,
524
+ propertyPath = globalExchangeRatesApi.propertyPath || 'rates.{{to}}',
525
+ headers = globalExchangeRatesApi.headers,
526
+ roundingMode = globalRoundingMode
527
+ } = {}
528
+ ) {
529
+ const options = Object.assign(
530
+ {},
531
+ {
532
+ endpoint,
533
+ propertyPath,
534
+ headers,
535
+ roundingMode
536
+ }
537
+ )
538
+ return CurrencyConverter(options)
539
+ .getExchangeRate(this.getCurrency(), currency)
540
+ .then(rate => {
541
+ assert(
542
+ !isUndefined(rate),
543
+ `No rate was found for the destination currency "${currency}".`,
544
+ TypeError
545
+ )
546
+ return create.call(this, {
547
+ amount: calculator.round(
548
+ calculator.multiply(this.getAmount(), parseFloat(rate)),
549
+ options.roundingMode
550
+ ),
551
+ currency
552
+ })
553
+ })
554
+ },
555
+ /**
556
+ * Checks whether the value represented by this object equals to the other.
557
+ *
558
+ * @param {Dinero} comparator - The Dinero object to compare to.
559
+ *
560
+ * @example
561
+ * // returns true
562
+ * Dinero({ amount: 500, currency: 'EUR' }).equalsTo(Dinero({ amount: 500, currency: 'EUR' }))
563
+ * @example
564
+ * // returns false
565
+ * Dinero({ amount: 500, currency: 'EUR' }).equalsTo(Dinero({ amount: 800, currency: 'EUR' }))
566
+ * @example
567
+ * // returns false
568
+ * Dinero({ amount: 500, currency: 'USD' }).equalsTo(Dinero({ amount: 500, currency: 'EUR' }))
569
+ * @example
570
+ * // returns false
571
+ * Dinero({ amount: 500, currency: 'USD' }).equalsTo(Dinero({ amount: 800, currency: 'EUR' }))
572
+ * @example
573
+ * // returns true
574
+ * Dinero({ amount: 1000, currency: 'EUR', precision: 2 }).equalsTo(Dinero({ amount: 10000, currency: 'EUR', precision: 3 }))
575
+ * @example
576
+ * // returns false
577
+ * Dinero({ amount: 10000, currency: 'EUR', precision: 2 }).equalsTo(Dinero({ amount: 10000, currency: 'EUR', precision: 3 }))
578
+ *
579
+ * @return {Boolean}
580
+ */
581
+ equalsTo(comparator) {
582
+ return this.hasSameAmount(comparator) && this.hasSameCurrency(comparator)
583
+ },
584
+ /**
585
+ * Checks whether the value represented by this object is less than the other.
586
+ *
587
+ * @param {Dinero} comparator - The Dinero object to compare to.
588
+ *
589
+ * @example
590
+ * // returns true
591
+ * Dinero({ amount: 500 }).lessThan(Dinero({ amount: 800 }))
592
+ * @example
593
+ * // returns false
594
+ * Dinero({ amount: 800 }).lessThan(Dinero({ amount: 500 }))
595
+ * @example
596
+ * // returns true
597
+ * Dinero({ amount: 5000, precision: 3 }).lessThan(Dinero({ amount: 800 }))
598
+ * @example
599
+ * // returns false
600
+ * Dinero({ amount: 800 }).lessThan(Dinero({ amount: 5000, precision: 3 }))
601
+ *
602
+ * @throws {TypeError} If `comparator` has a different currency.
603
+ *
604
+ * @return {Boolean}
605
+ */
606
+ lessThan(comparator) {
607
+ assertSameCurrency.call(this, comparator)
608
+ const comparators = Dinero.normalizePrecision([this, comparator])
609
+ return comparators[0].getAmount() < comparators[1].getAmount()
610
+ },
611
+ /**
612
+ * Checks whether the value represented by this object is less than or equal to the other.
613
+ *
614
+ * @param {Dinero} comparator - The Dinero object to compare to.
615
+ *
616
+ * @example
617
+ * // returns true
618
+ * Dinero({ amount: 500 }).lessThanOrEqual(Dinero({ amount: 800 }))
619
+ * @example
620
+ * // returns true
621
+ * Dinero({ amount: 500 }).lessThanOrEqual(Dinero({ amount: 500 }))
622
+ * @example
623
+ * // returns false
624
+ * Dinero({ amount: 500 }).lessThanOrEqual(Dinero({ amount: 300 }))
625
+ * @example
626
+ * // returns true
627
+ * Dinero({ amount: 5000, precision: 3 }).lessThanOrEqual(Dinero({ amount: 800 }))
628
+ * @example
629
+ * // returns true
630
+ * Dinero({ amount: 5000, precision: 3 }).lessThanOrEqual(Dinero({ amount: 500 }))
631
+ * @example
632
+ * // returns false
633
+ * Dinero({ amount: 800 }).lessThanOrEqual(Dinero({ amount: 5000, precision: 3 }))
634
+ *
635
+ * @throws {TypeError} If `comparator` has a different currency.
636
+ *
637
+ * @return {Boolean}
638
+ */
639
+ lessThanOrEqual(comparator) {
640
+ assertSameCurrency.call(this, comparator)
641
+ const comparators = Dinero.normalizePrecision([this, comparator])
642
+ return comparators[0].getAmount() <= comparators[1].getAmount()
643
+ },
644
+ /**
645
+ * Checks whether the value represented by this object is greater than the other.
646
+ *
647
+ * @param {Dinero} comparator - The Dinero object to compare to.
648
+ *
649
+ * @example
650
+ * // returns false
651
+ * Dinero({ amount: 500 }).greaterThan(Dinero({ amount: 800 }))
652
+ * @example
653
+ * // returns true
654
+ * Dinero({ amount: 800 }).greaterThan(Dinero({ amount: 500 }))
655
+ * @example
656
+ * // returns true
657
+ * Dinero({ amount: 800 }).greaterThan(Dinero({ amount: 5000, precision: 3 }))
658
+ * @example
659
+ * // returns false
660
+ * Dinero({ amount: 5000, precision: 3 }).greaterThan(Dinero({ amount: 800 }))
661
+ *
662
+ * @throws {TypeError} If `comparator` has a different currency.
663
+ *
664
+ * @return {Boolean}
665
+ */
666
+ greaterThan(comparator) {
667
+ assertSameCurrency.call(this, comparator)
668
+ const comparators = Dinero.normalizePrecision([this, comparator])
669
+ return comparators[0].getAmount() > comparators[1].getAmount()
670
+ },
671
+ /**
672
+ * Checks whether the value represented by this object is greater than or equal to the other.
673
+ *
674
+ * @param {Dinero} comparator - The Dinero object to compare to.
675
+ *
676
+ * @example
677
+ * // returns true
678
+ * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 300 }))
679
+ * @example
680
+ * // returns true
681
+ * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 500 }))
682
+ * @example
683
+ * // returns false
684
+ * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 800 }))
685
+ * @example
686
+ * // returns true
687
+ * Dinero({ amount: 800 }).greaterThanOrEqual(Dinero({ amount: 5000, precision: 3 }))
688
+ * @example
689
+ * // returns true
690
+ * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 5000, precision: 3 }))
691
+ * @example
692
+ * // returns false
693
+ * Dinero({ amount: 5000, precision: 3 }).greaterThanOrEqual(Dinero({ amount: 800 }))
694
+ *
695
+ * @throws {TypeError} If `comparator` has a different currency.
696
+ *
697
+ * @return {Boolean}
698
+ */
699
+ greaterThanOrEqual(comparator) {
700
+ assertSameCurrency.call(this, comparator)
701
+ const comparators = Dinero.normalizePrecision([this, comparator])
702
+ return comparators[0].getAmount() >= comparators[1].getAmount()
703
+ },
704
+ /**
705
+ * Checks if the value represented by this object is zero.
706
+ *
707
+ * @example
708
+ * // returns true
709
+ * Dinero({ amount: 0 }).isZero()
710
+ * @example
711
+ * // returns false
712
+ * Dinero({ amount: 100 }).isZero()
713
+ *
714
+ * @return {Boolean}
715
+ */
716
+ isZero() {
717
+ return this.getAmount() === 0
718
+ },
719
+ /**
720
+ * Checks if the value represented by this object is positive.
721
+ *
722
+ * @example
723
+ * // returns false
724
+ * Dinero({ amount: -10 }).isPositive()
725
+ * @example
726
+ * // returns true
727
+ * Dinero({ amount: 10 }).isPositive()
728
+ * @example
729
+ * // returns true
730
+ * Dinero({ amount: 0 }).isPositive()
731
+ *
732
+ * @return {Boolean}
733
+ */
734
+ isPositive() {
735
+ return this.getAmount() >= 0
736
+ },
737
+ /**
738
+ * Checks if the value represented by this object is negative.
739
+ *
740
+ * @example
741
+ * // returns true
742
+ * Dinero({ amount: -10 }).isNegative()
743
+ * @example
744
+ * // returns false
745
+ * Dinero({ amount: 10 }).isNegative()
746
+ * @example
747
+ * // returns false
748
+ * Dinero({ amount: 0 }).isNegative()
749
+ *
750
+ * @return {Boolean}
751
+ */
752
+ isNegative() {
753
+ return this.getAmount() < 0
754
+ },
755
+ /**
756
+ * Checks if this has minor currency units.
757
+ * Deprecates {@link module:Dinero~hasCents hasCents}.
758
+ *
759
+ * @example
760
+ * // returns false
761
+ * Dinero({ amount: 1100 }).hasSubUnits()
762
+ * @example
763
+ * // returns true
764
+ * Dinero({ amount: 1150 }).hasSubUnits()
765
+ *
766
+ * @return {Boolean}
767
+ */
768
+ hasSubUnits() {
769
+ return calculator.modulo(this.getAmount(), Math.pow(10, precision)) !== 0
770
+ },
771
+ /**
772
+ * Checks if this has minor currency units.
773
+ *
774
+ * @deprecated since version 1.4.0, will be removed in 2.0.0
775
+ * Use {@link module:Dinero~hasSubUnits hasSubUnits} instead.
776
+ *
777
+ * @example
778
+ * // returns false
779
+ * Dinero({ amount: 1100 }).hasCents()
780
+ * @example
781
+ * // returns true
782
+ * Dinero({ amount: 1150 }).hasCents()
783
+ *
784
+ * @return {Boolean}
785
+ */
786
+ hasCents() {
787
+ return calculator.modulo(this.getAmount(), Math.pow(10, precision)) !== 0
788
+ },
789
+ /**
790
+ * Checks whether the currency represented by this object equals to the other.
791
+ *
792
+ * @param {Dinero} comparator - The Dinero object to compare to.
793
+ *
794
+ * @example
795
+ * // returns true
796
+ * Dinero({ amount: 2000, currency: 'EUR' }).hasSameCurrency(Dinero({ amount: 1000, currency: 'EUR' }))
797
+ * @example
798
+ * // returns false
799
+ * Dinero({ amount: 1000, currency: 'EUR' }).hasSameCurrency(Dinero({ amount: 1000, currency: 'USD' }))
800
+ *
801
+ * @return {Boolean}
802
+ */
803
+ hasSameCurrency(comparator) {
804
+ return this.getCurrency() === comparator.getCurrency()
805
+ },
806
+ /**
807
+ * Checks whether the amount represented by this object equals to the other.
808
+ *
809
+ * @param {Dinero} comparator - The Dinero object to compare to.
810
+ *
811
+ * @example
812
+ * // returns true
813
+ * Dinero({ amount: 1000, currency: 'EUR' }).hasSameAmount(Dinero({ amount: 1000 }))
814
+ * @example
815
+ * // returns false
816
+ * Dinero({ amount: 2000, currency: 'EUR' }).hasSameAmount(Dinero({ amount: 1000, currency: 'EUR' }))
817
+ * @example
818
+ * // returns true
819
+ * Dinero({ amount: 1000, currency: 'EUR', precision: 2 }).hasSameAmount(Dinero({ amount: 10000, precision: 3 }))
820
+ * @example
821
+ * // returns false
822
+ * Dinero({ amount: 10000, currency: 'EUR', precision: 2 }).hasSameAmount(Dinero({ amount: 10000, precision: 3 }))
823
+ *
824
+ * @return {Boolean}
825
+ */
826
+ hasSameAmount(comparator) {
827
+ const comparators = Dinero.normalizePrecision([this, comparator])
828
+ return comparators[0].getAmount() === comparators[1].getAmount()
829
+ },
830
+ /**
831
+ * Returns this object formatted as a string.
832
+ *
833
+ * The format is a mask which defines how the output string will be formatted.
834
+ * It defines whether to display a currency, in what format, how many fraction digits to display and whether to use grouping separators.
835
+ * The output is formatted according to the applying locale.
836
+ *
837
+ * Object | Format | String
838
+ * :--------------------------- | :---------------- | :---
839
+ * `Dinero({ amount: 500050 })` | `'$0,0.00'` | $5,000.50
840
+ * `Dinero({ amount: 500050 })` | `'$0,0'` | $5,001
841
+ * `Dinero({ amount: 500050 })` | `'$0'` | $5001
842
+ * `Dinero({ amount: 500050 })` | `'$0.0'` | $5000.5
843
+ * `Dinero({ amount: 500050 })` | `'USD0,0.0'` | USD5,000.5
844
+ * `Dinero({ amount: 500050 })` | `'0,0.0 dollar'` | 5,000.5 dollars
845
+ *
846
+ * Don't try to substitute the `$` sign or the `USD` code with your target currency, nor adapt the format string to the exact format you want.
847
+ * The format is a mask which defines a pattern and returns a valid, localized currency string.
848
+ * If you want to display the object in a custom way, either use {@link module:Dinero~getAmount getAmount}, {@link module:Dinero~toUnit toUnit} or {@link module:Dinero~toRoundedUnit toRoundedUnit} and manipulate the output string as you wish.
849
+ *
850
+ * {@link module:Dinero~toFormat toFormat} wraps around `Number.prototype.toLocaleString`. For that reason, **format will vary depending on how it's implemented in the end user's environment**.
851
+ *
852
+ * You can also use `toLocaleString` directly:
853
+ * `Dinero().toRoundedUnit(digits, roundingMode).toLocaleString(locale, options)`.
854
+ *
855
+ * By default, amounts are rounded using the **half away from zero** rule ([commercial rounding](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)).
856
+ * You can also specify a different `roundingMode` to better fit your needs.
857
+ *
858
+ * @param {String} [format='$0,0.00'] - The format mask to format to.
859
+ * @param {String} [roundingMode='HALF_AWAY_FROM_ZERO'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
860
+ *
861
+ * @example
862
+ * // returns $2,000
863
+ * Dinero({ amount: 200000 }).toFormat('$0,0')
864
+ * @example
865
+ * // returns €50.5
866
+ * Dinero({ amount: 5050, currency: 'EUR' }).toFormat('$0,0.0')
867
+ * @example
868
+ * // returns 100 euros
869
+ * Dinero({ amount: 10000, currency: 'EUR' }).setLocale('fr-FR').toFormat('0,0 dollar')
870
+ * @example
871
+ * // returns 2000
872
+ * Dinero({ amount: 200000, currency: 'EUR' }).toFormat()
873
+ * @example
874
+ * // returns $10
875
+ * Dinero({ amount: 1050 }).toFormat('$0', 'HALF_EVEN')
876
+ *
877
+ * @return {String}
878
+ */
879
+ toFormat(format = globalFormat, roundingMode = globalFormatRoundingMode) {
880
+ const formatter = Format(format)
881
+
882
+ return this.toRoundedUnit(
883
+ formatter.getMinimumFractionDigits(),
884
+ roundingMode
885
+ ).toLocaleString(this.getLocale(), {
886
+ currencyDisplay: formatter.getCurrencyDisplay(),
887
+ useGrouping: formatter.getUseGrouping(),
888
+ minimumFractionDigits: formatter.getMinimumFractionDigits(),
889
+ style: formatter.getStyle(),
890
+ currency: this.getCurrency()
891
+ })
892
+ },
893
+ /**
894
+ * Returns the amount represented by this object in units.
895
+ *
896
+ * @example
897
+ * // returns 10.5
898
+ * Dinero({ amount: 1050 }).toUnit()
899
+ * @example
900
+ * // returns 10.545
901
+ * Dinero({ amount: 10545, precision: 3 }).toUnit()
902
+ *
903
+ * @return {Number}
904
+ */
905
+ toUnit() {
906
+ return calculator.divide(this.getAmount(), Math.pow(10, precision))
907
+ },
908
+ /**
909
+ * Returns the amount represented by this object in rounded units.
910
+ *
911
+ * By default, the method uses the **half away from zero** rule ([commercial rounding](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)).
912
+ * You can also specify a different `roundingMode` to better fit your needs.
913
+ *
914
+ * @example
915
+ * // returns 10.6
916
+ * Dinero({ amount: 1055 }).toRoundedUnit(1)
917
+ * @example
918
+ * // returns 10
919
+ * Dinero({ amount: 1050 }).toRoundedUnit(0, 'HALF_EVEN')
920
+ *
921
+ * @param {Number} digits - The number of fraction digits to round to.
922
+ * @param {String} [roundingMode='HALF_AWAY_FROM_ZERO'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
923
+ *
924
+ * @return {Number}
925
+ */
926
+ toRoundedUnit(digits, roundingMode = globalFormatRoundingMode) {
927
+ const factor = Math.pow(10, digits)
928
+ return calculator.divide(
929
+ calculator.round(
930
+ calculator.multiply(this.toUnit(), factor),
931
+ roundingMode
932
+ ),
933
+ factor
934
+ )
935
+ },
936
+ /**
937
+ * Returns the object's data as an object literal.
938
+ *
939
+ * @example
940
+ * // returns { amount: 500, currency: 'EUR', precision: 2 }
941
+ * Dinero({ amount: 500, currency: 'EUR', precision: 2 }).toObject()
942
+ *
943
+ * @return {Object}
944
+ */
945
+ toObject() {
946
+ return {
947
+ amount,
948
+ currency,
949
+ precision
950
+ }
951
+ },
952
+ /**
953
+ * Returns the object's data as an object literal.
954
+ *
955
+ * Alias of {@link module:Dinero~toObject toObject}.
956
+ * It is defined so that calling `JSON.stringify` on a Dinero object will automatically extract the relevant data.
957
+ *
958
+ * @example
959
+ * // returns '{"amount":500,"currency":"EUR","precision":2}'
960
+ * JSON.stringify(Dinero({ amount: 500, currency: 'EUR', precision: 2 }))
961
+ *
962
+ * @return {Object}
963
+ */
964
+ toJSON() {
965
+ return this.toObject()
966
+ }
967
+ }
968
+ }
969
+
970
+ export default Object.assign(Dinero, Defaults, Globals, Static)