low_balance.rb

  1# frozen_string_literal: true
  2
  3require_relative "expiring_lock"
  4require_relative "transaction"
  5
  6class LowBalance
  7	def self.for(customer)
  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(&method(:for_no_lock))
 15		end
 16	end
 17
 18	def self.for_no_lock(customer, auto: true)
 19		if auto && customer.auto_top_up_amount.positive?
 20			AutoTopUp.for(customer)
 21		else
 22			customer.btc_addresses.then do |btc_addresses|
 23				new(customer, btc_addresses)
 24			end
 25		end
 26	end
 27
 28	def initialize(customer, btc_addresses)
 29		@customer = customer
 30		@btc_addresses = btc_addresses
 31	end
 32
 33	def notify!
 34		m = Blather::Stanza::Message.new
 35		m.from = CONFIG[:notify_from]
 36		m.body =
 37			"Your balance of $#{'%.4f' % @customer.balance} is low." \
 38			"#{btc_addresses_for_notification}"
 39		@customer.stanza_to(m)
 40		EMPromise.resolve(0)
 41	end
 42
 43	def btc_addresses_for_notification
 44		return if @btc_addresses.empty?
 45
 46		"\nYou can buy credit by sending any amount of Bitcoin to one of " \
 47		"these addresses:\n#{@btc_addresses.join("\n")}"
 48	end
 49
 50	class AutoTopUp
 51		def self.for(customer)
 52			customer.payment_methods.then(&:default_payment_method).then do |method|
 53				blocked?(method).then do |block|
 54					next AutoTopUp.new(customer, method) if block.zero?
 55
 56					log.info("#{customer.customer_id} auto top up blocked")
 57					LowBalance.for_no_lock(customer, auto: false)
 58				end
 59			end
 60		end
 61
 62		def self.blocked?(method)
 63			return EMPromise.resolve(1) if method.nil?
 64
 65			REDIS.exists(
 66				"jmp_auto_top_up_block-#{method&.unique_number_identifier}"
 67			)
 68		end
 69
 70		def initialize(customer, method=nil)
 71			@customer = customer
 72			@method = method
 73			@message = Blather::Stanza::Message.new
 74			@message.from = CONFIG[:notify_from]
 75		end
 76
 77		def sale
 78			Transaction.sale(
 79				@customer,
 80				amount: @customer.auto_top_up_amount
 81			).then do |tx|
 82				tx.insert.then { tx }
 83			end
 84		end
 85
 86		def failed(e)
 87			@method && REDIS.setex(
 88				"jmp_auto_top_up_block-#{@method.unique_number_identifier}",
 89				60 * 60 * 24 * 7,
 90				Time.now
 91			)
 92			@message.body =
 93				"Automatic top-up transaction for " \
 94				"$#{@customer.auto_top_up_amount} failed: #{e.message}"
 95			0
 96		end
 97
 98		def notify!
 99			sale.then { |tx|
100				@message.body =
101					"Automatic top-up has charged your default " \
102					"payment method and added #{tx} to your balance."
103				tx.total
104			}.catch(&method(:failed)).then { |amount|
105				@customer.stanza_to(@message)
106				amount
107			}
108		end
109	end
110
111	class Locked
112		def notify!
113			EMPromise.resolve(0)
114		end
115	end
116end