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