M lib/customer.rb => lib/customer.rb +2 -1
@@ 22,7 22,8 @@ class Customer
attr_reader :customer_id, :balance, :jid, :tndetails
def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
- :currency, :merchant_account, :plan_name, :auto_top_up_amount
+ :currency, :merchant_account, :plan_name,
+ :auto_top_up_amount, :monthly_overage_limit
def_delegators :@sgx, :register!, :registered?, :set_ogm_url,
:fwd, :transcription_enabled
def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage
M lib/customer_info.rb => lib/customer_info.rb +7 -13
@@ 1,6 1,7 @@
# frozen_string_literal: true
require "bigdecimal"
+require "forwardable"
require "relative_time"
require "value_semantics/monkey_patched"
require_relative "proxied_jid"
@@ 8,17 9,15 @@ require_relative "customer_plan"
require_relative "form_template"
class PlanInfo
+ extend Forwardable
+
+ def_delegators :plan, :expires_at, :auto_top_up_amount
+
def self.for(plan)
return EMPromise.resolve(NoPlan.new) unless plan&.plan_name
- EMPromise.all([
- plan.activation_date,
- plan.auto_top_up_amount
- ]).then do |adate, atua|
- new(
- plan: plan, start_date: adate,
- auto_top_up_amount: atua
- )
+ plan.activation_date.then do |adate|
+ new(plan: plan, start_date: adate)
end
end
@@ 39,11 38,6 @@ class PlanInfo
value_semantics do
plan CustomerPlan
start_date Time
- auto_top_up_amount Integer
- end
-
- def expires_at
- plan.expires_at
end
def template
M lib/customer_plan.rb => lib/customer_plan.rb +15 -6
@@ 3,19 3,32 @@
require "forwardable"
require_relative "em"
+require_relative "plan"
class CustomerPlan
extend Forwardable
- attr_reader :expires_at
+ attr_reader :expires_at, :auto_top_up_amount, :monthly_overage_limit
def_delegator :@plan, :name, :plan_name
def_delegators :@plan, :currency, :merchant_account, :monthly_price
- def initialize(customer_id, plan: nil, expires_at: Time.now)
+ def self.for(customer_id, plan_name: nil, **kwargs)
+ new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
+ end
+
+ def initialize(
+ customer_id,
+ plan: nil,
+ expires_at: Time.now,
+ auto_top_up_amount: 0,
+ monthly_overage_limit: 0
+ )
@customer_id = customer_id
@plan = plan || OpenStruct.new
@expires_at = expires_at
+ @auto_top_up_amount = auto_top_up_amount
+ @monthly_overage_limit = monthly_overage_limit
end
def active?
@@ 30,10 43,6 @@ class CustomerPlan
)
end
- def auto_top_up_amount
- REDIS.get("jmp_customer_auto_top_up_amount-#{@customer_id}").then(&:to_i)
- end
-
def bill_plan
EM.promise_fiber do
DB.transaction do
M lib/customer_repo.rb => lib/customer_repo.rb +33 -18
@@ 65,10 65,8 @@ class CustomerRepo
@bandwidth_tn_repo.put_lidb_name(customer.registered?.phone, lidb_name)
end
- def put_transcription_enabled(customer, transcription_enabled)
- @sgx_repo.put_transcription_enabled(
- customer.customer_id, transcription_enabled
- )
+ def put_transcription_enabled(customer, enabled)
+ @sgx_repo.put_transcription_enabled(customer.customer_id, enabled)
end
def put_fwd(customer, customer_fwd)
@@ 82,14 80,17 @@ protected
TrivialBackendSgxRepo.new.get(customer_id).with(registered?: false)
end
- def hydrate_plan(customer_id, raw_customer)
- raw_customer.dup.tap do |data|
- data[:plan] = CustomerPlan.new(
- customer_id,
- plan: data.delete(:plan_name)&.then(&Plan.method(:for)),
- expires_at: data.delete(:expires_at)
- )
- end
+ def mget(*keys)
+ @redis.mget(keys).then { |values| Hash[keys.zip(values)] }
+ end
+
+ def fetch_redis(customer_id)
+ mget(
+ "jmp_customer_auto_top_up_amount-#{customer_id}",
+ "jmp_customer_monthly_overage_limit-#{customer_id}"
+ ).then { |r|
+ r.transform_keys { |k| k.match(/^jmp_customer_([^-]+)/)[1].to_sym }
+ }
end
SQL = <<~SQL
@@ 98,6 99,20 @@ protected
WHERE customer_id=$1 LIMIT 1
SQL
+ def fetch_sql(customer_id)
+ @db.query_defer(SQL, [customer_id]).then do |rows|
+ rows.first&.transform_keys(&:to_sym) || { balance: 0 }
+ end
+ end
+
+ def fetch_all(customer_id)
+ EMPromise.all([
+ @sgx_repo.get(customer_id),
+ fetch_sql(customer_id),
+ fetch_redis(customer_id)
+ ]).then { |sgx, sql, redis| [sgx, sql.merge(redis)] }
+ end
+
def tndetails(sgx)
return unless sgx.registered?
@@ 105,14 120,14 @@ protected
end
def find_inner(customer_id, jid)
- result = @db.query_defer(SQL, [customer_id])
- EMPromise.all([@sgx_repo.get(customer_id), result]).then do |(sgx, rows)|
- data = hydrate_plan(
- customer_id, rows.first&.transform_keys(&:to_sym) || {}
- )
+ fetch_all(customer_id).then do |(sgx, data)|
Customer.new(
customer_id, Blather::JID.new(jid),
- sgx: sgx, tndetails: tndetails(sgx), **data
+ sgx: sgx, balance: data[:balance], tndetails: tndetails(sgx),
+ plan: CustomerPlan.for(
+ customer_id,
+ **data.delete_if { |(k, _)| k == :balance }
+ )
)
end
end
M lib/low_balance.rb => lib/low_balance.rb +10 -10
@@ 11,15 11,13 @@ class LowBalance
"jmp_customer_low_balance-#{customer.customer_id}",
expiry: 60 * 60 * 24 * 7
).with(-> { Locked.new }) do
- customer.auto_top_up_amount.then do |auto_top_up_amount|
- for_auto_top_up_amount(customer, auto_top_up_amount)
- end
+ for_auto_top_up_amount(customer)
end
end
- def self.for_auto_top_up_amount(customer, auto_top_up_amount)
- if auto_top_up_amount.positive?
- AutoTopUp.new(customer, auto_top_up_amount)
+ def self.for_auto_top_up_amount(customer)
+ if customer.auto_top_up_amount.positive?
+ AutoTopUp.new(customer)
else
customer.btc_addresses.then do |btc_addresses|
new(customer, btc_addresses)
@@ 49,15 47,17 @@ class LowBalance
end
class AutoTopUp
- def initialize(customer, auto_top_up_amount)
+ def initialize(customer)
@customer = customer
- @auto_top_up_amount = auto_top_up_amount
@message = Blather::Stanza::Message.new
@message.from = CONFIG[:notify_from]
end
def sale
- Transaction.sale(@customer, amount: @auto_top_up_amount).then do |tx|
+ Transaction.sale(
+ @customer,
+ amount: @customer.auto_top_up_amount
+ ).then do |tx|
tx.insert.then { tx }
end
end
@@ 70,7 70,7 @@ class LowBalance
}.catch { |e|
@message.body =
"Automatic top-up transaction for " \
- "$#{@auto_top_up_amount} failed: #{e.message}"
+ "$#{customer.auto_top_up_amount} failed: #{e.message}"
}.then { @customer.stanza_to(@message) }
end
end
M test/test_customer_info.rb => test/test_customer_info.rb +0 -12
@@ 19,12 19,6 @@ class CustomerInfoTest < Minitest::Test
sgx = Minitest::Mock.new
sgx.expect(:registered?, false)
- CustomerPlan::REDIS.expect(
- :get,
- EMPromise.resolve(nil),
- ["jmp_customer_auto_top_up_amount-test"]
- )
-
CustomerPlan::DB.expect(
:query_defer,
EMPromise.resolve([{ "start_date" => Time.now }]),
@@ 44,12 38,6 @@ class CustomerInfoTest < Minitest::Test
fwd = CustomerFwd.for(uri: "tel:+12223334444", timeout: 15)
sgx.expect(:fwd, fwd)
- CustomerPlan::REDIS.expect(
- :get,
- EMPromise.resolve(nil),
- ["jmp_customer_auto_top_up_amount-test"]
- )
-
CustomerPlan::DB.expect(
:query_defer,
EMPromise.resolve([{ "start_date" => Time.now }]),
M test/test_helper.rb => test/test_helper.rb +15 -13
@@ 39,19 39,21 @@ require "tel_selections"
$VERBOSE = nil
Sentry.init
-def customer(customer_id="test", plan_name: nil, **kwargs)
- jid = kwargs.delete(:jid) || Blather::JID.new("#{customer_id}@example.net")
- if plan_name
- expires_at = kwargs.delete(:expires_at) || Time.now
- plan = CustomerPlan.new(
- customer_id,
- plan: Plan.for(plan_name),
- expires_at: expires_at
- )
- Customer.new(customer_id, jid, plan: plan, **kwargs)
- else
- Customer.new(customer_id, jid, **kwargs)
- end
+def customer(
+ customer_id="test",
+ plan_name: nil,
+ jid: Blather::JID.new("#{customer_id}@example.net"),
+ expires_at: Time.now,
+ auto_top_up_amount: 0,
+ **kwargs
+)
+ plan = CustomerPlan.for(
+ customer_id,
+ plan_name: plan_name,
+ expires_at: expires_at,
+ auto_top_up_amount: auto_top_up_amount
+ )
+ Customer.new(customer_id, jid, plan: plan, **kwargs)
end
CONFIG = {
M test/test_low_balance.rb => test/test_low_balance.rb +3 -13
@@ 24,11 24,6 @@ class LowBalanceTest < Minitest::Test
EMPromise.resolve(0),
["jmp_customer_low_balance-test"]
)
- CustomerPlan::REDIS.expect(
- :get,
- EMPromise.resolve(nil),
- ["jmp_customer_auto_top_up_amount-test"]
- )
Customer::REDIS.expect(
:smembers,
EMPromise.resolve([]),
@@ 53,11 48,6 @@ class LowBalanceTest < Minitest::Test
EMPromise.resolve(0),
["jmp_customer_low_balance-test"]
)
- CustomerPlan::REDIS.expect(
- :get,
- EMPromise.resolve("15"),
- ["jmp_customer_auto_top_up_amount-test"]
- )
ExpiringLock::REDIS.expect(
:setex,
EMPromise.resolve(nil),
@@ 65,7 55,7 @@ class LowBalanceTest < Minitest::Test
)
assert_kind_of(
LowBalance::AutoTopUp,
- LowBalance.for(customer).sync
+ LowBalance.for(customer(auto_top_up_amount: 15)).sync
)
assert_mock ExpiringLock::REDIS
end
@@ 75,8 65,8 @@ class LowBalanceTest < Minitest::Test
LowBalance::AutoTopUp::Transaction = Minitest::Mock.new
def setup
- @customer = customer
- @auto_top_up = LowBalance::AutoTopUp.new(@customer, 100)
+ @customer = customer(auto_top_up_amount: 100)
+ @auto_top_up = LowBalance::AutoTopUp.new(@customer)
end
def test_notify!