simple_wallet 0.1.1 → 0.1.2
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/app/services/simple_wallet/account_crediting_service.rb +55 -0
- data/app/services/simple_wallet/account_debiting_service.rb +81 -0
- data/app/services/simple_wallet/service.rb +35 -0
- data/config/locales/simple_wallet.en.yml +4 -0
- data/lib/simple_wallet/version.rb +1 -1
- data/test/dummy/log/test.log +10726 -0
- data/test/services/account_crediting_service_test.rb +113 -0
- data/test/services/account_debiting_service_test.rb +158 -0
- data/test/test_helper.rb +1 -0
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6eac1eb277d4d4262cd05c2ced9a10c7f790613a97fb3385e496953cfd7764a
|
|
4
|
+
data.tar.gz: c4147c19783452b10af6c63bd97e50c4b2c75e70032129e4e3aa5f734630d151
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e6c8f15d40e3d4c9612ace9b5e14ba6aadc55dd21b2fc807b8c3326d21ef83532cbbc21edad53ed2ec1e742aaae23af3ae51fbb7ffe7fa47fa0936ca1c9fed2
|
|
7
|
+
data.tar.gz: dacc153a8da1661f1cecc050b995740889d35359fcc1de4827d0ff28b573a8b8dd7a7bce895079ac2a8dc783558aa671eb1dcb4eedd605b92d295976d18db91e
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module SimpleWallet
|
|
2
|
+
class AccountCreditingService < Service
|
|
3
|
+
validates :account, presence: true
|
|
4
|
+
validates :amount, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
|
5
|
+
|
|
6
|
+
def initialize(account:, amount: nil, source: nil, note: nil)
|
|
7
|
+
@account = account
|
|
8
|
+
@amount = amount
|
|
9
|
+
@source = source
|
|
10
|
+
@note = note
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Use set_transaction_isolation_level: false to avoid getting this exception:
|
|
14
|
+
# ActiveRecord::TransactionIsolationError (cannot set isolation when joining a transaction)
|
|
15
|
+
def credit(set_transaction_isolation_level: true)
|
|
16
|
+
# Setting transaction isolation level in tests is problematic due to nested transactions :|
|
|
17
|
+
isolation_level = (::Rails.env.test? || !set_transaction_isolation_level) ? nil : :serializable
|
|
18
|
+
|
|
19
|
+
ActiveRecord::Base.transaction(isolation: isolation_level) do
|
|
20
|
+
return false unless valid?
|
|
21
|
+
unless crediting_transaction.valid?
|
|
22
|
+
copy_errors_from(crediting_transaction)
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
crediting_transaction.save!
|
|
27
|
+
after_operation
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
attr_reader :account, :source, :amount, :note
|
|
36
|
+
|
|
37
|
+
def crediting_transaction
|
|
38
|
+
@crediting_transaction ||= ::SimpleWallet::Transaction::Credit.new(
|
|
39
|
+
account: account,
|
|
40
|
+
source: source,
|
|
41
|
+
pre_account_balance: account&.balance,
|
|
42
|
+
amount: amount,
|
|
43
|
+
note: note
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def after_operation
|
|
48
|
+
update_accounts_balance
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update_accounts_balance
|
|
52
|
+
account.recalculate_balance!
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module SimpleWallet
|
|
2
|
+
class AccountDebitingService < Service
|
|
3
|
+
validates :account, presence: true
|
|
4
|
+
validates :amount, presence: true, numericality: { only_integer: true, greater_than: 0 }
|
|
5
|
+
validate :account_has_sufficient_funds
|
|
6
|
+
|
|
7
|
+
def initialize(account:, amount: nil, source: nil, note: nil, up_to_account_balance: false)
|
|
8
|
+
@account = account
|
|
9
|
+
@amount = amount
|
|
10
|
+
@source = source
|
|
11
|
+
@up_to_account_balance = up_to_account_balance
|
|
12
|
+
@note = note
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Use set_transaction_isolation_level: false to avoid getting this exception:
|
|
16
|
+
# ActiveRecord::TransactionIsolationError (cannot set isolation when joining a transaction)
|
|
17
|
+
def debit(set_transaction_isolation_level: true)
|
|
18
|
+
# Checking account's balance should be wrapped in a transaction:
|
|
19
|
+
# Setting transaction isolation level in tests is problematic due to nested transactions :|
|
|
20
|
+
isolation_level = (Rails.env.test? || !set_transaction_isolation_level) ? nil : :serializable
|
|
21
|
+
|
|
22
|
+
ActiveRecord::Base.transaction(isolation: isolation_level) do
|
|
23
|
+
return false unless valid?
|
|
24
|
+
unless debiting_transaction.valid?
|
|
25
|
+
copy_errors_from(debiting_transaction)
|
|
26
|
+
return false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
debiting_transaction.save!
|
|
30
|
+
after_operation
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
attr_reader :account, :source, :amount, :note, :up_to_account_balance
|
|
39
|
+
|
|
40
|
+
def ensure_negative(amount_to_convert)
|
|
41
|
+
-(amount_to_convert.abs)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# If debiting up to account balance is allowed then we debit as much as we can.
|
|
45
|
+
# If not then we will return error on insufficient funds.
|
|
46
|
+
def actual_amount_to_debit
|
|
47
|
+
if up_to_account_balance
|
|
48
|
+
[ account.balance, amount ].min
|
|
49
|
+
else
|
|
50
|
+
amount
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def account_has_sufficient_funds
|
|
55
|
+
return if errors.include?(:account)
|
|
56
|
+
return if errors.include?(:amount)
|
|
57
|
+
|
|
58
|
+
if account.balance < actual_amount_to_debit
|
|
59
|
+
errors.add(:amount, t(".exceeds_balance"))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def debiting_transaction
|
|
64
|
+
@debiting_transaction ||= ::SimpleWallet::Transaction::Debit.new(
|
|
65
|
+
account: account,
|
|
66
|
+
source: source,
|
|
67
|
+
pre_account_balance: account.balance,
|
|
68
|
+
amount: ensure_negative(actual_amount_to_debit),
|
|
69
|
+
note: note
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def after_operation
|
|
74
|
+
update_accounts_balance
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def update_accounts_balance
|
|
78
|
+
account.recalculate_balance!
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module SimpleWallet
|
|
2
|
+
class Service
|
|
3
|
+
include ActiveModel::Validations
|
|
4
|
+
|
|
5
|
+
def errors_sentence
|
|
6
|
+
errors.full_messages.to_sentence
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Shamelessly stolen from:
|
|
10
|
+
# https://github.com/rails/rails/blob/fbe2433be6e052a1acac63c7faf287c52ed3c5ba/actionpack/lib/abstract_controller/translation.rb#L13
|
|
11
|
+
def t(key, **options)
|
|
12
|
+
if key.to_s.first == "."
|
|
13
|
+
path = self.class.name.underscore.tr("/", ".")
|
|
14
|
+
parent_class_path = self.class.superclass.name.underscore.tr("/", ".")
|
|
15
|
+
defaults = [ :"#{path}#{key}", :"#{parent_class_path}#{key}" ]
|
|
16
|
+
defaults << options[:default] if options[:default]
|
|
17
|
+
options[:default] = defaults.flatten
|
|
18
|
+
key = "#{path}#{key}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
options[:raise] = (Rails.env.development? || Rails.env.test?)
|
|
22
|
+
I18n.t(key, **options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
delegate :logger, to: :Rails
|
|
28
|
+
|
|
29
|
+
def copy_errors_from(errors_source)
|
|
30
|
+
errors_source.errors.each do |error|
|
|
31
|
+
errors.add(error.attribute, error.message)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|