# frozen_string_literal: true require_relative "expiring_lock" require_relative "transaction" class LowBalance def self.for(customer, transaction_amount=0) return Locked.new unless customer.registered? ExpiringLock.new( "jmp_customer_low_balance-#{customer.billing_customer_id}", expiry: 60 * 60 * 24 * 7 ).with(-> { Locked.new }) do customer.billing_customer.then do |billing_customer| for_no_lock(billing_customer, transaction_amount) end end end def self.for_no_lock(customer, transaction_amount, auto: true) if auto && customer.auto_top_up_amount.positive? AutoTopUp.for(customer, transaction_amount) else customer.btc_addresses.then do |btc_addresses| new(customer, btc_addresses, transaction_amount) end end end def initialize(customer, btc_addresses, transaction_amount=0) @customer = customer @btc_addresses = btc_addresses @transaction_amount = transaction_amount end def notify! m = Blather::Stanza::Message.new m.from = CONFIG[:notify_from] m.body = "Your balance of $#{'%.4f' % @customer.balance} is low." \ "#{pending_cost_for_notification}" \ "#{btc_addresses_for_notification}" @customer.stanza_to(m) EMPromise.resolve(0) end def pending_cost_for_notification return unless @transaction_amount&.positive? return unless @transaction_amount > @customer.balance "You need an additional " \ "$#{'%.2f' % (@transaction_amount - @customer.balance)} "\ "to complete this transaction." end def btc_addresses_for_notification return if @btc_addresses.empty? "\nYou can buy credit by sending any amount of Bitcoin to one of " \ "these addresses:\n#{@btc_addresses.join("\n")}" end class AutoTopUp def self.for(customer, target=0) customer.payment_methods.then(&:default_payment_method).then do |method| blocked?(method).then do |block| next AutoTopUp.new(customer, method, target) if block.zero? log.info("#{customer.customer_id} auto top up blocked") LowBalance.for_no_lock(customer, target, auto: false) end end end def self.blocked?(method) return EMPromise.resolve(1) if method.nil? REDIS.exists( "jmp_auto_top_up_block-#{method&.unique_number_identifier}" ) end def initialize(customer, method=nil, target=0, margin: 10) @customer = customer @method = method @target = target @margin = margin @message = Blather::Stanza::Message.new @message.from = CONFIG[:notify_from] end def top_up_amount [ (@target + @margin) - @customer.balance, @customer.auto_top_up_amount ].max end def sale Transaction.sale(@customer, amount: top_up_amount).then do |tx| tx.insert.then { tx } end end def failed(e) @method && REDIS.setex( "jmp_auto_top_up_block-#{@method.unique_number_identifier}", 60 * 60 * 24 * 30, Time.now ) @message.body = "Automatic top-up transaction for " \ "$#{top_up_amount} failed: #{e.message}" 0 end def notify! sale.then { |tx| @message.body = "Automatic top-up has charged your default " \ "payment method and added #{tx} to your balance." tx.total }.catch(&method(:failed)).then { |amount| @customer.stanza_to(@message) amount } end end class Locked def notify! EMPromise.resolve(0) end end end