1# frozen_string_literal: true
  2
  3require "bigdecimal/util"
  4require "delegate"
  5
  6require_relative "transaction"
  7require_relative "trust_level_repo"
  8
  9class CreditCardSale
 10	def self.create(*args, transaction_class: Transaction, **kwargs)
 11		new(*args, **kwargs).sale.then do |response|
 12			tx = BraintreeTransaction.build(
 13				response,
 14				transaction_class: transaction_class
 15			)
 16			tx.insert.then { tx }
 17		end
 18	end
 19
 20	class BraintreeTransaction < SimpleDelegator
 21		def self.build(braintree_transaction, transaction_class: Transaction)
 22			new(braintree_transaction).to_transaction(transaction_class)
 23		end
 24
 25		def to_transaction(transaction_class)
 26			transaction_class.new(
 27				customer_id: customer_details.id,
 28				transaction_id: id,
 29				created_at: created_at,
 30				settled_after: created_at + (90 * 24 * 60 * 60),
 31				amount: amount,
 32				note: "Credit card payment"
 33			)
 34		end
 35	end
 36
 37	def initialize(
 38		customer, amount:, payment_method: nil,
 39		trust_repo: TrustLevelRepo.new
 40	)
 41		@customer = customer
 42		@amount = amount
 43		@payment_method = payment_method
 44		@trust_repo = trust_repo
 45	end
 46
 47	def sale
 48		EMPromise.all([validate!, resolve_payment_method]).then { |_, selected|
 49			BRAINTREE.transaction.sale(
 50				amount: @amount,
 51				merchant_account_id: @customer.merchant_account,
 52				options: { submit_for_settlement: true },
 53				payment_method_token: selected.token
 54			)
 55		}.then { |response| decline_guard(response) }
 56	end
 57
 58protected
 59
 60	def validate!
 61		EMPromise.all([
 62			REDIS.exists("jmp_customer_credit_card_lock-#{@customer.customer_id}"),
 63			@trust_repo.find(@customer), @customer.declines
 64		]).then do |(lock, tl, declines)|
 65			unless tl.credit_card_transaction?(@amount.to_d, declines)
 66				raise "Declined"
 67			end
 68			raise "Too many payments recently" if lock == 1
 69		end
 70	end
 71
 72	def resolve_payment_method
 73		EMPromise.all([
 74			@payment_method ||
 75				@customer.payment_methods.then(&:default_payment_method)
 76		]).then do |(selected_method)|
 77			raise "No valid payment method on file" unless selected_method
 78
 79			selected_method
 80		end
 81	end
 82
 83	def churnbuster_success(response)
 84		Churnbuster.new.successful_payment(
 85			@customer,
 86			@amount,
 87			response.transaction.id
 88		)
 89	end
 90
 91	def decline_guard(response)
 92		if response.success?
 93			churnbuster_success(response)
 94			REDIS.setex(
 95				"jmp_customer_credit_card_lock-#{@customer.customer_id}",
 96				60 * 60 * 24, "1"
 97			)
 98			return response.transaction
 99		end
100
101		@customer.mark_decline
102		raise BraintreeFailure, response
103	end
104end
105
106class BraintreeFailure < StandardError
107	attr_reader :response
108
109	def initialize(response)
110		super response.message
111		@response = response
112	end
113end