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