transaction.rb

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