shopify-money 0.14.2 → 0.14.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,96 @@
1
+ ---
2
+ jep:
3
+ priority: 100
4
+ iso_code: JEP
5
+ name: Jersey Pound
6
+ symbol: "£"
7
+ disambiguate_symbol: JEP
8
+ alternate_symbols: []
9
+ subunit: Penny
10
+ subunit_to_unit: 100
11
+ symbol_first: true
12
+ html_entity: "£"
13
+ decimal_mark: "."
14
+ thousands_separator: ","
15
+ iso_numeric: ''
16
+ smallest_denomination: 1
17
+ kid:
18
+ # The Kiribati Dollar is pegged 1:1 to the AUD, but banknotes and coins in both currencies circulate interchangeably.
19
+ # "Although Kiribati retained 1 and 2 cents coins well after Australia demoted theirs,
20
+ # redundancy and devaluation has slowly removed these coins from general circulation."
21
+ # Source: https://en.wikipedia.org/wiki/Kiribati_dollar
22
+ priority: 100
23
+ iso_code: KID
24
+ name: Kiribati Dollar
25
+ symbol: "$"
26
+ disambiguate_symbol: KID
27
+ alternate_symbols: []
28
+ subunit: Cent
29
+ subunit_to_unit: 100
30
+ symbol_first: true
31
+ html_entity: "$"
32
+ decimal_mark: "."
33
+ thousands_separator: ","
34
+ iso_numeric: ''
35
+ smallest_denomination: 5
36
+ ggp:
37
+ priority: 100
38
+ iso_code: GGP
39
+ name: Guernsey Pound
40
+ symbol: "£"
41
+ disambiguate_symbol: GGP
42
+ alternate_symbols: []
43
+ subunit: Penny
44
+ subunit_to_unit: 100
45
+ symbol_first: true
46
+ html_entity: "£"
47
+ decimal_mark: "."
48
+ thousands_separator: ","
49
+ iso_numeric: ''
50
+ smallest_denomination: 1
51
+ imp:
52
+ priority: 100
53
+ iso_code: IMP
54
+ name: Isle of Man Pound
55
+ symbol: "£"
56
+ disambiguate_symbol: IMP
57
+ alternate_symbols:
58
+ - M£
59
+ subunit: Penny
60
+ subunit_to_unit: 100
61
+ symbol_first: true
62
+ html_entity: "£"
63
+ decimal_mark: "."
64
+ thousands_separator: ","
65
+ iso_numeric: ''
66
+ smallest_denomination: 1
67
+ xfu:
68
+ priority: 100
69
+ iso_code: XFU
70
+ name: UIC Franc
71
+ symbol: ''
72
+ disambiguate_symbol: XFU
73
+ alternate_symbols: []
74
+ subunit: ''
75
+ subunit_to_unit: 100
76
+ symbol_first: true
77
+ html_entity: ''
78
+ decimal_mark: "."
79
+ thousands_separator: ","
80
+ iso_numeric: ''
81
+ smallest_denomination: ''
82
+ gbx:
83
+ priority: 100
84
+ iso_code: GBX
85
+ name: British Penny
86
+ symbol: ''
87
+ disambiguate_symbol: GBX
88
+ alternate_symbols: []
89
+ subunit: ''
90
+ subunit_to_unit: 1
91
+ symbol_first: true
92
+ html_entity: ''
93
+ decimal_mark: "."
94
+ thousands_separator: ","
95
+ iso_numeric: ''
96
+ smallest_denomination: 1
@@ -1,26 +1,38 @@
1
1
  # frozen_string_literal: true
2
- require 'json'
2
+ require 'yaml'
3
3
 
4
4
  class Money
5
5
  class Currency
6
6
  module Loader
7
- extend self
7
+ class << self
8
+ def load_currencies
9
+ currency_data_path = File.expand_path("../../../../config", __FILE__)
8
10
 
9
- CURRENCY_DATA_PATH = File.expand_path("../../../../config", __FILE__)
11
+ currencies = {}
12
+ currencies.merge! YAML.load_file("#{currency_data_path}/currency_historic.yml")
13
+ currencies.merge! YAML.load_file("#{currency_data_path}/currency_non_iso.yml")
14
+ currencies.merge! YAML.load_file("#{currency_data_path}/currency_iso.yml")
15
+ deep_deduplicate!(currencies)
16
+ end
10
17
 
11
- def load_currencies
12
- currencies = {}
13
- currencies.merge! parse_currency_file("currency_historic.json")
14
- currencies.merge! parse_currency_file("currency_non_iso.json")
15
- currencies.merge! parse_currency_file("currency_iso.json")
16
- end
17
-
18
- private
18
+ private
19
19
 
