From 4f0083d178c84ade0e10ec54bd36666fb0f80221 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Wed, 12 May 2021 14:19:42 -0500 Subject: [PATCH] Block repeated declines for 24 hours --- lib/registration.rb | 6 +++--- lib/transaction.rb | 42 ++++++++++++++++++++++++++++++--------- sgx_jmp.rb | 6 +++--- test/test_registration.rb | 24 +++++++++++----------- test/test_transaction.rb | 37 ++++++++++++++++++++++++++-------- 5 files changed, 80 insertions(+), 35 deletions(-) diff --git a/lib/registration.rb b/lib/registration.rb index b2c359f..bb5d02b 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -219,9 +219,9 @@ class Registration def write Transaction.sale( - @customer.merchant_account, - @payment_method, - CONFIG[:activation_amount] + @customer, + CONFIG[:activation_amount], + @payment_method ).then( method(:sold), ->(_) { declined } diff --git a/lib/transaction.rb b/lib/transaction.rb index f8bb2fb..80021dd 100644 --- a/lib/transaction.rb +++ b/lib/transaction.rb @@ -1,18 +1,42 @@ # frozen_string_literal: true class Transaction - def self.sale(merchant_account, payment_method, amount) - BRAINTREE.transaction.sale( - amount: amount, - payment_method_token: payment_method.token, - merchant_account_id: merchant_account, - options: { submit_for_settlement: true } - ).then do |response| - raise response.message unless response.success? - new(response.transaction) + def self.sale(customer, amount, payment_method=nil) + REDIS.get("jmp_pay_decline-#{customer.customer_id}").then do |declines| + raise "too many declines" if declines.to_i >= 2 + + BRAINTREE.transaction.sale( + amount: amount, + **sale_args_for(customer, payment_method) + ).then do |response| + decline_guard(customer, response) + new(response.transaction) + end end end + def self.decline_guard(customer, response) + return if response.success? + + REDIS.incr("jmp_pay_decline-#{customer.customer_id}").then do + REDIS.expire("jmp_pay_decline-#{customer.customer_id}", 60 * 60 * 24) + end + 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.id } + end + ) + end + attr_reader :amount def initialize(braintree_transaction) diff --git a/sgx_jmp.rb b/sgx_jmp.rb index c91e432..14554fe 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -205,17 +205,17 @@ command :execute?, node: "buy-credit", sessionid: nil do |iq| BuyAccountCreditForm.new(customer).add_to_form(reply.form).then { customer } }.then { |customer| EMPromise.all([ + customer, customer.payment_methods, - customer.merchant_account, COMMAND_MANAGER.write(reply) ]) - }.then { |(payment_methods, merchant_account, iq2)| + }.then { |(customer, payment_methods, iq2)| iq = iq2 # This allows the catch to use it also payment_method = payment_methods.fetch( iq.form.field("payment_method")&.value.to_i ) amount = iq.form.field("amount").value.to_s - Transaction.sale(merchant_account, payment_method, amount) + Transaction.sale(customer, amount, payment_method) }.then { |transaction| transaction.insert.then { transaction.amount } }.then { |amount| diff --git a/test/test_registration.rb b/test/test_registration.rb index 42ebe06..3b88760 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -214,19 +214,19 @@ class RegistrationTest < Minitest::Test :insert, EMPromise.resolve(nil) ) + customer = Minitest::Mock.new( + Customer.new("test", plan_name: "test_usd") + ) Registration::Payment::CreditCard::Activate::Transaction.expect( :sale, transaction, [ - "merchant_usd", - :test_default_method, - CONFIG[:activation_amount] + customer, + CONFIG[:activation_amount], + :test_default_method ] ) iq = Blather::Stanza::Iq::Command.new - customer = Minitest::Mock.new( - Customer.new("test", plan_name: "test_usd") - ) customer.expect(:bill_plan, nil) Registration::Payment::CreditCard::Activate::Finish.expect( :new, @@ -247,20 +247,20 @@ class RegistrationTest < Minitest::Test em :test_write def test_write_declines + customer = Minitest::Mock.new( + Customer.new("test", plan_name: "test_usd") + ) Registration::Payment::CreditCard::Activate::Transaction.expect( :sale, EMPromise.reject("declined"), [ - "merchant_usd", - :test_default_method, - CONFIG[:activation_amount] + customer, + CONFIG[:activation_amount], + :test_default_method ] ) iq = Blather::Stanza::Iq::Command.new iq.from = "test@example.com" - customer = Minitest::Mock.new( - Customer.new("test", plan_name: "test_usd") - ) result = Minitest::Mock.new result.expect(:then, nil) Registration::Payment::CreditCard::Activate::COMMAND_MANAGER.expect( diff --git a/test/test_transaction.rb b/test/test_transaction.rb index 5979ab5..f78eb03 100644 --- a/test/test_transaction.rb +++ b/test/test_transaction.rb @@ -5,6 +5,7 @@ require "transaction" Transaction::DB = Minitest::Mock.new Transaction::BRAINTREE = Minitest::Mock.new +Transaction::REDIS = Minitest::Mock.new class TransactionTest < Minitest::Test FAKE_BRAINTREE_TRANSACTION = @@ -16,26 +17,46 @@ class TransactionTest < Minitest::Test ) def test_sale_fails + Transaction::REDIS.expect( + :get, + EMPromise.resolve("1"), + ["jmp_pay_decline-test"] + ) + Transaction::REDIS.expect( + :incr, + EMPromise.resolve(nil), + ["jmp_pay_decline-test"] + ) + Transaction::REDIS.expect( + :expire, + EMPromise.resolve(nil), + ["jmp_pay_decline-test", 60 * 60 * 24] + ) braintree_transaction = Minitest::Mock.new Transaction::BRAINTREE.expect(:transaction, braintree_transaction) braintree_transaction.expect( :sale, EMPromise.resolve( - OpenStruct.new(success?: false) + OpenStruct.new(success?: false, message: "declined") ), [Hash] ) - assert_raises do + assert_raises("declined") do Transaction.sale( - "merchant_usd", - OpenStruct.new(token: "token"), - 123 + Customer.new("test", plan_name: "test_usd"), + 123, + OpenStruct.new(token: "token") ).sync end end em :test_sale_fails def test_sale + Transaction::REDIS.expect( + :get, + EMPromise.resolve("1"), + ["jmp_pay_decline-test"] + ) braintree_transaction = Minitest::Mock.new Transaction::BRAINTREE.expect(:transaction, braintree_transaction) braintree_transaction.expect( @@ -54,9 +75,9 @@ class TransactionTest < Minitest::Test }] ) result = Transaction.sale( - "merchant_usd", - OpenStruct.new(token: "token"), - 123 + Customer.new("test", plan_name: "test_usd"), + 123, + OpenStruct.new(token: "token") ).sync assert_kind_of Transaction, result end -- 2.38.5