M => +2 -1
@@ 9,6 9,7 @@ field(
description: "or put a new customer info",
options: [
{ value: "info", label: "Customer Info" },
{ value: "financial", label: "Customer Billing Information" }
{ value: "financial", label: "Customer Billing Information" },
{ value: "bill_plan", label: "Bill Customer" }
]
)
M lib/admin_command.rb => lib/admin_command.rb +5 -0
@@ 1,5 1,6 @@
# frozen_string_literal: true
+require_relative "bill_plan_command"
require_relative "customer_info_form"
require_relative "financial_info"
require_relative "form_template"
@@ 65,6 66,10 @@ class AdminCommand
end
end
+ def action_bill_plan
+ BillPlanCommand.for(@target_customer).call
+ end
+
def pay_methods(financial_info)
reply(FormTemplate.render(
"admin_payment_methods",
A lib/bill_plan_command.rb => lib/bill_plan_command.rb +69 -0
@@ 0,0 1,69 @@
+# frozen_string_literal: true
+
+class BillPlanCommand
+ def self.for(customer)
+ return ForUnregistered.new unless customer.registered?
+
+ unless customer.balance > customer.monthly_price
+ return ForLowBalance.new(customer)
+ end
+
+ new(customer)
+ end
+
+ def initialize(customer)
+ @customer = customer
+ end
+
+ def call
+ @customer.bill_plan
+ Command.reply do |reply|
+ reply.note_type = :info
+ reply.note_text = "Customer billed"
+ end
+ end
+
+ class ForLowBalance
+ def initialize(customer)
+ @customer = customer
+ end
+
+ def call
+ LowBalance.for(@customer).then(&:notify!).then do |amount|
+ return command_for(amount).call if amount&.positive?
+
+ notify_failure
+ Command.reply do |reply|
+ reply.note_type = :error
+ reply.note_text = "Customer balance is too low"
+ end
+ end
+ end
+
+ protected
+
+ def notify_failure
+ m = Blather::Stanza::Message.new
+ m.from = CONFIG[:notify_from]
+ m.body =
+ "Failed to renew account for #{@customer.registered?.phone}. " \
+ "To keep your number, please buy more credit soon."
+ @customer.stanza_to(m)
+ end
+
+ def command_for(amount)
+ BillPlanCommand.for(
+ @customer.with_balance(@customer.balance + amount)
+ )
+ end
+ end
+
+ class ForUnregistered
+ def call
+ Command.reply do |reply|
+ reply.note_type = :error
+ reply.note_text = "Customer is not registered"
+ end
+ end
+ end
+end
M lib/command.rb => lib/command.rb +5 -1
@@ 11,6 11,10 @@ class Command
Thread.current[:execution]
end
+ def self.execution=(exe)
+ Thread.current[:execution] = exe
+ end
+
def self.reply(stanza=nil, &blk)
execution.reply(stanza, &blk)
end
@@ 51,7 55,7 @@ class Command
def execute
StatsD.increment("command", tags: ["node:#{iq.node}"])
EMPromise.resolve(nil).then {
- Thread.current[:execution] = self
+ Command.execution = self
sentry_hub
catch_after(EMPromise.resolve(yield self))
}.catch(&method(:panic))
M lib/customer.rb => lib/customer.rb +2 -1
@@ 24,7 24,8 @@ class Customer
def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
:currency, :merchant_account, :plan_name, :minute_limit,
- :message_limit, :auto_top_up_amount, :monthly_overage_limit
+ :message_limit, :auto_top_up_amount, :monthly_overage_limit,
+ :monthly_price
def_delegators :@sgx, :register!, :registered?, :set_ogm_url,
:fwd, :transcription_enabled
def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage
A lib/db_notification.rb => lib/db_notification.rb +17 -0
@@ 0,0 1,17 @@
+# frozen_string_literal: true
+
+require_relative "dummy_command"
+
+module DbNotification
+ def self.for(notify, customer)
+ case notify[:relname]
+ when "low_balance"
+ LowBalance.for(customer).then { |lb| lb.method(:notify!) }
+ when "possible_renewal"
+ Command.execution = DummyCommand.new(customer)
+ BillPlanCommand.for(customer)
+ else
+ raise "Unknown notification: #{notify[:relname]}"
+ end
+ end
+end
A lib/dummy_command.rb => lib/dummy_command.rb +17 -0
@@ 0,0 1,17 @@
+# frozen_string_literal: true
+
+class DummyCommand
+ attr_reader :customer
+
+ def initialize(customer)
+ @customer = customer
+ end
+
+ def reply(*); end
+
+ def finish(*); end
+
+ def log
+ ::LOG
+ end
+end
M lib/low_balance.rb => lib/low_balance.rb +2 -2
@@ 11,11 11,11 @@ class LowBalance
"jmp_customer_low_balance-#{customer.customer_id}",
expiry: 60 * 60 * 24 * 7
).with(-> { Locked.new }) do
- for_auto_top_up_amount(customer)
+ for_no_lock(customer)
end
end
- def self.for_auto_top_up_amount(customer)
+ def self.for_no_lock(customer)
if customer.auto_top_up_amount.positive?
AutoTopUp.new(customer)
else
M sgx_jmp.rb => sgx_jmp.rb +23 -5
@@ 81,6 81,8 @@ require_relative "lib/command_list"
require_relative "lib/customer"
require_relative "lib/customer_info_form"
require_relative "lib/customer_repo"
+require_relative "lib/dummy_command"
+require_relative "lib/db_notification"
require_relative "lib/electrum"
require_relative "lib/empty_repo"
require_relative "lib/expiring_lock"
@@ 178,10 180,27 @@ end
EM.error_handler(&method(:panic))
+# Infer anything we might have been notified about while we were down
+def catchup_notify(db)
+ db.query("SELECT customer_id FROM balances WHERE balance < 5").each do |c|
+ db.query("SELECT pg_notify('low_balance', $1)", c.values)
+ end
+ db.query(<<~SQL).each do |c|
+ SELECT customer_id
+ FROM customer_plans INNER JOIN balances USING (customer_id)
+ WHERE expires_at < LOCALTIMESTAMP AND balance >= 5
+ SQL
+ db.query("SELECT pg_notify('possible_renewal', $1)", c.values)
+ end
+end
+
def poll_for_notify(db)
db.wait_for_notify_defer.then { |notify|
- CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new).find(notify[:extra])
- }.then(&LowBalance.method(:for)).then(&:notify!).then {
+ CustomerRepo
+ .new(sgx_repo: Bwmsgsv2Repo.new)
+ .find(notify[:extra])
+ .then { |customer| DbNotification.for(notify, customer) }
+ }.then(&:call).then {
poll_for_notify(db)
}.catch(&method(:panic))
end
@@ 208,9 227,8 @@ when_ready do
DB.hold do |conn|
conn.query("LISTEN low_balance")
- conn.query("SELECT customer_id FROM balances WHERE balance < 5").each do |c|
- conn.query("SELECT pg_notify('low_balance', $1)", c.values)
- end
+ conn.query("LISTEN possible_renewal")
+ catchup_notify(conn)
poll_for_notify(conn)
end