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