Send 3DS id when making a transaction

Stephen Paul Weber created

Change summary

lib/customer_finacials.rb            | 13 +++++++++++--
lib/payment_method.rb                | 29 +++++++++++++++++++++++++++++
lib/payment_methods.rb               |  6 ++++--
lib/transaction.rb                   |  7 +++----
test/test_buy_account_credit_form.rb |  3 +++
test/test_payment_methods.rb         | 29 +++++++++++++++++++++++++----
test/test_registration.rb            |  3 +++
test/test_transaction.rb             |  5 +++--
8 files changed, 81 insertions(+), 14 deletions(-)

Detailed changes

lib/customer_finacials.rb 🔗

@@ -11,8 +11,11 @@ class CustomerFinancials
 				.customer
 				.find(@customer_id)
 				.catch { OpenStruct.new(payment_methods: []) },
-			REDIS.smembers("block_credit_cards")
-		]).then { |(braintree, badcards)| PaymentMethods.for(braintree, badcards) }
+			REDIS.smembers("block_credit_cards"),
+			three_d_secure
+		]).then do |(braintree, badcards, three_d)|
+			PaymentMethods.for(braintree, badcards, three_d)
+		end
 	end
 
 	def btc_addresses
@@ -42,6 +45,12 @@ class CustomerFinancials
 		end
 	end
 
+	def three_d_secure
+		REDIS.hgetall(
+			"jmp_customer_three_d_secure_authentication_id-#{@customer_id}"
+		).then { |all| Hash[*all] }
+	end
+
 	class TransactionInfo
 		value_semantics do
 			transaction_id String

lib/payment_method.rb 🔗

@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require "delegate"
+
+class PaymentMethod < SimpleDelegator
+	def self.for(method, three_d_secure={})
+		three_d = three_d_secure[method.token]
+		return ThreeDSecure.new(method, three_d) if three_d
+
+		new(method)
+	end
+
+	def transaction_details
+		{
+			payment_method_token: token
+		}
+	end
+
+	class ThreeDSecure < PaymentMethod
+		def initialize(method, three_d)
+			super(method)
+			@three_d = three_d
+		end
+
+		def transaction_details
+			super.merge(three_d_secure_authentication_id: @three_d)
+		end
+	end
+end

lib/payment_methods.rb 🔗

@@ -1,14 +1,16 @@
 # frozen_string_literal: true
 
+require_relative "payment_method"
+
 class PaymentMethods
-	def self.for(braintree_customer, badcards)
+	def self.for(braintree_customer, badcards, three_d_secure)
 		methods = braintree_customer.payment_methods.reject { |m|
 			badcards.include?(m.unique_number_identifier)
 		}
 		if methods.empty?
 			Empty.new
 		else
-			new(methods)
+			new(methods.map { |m| PaymentMethod.for(m, three_d_secure) })
 		end
 	end
 

lib/transaction.rb 🔗

@@ -5,12 +5,11 @@ require "bigdecimal"
 class Transaction
 	def self.sale(customer, amount:, payment_method: nil)
 		resolve_payment_method(customer, payment_method).then do |selected_method|
-			BRAINTREE.transaction.sale(
+			BRAINTREE.transaction.sale(selected_method.transaction_details.merge(
 				amount: amount,
 				merchant_account_id: customer.merchant_account,
-				options: { submit_for_settlement: true },
-				payment_method_token: selected_method.token
-			).then do |response|
+				options: { submit_for_settlement: true }
+			)).then do |response|
 				new(decline_guard(customer, response))
 			end
 		end

test/test_buy_account_credit_form.rb 🔗

@@ -20,6 +20,9 @@ class BuyAccountCreditFormTest < Minitest::Test
 		braintree_customer = Minitest::Mock.new
 		CustomerFinancials::BRAINTREE.expect(:customer, braintree_customer)
 		CustomerFinancials::REDIS.expect(:smembers, [], ["block_credit_cards"])
