shopify-money 4.0.0 → 4.1.0
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +39 -0
- data/lib/money/allocator.rb +8 -2
- data/lib/money/config.rb +3 -1
- data/lib/money/currency/loader.rb +7 -0
- data/lib/money/currency.rb +13 -0
- data/lib/money/money.rb +5 -2
- data/lib/money/version.rb +1 -1
- data/sig/money/config.rbs +3 -1
- data/sig/money/currency.rbs +3 -0
- data/spec/allocator_spec.rb +24 -1
- data/spec/config_spec.rb +24 -0
- data/spec/currency/loader_spec.rb +52 -0
- data/spec/currency_spec.rb +118 -0
- data/spec/spec_helper.rb +13 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cadc7891cca0acc6f6128c570b259a5c1bead3a6a87175ebe3def93d9a618afb
|
|
4
|
+
data.tar.gz: 3fcc02abc7af2a86aba449ed135361a1024a18975430c8075f4b36d36d70c72a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 982ccb02e8ad20c3ad3ba9d217e0a53531d370cf32e7664947d859997ee2bc493614908f722fd1dc1ee0f234e114cb4c7e3552c85401d978afac4d462e35344c
|
|
7
|
+
data.tar.gz: 4a5e188fb292ad0ca2afdc2ba3dbcde89507a352d34ac7b7964870fc1584c0f9fd10d1036bd5b78a1971649d107b89f549f3b38a4f9bad823e829ac0c60d29ea
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -157,6 +157,23 @@ Money.new(money.value * exchange_rate, "JPY")
|
|
|
157
157
|
money.convert_currency(exchange_rate, "JPY")
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
+
### Allocation Strategy
|
|
161
|
+
|
|
162
|
+
By default `allocate` distributes leftover subunits using the `:roundrobin` strategy.
|
|
163
|
+
You can change the default:
|
|
164
|
+
|
|
165
|
+
``` ruby
|
|
166
|
+
Money.configure do |config|
|
|
167
|
+
config.default_allocation_strategy = :nearest
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Or pass a strategy per call:
|
|
172
|
+
|
|
173
|
+
``` ruby
|
|
174
|
+
Money.new(10.55, "USD").allocate([0.25, 0.5, 0.25], :nearest)
|
|
175
|
+
```
|
|
176
|
+
|
|
160
177
|
### Crypto Currencies
|
|
161
178
|
To enable support for currencies listed in `crypto.yml` use
|
|
162
179
|
``` ruby
|
|
@@ -165,6 +182,28 @@ Money.configure do |config|
|
|
|
165
182
|
end
|
|
166
183
|
```
|
|
167
184
|
|
|
185
|
+
### Custom Currencies
|
|
186
|
+
To load custom currencies from a YAML file:
|
|
187
|
+
``` ruby
|
|
188
|
+
Money.configure do |config|
|
|
189
|
+
config.experimental_custom_currency_path = Rails.root.join("config/custom_currencies.yml")
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
``` yaml
|
|
194
|
+
# config/custom_currencies.yml
|
|
195
|
+
credits:
|
|
196
|
+
iso_code: "CREDITS"
|
|
197
|
+
name: "Loyalty Points"
|
|
198
|
+
symbol: "CR"
|
|
199
|
+
disambiguate_symbol: "CR"
|
|
200
|
+
subunit_to_unit: 1
|
|
201
|
+
smallest_denomination: 1
|
|
202
|
+
decimal_mark: "."
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Custom currencies are looked up after ISO and crypto currencies, so they cannot shadow built-in currencies.
|
|
206
|
+
|
|
168
207
|
### Converters
|
|
169
208
|
The Money gem provides a flexible converter system for handling different subunit formats. This is particularly useful when working with payment providers or APIs that have their own conventions for handling currency subunits.
|
|
170
209
|
|
data/lib/money/allocator.rb
CHANGED
|
@@ -33,6 +33,8 @@ class Money
|
|
|
33
33
|
# - `:roundrobin_reverse`: leftover subunits will be accumulated starting from the last allocation right to left
|
|
34
34
|
# - `:nearest`: leftover subunits will by given first to the party closest to the next whole subunit
|
|
35
35
|
#
|
|
36
|
+
# The default strategy can be changed via `Money::Config.current.default_allocation_strategy`.
|
|
37
|
+
#
|
|
36
38
|
# @example
|
|
37
39
|
# Money.new(5, "USD").allocate([0.50, 0.25, 0.25])
|
|
38
40
|
# #=> [#<Money value:2.50 currency:USD>, #<Money value:1.25 currency:USD>, #<Money value:1.25 currency:USD>]
|
|
@@ -57,12 +59,16 @@ class Money
|
|
|
57
59
|
# Money.new(10.55, "USD").allocate([0.25, 0.5, 0.25], :nearest)
|
|
58
60
|
# #=> [#<Money value:2.64 currency:USD>, #<Money value:5.27 currency:USD>, #<Money value:2.64 currency:USD>]
|
|
59
61
|
|
|
60
|
-
def allocate(splits, strategy =
|
|
62
|
+
def allocate(splits, strategy = nil)
|
|
61
63
|
if splits.empty?
|
|
62
64
|
raise ArgumentError, 'at least one split must be provided'
|
|
63
65
|
end
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
strategy ||= Money::Config.current.default_allocation_strategy
|
|
68
|
+
|
|
69
|
+
# Float#to_r preserves float imprecision (0.98.to_r != 98/100).
|
|
70
|
+
# Rationalize gives the clean fraction (0.98.rationalize == 49/50).
|
|
71
|
+
splits.map!(&:rationalize)
|
|
66
72
|
allocations = splits.inject(0, :+)
|
|
67
73
|
|
|
68
74
|
if (allocations - ONE) > Float::EPSILON
|
data/lib/money/config.rb
CHANGED
|
@@ -41,7 +41,7 @@ class Money
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
attr_accessor :legacy_json_format, :experimental_crypto_currencies, :default_subunit_format
|
|
44
|
+
attr_accessor :legacy_json_format, :experimental_crypto_currencies, :default_subunit_format, :experimental_custom_currency_path, :default_allocation_strategy
|
|
45
45
|
|
|
46
46
|
attr_reader :default_currency
|
|
47
47
|
alias_method :currency, :default_currency
|
|
@@ -72,6 +72,8 @@ class Money
|
|
|
72
72
|
@legacy_json_format = false
|
|
73
73
|
@experimental_crypto_currencies = false
|
|
74
74
|
@default_subunit_format = :iso4217
|
|
75
|
+
@experimental_custom_currency_path = nil
|
|
76
|
+
@default_allocation_strategy = :roundrobin
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
end
|
|
@@ -22,6 +22,13 @@ class Money
|
|
|
22
22
|
deep_deduplicate!(currencies)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
def load_custom_currencies(path)
|
|
26
|
+
raise ArgumentError, "Custom currency file not found: #{path}" unless File.exist?(path)
|
|
27
|
+
data = YAML.safe_load_file(path, permitted_classes: [])
|
|
28
|
+
raise ArgumentError, "Custom currency file must contain a YAML hash" unless data.is_a?(Hash)
|
|
29
|
+
deep_deduplicate!(data)
|
|
30
|
+
end
|
|
31
|
+
|
|
25
32
|
private
|
|
26
33
|
|
|
27
34
|
def deep_deduplicate!(data)
|
data/lib/money/currency.rb
CHANGED
|
@@ -31,6 +31,15 @@ class Money
|
|
|
31
31
|
@@crypto_currencies ||= Loader.load_crypto_currencies
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
def custom_currencies(path)
|
|
35
|
+
@@custom_currencies_cache ||= {}
|
|
36
|
+
@@custom_currencies_cache[path] ||= Loader.load_custom_currencies(path)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset_custom_currencies
|
|
40
|
+
@@custom_currencies_cache = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
34
43
|
def reset_loaded_currencies
|
|
35
44
|
@@loaded_currencies = {}
|
|
36
45
|
end
|
|
@@ -52,6 +61,10 @@ class Money
|
|
|
52
61
|
if data.nil? && Money::Config.current.experimental_crypto_currencies
|
|
53
62
|
data = self.class.crypto_currencies[currency_iso]
|
|
54
63
|
end
|
|
64
|
+
if data.nil?
|
|
65
|
+
custom_path = Money::Config.current.experimental_custom_currency_path
|
|
66
|
+
data = self.class.custom_currencies(custom_path)[currency_iso] if custom_path
|
|
67
|
+
end
|
|
55
68
|
|
|
56
69
|
raise UnknownCurrency, "Invalid iso4217 currency '#{currency_iso}'" unless data
|
|
57
70
|
@symbol = data['symbol']
|
data/lib/money/money.rb
CHANGED
|
@@ -128,13 +128,16 @@ class Money
|
|
|
128
128
|
raise ArgumentError if value.nan?
|
|
129
129
|
raise ArgumentError if value.infinite?
|
|
130
130
|
|
|
131
|
-
@currency =
|
|
131
|
+
@currency = currency
|
|
132
132
|
@value = BigDecimal(value.round(@currency.minor_units))
|
|
133
133
|
freeze
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
def init_with(coder)
|
|
137
|
-
initialize(
|
|
137
|
+
initialize(
|
|
138
|
+
Helpers.value_to_decimal(coder['value']),
|
|
139
|
+
Helpers.value_to_currency(coder['currency']),
|
|
140
|
+
)
|
|
138
141
|
end
|
|
139
142
|
|
|
140
143
|
def encode_with(coder)
|
data/lib/money/version.rb
CHANGED
data/sig/money/config.rbs
CHANGED
|
@@ -7,6 +7,8 @@ class Money
|
|
|
7
7
|
attr_accessor legacy_json_format: bool
|
|
8
8
|
attr_accessor experimental_crypto_currencies: bool
|
|
9
9
|
attr_accessor default_subunit_format: Symbol
|
|
10
|
+
attr_accessor experimental_custom_currency_path: String?
|
|
11
|
+
attr_accessor default_allocation_strategy: Symbol
|
|
10
12
|
|
|
11
13
|
attr_reader default_currency: (Currency | NullCurrency | nil)
|
|
12
14
|
def default_currency=: (String | Currency | NullCurrency | nil value) -> (Currency | NullCurrency | nil)
|
|
@@ -16,7 +18,7 @@ class Money
|
|
|
16
18
|
def self.global: () -> Config
|
|
17
19
|
def self.current: () -> Config
|
|
18
20
|
def self.current=: (Config config) -> Config
|
|
19
|
-
def self.configure_current: [T] (?currency: (String | Currency | NullCurrency | nil), ?legacy_json_format: bool, ?experimental_crypto_currencies: bool, ?default_subunit_format: Symbol) { () -> T } -> T
|
|
21
|
+
def self.configure_current: [T] (?currency: (String | Currency | NullCurrency | nil), ?legacy_json_format: bool, ?experimental_crypto_currencies: bool, ?default_subunit_format: Symbol, ?experimental_custom_currency_path: String?, ?default_allocation_strategy: Symbol) { () -> T } -> T
|
|
20
22
|
def self.reset_current: () -> nil
|
|
21
23
|
|
|
22
24
|
private
|
data/sig/money/currency.rbs
CHANGED
|
@@ -7,6 +7,7 @@ class Money
|
|
|
7
7
|
|
|
8
8
|
def self.load_currencies: () -> Hash[String, untyped]
|
|
9
9
|
def self.load_crypto_currencies: () -> Hash[String, untyped]
|
|
10
|
+
def self.load_custom_currencies: (String path) -> Hash[String, untyped]
|
|
10
11
|
|
|
11
12
|
private
|
|
12
13
|
|
|
@@ -31,6 +32,8 @@ class Money
|
|
|
31
32
|
def self.find: (String | Symbol currency_iso) -> Currency?
|
|
32
33
|
def self.currencies: () -> Hash[String, Hash[String, untyped]]
|
|
33
34
|
def self.crypto_currencies: () -> Hash[String, Hash[String, untyped]]
|
|
35
|
+
def self.custom_currencies: (String path) -> Hash[String, Hash[String, untyped]]
|
|
36
|
+
def self.reset_custom_currencies: () -> nil
|
|
34
37
|
def self.reset_loaded_currencies: () -> Hash[String, Currency]
|
|
35
38
|
|
|
36
39
|
def initialize: (String currency_iso) -> void
|
data/spec/allocator_spec.rb
CHANGED
|
@@ -42,7 +42,7 @@ RSpec.describe "Allocator" do
|
|
|
42
42
|
|
|
43
43
|
specify "#allocate will use rationals if provided" do
|
|
44
44
|
splits = [128400,20439,14589,14589,25936].map{ |num| Rational(num, 203953) } # sums to > 1 if converted to float
|
|
45
|
-
expect(new_allocator(2.25).allocate(splits)).to eq([Money.new(1.42), Money.new(0.23), Money.new(0.16), Money.new(0.16), Money.new(0.28)])
|
|
45
|
+
expect(new_allocator(2.25).allocate(splits, :roundrobin)).to eq([Money.new(1.42), Money.new(0.23), Money.new(0.16), Money.new(0.16), Money.new(0.28)])
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
specify "#allocate will convert rationals with high precision" do
|
|
@@ -61,6 +61,29 @@ RSpec.describe "Allocator" do
|
|
|
61
61
|
expect { new_allocator(0.03).allocate([0.5, 0.5], :bad_strategy_name) }.to raise_error(ArgumentError, "Invalid strategy. Valid options: :roundrobin, :roundrobin_reverse, :nearest")
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
specify "#allocate defaults to :nearest strategy" do
|
|
65
|
+
# With :nearest, the party closest to the next whole subunit gets the leftover.
|
|
66
|
+
monies = new_allocator(1).allocate([0.02, 0.98])
|
|
67
|
+
expect(monies[0]).to eq(Money.new(0.02))
|
|
68
|
+
expect(monies[1]).to eq(Money.new(0.98))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
specify "#allocate uses the configured default_allocation_strategy" do
|
|
72
|
+
Money.with_config(default_allocation_strategy: :roundrobin) do
|
|
73
|
+
# With :roundrobin, leftover subunits accumulate from the first allocation.
|
|
74
|
+
monies = new_allocator(0.05).allocate([0.33, 0.34, 0.33])
|
|
75
|
+
expect(monies[0]).to eq(Money.new(0.02))
|
|
76
|
+
expect(monies[1]).to eq(Money.new(0.02))
|
|
77
|
+
expect(monies[2]).to eq(Money.new(0.01))
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
specify "#allocate exact splits produce no leftover regardless of strategy" do
|
|
82
|
+
monies = new_allocator(1).allocate([0.02, 0.98], :roundrobin)
|
|
83
|
+
expect(monies[0]).to eq(Money.new(0.02))
|
|
84
|
+
expect(monies[1]).to eq(Money.new(0.98))
|
|
85
|
+
end
|
|
86
|
+
|
|
64
87
|
specify "#allocate raises an error when splits exceed 100%" do
|
|
65
88
|
expect { new_allocator(0.03).allocate([0.5, 0.6]) }.to raise_error(ArgumentError, "allocations add to more than 100%")
|
|
66
89
|
end
|
data/spec/config_spec.rb
CHANGED
|
@@ -72,6 +72,30 @@ RSpec.describe "Money::Config" do
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
describe 'experimental_custom_currency_path' do
|
|
76
|
+
it 'defaults to nil' do
|
|
77
|
+
expect(Money::Config.new.experimental_custom_currency_path).to eq(nil)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'can be set to a path' do
|
|
81
|
+
config = Money::Config.new
|
|
82
|
+
config.experimental_custom_currency_path = '/tmp/custom.yml'
|
|
83
|
+
expect(config.experimental_custom_currency_path).to eq('/tmp/custom.yml')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe 'default_allocation_strategy' do
|
|
88
|
+
it 'defaults to :roundrobin' do
|
|
89
|
+
expect(Money::Config.new.default_allocation_strategy).to eq(:roundrobin)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'can be set to a different strategy' do
|
|
93
|
+
config = Money::Config.new
|
|
94
|
+
config.default_allocation_strategy = :roundrobin
|
|
95
|
+
expect(config.default_allocation_strategy).to eq(:roundrobin)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
75
99
|
describe 'legacy_json_format' do
|
|
76
100
|
it 'defaults to false' do
|
|
77
101
|
expect(Money::Config.new.legacy_json_format).to eq(false)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'spec_helper'
|
|
3
|
+
require 'tempfile'
|
|
3
4
|
|
|
4
5
|
RSpec.describe Money::Currency::Loader do
|
|
5
6
|
|
|
@@ -37,4 +38,55 @@ RSpec.describe Money::Currency::Loader do
|
|
|
37
38
|
expect(currencies['usdc']['iso_code']).to be_frozen
|
|
38
39
|
end
|
|
39
40
|
end
|
|
41
|
+
|
|
42
|
+
describe 'load_custom_currencies' do
|
|
43
|
+
it 'loads custom currencies from the given path' do
|
|
44
|
+
file = custom_currency_file(
|
|
45
|
+
"credits" => {
|
|
46
|
+
"iso_code" => "CREDITS",
|
|
47
|
+
"name" => "Loyalty Points",
|
|
48
|
+
"symbol" => "CR",
|
|
49
|
+
"disambiguate_symbol" => "CR",
|
|
50
|
+
"subunit_to_unit" => 1,
|
|
51
|
+
"smallest_denomination" => 1,
|
|
52
|
+
"decimal_mark" => "."
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
currencies = subject.load_custom_currencies(file.path)
|
|
57
|
+
expect(currencies['credits']['iso_code']).to eq('CREDITS')
|
|
58
|
+
expect(currencies['credits']['name']).to eq('Loyalty Points')
|
|
59
|
+
expect(currencies['credits']['symbol']).to eq('CR')
|
|
60
|
+
expect(currencies['credits']['subunit_to_unit']).to eq(1)
|
|
61
|
+
ensure
|
|
62
|
+
file.unlink
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'raises ArgumentError when file does not exist' do
|
|
66
|
+
expect {
|
|
67
|
+
subject.load_custom_currencies('/nonexistent/path.yml')
|
|
68
|
+
}.to raise_error(ArgumentError, /not found/)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'returns frozen and deduplicated data' do
|
|
72
|
+
file = custom_currency_file(
|
|
73
|
+
"credits" => {
|
|
74
|
+
"iso_code" => "CREDITS",
|
|
75
|
+
"name" => "Loyalty Points",
|
|
76
|
+
"symbol" => "CR",
|
|
77
|
+
"disambiguate_symbol" => "CR",
|
|
78
|
+
"subunit_to_unit" => 1,
|
|
79
|
+
"smallest_denomination" => 1,
|
|
80
|
+
"decimal_mark" => "."
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
currencies = subject.load_custom_currencies(file.path)
|
|
85
|
+
expect(currencies).to be_frozen
|
|
86
|
+
expect(currencies['credits']).to be_frozen
|
|
87
|
+
expect(currencies['credits']['iso_code']).to be_frozen
|
|
88
|
+
ensure
|
|
89
|
+
file.unlink
|
|
90
|
+
end
|
|
91
|
+
end
|
|
40
92
|
end
|
data/spec/currency_spec.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'spec_helper'
|
|
3
|
+
require 'tempfile'
|
|
3
4
|
|
|
4
5
|
RSpec.describe "Currency" do
|
|
5
6
|
CURRENCY_DATA = {
|
|
@@ -16,6 +17,20 @@ RSpec.describe "Currency" do
|
|
|
16
17
|
|
|
17
18
|
let(:currency) { Money::Currency.new('usd') }
|
|
18
19
|
|
|
20
|
+
let(:mock_custom_currency) do
|
|
21
|
+
{
|
|
22
|
+
"credits" => {
|
|
23
|
+
"iso_code" => "CREDITS",
|
|
24
|
+
"name" => "Loyalty Points",
|
|
25
|
+
"symbol" => "CR",
|
|
26
|
+
"disambiguate_symbol" => "CR",
|
|
27
|
+
"subunit_to_unit" => 1,
|
|
28
|
+
"smallest_denomination" => 1,
|
|
29
|
+
"decimal_mark" => "."
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
19
34
|
let(:mock_crypto_currency) do
|
|
20
35
|
{
|
|
21
36
|
"usdc" => {
|
|
@@ -68,6 +83,80 @@ RSpec.describe "Currency" do
|
|
|
68
83
|
expect(Money::Currency.find("USDC")).to be_nil
|
|
69
84
|
end
|
|
70
85
|
end
|
|
86
|
+
|
|
87
|
+
it "looks up custom currencies when path is set" do
|
|
88
|
+
file = custom_currency_file(mock_custom_currency)
|
|
89
|
+
|
|
90
|
+
configure(experimental_custom_currency_path: file.path) do
|
|
91
|
+
currency = Money::Currency.new('CREDITS')
|
|
92
|
+
expect(currency.iso_code).to eq('CREDITS')
|
|
93
|
+
expect(currency.symbol).to eq('CR')
|
|
94
|
+
end
|
|
95
|
+
ensure
|
|
96
|
+
file.unlink
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "doesn't look up custom currencies when path is nil" do
|
|
100
|
+
expect(Money::Currency.find("CREDITS")).to eq(nil)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "can't override ISO currencies with custom currencies" do
|
|
104
|
+
file = custom_currency_file(
|
|
105
|
+
"usd" => {
|
|
106
|
+
"iso_code" => "USD",
|
|
107
|
+
"name" => "Fake Dollar",
|
|
108
|
+
"symbol" => "FAKE",
|
|
109
|
+
"disambiguate_symbol" => "FAKE",
|
|
110
|
+
"subunit_to_unit" => 1,
|
|
111
|
+
"smallest_denomination" => 1,
|
|
112
|
+
"decimal_mark" => "."
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
configure(experimental_custom_currency_path: file.path) do
|
|
117
|
+
currency = Money::Currency.new('USD')
|
|
118
|
+
expect(currency.name).to eq('United States Dollar')
|
|
119
|
+
expect(currency.symbol).to eq('$')
|
|
120
|
+
end
|
|
121
|
+
ensure
|
|
122
|
+
file.unlink
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "can't override crypto currencies with custom currencies" do
|
|
126
|
+
file = custom_currency_file(
|
|
127
|
+
"usdc" => {
|
|
128
|
+
"iso_code" => "USDC",
|
|
129
|
+
"name" => "Fake USDC",
|
|
130
|
+
"symbol" => "FAKE",
|
|
131
|
+
"disambiguate_symbol" => "FAKE",
|
|
132
|
+
"subunit_to_unit" => 1,
|
|
133
|
+
"smallest_denomination" => 1,
|
|
134
|
+
"decimal_mark" => "."
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
configure(experimental_crypto_currencies: true, experimental_custom_currency_path: file.path) do
|
|
139
|
+
currency = Money::Currency.new('USDC')
|
|
140
|
+
expect(currency.name).to eq('USD Coin')
|
|
141
|
+
expect(currency.symbol).to eq('USDC')
|
|
142
|
+
end
|
|
143
|
+
ensure
|
|
144
|
+
file.unlink
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "loads custom currencies end-to-end from a YAML file" do
|
|
148
|
+
file = custom_currency_file(mock_custom_currency)
|
|
149
|
+
|
|
150
|
+
configure(experimental_custom_currency_path: file.path) do
|
|
151
|
+
money = Money.new(500, "CREDITS")
|
|
152
|
+
expect(money.currency.iso_code).to eq("CREDITS")
|
|
153
|
+
expect(money.currency.symbol).to eq("CR")
|
|
154
|
+
expect(money.currency.name).to eq("Loyalty Points")
|
|
155
|
+
expect(money.value).to eq(500)
|
|
156
|
+
end
|
|
157
|
+
ensure
|
|
158
|
+
file.unlink
|
|
159
|
+
end
|
|
71
160
|
end
|
|
72
161
|
|
|
73
162
|
describe ".find" do
|
|
@@ -95,6 +184,21 @@ RSpec.describe "Currency" do
|
|
|
95
184
|
expect(Money::Currency.find('USDC')).to eq(nil)
|
|
96
185
|
end
|
|
97
186
|
end
|
|
187
|
+
|
|
188
|
+
it "returns custom currency when path is set" do
|
|
189
|
+
file = custom_currency_file(mock_custom_currency)
|
|
190
|
+
|
|
191
|
+
configure(experimental_custom_currency_path: file.path) do
|
|
192
|
+
expect(Money::Currency.find('CREDITS')).not_to eq(nil)
|
|
193
|
+
expect(Money::Currency.find('CREDITS').iso_code).to eq('CREDITS')
|
|
194
|
+
end
|
|
195
|
+
ensure
|
|
196
|
+
file.unlink
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "returns nil for custom currency when path is not set" do
|
|
200
|
+
expect(Money::Currency.find('CREDITS')).to eq(nil)
|
|
201
|
+
end
|
|
98
202
|
end
|
|
99
203
|
|
|
100
204
|
describe ".find!" do
|
|
@@ -122,6 +226,20 @@ RSpec.describe "Currency" do
|
|
|
122
226
|
end
|
|
123
227
|
end
|
|
124
228
|
|
|
229
|
+
describe ".custom_currencies" do
|
|
230
|
+
after { Money::Currency.reset_custom_currencies }
|
|
231
|
+
|
|
232
|
+
it "loads custom currencies from the loader" do
|
|
233
|
+
allow(Money::Currency::Loader).to receive(:load_custom_currencies)
|
|
234
|
+
.with('/tmp/custom.yml')
|
|
235
|
+
.and_return(mock_custom_currency)
|
|
236
|
+
|
|
237
|
+
expect(Money::Currency.custom_currencies('/tmp/custom.yml')).to eq(mock_custom_currency)
|
|
238
|
+
expect(Money::Currency.custom_currencies('/tmp/custom.yml')).to eq(mock_custom_currency) # Second call to verify caching
|
|
239
|
+
expect(Money::Currency::Loader).to have_received(:load_custom_currencies).with('/tmp/custom.yml').once
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
125
243
|
CURRENCY_DATA.each do |attribute, value|
|
|
126
244
|
describe "##{attribute}" do
|
|
127
245
|
it 'returns the correct value' do
|
data/spec/spec_helper.rb
CHANGED
|
@@ -75,17 +75,27 @@ RSpec::Matchers.define :quack_like do
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
def configure(default_currency: nil, legacy_json_format: nil, experimental_crypto_currencies: nil)
|
|
78
|
+
def configure(default_currency: nil, legacy_json_format: nil, experimental_crypto_currencies: nil, experimental_custom_currency_path: nil, default_allocation_strategy: nil)
|
|
79
79
|
Money::Config.current = Money::Config.new.tap do |config|
|
|
80
80
|
config.default_currency = default_currency if default_currency
|
|
81
81
|
config.legacy_json_format! if legacy_json_format
|
|
82
|
-
config.experimental_crypto_currencies
|
|
82
|
+
config.experimental_crypto_currencies = experimental_crypto_currencies unless experimental_crypto_currencies.nil?
|
|
83
|
+
config.experimental_custom_currency_path = experimental_custom_currency_path if experimental_custom_currency_path
|
|
84
|
+
config.default_allocation_strategy = default_allocation_strategy if default_allocation_strategy
|
|
83
85
|
end
|
|
84
|
-
Money::Currency.reset_loaded_currencies if experimental_crypto_currencies == false
|
|
85
86
|
|
|
86
87
|
yield
|
|
87
88
|
ensure
|
|
88
89
|
Money::Config.reset_current
|
|
90
|
+
Money::Currency.reset_loaded_currencies
|
|
91
|
+
Money::Currency.reset_custom_currencies if experimental_custom_currency_path
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def custom_currency_file(currencies_hash)
|
|
95
|
+
file = Tempfile.new(['custom_currencies', '.yml'])
|
|
96
|
+
file.write(currencies_hash.to_yaml)
|
|
97
|
+
file.close
|
|
98
|
+
file
|
|
89
99
|
end
|
|
90
100
|
|
|
91
101
|
def yaml_load(yaml)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shopify-money
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shopify Inc
|
|
@@ -252,7 +252,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
252
252
|
- !ruby/object:Gem::Version
|
|
253
253
|
version: '0'
|
|
254
254
|
requirements: []
|
|
255
|
-
rubygems_version: 4.0.
|
|
255
|
+
rubygems_version: 4.0.10
|
|
256
256
|
specification_version: 4
|
|
257
257
|
summary: Shopify's money gem
|
|
258
258
|
test_files: []
|