Bill plan command

Stephen Paul Weber created

Implements the full logic of the billing_monthly_cronjob for one customer by
reusing code that has been written more robustly in sgx-jmp for some time.  This
will allow the cronjob to just execute this command once a day for each expiring
customer to get all the correct billing behaviours.

Change summary

forms/admin_menu.rb      |  3 +
lib/admin_command.rb     |  5 +++
lib/bill_plan_command.rb | 69 ++++++++++++++++++++++++++++++++++++++++++
lib/customer.rb          |  3 +
4 files changed, 78 insertions(+), 2 deletions(-)

Detailed changes

forms/admin_menu.rb 🔗

@@ -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" }
 	]
 )

lib/admin_command.rb 🔗

@@ -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",

lib/bill_plan_command.rb 🔗

@@ -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

lib/customer.rb 🔗

@@ -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