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 BraintreeFailure, response
95 end
96end
97
98class BraintreeFailure < StandardError
99 attr_reader :response
100
101 def initialize(response)
102 super response.message
103 @response = response
104 end
105end