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