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("#{customer.customer_id} auto top up blocked")
 89					LowBalance.for_no_lock(customer, target, auto: false)
 90				end
 91			end
 92		end
 93
 94		def self.blocked?(method)
 95			return EMPromise.resolve(1) if method.nil?
 96
 97			REDIS.exists(
 98				"jmp_auto_top_up_block-#{method&.unique_number_identifier}"
 99			)
100		end
101
102		def initialize(customer, method=nil, target=0, margin: 10)
103			@customer = customer
104			@method = method
105			@target = target
106			@margin = margin
107			@message = Blather::Stanza::Message.new
108			@message.from = CONFIG[:notify_from]
109		end
110
111		def top_up_amount
112			[
113				((@target + @margin) - @customer.balance).round(2),
114				@customer.auto_top_up_amount
115			].max
116		end
117
118		def can_top_up?
119			true
120		end
121
122		def sale
123			CreditCardSale.create(@customer, amount: top_up_amount)
124		end
125
126		def churnbuster(e)
127			return unless e.is_a?(BraintreeFailure)
128
129			Churnbuster.new.failed_payment(
130				@customer,
131				top_up_amount,
132				e.response.transaction.id
133			)
134		end
135
136		def failed(e)
137			churnbuster(e)
138			@method && REDIS.setex(
139				"jmp_auto_top_up_block-#{@method.unique_number_identifier}",
140				60 * 60 * 24 * 30,
141				Time.now
142			)
143			@message.body =
144				"Automatic top-up transaction for " \
145				"$#{'%.2f' % top_up_amount} failed: #{e.message}"
146			0
147		end
148
149		def notify!
150			sale.then { |tx|
151				@message.body =
152					"Automatic top-up has charged your default " \
153					"payment method and added #{tx} to your balance."
154				tx.total
155			}.catch(&method(:failed)).then { |amount|
156				@customer.stanza_to(@message)
157				amount
158			}
159		end
160	end
161
162	class Locked
163		def notify!
164			EMPromise.resolve(0)
165		end
166	end
167end