20
- def parse_currency_file(filename)
21
- json = File.read("#{CURRENCY_DATA_PATH}/#{filename}")
22
- json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
23
- JSON.parse(json)
20
+ def deep_deduplicate!(data)
21
+ case data
22
+ when Hash
23
+ return data if data.frozen?
24
+ data.transform_keys! { |k| deep_deduplicate!(k) }
25
+ data.transform_values! { |v| deep_deduplicate!(v) }
26
+ data.freeze
27
+ when Array
28
+ return data if data.frozen?
29
+ data.map! { |d| deep_deduplicate!(d) }.freeze
30
+ when String
31
+ -data
32
+ else
33
+ data.freeze
34
+ end
35
+ end
24
36
  end
25
37
  end
26
38
  end
@@ -8,6 +8,10 @@ class Money
8
8
  DECIMAL_ZERO = BigDecimal(0).freeze
9
9
  MAX_DECIMAL = 21
10
10
 
11
+ STRIPE_SUBUNIT_OVERRIDE = {
12
+ 'ISK' => 100,
13
+ }.freeze
14
+
11
15
  def value_to_decimal(num)
12
16
  value =
13
17
  case num
@@ -39,9 +39,18 @@ class Money
39
39
  new(cents.round.to_f / 100, currency)
40
40
  end
41
41
 
42
- def from_subunits(subunits, currency_iso)
42
+ def from_subunits(subunits, currency_iso, format: :iso4217)
43
43
  currency = Helpers.value_to_currency(currency_iso)
44
- value = Helpers.value_to_decimal(subunits) / currency.subunit_to_unit
44
+
45
+ subunit_to_unit_value = if format == :iso4217
46
+ currency.subunit_to_unit
47
+ elsif format == :stripe
48
+ Helpers::STRIPE_SUBUNIT_OVERRIDE.fetch(currency.iso_code, currency.subunit_to_unit)
49
+ else
50
+ raise ArgumentError, "unknown format #{format}"
51
+ end
52
+
53
+ value = Helpers.value_to_decimal(subunits) / subunit_to_unit_value
45
54
  new(value, currency)
46
55
  end
47
56
 
@@ -102,8 +111,16 @@ class Money
102
111
  (value * 100).to_i
103
112
  end
104
113
 
105
- def subunits
106
- (@value * @currency.subunit_to_unit).to_i
114
+ def subunits(format: :iso4217)
115
+ subunit_to_unit_value = if format == :iso4217
116
+ @currency.subunit_to_unit
117
+ elsif format == :stripe
118
+ Helpers::STRIPE_SUBUNIT_OVERRIDE.fetch(@currency.iso_code, @currency.subunit_to_unit)
119
+ else
120
+ raise ArgumentError, "unknown format #{format}"
121
+ end
122
+
123
+ (@value * subunit_to_unit_value).to_i
107
124
  end
108
125
 
109
126
  def no_currency?
@@ -207,11 +224,22 @@ class Money
207
224
  end
208
225
 
209
226
  def to_s(style = nil)
210
- case style
227
+ units = case style
211
228
  when :legacy_dollars
212
- sprintf("%.2f", value)
229
+ 2
213
230
  when :amount, nil
214
- sprintf("%.#{currency.minor_units}f", value)
231
+ currency.minor_units
232
+ else
233
+ raise ArgumentError, "Unexpected style: #{style}"
234
+ end
235
+
236
+ rounded_value = value.round(units)
237
+ if units == 0
238
+ sprintf("%d", rounded_value)
239
+ else
240
+ sign = rounded_value < 0 ? '-' : ''
241
+ rounded_value = rounded_value.abs
242
+ sprintf("%s%d.%0#{units}d", sign, rounded_value.truncate, rounded_value.frac * (10 ** units))
215
243
  end
216
244
  end
217
245
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  class Money
3
- VERSION = "0.14.2"
3
+ VERSION = "0.14.7"
4
4
  end
@@ -80,6 +80,33 @@ RSpec.describe "Money" do
80
80
  expect(non_fractional_money.to_s(:amount)).to eq("1")
81
81
  end
82
82
 
83
+ it "to_s correctly displays negative numbers" do
84
+ expect((-money).to_s).to eq("-1.00")
85
+ expect((-amount_money).to_s).to eq("-1.23")
86
+ expect((-non_fractional_money).to_s).to eq("-1")
87
+ expect((-Money.new("0.05")).to_s).to eq("-0.05")
88
+ end
89
+
90
+ it "to_s rounds when more fractions than currency allows" do
91
+ expect(Money.new("9.999", "USD").to_s).to eq("10.00")
92
+ expect(Money.new("9.889", "USD").to_s).to eq("9.89")
93
+ end
94
+
95
+ it "to_s does not round when fractions same as currency allows" do
96
+ expect(Money.new("1.25", "USD").to_s).to eq("1.25")
97
+ expect(Money.new("9.99", "USD").to_s).to eq("9.99")
98
+ expect(Money.new("9.999", "BHD").to_s).to eq("9.999")
99
+ end
100
+
101
+ it "to_s does not round if amount is larger than float allows" do
102
+ expect(Money.new("99999999999999.99", "USD").to_s).to eq("99999999999999.99")
103
+ expect(Money.new("999999999999999999.99", "USD").to_s).to eq("999999999999999999.99")
104
+ end
105
+
106
+ it "to_s raises ArgumentError on unsupported style" do
107
+ expect{ money.to_s(:some_weird_style) }.to raise_error(ArgumentError)
108
+ end
109
+
83
110
  it "as_json as a float with 2 decimal places" do
