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 decline_guard(response)
84		if response.success?
85			REDIS.setex(
86				"jmp_customer_credit_card_lock-#{@customer.customer_id}",
87				60 * 60 * 24,
88				"1"
89			)
90			return response.transaction
91		end
92
93		@customer.mark_decline
94		raise response.message
95	end
96end