Activate pending plan for customer when they pay enough BTC

Stephen Paul Weber created

This is effectively the "BTC auto-accept" new way.  If they send enough to
activate (as set by activation_amount in config) and they have a pending plan in
redis and no plan in the db, then we buy them that plan.

We can't use the normal way to notify the user, because they likely haven't
bought a phone number yet.  Eventually we will be able to tell new-signup about
this and have it inserted into the flow there, I think? Not sure what we want to
do transitionally.

Change summary

bin/process_pending_btc_transactions | 111 ++++++++++++++++++++++++++---
1 file changed, 98 insertions(+), 13 deletions(-)

Detailed changes

bin/process_pending_btc_transactions 🔗

@@ -10,7 +10,8 @@
 #          target = \(tel: Text) -> "${tel}@cheogram.com"
 #        },
 #        electrum = env:ELECTRUM_CONFIG,
-#        plans = ./plans.dhall
+#        plans = ./plans.dhall,
+#        activation_amount = 10000
 #        }'
 
 require "bigdecimal"
@@ -63,19 +64,36 @@ btc_sell_price[:CAD] = BigDecimal.new(
 btc_sell_price[:USD] = btc_sell_price[:CAD] * cad_to_usd
 
 class Plan
-	def self.for_customer(customer_id)
-		row = DB.exec_params(<<-SQL, [customer_id]).first
+	def self.for_customer(customer)
+		row = DB.exec_params(<<-SQL, [customer.id]).first
 			SELECT plan_name FROM customer_plans WHERE customer_id=$1 LIMIT 1
 		SQL
-		return unless row
-		plan = CONFIG[:plans].find { |p| p["plan_name"] = row["plan_name"] }
-		new(plan) if plan
+		from_name(customer, row&.[]("plan_name"))
 	end
 
-	def initialize(plan)
+	def self.pending_for_customer(customer)
+		from_name(
+			customer,
+			REDIS.get("pending_plan_for-#{customer.id}"),
+			klass: Pending
+		)
+	end
+
+	def self.from_name(customer, plan_name, klass: Plan)
+		return unless plan_name
+		plan = CONFIG[:plans].find { |p| p[:name] == plan_name }
+		klass.new(customer, plan) if plan
+	end
+
+	def initialize(customer, plan)
+		@customer = customer
 		@plan = plan
 	end
 
+	def name
+		@plan[:name]
+	end
+
 	def currency
 		@plan[:currency]
 	end
@@ -84,6 +102,60 @@ class Plan
 		bonus = (0.050167 * fiat_amount) - (currency == :CAD ? 1 : cad_to_usd)
 		return bonus.round(4, :floor) if bonus > 0
 	end
+
+	def price
+		BigDecimal.new(@plan[:monthly_price].to_i) * 0.0001
+	end
+
+	def insert(start:, expire:)
+		params = [@customer.id, name, start, expire]
+		DB.exec_params(<<-SQL, params)
+			INSERT INTO plan_log
+				(customer_id, plan_name, starts_at, expires_at)
+			VALUES
+				($1, $2, $3, $4)
+		SQL
+	end
+
+	def activate_any_pending_plan!; end
+
+	class Pending < Plan
+		def initialize(customer, plan)
+			super
+			@go_until = Date.today >> 1
+		end
+
+		def activation_amount
+			camnt = BigDecimal.new(CONFIG[:activation_amount].to_i) * 0.0001
+			[camnt, price].max
+		end
+
+		def activate_any_pending_plan!
+			if @customer.balance < activation_amount
+				@customer.notify(
+					"Your account could not be activated because your " \
+					"balance of $#{@customer.balance} is less that the " \
+					"required activation amount of $#{activation_amount}. " \
+					"Please buy more credit to have your account activated."
+				)
+			else
+				charge_insert_notify
+			end
+		end
+
+	protected
+
+		def charge_insert_notify
+			@customer.add_transaction(
+				"activate_#{name}_until_#{@go_until}",
+				-price,
+				"Activate pending plan"
+			)
+			insert(start: Date.today, expire: @go_until)
+			REDIS.del("pending_plan_for-#{@customer.id}")
+			# TODO: @customer.notify("Your account has been activated")
+		end
+	end
 end
 
 class Customer
@@ -91,6 +163,10 @@ class Customer
 		@customer_id = customer_id
 	end
 
+	def id
+		@customer_id
+	end
+
 	def notify(body)
 		jid = REDIS.get("jmp_customer_jid-#{@customer_id}")
 		tel = REDIS.lindex("catapult_cred-#{jid}", 3)
@@ -101,7 +177,18 @@ class Customer
 	end
 
 	def plan
-		Plan.for_customer(@customer_id)
+		Plan.for_customer(self) || pending_plan
+	end
+
+	def pending_plan
+		Plan.pending_for_customer(self)
+	end
+
+	def balance
+		result = DB.exec_params(<<-SQL, [@customer_id]).first&.[]("balance")
+			SELECT balance FROM balances WHERE customer_id=$1
+		SQL
+		result || BigDecimal.new(0)
 	end
 
 	def add_btc_credit(txid, fiat_amount, cad_to_usd)
@@ -122,8 +209,6 @@ class Customer
 		].compact.join)
 	end
 
-protected
-
 	def add_transaction(id, amount, note)
 		DB.exec_params(<<-SQL, [@customer_id, id, amount, note])
 			INSERT INTO transactions
@@ -146,13 +231,13 @@ REDIS.hgetall("pending_btc_transactions").each do |(txid, customer_id)|
 	end
 	DB.transaction do
 		customer = Customer.new(customer_id)
-		plan = customer.plan
-		if plan
+		if (plan = customer.plan)
 			amount = btc * btc_sell_price.fetch(plan.currency).round(4, :floor)
 			customer.add_btc_credit(txid, amount, cad_to_usd)
+			customer.plan.activate_any_pending_plan!
+			REDIS.hdel("pending_btc_transactions", txid)
 		else
 			warn "No plan for #{customer_id} cannot save #{txid}"
 		end
 	end
-	REDIS.hdel("pending_btc_transactions", txid)
 end