84
111
  expect(money.as_json).to eq("1.00")
85
112
  end
@@ -315,12 +342,28 @@ RSpec.describe "Money" do
315
342
  expect(Money.from_cents(1950.5)).to eq(Money.new(19.51))
316
343
  end
317
344
 
318
- it "is creatable from an integer value in cents and currency" do
319
- expect(Money.from_subunits(1950, 'CAD')).to eq(Money.new(19.50))
320
- end
345
+ describe '#from_subunits' do
346
+ it "creates Money object from an integer value in cents and currency" do
347
+ expect(Money.from_subunits(1950, 'CAD')).to eq(Money.new(19.50))
348
+ end
349
+
350
+ it "creates Money object from an integer value in dollars and currency with no cents" do
351
+ expect(Money.from_subunits(1950, 'JPY')).to eq(Money.new(1950, 'JPY'))
352
+ end
321
353
 
322
- it "is creatable from an integer value in dollars and currency with no cents" do
323
- expect(Money.from_subunits(1950, 'JPY')).to eq(Money.new(1950, 'JPY'))
354
+ describe 'with format specified' do
355
+ it 'overrides the subunit_to_unit amount' do
356
+ expect(Money.from_subunits(100, 'ISK', format: :stripe)).to eq(Money.new(1, 'ISK'))
357
+ end
358
+
359
+ it 'fallbacks to the default subunit_to_unit amount if no override is specified' do
360
+ expect(Money.from_subunits(100, 'USD', format: :stripe)).to eq(Money.new(1, 'USD'))
361
+ end
362
+
363
+ it 'raises if the format is not found' do
364
+ expect { Money.from_subunits(100, 'ISK', format: :unknown) }.to(raise_error(ArgumentError))
365
+ end
366
+ end
324
367
  end
325
368
 
326
369
  it "raises when constructed with a NaN value" do
@@ -517,6 +560,20 @@ RSpec.describe "Money" do
517
560
  expect(Money.new(1, 'IQD').subunits).to eq(1000)
518
561
  expect(Money.new(1).subunits).to eq(100)
519
562
  end
563
+
564
+ describe 'with format specified' do
565
+ it 'overrides the subunit_to_unit amount' do
566
+ expect(Money.new(1, 'ISK').subunits(format: :stripe)).to eq(100)
567
+ end
568
+
569
+ it 'fallbacks to the default subunit_to_unit amount if no override is specified' do
570
+ expect(Money.new(1, 'USD').subunits(format: :stripe)).to eq(100)
571
+ end
572
+
573
+ it 'raises if the format is not found' do
574
+ expect { Money.new(1, 'ISK').subunits(format: :unknown) }.to(raise_error(ArgumentError))
575
+ end
576
+ end
520
577
  end
521
578
 
522
579
  describe "value" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify-money
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.2
4
+ version: 0.14.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-29 00:00:00.000000000 Z
11
+ date: 2020-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -111,9 +111,9 @@ files:
111
111
  - README.md
112
112
  - Rakefile
113
113
  - bin/console
114
- - config/currency_historic.json
115
- - config/currency_iso.json
116
- - config/currency_non_iso.json
114
+ - config/currency_historic.yml
115
+ - config/currency_iso.yml
116
+ - config/currency_non_iso.yml
117
117
  - dev.yml
118
118
  - lib/money.rb
119
119
  - lib/money/accounting_money_parser.rb
