M lib/customer.rb => lib/customer.rb +1 -10
@@ 24,7 24,7 @@ class Customer
def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
:currency, :merchant_account, :plan_name, :minute_limit,
:message_limit, :monthly_overage_limit, :activation_date,
- :expires_at, :monthly_price, :save_plan!
+ :expires_at, :monthly_price, :save_plan!, :auto_top_up_amount
def_delegators :@sgx, :deregister!, :register!, :registered?, :set_ogm_url,
:fwd, :transcription_enabled
def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage,
@@ 85,15 85,6 @@ class Customer
EMPromise.resolve(self)
end
- def auto_top_up_amount
- if @plan.auto_top_up_amount.positive? &&
- balance < -@plan.auto_top_up_amount + 5
- -balance + @plan.auto_top_up_amount
- else
- @plan.auto_top_up_amount
- end
- end
-
def unused_invites
InvitesRepo.new(DB).unused_invites(customer_id)
end
M lib/low_balance.rb => lib/low_balance.rb +34 -15
@@ 4,30 4,33 @@ require_relative "expiring_lock"
require_relative "transaction"
class LowBalance
- def self.for(customer)
+ def self.for(customer, transaction_amount=0)
return Locked.new unless customer.registered?
ExpiringLock.new(
"jmp_customer_low_balance-#{customer.billing_customer_id}",
expiry: 60 * 60 * 24 * 7
).with(-> { Locked.new }) do
- customer.billing_customer.then(&method(:for_no_lock))
+ customer.billing_customer.then do |billing_customer|
+ for_no_lock(billing_customer, transaction_amount)
+ end
end
end
- def self.for_no_lock(customer, auto: true)
+ def self.for_no_lock(customer, transaction_amount, auto: true)
if auto && customer.auto_top_up_amount.positive?
- AutoTopUp.for(customer)
+ AutoTopUp.for(customer, transaction_amount)
else
customer.btc_addresses.then do |btc_addresses|
- new(customer, btc_addresses)
+ new(customer, btc_addresses, transaction_amount)
end
end
end
- def initialize(customer, btc_addresses)
+ def initialize(customer, btc_addresses, transaction_amount=0)
@customer = customer
@btc_addresses = btc_addresses
+ @transaction_amount = transaction_amount
end
def notify!
@@ 35,11 38,21 @@ class LowBalance
m.from = CONFIG[:notify_from]
m.body =
"Your balance of $#{'%.4f' % @customer.balance} is low." \
+ "#{pending_cost_for_notification}" \
"#{btc_addresses_for_notification}"
@customer.stanza_to(m)
EMPromise.resolve(0)
end
+ def pending_cost_for_notification
+ return unless @transaction_amount&.positive?
+ return unless @transaction_amount > @customer.balance
+
+ "You need an additional " \
+ "$#{'%.2f' % (@transaction_amount - @customer.balance)} "\
+ "to complete this transaction."
+ end
+
def btc_addresses_for_notification
return if @btc_addresses.empty?
@@ 48,13 61,13 @@ class LowBalance
end
class AutoTopUp
- def self.for(customer)
+ def self.for(customer, target=0)
customer.payment_methods.then(&:default_payment_method).then do |method|
blocked?(method).then do |block|
- next AutoTopUp.new(customer, method) if block.zero?
+ next AutoTopUp.new(customer, method, target) if block.zero?
log.info("#{customer.customer_id} auto top up blocked")
- LowBalance.for_no_lock(customer, auto: false)
+ LowBalance.for_no_lock(customer, target, auto: false)
end
end
end
@@ 67,18 80,24 @@ class LowBalance
)
end
- def initialize(customer, method=nil)
+ def initialize(customer, method=nil, target=0, margin: 10)
@customer = customer
@method = method
+ @target = target
+ @margin = margin
@message = Blather::Stanza::Message.new
@message.from = CONFIG[:notify_from]
end
+ def top_up_amount
+ [
+ (@target + @margin) - @customer.balance,
+ @customer.auto_top_up_amount
+ ].max
+ end
+
def sale
- Transaction.sale(
- @customer,
- amount: @customer.auto_top_up_amount
- ).then do |tx|
+ Transaction.sale(@customer, amount: top_up_amount).then do |tx|
tx.insert.then { tx }
end
end
@@ 91,7 110,7 @@ class LowBalance
)
@message.body =
"Automatic top-up transaction for " \
- "$#{@customer.auto_top_up_amount} failed: #{e.message}"
+ "$#{top_up_amount} failed: #{e.message}"
0
end
M test/test_low_balance.rb => test/test_low_balance.rb +72 -2
@@ 38,6 38,43 @@ class LowBalanceTest < Minitest::Test
end
em :test_for_no_auto_top_up
+ def test_for_auto_top_up_on_transaction_amount
+ ExpiringLock::REDIS.expect(
+ :set,
+ EMPromise.resolve("OK"),
+ ["jmp_customer_low_balance-test", Time, "EX", 604800, "NX"]
+ )
+ CustomerFinancials::REDIS.expect(
+ :smembers,
+ EMPromise.resolve([]),
+ ["block_credit_cards"]
+ )
+ LowBalance::AutoTopUp::REDIS.expect(
+ :exists,
+ 0,
+ ["jmp_auto_top_up_block-abcd"]
+ )
+ braintree_customer = Minitest::Mock.new
+ CustomerFinancials::BRAINTREE.expect(:customer, braintree_customer)
+ payment_methods = OpenStruct.new(payment_methods: [
+ OpenStruct.new(default?: true, unique_number_identifier: "abcd")
+ ])
+ braintree_customer.expect(
+ :find,
+ EMPromise.resolve(payment_methods),
+ ["test"]
+ )
+ assert_kind_of(
+ LowBalance::AutoTopUp,
+ LowBalance.for(customer(auto_top_up_amount: 1), 15).sync
+ )
+ assert_mock ExpiringLock::REDIS
+ assert_mock CustomerFinancials::REDIS
+ assert_mock CustomerFinancials::BRAINTREE
+ assert_mock braintree_customer
+ end
+ em :test_for_auto_top_up_on_transaction_amount
+
def test_for_auto_top_up
ExpiringLock::REDIS.expect(
:set,
@@ 138,6 175,39 @@ class LowBalanceTest < Minitest::Test
end
em :test_notify!
+ def test_top_up_amount_when_target_greater_than_expected_balance
+ customer = Minitest::Mock.new(customer(
+ balance: 10,
+ auto_top_up_amount: 15
+ ))
+ auto_top_up = LowBalance::AutoTopUp.new(customer, nil, 30, margin: 5)
+
+ assert_equal 25, auto_top_up.top_up_amount
+ end
+ em :test_top_up_amount_when_target_greater_than_expected_balance
+
+ def test_top_up_amount_when_target_less_than_expected_balance
+ customer = Minitest::Mock.new(customer(
+ balance: 10,
+ auto_top_up_amount: 15
+ ))
+ auto_top_up = LowBalance::AutoTopUp.new(customer, nil, 12, margin: 5)
+
+ assert_equal 15, auto_top_up.top_up_amount
+ end
+ em :test_top_up_amount_when_target_less_than_expected_balance
+
+ def test_negative_balance_target_less_than_expected_balance
+ customer = Minitest::Mock.new(customer(
+ balance: -11,
+ auto_top_up_amount: 15
+ ))
+ auto_top_up = LowBalance::AutoTopUp.new(customer, nil, 35, margin: 5)
+
+ assert_equal 51, auto_top_up.top_up_amount
+ end
+ em :test_negative_balance_target_less_than_expected_balance
+
def test_very_low_balance_notify!
customer = Minitest::Mock.new(customer(
balance: -100,
@@ 150,7 220,7 @@ class LowBalanceTest < Minitest::Test
LowBalance::AutoTopUp::Transaction.expect(
:sale,
tx,
- [customer], amount: 115
+ [customer], amount: 110
)
auto_top_up.notify!
assert_mock tx
@@ 169,7 239,7 @@ class LowBalanceTest < Minitest::Test
LowBalance::AutoTopUp::Transaction.expect(
:sale,
tx,
- [customer], amount: 26
+ [customer], amount: 21
)
auto_top_up.notify!
assert_mock tx