~singpolyma/sgx-jmp

4f0083d178c84ade0e10ec54bd36666fb0f80221 — Stephen Paul Weber 1 year, 6 months ago b0094db
Block repeated declines for 24 hours
5 files changed, 80 insertions(+), 35 deletions(-)

M lib/registration.rb
M lib/transaction.rb
M sgx_jmp.rb
M test/test_registration.rb
M test/test_transaction.rb
M lib/registration.rb => lib/registration.rb +3 -3
@@ 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 }

M lib/transaction.rb => lib/transaction.rb +33 -9
@@ 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)

M sgx_jmp.rb => sgx_jmp.rb +3 -3
@@ 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|

M test/test_registration.rb => test/test_registration.rb +12 -12
@@ 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(

M test/test_transaction.rb => test/test_transaction.rb +29 -8
@@ 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