+		CustomerFinancials::REDIS.expect(
+			:hgetall, [], ["jmp_customer_three_d_secure_authentication_id-test"]
+		)
 		braintree_customer.expect(
 			:find,
 			EMPromise.resolve(OpenStruct.new(payment_methods: [])),

test/test_payment_methods.rb 🔗

@@ -7,12 +7,16 @@ class PaymentMethodsTest < Minitest::Test
 	def test_for
 		braintree_customer = Minitest::Mock.new
 		braintree_customer.expect(:payment_methods, [
-			OpenStruct.new(card_type: "Test", last_4: "1234")
+			OpenStruct.new(card_type: "Test", last_4: "1234", token: "wut")
 		])
-		methods = PaymentMethods.for(braintree_customer, [])
+		methods = PaymentMethods.for(braintree_customer, [], {})
 		assert_kind_of PaymentMethods, methods
 		assert_equal 1, methods.to_a.length
 		refute methods.empty?
+		assert_equal(
+			{ payment_method_token: "wut" },
+			methods.fetch(0).transaction_details
+		)
 	end
 
 	def test_for_badcards
@@ -24,16 +28,33 @@ class PaymentMethodsTest < Minitest::Test
 				unique_number_identifier: "wut"
 			)
 		])
-		methods = PaymentMethods.for(braintree_customer, ["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_three_d_secure
+		braintree_customer = Minitest::Mock.new
+		braintree_customer.expect(:payment_methods, [
+			OpenStruct.new(
+				card_type: "Test",
+				last_4: "1234",
+				token: "wut"
+			)
+		])
+		methods = PaymentMethods.for(braintree_customer, [], { "wut" => "hai" })
+		assert_kind_of PaymentMethods, methods
+		assert_equal(
+			{ payment_method_token: "wut", three_d_secure_authentication_id: "hai" },
+			methods.fetch(0).transaction_details
+		)
+	end
+
 	def test_for_no_methods
 		braintree_customer = Minitest::Mock.new
 		braintree_customer.expect(:payment_methods, [])
-		methods = PaymentMethods.for(braintree_customer, [])
+		methods = PaymentMethods.for(braintree_customer, [], {})
 		assert_kind_of PaymentMethods, methods
 		assert_raises do
 			methods.to_list_single

test/test_registration.rb 🔗

@@ -267,6 +267,9 @@ class RegistrationTest < Minitest::Test
 				braintree_customer
 			)
 			CustomerFinancials::REDIS.expect(:smembers, [], ["block_credit_cards"])
+			CustomerFinancials::REDIS.expect(
+				:hgetall, [], ["jmp_customer_three_d_secure_authentication_id-test"]
+			)
 			braintree_customer.expect(
 				:find,
 				EMPromise.resolve(OpenStruct.new(payment_methods: [])),

test/test_transaction.rb 🔗

@@ -3,6 +3,7 @@
 require "test_helper"
 require "customer"
 require "transaction"
+require "payment_method"
 
 Transaction::DB = Minitest::Mock.new
 Transaction::BRAINTREE = Minitest::Mock.new
@@ -46,7 +47,7 @@ class TransactionTest < Minitest::Test
 			Transaction.sale(
 				customer(plan_name: "test_usd"),
 				amount: 123,
-				payment_method: OpenStruct.new(token: "token")
+				payment_method: PaymentMethod.for(OpenStruct.new(token: "token"))
 			).sync
 		end
 		assert_mock CustomerFinancials::REDIS
@@ -79,7 +80,7 @@ class TransactionTest < Minitest::Test
 		result = Transaction.sale(
 			customer(plan_name: "test_usd"),
 			amount: 123,
-			payment_method: OpenStruct.new(token: "token")
+			payment_method: PaymentMethod.for(OpenStruct.new(token: "token"))
 		).sync
 		assert_kind_of Transaction, result
 		assert_mock CustomerFinancials::REDIS