shopify-money 0.14.3 → 0.14.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e8e01e6d1e01fca5740240f5a229ae89f9ab26a9f351ee3b3adfdfda2460d55
4
- data.tar.gz: 04e697b1be43c759ee3a69febedc2b3d160b674c0c04e63a621ba7a0b59c77e4
3
+ metadata.gz: 186250af5f49f67f218ce8b30f47cd6c20ff762f9fdb2813ca9ffe1d12a7700d
4
+ data.tar.gz: 1e6ce179c120a2781dc0be48ee0377c4179e6dc07c8fd6306689947a0847daf4
5
5
  SHA512:
6
- metadata.gz: cfdbc7082d679edf08bd54c397484255eb68227529a1d444b88d549f4bffe3d3c8abb8a29f387b1032aa4f9a8641c7126494972db231393053c4a40a8ec55324
7
- data.tar.gz: 1ee9f16a6b1c2f7583dd907902ac9e45e309e18bc7f657f95756e5e62af9ded5725c317adfba9adef623c2e225702ee166736aa3e6253a5a8baae81a5678086c
6
+ metadata.gz: 3d2076a57d0b364a782ce10ffe8961e8ca57b535b8c12ba006887a92336902694ecf1a9b34538632bdad51ea3bbbf2e44aa15988344a406d16890168c72823a8
7
+ data.tar.gz: 8b539f90a996544a93fbb733dbde048460a4a5069e1efef6e44b63ae8e790dd6f90a407b7e4ae0fbbb2d850864b5015204ec8a4663b2d45673589611e16ce02b
@@ -0,0 +1,34 @@
1
+ name: tests
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+
8
+ runs-on: ubuntu-latest
9
+
10
+ strategy:
11
+ matrix:
12
+ ruby: ['2.6', '2.7', '3.0']
13
+
14
+ name: Ruby ${{ matrix.ruby }}
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Set up Ruby ${{ matrix.ruby }}
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby }}
21
+ - uses: actions/cache@v1
22
+ with:
23
+ path: vendor/bundle
24
+ key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
25
+ restore-keys: |
26
+ ${{ runner.os }}-gems-
27
+ - name: Install dependencies
28
+ run: |
29
+ gem install bundler
30
+ bundle config path vendor/bundle
31
+ bundle install --jobs 4 --retry 3
32
+ - name: Run tests
33
+ run: |
34
+ bundle exec rake
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # money
2
2
 
