transaction.rb

  1# frozen_string_literal: true
  2
  3require "bigdecimal"
  4
  5class Transaction
  6	def self.sale(customer, amount:, payment_method: nil)
  7		resolve_payment_method(customer, payment_method).then do |selected_method|
  8			BRAINTREE.transaction.sale(
  9				amount: amount,
 10				merchant_account_id: customer.merchant_account,
 11				options: { submit_for_settlement: true },
 12				payment_method_token: selected_method.token
 13			).then do |response|
 14				new(decline_guard(customer, response))
 15			end
 16		end
 17	end
 18
 19	def self.resolve_payment_method(customer, payment_method)
 20		EMPromise.all([
 21			REDIS.exists("jmp_customer_credit_card_lock-#{customer.customer_id}"),
 22			customer.declines,
 23			payment_method || customer.payment_methods.then(&:default_payment_method)
 24		]).then do |(lock, declines, selected_method)|
 25			raise "Declined" if declines >= 2
 26			raise "Too many payments recently" if lock == 1
 27			raise "No valid payment method on file" unless selected_method
 28
 29			selected_method
 30		end
 31	end
 32
 33	def self.decline_guard(customer, response)
 34		if response.success?
 35			REDIS.setex(
 36				"jmp_customer_credit_card_lock-#{customer.customer_id}",
 37				60 * 60 * 24,
 38				"1"
 39			)
 40			return response.transaction
 41		end
 42
 43		customer.mark_decline
 44		raise response.message
 45	end
 46
 47	attr_reader :amount
 48
 49	def initialize(braintree_transaction)
 50		@customer_id = braintree_transaction.customer_details.id
 51		@transaction_id = braintree_transaction.id
 52		@created_at = braintree_transaction.created_at
 53		@amount = BigDecimal(braintree_transaction.amount, 4)
 54	end
 55
 56	def insert
 57		EM.promise_fiber do
 58			DB.transaction do
 59				insert_tx
 60				insert_bonus
 61			end
 62		end
 63	end
 64
 65	def total
 66		amount + bonus
 67	end
 68
 69	def bonus
 70		return BigDecimal(0) if amount <= 15
 71
 72		amount *
 73			case amount
 74			when (15..29.99)
 75				0.01
 76			when (30..139.99)
 77				0.03
 78			else
 79				0.05
 80			end
 81	end
 82
 83	def to_s
 84		plus = " + #{'%.4f' % bonus} bonus"
 85		"$#{'%.2f' % amount}#{plus if bonus.positive?}"
 86	end
 87
 88	def settled_after
 89		@created_at + (90 * 24 * 60 * 60)
 90	end
 91
 92protected
 93
 94	def insert_tx
 95		params = [
 96			@customer_id, @transaction_id, @created_at, settled_after, @amount
 97		]
 98		DB.exec(<<~SQL, params)
 99			INSERT INTO transactions
100				(customer_id, transaction_id, created_at, settled_after, amount, note)
101			VALUES
102				($1, $2, $3, $4, $5, 'Credit card payment')
103		SQL
104	end
105
106	def insert_bonus
107		return if bonus <= 0
108
109		params = [
110			@customer_id, "bonus_for_#{@transaction_id}", @created_at,
111			settled_after, bonus
112		]
113		DB.exec(<<~SQL, params)
114			INSERT INTO transactions
115				(customer_id, transaction_id, created_at, settled_after, amount, note)
116			VALUES
117				($1, $2, $3, $4, $5, 'Credit card payment bonus')
118		SQL
119	end
120end