gera 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +67 -0
- data/Rakefile +43 -0
- data/app/assets/config/gera_manifest.js +2 -0
- data/app/assets/javascripts/gera/application.js +14 -0
- data/app/assets/stylesheets/gera/application.css +15 -0
- data/app/authorizers/gera/application_authorizer.rb +6 -0
- data/app/authorizers/gera/currency_rate_authorizer.rb +9 -0
- data/app/authorizers/gera/currency_rate_mode_authorizer.rb +7 -0
- data/app/authorizers/gera/direction_rate_authorizer.rb +8 -0
- data/app/authorizers/gera/exchange_rate_authorizer.rb +8 -0
- data/app/authorizers/gera/payment_system_authorizer.rb +13 -0
- data/app/authorizers/gera/rate_source_authorizer.rb +7 -0
- data/app/controllers/gera/application_controller.rb +30 -0
- data/app/controllers/gera/currencies_controller.rb +8 -0
- data/app/controllers/gera/currency_rate_history_intervals_controller.rb +53 -0
- data/app/controllers/gera/currency_rate_mode_snapshots_controller.rb +83 -0
- data/app/controllers/gera/currency_rate_modes_controller.rb +60 -0
- data/app/controllers/gera/currency_rates_controller.rb +70 -0
- data/app/controllers/gera/direction_rate_history_intervals_controller.rb +62 -0
- data/app/controllers/gera/direction_rates_controller.rb +28 -0
- data/app/controllers/gera/exchange_rates_controller.rb +25 -0
- data/app/controllers/gera/external_rate_snapshots_controller.rb +35 -0
- data/app/controllers/gera/external_rates_controller.rb +28 -0
- data/app/controllers/gera/payment_systems_controller.rb +78 -0
- data/app/controllers/gera/rate_sources_controller.rb +8 -0
- data/app/decorators/gera/currency_rate_decorator.rb +148 -0
- data/app/decorators/gera/direction_rate_decorator.rb +47 -0
- data/app/decorators/gera/payment_system_decorator.rb +19 -0
- data/app/helpers/gera/application_helper.rb +110 -0
- data/app/helpers/gera/currency_rate_helper.rb +19 -0
- data/app/helpers/gera/currency_rate_modes_helper.rb +73 -0
- data/app/helpers/gera/direction_rate_helper.rb +36 -0
- data/app/helpers/gera/exchange_rates_helper.rb +21 -0
- data/app/jobs/gera/application_job.rb +6 -0
- data/app/models/concerns/gera/currency_pair_generator.rb +14 -0
- data/app/models/concerns/gera/currency_pair_support.rb +42 -0
- data/app/models/concerns/gera/currency_rate_mode_builder_support.rb +35 -0
- data/app/models/concerns/gera/direction_support.rb +14 -0
- data/app/models/concerns/gera/history_interval_concern.rb +46 -0
- data/app/models/gera/application_record.rb +8 -0
- data/app/models/gera/cbr_external_rate.rb +13 -0
- data/app/models/gera/cross_rate_mode.rb +13 -0
- data/app/models/gera/currency_rate.rb +78 -0
- data/app/models/gera/currency_rate_history_interval.rb +25 -0
- data/app/models/gera/currency_rate_history_interval_filter.rb +30 -0
- data/app/models/gera/currency_rate_mode.rb +20 -0
- data/app/models/gera/currency_rate_mode_snapshot.rb +26 -0
- data/app/models/gera/currency_rate_snapshot.rb +12 -0
- data/app/models/gera/direction.rb +44 -0
- data/app/models/gera/direction_rate.rb +136 -0
- data/app/models/gera/direction_rate_history_interval.rb +33 -0
- data/app/models/gera/direction_rate_history_interval_filter.rb +30 -0
- data/app/models/gera/direction_rate_snapshot.rb +7 -0
- data/app/models/gera/exchange_rate.rb +130 -0
- data/app/models/gera/external_rate.rb +38 -0
- data/app/models/gera/external_rate_snapshot.rb +20 -0
- data/app/models/gera/payment_system.rb +98 -0
- data/app/models/gera/rate_source.rb +77 -0
- data/app/models/gera/rate_source_auto.rb +25 -0
- data/app/models/gera/rate_source_bitfinex.rb +9 -0
- data/app/models/gera/rate_source_cbr.rb +13 -0
- data/app/models/gera/rate_source_cbr_avg.rb +6 -0
- data/app/models/gera/rate_source_exmo.rb +9 -0
- data/app/models/gera/rate_source_manual.rb +13 -0
- data/app/views/application/_exchange_rate_monitor.slim +1 -0
- data/app/views/gera/application/_attributes_table.slim +5 -0
- data/app/views/gera/application/_currencies_table.slim +18 -0
- data/app/views/gera/application/_currency_rate_mode_snapshots.slim +6 -0
- data/app/views/gera/application/_direction_rate.slim +6 -0
- data/app/views/gera/application/_directions_submenu.slim +11 -0
- data/app/views/gera/application/_directions_table.slim +15 -0
- data/app/views/gera/application/_exchange_rate.slim +6 -0
- data/app/views/gera/application/_external_rate.slim +6 -0
- data/app/views/gera/application/_header.slim +12 -0
- data/app/views/gera/application/_table.slim +28 -0
- data/app/views/gera/application/_topnav.slim +21 -0
- data/app/views/gera/application/direction_details.slim +75 -0
- data/app/views/gera/application/page.html.slim +1 -0
- data/app/views/gera/currencies/index.html.slim +20 -0
- data/app/views/gera/currency_rate_history_intervals/_filter.slim +11 -0
- data/app/views/gera/currency_rate_history_intervals/index.slim +39 -0
- data/app/views/gera/currency_rate_mode_snapshots/_snapshot.slim +20 -0
- data/app/views/gera/currency_rate_mode_snapshots/edit.html.slim +30 -0
- data/app/views/gera/currency_rate_modes/_cross_rate_mode_fields.slim +21 -0
- data/app/views/gera/currency_rate_modes/_currency_rate_mode.slim +7 -0
- data/app/views/gera/currency_rate_modes/_form.slim +29 -0
- data/app/views/gera/currency_rate_modes/edit.slim +11 -0
- data/app/views/gera/currency_rate_modes/index.html.slim +12 -0
- data/app/views/gera/currency_rates/_currency_rate.slim +22 -0
- data/app/views/gera/currency_rates/_currency_rate_humanized.slim +35 -0
- data/app/views/gera/currency_rates/diff.slim +12 -0
- data/app/views/gera/currency_rates/index.slim +27 -0
- data/app/views/gera/currency_rates/modes.slim +14 -0
- data/app/views/gera/currency_rates/show.slim +4 -0
- data/app/views/gera/direction_rate_history_intervals/_filter.slim +11 -0
- data/app/views/gera/direction_rate_history_intervals/index.slim +41 -0
- data/app/views/gera/direction_rates/_direction_rate_example_calculator.slim +28 -0
- data/app/views/gera/direction_rates/diff.slim +19 -0
- data/app/views/gera/direction_rates/index.slim +26 -0
- data/app/views/gera/direction_rates/legacy.html.slim +5 -0
- data/app/views/gera/direction_rates/minimals.slim +5 -0
- data/app/views/gera/direction_rates/show.slim +14 -0
- data/app/views/gera/external_rate_snapshots/index.slim +25 -0
- data/app/views/gera/external_rate_snapshots/show.slim +22 -0
- data/app/views/gera/external_rates/_flat_list.slim +22 -0
- data/app/views/gera/external_rates/index.html.slim +28 -0
- data/app/views/gera/external_rates/show.slim +1 -0
- data/app/views/gera/payment_systems/_fields.slim +4 -0
- data/app/views/gera/payment_systems/_fields_table.slim +18 -0
- data/app/views/gera/payment_systems/_form.slim +9 -0
- data/app/views/gera/payment_systems/edit.slim +7 -0
- data/app/views/gera/payment_systems/index.slim +3 -0
- data/app/views/gera/payment_systems/new.slim +9 -0
- data/app/views/gera/payment_systems/show.slim +1 -0
- data/app/views/gera/rate_sources/_list.slim +13 -0
- data/app/views/gera/rate_sources/_rate_source.slim +23 -0
- data/app/views/gera/rate_sources/index.html.slim +6 -0
- data/app/views/layouts/application.html.slim +15 -0
- data/app/views/layouts/gera/blank.html.slim +10 -0
- data/app/workers/concerns/gera/rates_worker.rb +87 -0
- data/app/workers/gera/bitfinex_rates_worker.rb +65 -0
- data/app/workers/gera/cbr_avg_rates_worker.rb +42 -0
- data/app/workers/gera/cbr_rates_worker.rb +192 -0
- data/app/workers/gera/create_history_intervals_worker.rb +38 -0
- data/app/workers/gera/currency_rates_worker.rb +64 -0
- data/app/workers/gera/directions_rates_worker.rb +51 -0
- data/app/workers/gera/exmo_rates_worker.rb +62 -0
- data/app/workers/gera/purge_currency_rates_worker.rb +21 -0
- data/app/workers/gera/purge_direction_rates_worker.rb +30 -0
- data/config/currencies.yml +559 -0
- data/config/routes.rb +30 -0
- data/db/migrate/20180912130000_setup.rb +228 -0
- data/db/migrate/20190314114844_add_is_available_to_payment_systems.rb +7 -0
- data/db/migrate/20190315113046_add_icon_url_to_payment_systems.rb +7 -0
- data/db/migrate/20190315132137_add_commission_to_payment_systems.rb +7 -0
- data/lib/banks/currency_exchange.rb +12 -0
- data/lib/builders/currency_rate_auto_builder.rb +38 -0
- data/lib/builders/currency_rate_builder.rb +60 -0
- data/lib/builders/currency_rate_cross_builder.rb +70 -0
- data/lib/builders/currency_rate_direct_builder.rb +26 -0
- data/lib/gera.rb +32 -0
- data/lib/gera/bitfinex_fetcher.rb +36 -0
- data/lib/gera/configuration.rb +47 -0
- data/lib/gera/currencies_purger.rb +28 -0
- data/lib/gera/currency_pair.rb +93 -0
- data/lib/gera/engine.rb +14 -0
- data/lib/gera/mathematic.rb +156 -0
- data/lib/gera/money_support.rb +89 -0
- data/lib/gera/numeric.rb +25 -0
- data/lib/gera/railtie.rb +8 -0
- data/lib/gera/rate.rb +22 -0
- data/lib/gera/rate_from_multiplicator.rb +54 -0
- data/lib/gera/repositories/currency_rate_modes_repository.rb +37 -0
- data/lib/gera/repositories/currency_rates_repository.rb +25 -0
- data/lib/gera/repositories/direction_rates_repository.rb +46 -0
- data/lib/gera/repositories/exchange_rates_repository.rb +23 -0
- data/lib/gera/repositories/payment_systems_repository.rb +21 -0
- data/lib/gera/repositories/universe.rb +47 -0
- data/lib/gera/version.rb +3 -0
- data/lib/tasks/auto_generate_diagram.rake +6 -0
- data/lib/tasks/gera_tasks.rake +4 -0
- metadata +765 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'currency_rate_builder'
|
2
|
+
|
3
|
+
module Gera
|
4
|
+
class CurrencyRateDirectBuilder < CurrencyRateBuilder
|
5
|
+
attribute :source #, RateSource
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def build
|
10
|
+
raise Error, "Источником (#{source}) валюта #{currency_pair.cur_from} не поддерживается" unless source.is_currency_supported? currency_pair.cur_from
|
11
|
+
raise Error, "Источником (#{source}) валюта #{currency_pair.cur_to} не поддерживается" unless source.is_currency_supported? currency_pair.cur_to
|
12
|
+
|
13
|
+
external_rate = source.find_rate_by_currency_pair currency_pair
|
14
|
+
|
15
|
+
raise Error, "В источнике (#{source}) не найден курс для #{currency_pair}" unless external_rate.present?
|
16
|
+
|
17
|
+
CurrencyRate.new(
|
18
|
+
currency_pair: currency_pair,
|
19
|
+
rate_value: external_rate.rate_value,
|
20
|
+
rate_source: source,
|
21
|
+
mode: :direct,
|
22
|
+
external_rate_id: external_rate.id
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/gera.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'money'
|
2
|
+
require 'require_all'
|
3
|
+
require 'percentable'
|
4
|
+
|
5
|
+
require 'sidekiq'
|
6
|
+
require 'auto_logger'
|
7
|
+
|
8
|
+
require "gera/version"
|
9
|
+
require 'gera/numeric'
|
10
|
+
|
11
|
+
require "gera/configuration"
|
12
|
+
require "gera/mathematic"
|
13
|
+
require 'gera/bitfinex_fetcher'
|
14
|
+
require 'gera/currency_pair'
|
15
|
+
require 'gera/rate'
|
16
|
+
require 'gera/money_support'
|
17
|
+
|
18
|
+
|
19
|
+
module Gera
|
20
|
+
CURRENCIES_PATH = File.expand_path("../config/currencies.yml", __dir__)
|
21
|
+
|
22
|
+
extend Configuration
|
23
|
+
end
|
24
|
+
|
25
|
+
require_rel 'banks'
|
26
|
+
require_rel 'builders'
|
27
|
+
require_rel 'gera/repositories'
|
28
|
+
|
29
|
+
if defined? ::Rails::Railtie
|
30
|
+
require "gera/engine"
|
31
|
+
require "gera/railtie"
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rest-client'
|
4
|
+
require 'virtus'
|
5
|
+
|
6
|
+
module Gera
|
7
|
+
class BitfinexFetcher
|
8
|
+
API_URL = 'https://api.bitfinex.com/v1/pubticker/'
|
9
|
+
|
10
|
+
include Virtus.model strict: true
|
11
|
+
|
12
|
+
# Например btcusd
|
13
|
+
attribute :ticker, String
|
14
|
+
|
15
|
+
def perform
|
16
|
+
response = RestClient::Request.execute url: url, method: :get, verify_ssl: false
|
17
|
+
|
18
|
+
raise response.code unless response.code == 200
|
19
|
+
JSON.parse response.body
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def url
|
25
|
+
API_URL + ticker
|
26
|
+
end
|
27
|
+
|
28
|
+
def http
|
29
|
+
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
30
|
+
http.use_ssl = true
|
31
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
32
|
+
http
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Gera
|
2
|
+
# Gera configuration module.
|
3
|
+
# This is extended by Gera to provide configuration settings.
|
4
|
+
module Configuration
|
5
|
+
|
6
|
+
# Start a Gera configuration block in an initializer.
|
7
|
+
#
|
8
|
+
# example: Provide a default currency for the application
|
9
|
+
# Gera.configure do |config|
|
10
|
+
# config.default_currency = :eur
|
11
|
+
# end
|
12
|
+
def configure
|
13
|
+
yield self
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Class] Декоратор для PaymentSystem
|
17
|
+
mattr_accessor :payment_system_decorator
|
18
|
+
def payment_system_decorator
|
19
|
+
@@payment_system_decorator || PaymentSystemDecorator
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [Symbol] Валюта для кросс-расчетов по-умолчанию
|
23
|
+
mattr_accessor :default_cross_currency
|
24
|
+
@@default_cross_currency = :usd
|
25
|
+
|
26
|
+
def default_cross_currency
|
27
|
+
return @@default_cross_currency if @@default_cross_currency.is_a? Money::Currency
|
28
|
+
Money::Currency.find! @@default_cross_currency
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [Hash] Набор кросс-валют для расчета
|
32
|
+
mattr_accessor :cross_pairs
|
33
|
+
# В данном примере курс к KZT считать через RUB
|
34
|
+
@@cross_pairs = { kzt: :rub }
|
35
|
+
|
36
|
+
def cross_pairs
|
37
|
+
h = {}
|
38
|
+
@@cross_pairs.each do |k, v|
|
39
|
+
h[Money::Currency.find!(k)] = Money::Currency.find! v
|
40
|
+
end
|
41
|
+
h
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Пример:
|
47
|
+
# https://github.com/RubyMoney/money-rails/blob/master/lib/money-rails/configuration.rb
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Gera
|
2
|
+
module CurrenciesPurger
|
3
|
+
def self.purge_all(env)
|
4
|
+
raise unless env == Rails.env
|
5
|
+
|
6
|
+
if Rails.env.prodiction?
|
7
|
+
puts 'Disable all sidekiqs'
|
8
|
+
Sidekiq::Cron::Job.all.each(&:disable!)
|
9
|
+
sleep 2
|
10
|
+
end
|
11
|
+
|
12
|
+
DirectionRateSnapshot.batch_purge if DirectionRateSnapshot.table_exists?
|
13
|
+
DirectionRate.batch_purge
|
14
|
+
|
15
|
+
ExternalRate.batch_purge
|
16
|
+
ExternalRateSnapshot.batch_purge
|
17
|
+
|
18
|
+
CurrencyRate.batch_purge
|
19
|
+
RateSource.update_all actual_snapshot_id: nil
|
20
|
+
CurrencyRateSnapshot.batch_purge
|
21
|
+
|
22
|
+
if Rails.env.prodiction?
|
23
|
+
puts 'Enable all sidekiqs'
|
24
|
+
Sidekiq::Cron::Job.all.each(&:enable!)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
require 'money'
|
3
|
+
|
4
|
+
module Gera
|
5
|
+
# Валютная пара
|
6
|
+
#
|
7
|
+
class CurrencyPair
|
8
|
+
include Virtus.value_object strict: true
|
9
|
+
|
10
|
+
values do
|
11
|
+
attribute :cur_from, Money::Currency
|
12
|
+
attribute :cur_to, Money::Currency
|
13
|
+
end
|
14
|
+
|
15
|
+
# Варианты:
|
16
|
+
#
|
17
|
+
# new cur_from: :rub, cur_to: usd
|
18
|
+
# new :rub, :usd
|
19
|
+
# new 'rub/usd'
|
20
|
+
def initialize(*args)
|
21
|
+
if args.first.is_a? Hash
|
22
|
+
super(args.first).freeze
|
23
|
+
|
24
|
+
elsif args.count ==1
|
25
|
+
initialize(*args.first.split(/[\/_-]/)).freeze
|
26
|
+
|
27
|
+
elsif args.count == 2
|
28
|
+
super(cur_from: args[0], cur_to: args[1]).freeze
|
29
|
+
|
30
|
+
else
|
31
|
+
raise "WTF? #{args}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
delegate :include?, :first, :last, :second, :join, to: :to_a
|
36
|
+
|
37
|
+
def self.all
|
38
|
+
@all ||= Money::Currency.all.each_with_object([]) { |cur_from, list|
|
39
|
+
Money::Currency.all.each { |cur_to| list << CurrencyPair.new(cur_from, cur_to) }
|
40
|
+
}.freeze
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def inverse
|
48
|
+
self.class.new cur_to, cur_from
|
49
|
+
end
|
50
|
+
|
51
|
+
def cur_to=(value)
|
52
|
+
if value.is_a? Money::Currency
|
53
|
+
super value
|
54
|
+
else
|
55
|
+
super Money::Currency.find(value) || raise("Не известная валюта #{value} в cur_to")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def cur_from=(value)
|
60
|
+
if value.is_a? Money::Currency
|
61
|
+
super value
|
62
|
+
else
|
63
|
+
super Money::Currency.find(value) || raise("Не известная валюта #{value} в cur_from")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def change_to(cur)
|
68
|
+
CurrencyPair.new cur_from, cur
|
69
|
+
end
|
70
|
+
|
71
|
+
def change_from(cur)
|
72
|
+
CurrencyPair.new cur, cur_to
|
73
|
+
end
|
74
|
+
|
75
|
+
def same?
|
76
|
+
cur_from == cur_to
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_a
|
80
|
+
[cur_from, cur_to]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Для людей
|
84
|
+
def to_s
|
85
|
+
join '/'
|
86
|
+
end
|
87
|
+
|
88
|
+
# Для машин
|
89
|
+
def key
|
90
|
+
join '_'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/gera/engine.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Gera
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Gera
|
4
|
+
|
5
|
+
paths.add "app/workers", eager_load: true
|
6
|
+
paths.add "app/authorizers", eager_load: true
|
7
|
+
|
8
|
+
# Идея отсюда - https://github.com/thoughtbot/factory_bot_rails/pull/149/files?diff=split&short_path=04c6e90
|
9
|
+
#
|
10
|
+
initializer 'gera.factories', after: 'factory_bot.set_factory_paths' do
|
11
|
+
FactoryBot.definition_file_paths << File.expand_path('../../../factories', __FILE__) if defined?(FactoryBot)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
3
|
+
module Gera
|
4
|
+
module Mathematic
|
5
|
+
class Result
|
6
|
+
include Virtus.model strict: true
|
7
|
+
|
8
|
+
# Входные параметры
|
9
|
+
#
|
10
|
+
attribute :base_rate, Float # Базовая ставка
|
11
|
+
attribute :comission, Float # Комиссия обменника
|
12
|
+
attribute :ps_interest, Float # Интерес платежной системы (%)
|
13
|
+
attribute :income_amount, Float # Сумма во входящей валюте которую клиент закинул
|
14
|
+
|
15
|
+
# Расчетные параметры
|
16
|
+
#
|
17
|
+
attribute :finite_rate, Float # Конечная ставка
|
18
|
+
attribute :finite_amount, Float # сумма которую получет пользователь на счет
|
19
|
+
attribute :ps_amount, Float # Сумма интереса платежной системы в выходящей валюте
|
20
|
+
attribute :outcome_amount, Float # Сумма которая уйдет с выходящего кошелька (что получит клиент + комиссия ПС)
|
21
|
+
attribute :profit_amount, Float # чистая прибыл обменика
|
22
|
+
end
|
23
|
+
|
24
|
+
def calculate_profits(base_rate:,
|
25
|
+
comission:,
|
26
|
+
ps_interest:,
|
27
|
+
income_amount:
|
28
|
+
)
|
29
|
+
|
30
|
+
finite_rate = calculate_finite_rate base_rate, comission
|
31
|
+
finite_amount = income_amount * finite_rate
|
32
|
+
ps_amount = ps_interest.percent_of finite_amount
|
33
|
+
outcome_amount = ps_amount + finite_amount
|
34
|
+
|
35
|
+
Result.new(
|
36
|
+
base_rate: base_rate,
|
37
|
+
comission: comission,
|
38
|
+
ps_interest: ps_interest,
|
39
|
+
income_amount: income_amount,
|
40
|
+
|
41
|
+
finite_rate: finite_rate,
|
42
|
+
finite_amount: finite_amount, # сумма которую получет пользователь на счет
|
43
|
+
ps_amount: ps_amount,
|
44
|
+
outcome_amount: outcome_amount, # сумма которую нужно перевести чтобы хватило и платежной системе и пользователю сколько обещали
|
45
|
+
profit_amount: income_amount - outcome_amount / base_rate
|
46
|
+
).freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
# Отдает базовую тавку из конечной и комиссии
|
50
|
+
#
|
51
|
+
def calculate_base_rate(finite_rate, comission)
|
52
|
+
if finite_rate >= 1
|
53
|
+
100.0 * finite_rate / (100.0 - comission.to_f)
|
54
|
+
else
|
55
|
+
100.0 * finite_rate / (100.0 - comission.to_f)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Отдает комиссию исходя из конечной и базовой ставки
|
60
|
+
#
|
61
|
+
def calculate_comission(finite_rate, base_rate)
|
62
|
+
if finite_rate <= 1
|
63
|
+
a = 1.0 / finite_rate.to_f - 1.0 / base_rate.to_f
|
64
|
+
(a.as_percentage_of(1.0 / finite_rate.to_f).to_f * 100)
|
65
|
+
else
|
66
|
+
(base_rate.to_f - finite_rate.to_f).as_percentage_of(base_rate.to_f).to_f * 100
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Конечная ставка из базовой
|
71
|
+
#
|
72
|
+
def calculate_finite_rate(base_rate, comission)
|
73
|
+
if base_rate <= 1
|
74
|
+
base_rate.to_f * (1.0 - comission.to_f/100)
|
75
|
+
else
|
76
|
+
base_rate - comission.to_percent
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# На сколько абсолютных процентов отличается число b от числа a
|
81
|
+
#
|
82
|
+
def diff_percents(a, b)
|
83
|
+
percent_value = (a.to_d / 100.0)
|
84
|
+
res = 100.0 - (b.to_d / percent_value)
|
85
|
+
res = -res if res < 0
|
86
|
+
res.to_percent
|
87
|
+
end
|
88
|
+
|
89
|
+
# Отдает сумму которую получим в результате обмена
|
90
|
+
# rate - курсовой мультимликатор
|
91
|
+
# amount - входящая сумма
|
92
|
+
# to_currency - валютя в которой нужно получить результат
|
93
|
+
#
|
94
|
+
def money_exchange(rate, amount, to_currency)
|
95
|
+
fractional = BigDecimal(amount.fractional.to_s) / (
|
96
|
+
BigDecimal(amount.currency.subunit_to_unit.to_s) /
|
97
|
+
BigDecimal(to_currency.subunit_to_unit.to_s)
|
98
|
+
)
|
99
|
+
|
100
|
+
res = fractional * rate
|
101
|
+
res = res.round(0, BigDecimal::ROUND_DOWN)
|
102
|
+
Money.new(res, to_currency)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Обратный обмен. Когда у нас есть курс и сумма которую мы хотим
|
106
|
+
# получить и мы вычисляем из какой суммы на входе она получится
|
107
|
+
#
|
108
|
+
def money_reverse_exchange(rate, amount, to_currency)
|
109
|
+
fractional = BigDecimal(amount.fractional.to_s) / (
|
110
|
+
BigDecimal(amount.currency.subunit_to_unit.to_s) /
|
111
|
+
BigDecimal(to_currency.subunit_to_unit.to_s)
|
112
|
+
)
|
113
|
+
|
114
|
+
res = fractional / rate
|
115
|
+
res = res.round(0, BigDecimal::ROUND_UP)
|
116
|
+
Money.new(res, to_currency)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Рассчет суммы с комиссей. Процент комиссии считается от изначальной суммы.
|
120
|
+
# Комиссия указывается в процентах.
|
121
|
+
#
|
122
|
+
def calculate_total_using_regular_comission(amount, comission)
|
123
|
+
amount + (amount * comission / 100.0)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Рассчет суммы с обратной комиссей. Процент комиссии считается от итоговой суммы
|
127
|
+
# Комиссия указывается в процентах.
|
128
|
+
#
|
129
|
+
def calculate_total_using_reverse_comission(amount, comission)
|
130
|
+
100.0 / (100 - comission) * amount
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
# Отдает комиссию исходя из конечной и базовой ставки
|
136
|
+
#
|
137
|
+
def modern_base_rate_percent(finite_rate, base_rate)
|
138
|
+
if finite_rate <= 1
|
139
|
+
(1.0/finite_rate.to_f - 1.0/base_rate.to_f) / (1.0/base_rate.to_f/100)
|
140
|
+
else
|
141
|
+
(base_rate - finite_rate.to_f) / (base_rate.to_f/100)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def modern_calcualte_finite_rate(base_rate, comission)
|
146
|
+
# Через обратный процент
|
147
|
+
# base_rate / (1.0 + comission/100.0)
|
148
|
+
#
|
149
|
+
if base_rate <= 1
|
150
|
+
1.0 / ( 1.0 / base_rate + comission.to_percent )
|
151
|
+
else
|
152
|
+
base_rate - comission.to_percent
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Gera
|
2
|
+
module MoneySupport
|
3
|
+
def self.init
|
4
|
+
# Убираем все валюты
|
5
|
+
Money::Currency.all.each do |cur|
|
6
|
+
Money::Currency.unregister cur.id.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
Psych.load( File.read CURRENCIES_PATH ).each { |key, cur| Money::Currency.register cur.symbolize_keys }
|
10
|
+
|
11
|
+
# Создают константы-валюты, типа RUB, USD и тп
|
12
|
+
Money::Currency.all.each do |cur|
|
13
|
+
Object.const_set cur.iso_code, cur
|
14
|
+
end
|
15
|
+
|
16
|
+
# Gera::Hooks.init
|
17
|
+
end
|
18
|
+
|
19
|
+
module CurrencyExtend
|
20
|
+
# TODO Вынести в app
|
21
|
+
attr_reader :local_id
|
22
|
+
|
23
|
+
# TODO отказаться
|
24
|
+
attr_reader :authorized_round
|
25
|
+
|
26
|
+
# TODO Вынести в базу в app
|
27
|
+
def minimal_output_value
|
28
|
+
Money.from_amount @minimal_output_value, self
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO Вынести в базу в app
|
32
|
+
def minimal_input_value
|
33
|
+
Money.from_amount @minimal_input_value, self
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_crypto?
|
37
|
+
!!@is_crypto
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize_data!
|
41
|
+
super
|
42
|
+
|
43
|
+
data = self.class.table[@id]
|
44
|
+
|
45
|
+
@is_crypto = data[:is_crypto]
|
46
|
+
@local_id = data[:local_id]
|
47
|
+
@minimal_input_value = data[:minimal_input_value]
|
48
|
+
@minimal_output_value = data[:minimal_output_value]
|
49
|
+
@authorized_round = data[:authorized_round]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ::Money::Currency
|
54
|
+
prepend CurrencyExtend
|
55
|
+
|
56
|
+
def self.find!(query)
|
57
|
+
find(query) || raise("No found currency (#{query.inspect})")
|
58
|
+
end
|
59
|
+
|
60
|
+
# TODO Вынести в app
|
61
|
+
#
|
62
|
+
def self.find_by_local_id(local_id)
|
63
|
+
local_id = local_id.to_i
|
64
|
+
id, _ = self.table.find{|key, currency| currency[:local_id] == local_id}
|
65
|
+
new(id)
|
66
|
+
rescue UnknownCurrency
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.all_crypto
|
71
|
+
@all_crypto ||= all.select(&:is_crypto?)
|
72
|
+
end
|
73
|
+
|
74
|
+
def zero_money
|
75
|
+
Money.from_amount(0, self)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class ::Money
|
80
|
+
# TODO Отказаться
|
81
|
+
# Это сумма, до которой разрешено безопасное округление
|
82
|
+
# при приеме суммы от клиента
|
83
|
+
def authorized_round
|
84
|
+
return self unless currency.authorized_round.is_a? Numeric
|
85
|
+
Money.from_amount to_f.round(currency.authorized_round), currency
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|