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