Detailed changes
@@ -1,21 +1,25 @@
# frozen_string_literal: true
+require_relative "trust_level_repo"
+require_relative "credit_card_sale"
+
class BuyAccountCreditForm
- class AmountValidationError < StandardError
- def initialize(amount)
- super("amount #{amount} must be more than $15")
- end
+ def self.trust_level_repo(**kwargs)
+ kwargs[:trust_level_repo] || TrustLevelRepo.new(**kwargs)
end
def self.for(customer)
- customer.payment_methods.then do |payment_methods|
- new(customer.balance, payment_methods)
+ trust_level_repo.find(customer).then do |trust_level|
+ customer.payment_methods.then do |payment_methods|
+ new(customer.balance, payment_methods, trust_level.max_top_up_amount)
+ end
end
end
- def initialize(balance, payment_methods)
+ def initialize(balance, payment_methods, max_top_up_amount)
@balance = balance
@payment_methods = payment_methods
+ @max_top_up_amount = max_top_up_amount
end
def form
@@ -23,13 +27,12 @@ class BuyAccountCreditForm
:top_up,
balance: @balance,
payment_methods: @payment_methods,
- range: [15, nil]
+ range: [15, @max_top_up_amount]
)
end
def parse(form)
amount = form.field("amount")&.value&.to_s
- raise AmountValidationError, amount unless amount.to_i >= 15
{
payment_method: @payment_methods.fetch(
@@ -6,6 +6,38 @@ require "delegate"
require_relative "transaction"
require_relative "trust_level_repo"
+class TransactionDeclinedError < StandardError; end
+
+class AmountTooHighError < TransactionDeclinedError
+ attr_reader :amount, :max_amount
+
+ def initialize(amount, max_amount)
+ @amount = amount
+ @max_amount = max_amount
+ super("Amount $#{amount} exceeds maximum allowed amount of $#{max_amount}")
+ end
+end
+
+class AmountTooLowError < TransactionDeclinedError
+ attr_reader :amount, :min_amount
+
+ def initialize(amount, min_amount)
+ @amount = amount
+ @min_amount = min_amount
+ super("Amount $#{amount} is below minimum amount of $#{min_amount}")
+ end
+end
+
+class DeclinedError < TransactionDeclinedError
+ attr_reader :declines, :max_declines
+
+ def initialize(declines, max_declines)
+ @declines = declines
+ @max_declines = max_declines
+ super("Transaction declined")
+ end
+end
+
class CreditCardSale
def self.create(*args, transaction_class: Transaction, **kwargs)
new(*args, **kwargs).sale.then do |response|
@@ -62,10 +94,9 @@ protected
REDIS.exists("jmp_customer_credit_card_lock-#{@customer.customer_id}"),
@trust_repo.find(@customer), @customer.declines
]).then do |(lock, tl, declines)|
- unless tl.credit_card_transaction?(@amount.to_d, declines)
- raise "Declined"
- end
- raise "Too many payments recently" if lock == 1
+ raise TransactionDeclinedError, "Too many payments recently" if lock == 1
+
+ tl.validate_credit_card_transaction!(@amount.to_d, declines)
end
end
@@ -107,7 +138,7 @@ class BraintreeFailure < StandardError
attr_reader :response
def initialize(response)
- super response.message
+ super(response.message)
@response = response
end
end
@@ -32,6 +32,10 @@ module TrustLevel
new if manual == "Tomb"
end
+ def max_top_up_amount
+ 0
+ end
+
def write_cdr?
false
end
@@ -44,8 +48,9 @@ module TrustLevel
false
end
- def credit_card_transaction?(*)
- false
+ def validate_credit_card_transaction!(_amount, _declines)
+ # Give a more ambiguous error so they don't know they're tombed.
+ raise DeclinedError
end
def create_subaccount?(*)
@@ -62,6 +67,14 @@ module TrustLevel
new if manual == "Basement" || (!manual && settled_amount < 10)
end
+ def max_top_up_amount
+ 35
+ end
+
+ def max_declines
+ 2
+ end
+
def write_cdr?
true
end
@@ -74,8 +87,11 @@ module TrustLevel
messages_today < 40
end
- def credit_card_transaction?(amount, declines)
- amount <= 35 && declines <= 2
+ def validate_credit_card_transaction!(amount, declines)
+ raise DeclinedError.new(declines, max_declines) if declines > max_declines
+ return unless amount > max_top_up_amount
+
+ raise AmountTooHighError.new(amount, max_top_up_amount)
end
def create_subaccount?(already_have)
@@ -92,6 +108,14 @@ module TrustLevel
new if manual == "Paragon" || (!manual && settled_amount > 60)
end
+ def max_top_up_amount
+ 500
+ end
+
+ def max_declines
+ 3
+ end
+
def write_cdr?
true
end
@@ -104,8 +128,11 @@ module TrustLevel
messages_today < 700
end
- def credit_card_transaction?(amount, declines)
- amount <= 500 && declines <= 3
+ def validate_credit_card_transaction!(amount, declines)
+ raise DeclinedError.new(declines, max_declines) if declines > max_declines
+ return unless amount > max_top_up_amount
+
+ raise AmountTooHighError.new(amount, max_top_up_amount)
end
def create_subaccount?(already_have)
@@ -134,9 +161,7 @@ module TrustLevel
true
end
- def credit_card_transaction?(*)
- true
- end
+ def validate_credit_card_transaction!(*) end
def create_subaccount?(*)
true
@@ -167,6 +192,14 @@ module TrustLevel
@max_rate = EXPENSIVE_ROUTE.fetch(plan_name, 0.1)
end
+ def max_top_up_amount
+ 130
+ end
+
+ def max_declines
+ 2
+ end
+
def write_cdr?
true
end
@@ -179,8 +212,11 @@ module TrustLevel
messages_today < 500
end
- def credit_card_transaction?(amount, declines)
- amount <= 130 && declines <= 2
+ def validate_credit_card_transaction!(amount, declines)
+ raise DeclinedError.new(declines, max_declines) if declines > max_declines
+ return unless amount > max_top_up_amount
+
+ raise AmountTooHighError.new(amount, max_top_up_amount)
end
def create_subaccount?(already_have)
@@ -670,7 +670,10 @@ Command.new(
end
}.then { |transaction|
Command.finish("#{transaction} added to your account balance.")
- }.catch_only(BuyAccountCreditForm::AmountValidationError) do |e|
+ }.catch_only(
+ BuyAccountCreditForm::AmountTooHighError,
+ BuyAccountCreditForm::AmountTooLowError
+ ) do |e|
Command.finish(e.message, type: :error)
end
}.register(self).then(&CommandList.method(:register))
@@ -3,16 +3,21 @@
require "test_helper"
require "buy_account_credit_form"
require "customer"
+require "credit_card_sale"
CustomerFinancials::BRAINTREE = Minitest::Mock.new
CustomerFinancials::REDIS = Minitest::Mock.new
+TrustLevelRepo::REDIS = Minitest::Mock.new
+TrustLevelRepo::DB = Minitest::Mock.new
class BuyAccountCreditFormTest < Minitest::Test
def setup
@payment_method = OpenStruct.new(card_type: "Test", last_4: "1234")
+ @max_top_up_amount = 130
@form = BuyAccountCreditForm.new(
BigDecimal("15.1234"),
- PaymentMethods.new([@payment_method])
+ PaymentMethods.new([@payment_method]),
+ @max_top_up_amount
)
end
@@ -26,10 +31,24 @@ class BuyAccountCreditFormTest < Minitest::Test
["test"]
)
+ TrustLevelRepo::REDIS.expect(
+ :get,
+ EMPromise.resolve("Customer"),
+ ["jmp_customer_trust_level-test"]
+ )
+ TrustLevelRepo::DB.expect(
+ :query_one,
+ EMPromise.resolve({}),
+ [String, "test"], default: {}
+ )
+
assert_kind_of(
BuyAccountCreditForm,
BuyAccountCreditForm.for(customer).sync
)
+
+ assert_mock TrustLevelRepo::REDIS
+ assert_mock TrustLevelRepo::DB
end
em :test_for
@@ -67,14 +86,6 @@ class BuyAccountCreditFormTest < Minitest::Test
assert_equal "123", @form.parse(iq_form)[:amount]
end
- def test_parse_bad_amount
- iq_form = Blather::Stanza::X.new
- iq_form.fields = [{ var: "amount", value: "1" }]
- assert_raises(BuyAccountCreditForm::AmountValidationError) do
- @form.parse(iq_form)[:amount]
- end
- end
-
def test_parse_payment_method
iq_form = Blather::Stanza::X.new
iq_form.fields = [
@@ -9,6 +9,7 @@ CreditCardSale::BRAINTREE = Minitest::Mock.new
CreditCardSale::REDIS = Minitest::Mock.new
TrustLevelRepo::REDIS = Minitest::Mock.new
TrustLevelRepo::DB = Minitest::Mock.new
+CustomerFinancials::REDIS = Minitest::Mock.new
class CreditCardSaleTest < Minitest::Test
FAKE_BRAINTREE_TRANSACTION =
@@ -111,6 +112,80 @@ class CreditCardSaleTest < Minitest::Test
end
em :test_sale_locked
+ def test_sale_amount_too_high
+ CreditCardSale::REDIS.expect(
+ :exists,
+ EMPromise.resolve(0),
+ ["jmp_customer_credit_card_lock-test"]
+ )
+ TrustLevelRepo::REDIS.expect(
+ :get,
+ EMPromise.resolve("Customer"),
+ ["jmp_customer_trust_level-test"]
+ )
+ TrustLevelRepo::DB.expect(
+ :query_one,
+ EMPromise.resolve({}),
+ [String, "test"], default: {}
+ )
+ CustomerFinancials::REDIS.expect(
+ :get,
+ EMPromise.resolve("0"),
+ ["jmp_pay_decline-test"]
+ )
+
+ assert_raises(AmountTooHighError) do
+ CreditCardSale.new(
+ customer(plan_name: "test_usd"),
+ amount: 131,
+ payment_method: OpenStruct.new(token: "token")
+ ).sale.sync
+ end
+
+ assert_mock CustomerFinancials::REDIS
+ assert_mock CreditCardSale::REDIS
+ assert_mock TrustLevelRepo::REDIS
+ assert_mock TrustLevelRepo::DB
+ end
+ em :test_sale_amount_too_high
+
+ def test_sale_too_many_declines
+ CreditCardSale::REDIS.expect(
+ :exists,
+ EMPromise.resolve(0),
+ ["jmp_customer_credit_card_lock-test"]
+ )
+ TrustLevelRepo::REDIS.expect(
+ :get,
+ EMPromise.resolve("Customer"),
+ ["jmp_customer_trust_level-test"]
+ )
+ TrustLevelRepo::DB.expect(
+ :query_one,
+ EMPromise.resolve({}),
+ [String, "test"], default: {}
+ )
+ CustomerFinancials::REDIS.expect(
+ :get,
+ EMPromise.resolve("3"),
+ ["jmp_pay_decline-test"]
+ )
+
+ assert_raises(DeclinedError) do
+ CreditCardSale.new(
+ customer(plan_name: "test_usd"),
+ amount: 50,
+ payment_method: OpenStruct.new(token: "token")
+ ).sale.sync
+ end
+
+ assert_mock CustomerFinancials::REDIS
+ assert_mock CreditCardSale::REDIS
+ assert_mock TrustLevelRepo::REDIS
+ assert_mock TrustLevelRepo::DB
+ end
+ em :test_sale_too_many_declines
+
def test_sale
req = stub_request(
:post,