1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# frozen_string_literal: true
require_relative "expiring_lock"
require_relative "transaction"
class LowBalance
def self.for(customer, transaction_amount=0)
return Locked.new unless customer.registered?
ExpiringLock.new(
"jmp_customer_low_balance-#{customer.billing_customer_id}",
expiry: 60 * 60 * 24 * 7
).with(-> { Locked.new }) do
customer.billing_customer.then do |customer|
self.for_no_lock(customer, transaction_amount)
end
end
end
def self.for_no_lock(customer, transaction_amount=0, auto: true)
if auto && (customer.auto_top_up_amount.positive? || transaction_amount.positive?)
AutoTopUp.for(customer, transaction_amount)
else
customer.btc_addresses.then do |btc_addresses|
new(customer, btc_addresses, transaction_amount)
end
end
end
def initialize(customer, btc_addresses, transaction_amount=0)
@customer = customer
@btc_addresses = btc_addresses
@transaction_amount = transaction_amount
end
def notify!
m = Blather::Stanza::Message.new
m.from = CONFIG[:notify_from]
m.body =
"Your balance of $#{'%.4f' % @customer.balance} is low." \
"#{pending_cost_for_notification}" \
"#{btc_addresses_for_notification}"
@customer.stanza_to(m)
EMPromise.resolve(0)
end
def pending_cost_for_notification
return unless (@transaction_amount.positive? && @transaction_amount > @customer.balance)
"\nYou tried to perform an activity that cost #{@transaction_amount}"\
"You need an additional #{'%.4f' % (@transaction_amount - @customer.balance)} "\
"to perform this activity."
end
def btc_addresses_for_notification
return if @btc_addresses.empty?
"\nYou can buy credit by sending any amount of Bitcoin to one of " \
"these addresses:\n#{@btc_addresses.join("\n")}"
end
class AutoTopUp
def self.for(customer, target=0)
customer.payment_methods.then(&:default_payment_method).then do |method|
blocked?(method).then do |block|
next AutoTopUp.new(customer, method, target) if block.zero?
log.info("#{customer.customer_id} auto top up blocked")
LowBalance.for_no_lock(customer, target, auto: false)
end
end
end
def self.blocked?(method)
return EMPromise.resolve(1) if method.nil?
REDIS.exists(
"jmp_auto_top_up_block-#{method&.unique_number_identifier}"
)
end
def initialize(customer, method=nil, target=0, margin=10)
@customer = customer
@method = method
@target = target
@margin = margin
@message = Blather::Stanza::Message.new
@message.from = CONFIG[:notify_from]
end
def top_up_amount()
expected_balance = @customer.balance + @customer.auto_top_up_amount # @cutomer.auto_top_up is the adjusted auto_to_up
if expected_balance < @target
deficit = @target - expected_balance
@customer.auto_top_up_amount + deficit + @margin
else
@customer.auto_top_up_amount
end
end
def sale
Transaction.sale(
@customer,
amount: top_up_amount()
).then do |tx|
tx.insert.then { tx }
end
end
def failed(e)
@method && REDIS.setex(
"jmp_auto_top_up_block-#{@method.unique_number_identifier}",
60 * 60 * 24 * 30,
Time.now
)
@message.body =
"Automatic top-up transaction for " \
"$#{top_up_amount()} failed: #{e.message}"
0
end
def notify!
sale.then { |tx|
@message.body =
"Automatic top-up has charged your default " \
"payment method and added #{tx} to your balance."
tx.total
}.catch(&method(:failed)).then { |amount|
@customer.stanza_to(@message)
amount
}
end
end
class Locked
def notify!
EMPromise.resolve(0)
end
end
end