3
- [![Build Status](https://travis-ci.org/Shopify/money.svg?branch=master)](https://travis-ci.org/Shopify/money)
3
+ [![tests](https://github.com/Shopify/money/workflows/tests/badge.svg)](https://github.com/Shopify/money/actions?query=workflow%3Atests+branch%3Amaster)
4
4
 
5
5
 
6
6
  money_column expects a DECIMAL(21,3) database field.
@@ -43,6 +43,21 @@ ghc:
43
43
  thousands_separator: ","
44
44
  iso_numeric: '288'
45
45
  smallest_denomination: 1
46
+ mro:
47
+ priority: 100
48
+ iso_code: MRO
49
+ name: Mauritanian Ouguiya
50
+ symbol: UM
51
+ disambiguate_symbol: MRO
52
+ alternate_symbols: []
53
+ subunit: Khoums
54
+ subunit_to_unit: 5
55
+ symbol_first: false
56
+ html_entity: ''
57
+ decimal_mark: "."
58
+ thousands_separator: ","
59
+ iso_numeric: '478'
60
+ smallest_denomination: 1
46
61
  mtl:
47
62
  priority: 100
48
63
  iso_code: MTL
@@ -86,6 +101,20 @@ VEB:
86
101
  thousands_separator: "."
87
102
  iso_numeric: '862'
88
103
  smallest_denomination: 1
104
+ vef:
105
+ priority: 100
106
+ iso_code: VEF
107
+ name: Venezuelan Bolívar fuerte
108
+ symbol: Bs.F.
109
+ alternate_symbols: []
110
+ subunit: Céntimo
111
+ subunit_to_unit: 100
112
+ symbol_first: true
113
+ html_entity: ''
114
+ decimal_mark: ","
115
+ thousands_separator: "."
116
+ iso_numeric: '937'
117
+ smallest_denomination: 1
89
118
  zwd:
90
119
  priority: 100
91
120
  iso_code: ZWD
@@ -1446,19 +1446,23 @@ mop:
1446
1446
  thousands_separator: ","
1447
1447
  iso_numeric: '446'
1448
1448
  smallest_denomination: 10
1449
- mro:
1449
+ mru:
1450
1450
  priority: 100
1451
- iso_code: MRO
1452
- name: Mauritanian Ouguiya
1451
+ iso_code: MRU
1452
+ name: Mauritanian New Ouguiya
1453
1453
  symbol: UM
1454
+ disambiguate_symbol: MRU
1454
1455
  alternate_symbols: []
1455
1456
  subunit: Khoums
1456
- subunit_to_unit: 5
1457
+ # MRU actually has 5 Khoums per Ouguiya, but we treat it as if it has no subunits.
1458
+ # We do this because the Khoum's value is so small that it's not likely to be relevant in an online transaction,
1459
+ # and we suspect that, when our payment partners add support for MRU, they're likely to ignore subunits.
1460
+ subunit_to_unit: 1
1457
1461
  symbol_first: false
1458
1462
  html_entity: ''
1459
1463
  decimal_mark: "."
1460
1464
  thousands_separator: ","
1461
- iso_numeric: '478'
1465
+ iso_numeric: '929'
1462
1466
  smallest_denomination: 1
1463
1467
  mur:
1464
1468
  priority: 100
@@ -2300,11 +2304,11 @@ uzs:
2300
2304
  thousands_separator: ","
2301
2305
  iso_numeric: '860'
2302
2306
  smallest_denomination: 100
2303
- vef:
2307
+ ves:
2304
2308
  priority: 100
2305
- iso_code: VEF
2306
- name: Venezuelan Bolívar fuerte
2307
- symbol: Bs.F.
2309
+ iso_code: VES
2310
+ name: Venezuelan Bolívar soberano
2311
+ symbol: Bs.S.
2308
2312
  alternate_symbols: []
2309
2313
  subunit: Céntimo
2310
2314
  subunit_to_unit: 100
@@ -2312,8 +2316,11 @@ vef:
2312
2316
  html_entity: ''
2313
2317
  decimal_mark: ","
2314
2318
  thousands_separator: "."
2315
- iso_numeric: '937'
2316
- smallest_denomination: 1
2319
+ iso_numeric: '928'
2320
+ # "On 20 August 2018, [...] New coins in denominations of 50 céntimos and 1 bolívar soberano, and new banknotes
2321
+ # in denominations of 2, 5, 10, 20, 50, 100, 200 and 500 bolívares soberanos were introduced."
2322
+ # Source: https://en.wikipedia.org/wiki/Venezuelan_bol%C3%ADvar
2323
+ smallest_denomination: 50
2317
2324
  vnd:
2318
2325
  priority: 100
2319
2326
  iso_code: VND
@@ -14,6 +14,25 @@ jep:
14
14
  thousands_separator: ","
15
15
  iso_numeric: ''
16
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
17
36
  ggp:
18
37
  priority: 100
19
38
  iso_code: GGP
@@ -30,7 +30,7 @@ class Money
30
30
  when String
31
31
  -data
32
32
  else
33
- data.duplicable? ? data.freeze : data
33
+ data.freeze
34
34
  end
35
35
  end
36
36
  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
 
@@ -84,7 +93,7 @@ class Money
84
93
  def initialize(value, currency)
85
94
  raise ArgumentError if value.nan?
86
95
  @currency = Helpers.value_to_currency(currency)
87
- @value = value.round(@currency.minor_units)
96
+ @value = BigDecimal(value.round(@currency.minor_units))
88
97
  freeze
89
98
  end
90
99
 
@@ -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.3"
3
+ VERSION = "0.14.8"
4
4
  end
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.metadata['allowed_push_host'] = "https://rubygems.org"
17
17
 
18
- s.add_development_dependency("bundler", ">= 1.5")
18
+ s.add_development_dependency("bundler")
19
19
  s.add_development_dependency("simplecov", ">= 0")
20
20
  s.add_development_dependency("rails", "~> 6.0")
21
21
  s.add_development_dependency("rspec", "~> 3.2")
@@ -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
353
+
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
321
362
 
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'))
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
@@ -906,4 +963,10 @@ RSpec.describe "Money" do
906
963
  expect(money.currency.iso_code).to eq('EUR')
907
964
  end
908
965
  end
966
+
967
+ describe '#to_yaml' do
968
+ it 'returns a yaml representation' do
969
+ expect(Money.new(100, 'JPY').to_yaml).to eq("--- !ruby/object:Money\nvalue: '100.0'\ncurrency: JPY\n")
970
+ end
971
+ end
909
972
  end
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.3
4
+ version: 0.14.8
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-07-20 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.5'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.5'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: simplecov
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -103,9 +103,9 @@ extra_rdoc_files: []
103
103
  files:
104
104
  - ".document"
105
105
  - ".github/probots.yml"
106
+ - ".github/workflows/tests.yml"
106
107
  - ".gitignore"
107
108
  - ".rspec"
108
- - ".travis.yml"
109
109
  - Gemfile
110
110
  - LICENSE.txt
111
111
  - README.md
@@ -1,13 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- branches:
5
- only:
6
- - master
7
- rvm:
8
- - 2.7
9
- - 2.6
10
- before_install:
11
- # https://github.com/travis-ci/travis-ci/issues/8978#issuecomment-354036443
12
- - gem update --system
13
- - gem install bundler