@@ -1,157 +0,0 @@
1
- {
2
- "cyp": {
3
- "priority": 100,
4
- "iso_code": "CYP",
5
- "name": "Cypriot pound",
6
- "symbol": "£",
7
- "alternate_symbols": [],
8
- "subunit": "Cent",
9
- "subunit_to_unit": 100,
10
- "symbol_first": true,
11
- "html_entity": "&#x00A3;",
12
- "decimal_mark": ".",
13
- "thousands_separator": ",",
14
- "iso_numeric": "196",
15
- "smallest_denomination": 1
16
- },
17
- "eek": {
18
- "priority": 100,
19
- "iso_code": "EEK",
20
- "name": "Estonian Kroon",
21
- "symbol": "KR",
22
- "alternate_symbols": [],
23
- "subunit": "Sent",
24
- "subunit_to_unit": 100,
25
- "symbol_first": false,
26
- "html_entity": "",
27
- "decimal_mark": ".",
28
- "thousands_separator": ",",
29
- "iso_numeric": "233",
30
- "smallest_denomination": 5
31
- },
32
- "ghc": {
33
- "priority": 100,
34
- "iso_code": "GHS",
35
- "name": "Ghanaian Cedi",
36
- "symbol": "₵",
37
- "disambiguate_symbol": "GH₵",
38
- "alternate_symbols": ["GH₵"],
39
- "subunit": "Pesewa",
40
- "subunit_to_unit": 100,
41
- "symbol_first": true,
42
- "html_entity": "&#x20B5;",
43
- "decimal_mark": ".",
44
- "thousands_separator": ",",
45
- "iso_numeric": "288",
46
- "smallest_denomination": 1
47
- },
48
- "mtl": {
49
- "priority": 100,
50
- "iso_code": "MTL",
51
- "name": "Maltese Lira",
52
- "symbol": "₤",
53
- "alternate_symbols": ["Lm"],
54
- "subunit": "Cent",
55
- "subunit_to_unit": 100,
56
- "symbol_first": true,
57
- "html_entity": "&#x00A3;",
58
- "decimal_mark": ".",
59
- "thousands_separator": ",",
60
- "iso_numeric": "470",
61
- "smallest_denomination": 1
62
- },
63
- "tmm": {
64
- "priority": 100,
65
- "iso_code": "TMM",
66
- "name": "Turkmenistani Manat",
67
- "symbol": "m",
68
- "alternate_symbols": [],
69
- "subunit": "Tennesi",
70
- "subunit_to_unit": 100,
71
- "symbol_first": false,
72
- "html_entity": "",
73
- "decimal_mark": ".",
74
- "thousands_separator": ",",
75
- "iso_numeric": "795",
76
- "smallest_denomination": 1
77
- },
78
- "VEB": {
79
- "priority": 100,
80
- "iso_code": "VEB",
81
- "name": "Venezuelan Bolívar",
82
- "symbol": "Bs.",
83
- "alternate_symbols": [],
84
- "subunit": "Céntimo",
85
- "subunit_to_unit": 100,
86
- "symbol_first": true,
87
- "html_entity": "",
88
- "decimal_mark": ",",
89
- "thousands_separator": ".",
90
- "iso_numeric": "862",
91
- "smallest_denomination": 1
92
- },
93
- "zwd": {
94
- "priority": 100,
95
- "iso_code": "ZWD",
96
- "name": "Zimbabwean Dollar",
97
- "symbol": "$",
98
- "disambiguate_symbol": "ZWD",
99
- "alternate_symbols": ["Z$"],
100
- "subunit": "Cent",
101
- "subunit_to_unit": 100,
102
- "symbol_first": true,
103
- "html_entity": "$",
104
- "decimal_mark": ".",
105
- "thousands_separator": ",",
106
- "iso_numeric": "716",
107
- "smallest_denomination": 100
108
- },
109
- "zwl": {
110
- "priority": 100,
111
- "iso_code": "ZWL",
112
- "name": "Zimbabwean Dollar",
113
- "symbol": "$",
114
- "disambiguate_symbol": "ZWL",
115
- "alternate_symbols": ["Z$"],
116
- "subunit": "Cent",
117
- "subunit_to_unit": 100,
118
- "symbol_first": true,
119
- "html_entity": "$",
120
- "decimal_mark": ".",
121
- "thousands_separator": ",",
122
- "iso_numeric": "932",
123
- "smallest_denomination": 100
124
- },
125
- "zwn": {
126
- "priority": 100,
127
- "iso_code": "ZWN",
128
- "name": "Zimbabwean Dollar",
129
- "symbol": "$",
130
- "disambiguate_symbol": "ZWN",
131
- "alternate_symbols": ["Z$"],
132
- "subunit": "Cent",
133
- "subunit_to_unit": 100,
134
- "symbol_first": true,
135
- "html_entity": "$",
136
- "decimal_mark": ".",
137
- "thousands_separator": ",",
138
- "iso_numeric": "942",
139
- "smallest_denomination": 100
140
- },
141
- "zwr": {
142
- "priority": 100,
143
- "iso_code": "ZWR",
144
- "name": "Zimbabwean Dollar",
145
- "symbol": "$",
146
- "disambiguate_symbol": "ZWR",
147
- "alternate_symbols": ["Z$"],
148
- "subunit": "Cent",
149
- "subunit_to_unit": 100,
150
- "symbol_first": true,
151
- "html_entity": "$",
152
- "decimal_mark": ".",
153
- "thousands_separator": ",",
154
- "iso_numeric": "935",
155
- "smallest_denomination": 100
156
- }
157
- }