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 return Locked.new unless customer.registered?
10
11 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
21 def self.for_no_lock(customer, transaction_amount, auto: true)
22 if auto && customer.auto_top_up_amount.positive?
23 AutoTopUp.for(customer, transaction_amount)
24 else
25 customer.btc_addresses.then do |btc_addresses|
26 new(customer, btc_addresses, transaction_amount)
27 end
28 end
29 end
30
31 def initialize(customer, btc_addresses, transaction_amount=0)
32 @customer = customer
33 @btc_addresses = btc_addresses
34 @transaction_amount = transaction_amount
35 end
36
37 def can_top_up?
38 false
39 end
40
41 def notify!
42 m = Blather::Stanza::Message.new
43 m.from = CONFIG[:notify_from]
44 m.body =
45 "Your balance of $#{'%.4f' % @customer.balance} is low." \
46 "#{pending_cost_for_notification}" \
47 "#{btc_addresses_for_notification}"
48 @customer.stanza_to(m)
49 EMPromise.resolve(0)
50 end
51
52 def pending_cost_for_notification
53 return unless @transaction_amount&.positive?
54 return unless @transaction_amount > @customer.balance
55
56 "\nYou need an additional " \
57 "$#{'%.2f' % (@transaction_amount - @customer.balance)} "\
58 "to complete this transaction."
59 end
60
61 def btc_addresses_for_notification
62 return if @btc_addresses.empty?
63
64 "\nYou can buy credit by sending any amount of Bitcoin to one of " \
65 "these addresses:\n#{@btc_addresses.join("\n")}"
66 end
67
68 class AutoTopUp
69 def self.for(customer, target=0)
70 customer.payment_methods.then(&:default_payment_method).then do |method|
71 blocked?(method).then do |block|
72 next AutoTopUp.new(customer, method, target) if block.zero?
73
74 log.info("#{customer.customer_id} auto top up blocked")
75 LowBalance.for_no_lock(customer, target, auto: false)
76 end
77 end
78 end
79
80 def self.blocked?(method)
81 return EMPromise.resolve(1) if method.nil?
82
83 REDIS.exists(
84 "jmp_auto_top_up_block-#{method&.unique_number_identifier}"
85 )
86 end
87
88 def initialize(customer, method=nil, target=0, margin: 10)
89 @customer = customer
90 @method = method
91 @target = target
92 @margin = margin
93 @message = Blather::Stanza::Message.new
94 @message.from = CONFIG[:notify_from]
95 end
96
97 def top_up_amount
98 [
99 ((@target + @margin) - @customer.balance).round(2),
100 @customer.auto_top_up_amount
101 ].max
102 end
103
104 def can_top_up?
105 true
106 end
107
108 def sale
109 CreditCardSale.create(@customer, amount: top_up_amount)
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 "$#{'%.2f' % 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