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