diff --git a/app/models/solidus_stripe/gateway.rb b/app/models/solidus_stripe/gateway.rb index 0deba9fd..b4e17488 100644 --- a/app/models/solidus_stripe/gateway.rb +++ b/app/models/solidus_stripe/gateway.rb @@ -39,6 +39,70 @@ def void(transaction_id, source, options = {}) def credit(money, source, transaction_id, options = {}) ActiveMerchant::Billing::Response.new() end + + module MoneyToStripeAmountConverter + extend self + + ZERO_DECIMAL_CURRENCIES = %w[ + BIF + CLP + DJF + GNF + JPY + KMF + KRW + MGA + PYG + RWF + UGX + VND + VUV + XAF + XOF + XPF + ].freeze + + THREE_DECIMAL_CURRENCIES = %w[ + BHD + JOD + KWD + OMR + TND + ].freeze + + # special currencies that are represented in cents but + # should be divisible by 100, thus making them integer only. + DIVISIBLE_BY_100 = %w[ + HUF + TWD + UGX + ].freeze + + # Solidus will provide a "fractional" amount, that is specific for each currency + # following the configurationo defined in the Money gem. + # + # Stripe uses the "smallest currency unit", + # (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency) + # https://stripe.com/docs/currencies#zero-decimal + # + # We need to ensure the fractional amount is considering the same number of decimals. + def to_stripe_amount(fractional, currency) + money_subunit_to_unit = ::Money::Currency.new(currency).subunit_to_unit + stripe_subunit_to_unit = + case currency.to_s.upcase + when *ZERO_DECIMAL_CURRENCIES then 1 + when *THREE_DECIMAL_CURRENCIES then 1000 + when *DIVISIBLE_BY_100 then 100 + else 100 + end + + if stripe_subunit_to_unit == money_subunit_to_unit + fractional + else + (fractional / money_subunit_to_unit.to_d) * stripe_subunit_to_unit + end + end + end end end # rubocop:enable Style/MethodCallWithoutArgsParentheses, Lint/UnusedMethodArgument diff --git a/spec/models/solidus_stripe/gateway_spec.rb b/spec/models/solidus_stripe/gateway_spec.rb new file mode 100644 index 00000000..1facb795 --- /dev/null +++ b/spec/models/solidus_stripe/gateway_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'solidus_stripe_spec_helper' + +# rubocop:disable Style/NumericLiterals +RSpec.describe SolidusStripe::Gateway do + describe SolidusStripe::Gateway::MoneyToStripeAmountConverter do + describe '.to_stripe_amount' do + it 'converts to the fractional expected by stripe' do # rubocop:disable RSpec/MultipleExpectations + (described_class::ZERO_DECIMAL_CURRENCIES - %w[MGA]).each do |currency| + expect([currency, described_class.to_stripe_amount(12345, currency).to_i]).to eq([currency, 12345]) + end + + %w[USD EUR ILS MXN TWD].each do |currency| # 2 decimals on both sides, default case + expect([currency, described_class.to_stripe_amount(123_45, currency).to_i]).to eq([currency, 123_45]) + end + + described_class::THREE_DECIMAL_CURRENCIES.each do |currency| + expect([currency, described_class.to_stripe_amount(12_345, currency).to_i]).to eq([currency, 12_345]) + end + + # Special cases + expect(['MGA', described_class.to_stripe_amount(255, 'MGA').to_i]).to eq(['MGA', 51]) + expect(['HUF', described_class.to_stripe_amount(1_2345, 'HUF').to_i]).to eq(['HUF', 12_345_00]) + expect(['UGX', described_class.to_stripe_amount(12_3450_00, 'UGX').to_i]).to eq(['UGX', 123_450_00]) + expect(['TWD', described_class.to_stripe_amount(1_2345_00, 'TWD').to_i]).to eq(['TWD', 12_345_00]) + end + end + end +end +# rubocop:enable Style/NumericLiterals