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
# 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 |billing_customer|
for_no_lock(billing_customer, transaction_amount)
end
end
end
def self.for_no_lock(customer, transaction_amount, auto: true)
if auto && customer.auto_top_up_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?
return unless @transaction_amount > @customer.balance
"You need an additional " \
"$#{'%.2f' % (@transaction_amount - @customer.balance)} "\
"to complete this transaction."
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
[
(@target + @margin) - @customer.balance,
@customer.auto_top_up_amount
].max
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