Credit card blacklist

Stephen Paul Weber created

Any card on the list is just treated as though it is not present, preventing it
from being used.

Change summary

lib/customer_finacials.rb            | 12 +++++---
lib/payment_methods.rb               |  6 +++-
lib/transaction.rb                   | 38 ++++++++++++++---------------
test/test_buy_account_credit_form.rb |  4 ++
test/test_payment_methods.rb         | 28 ++++++++++++++++++---
test/test_registration.rb            |  1 
6 files changed, 57 insertions(+), 32 deletions(-)

Detailed changes

lib/customer_finacials.rb 🔗

@@ -6,11 +6,13 @@ class CustomerFinancials
 	end
 
 	def payment_methods
-		BRAINTREE
-			.customer
-			.find(@customer_id)
-			.catch { OpenStruct.new(payment_methods: []) }
-			.then(PaymentMethods.method(:for_braintree_customer))
+		EMPromise.all([
+			BRAINTREE
+				.customer
+				.find(@customer_id)
+				.catch { OpenStruct.new(payment_methods: []) },
+			REDIS.smembers("block_credit_cards")
+		]).then { |(braintree, badcards)| PaymentMethods.for(braintree, badcards) }
 	end
 
 	def btc_addresses

lib/payment_methods.rb 🔗

@@ -1,8 +1,10 @@
 # frozen_string_literal: true
 
 class PaymentMethods
-	def self.for_braintree_customer(braintree_customer)
-		methods = braintree_customer.payment_methods
+	def self.for(braintree_customer, badcards)
+		methods = braintree_customer.payment_methods.reject { |m|
+			badcards.include?(m.unique_number_identifier)
+		}
 		if methods.empty?
 			Empty.new
 		else

lib/transaction.rb 🔗

@@ -4,39 +4,37 @@ require "bigdecimal"
 
 class Transaction
 	def self.sale(customer, amount:, payment_method: nil)
-		customer.declines.then do |declines|
-			raise "too many declines" if declines >= 2
-
+		resolve_payment_method(customer, payment_method).then do |selected_method|
 			BRAINTREE.transaction.sale(
 				amount: amount,
-				**sale_args_for(customer, payment_method)
+				merchant_account_id: customer.merchant_account,
+				options: { submit_for_settlement: true },
+				payment_method_token: selected_method.token
 			).then do |response|
-				decline_guard(customer, response)
-				new(response.transaction)
+				new(decline_guard(customer, response))
 			end
 		end
 	end
 
+	def self.resolve_payment_method(customer, payment_method)
+		EMPromise.all([
+			customer.declines,
+			payment_method || customer.payment_methods.then(&:default_payment_method)
+		]).then do |(declines, selected_method)|
+			raise "Declined" if declines >= 2
+			raise "No valid payment method on file" unless selected_method
+
+			selected_method
+		end
+	end
+
 	def self.decline_guard(customer, response)
-		return if response.success?
+		return response.transaction if response.success?
 
 		customer.mark_decline
 		raise response.message
 	end
 
-	def self.sale_args_for(customer, payment_method=nil)
-		{
-			merchant_account_id: customer.merchant_account,
-			options: { submit_for_settlement: true }
-		}.merge(
-			if payment_method
-				{ payment_method_token: payment_method.token }
-			else
-				{ customer_id: customer.customer_id }
-			end
-		)
-	end
-
 	attr_reader :amount
 
 	def initialize(braintree_transaction)

test/test_buy_account_credit_form.rb 🔗

@@ -4,7 +4,8 @@ require "test_helper"
 require "buy_account_credit_form"
 require "customer"
 
-Customer::BRAINTREE = Minitest::Mock.new
+CustomerFinancials::BRAINTREE = Minitest::Mock.new
+CustomerFinancials::REDIS = Minitest::Mock.new
 
 class BuyAccountCreditFormTest < Minitest::Test
 	def setup
@@ -18,6 +19,7 @@ class BuyAccountCreditFormTest < Minitest::Test
 	def test_for
 		braintree_customer = Minitest::Mock.new
 		CustomerFinancials::BRAINTREE.expect(:customer, braintree_customer)
+		CustomerFinancials::REDIS.expect(:smembers, [], ["block_credit_cards"])
 		braintree_customer.expect(
 			:find,
 			EMPromise.resolve(OpenStruct.new(payment_methods: [])),

test/test_payment_methods.rb 🔗

@@ -4,22 +4,42 @@ require "test_helper"
 require "payment_methods"
 
 class PaymentMethodsTest < Minitest::Test
-	def test_for_braintree_customer
+	def test_for
 		braintree_customer = Minitest::Mock.new
 		braintree_customer.expect(:payment_methods, [
 			OpenStruct.new(card_type: "Test", last_4: "1234")
 		])
-		methods = PaymentMethods.for_braintree_customer(braintree_customer)
+		methods = PaymentMethods.for(braintree_customer, [])
 		assert_kind_of PaymentMethods, methods
+		assert_equal 1, methods.to_a.length
+		refute methods.empty?
 	end
 
-	def test_for_braintree_customer_no_methods
+	def test_for_badcards
+		braintree_customer = Minitest::Mock.new
+		braintree_customer.expect(:payment_methods, [
+			OpenStruct.new(
+				card_type: "Test",
+				last_4: "1234",
+				unique_number_identifier: "wut"
+			)
+		])
+		methods = PaymentMethods.for(braintree_customer, ["wut"])
+		assert_kind_of PaymentMethods, methods
+		assert_equal 0, methods.to_a.length
+		assert methods.empty?
+	end
+
+	def test_for_no_methods
 		braintree_customer = Minitest::Mock.new
 		braintree_customer.expect(:payment_methods, [])
-		methods = PaymentMethods.for_braintree_customer(braintree_customer)
+		methods = PaymentMethods.for(braintree_customer, [])
+		assert_kind_of PaymentMethods, methods
 		assert_raises do
 			methods.to_list_single
 		end
+		assert_equal 0, methods.to_a.length
+		assert methods.empty?
 	end
 
 	def test_default_payment_method

test/test_registration.rb 🔗

@@ -266,6 +266,7 @@ class RegistrationTest < Minitest::Test
 				:customer,
 				braintree_customer
 			)
+			CustomerFinancials::REDIS.expect(:smembers, [], ["block_credit_cards"])
 			braintree_customer.expect(
 				:find,
 				EMPromise.resolve(OpenStruct.new(payment_methods: [])),