Method to bill the plan of a Customer

Stephen Paul Weber created

Bills their balance for the cost of one month of plan.
Activates the plan by insert into plan_log, unless already active in which case
extends current plan by one month.

Change summary

lib/customer.rb       | 47 ++++++++++++++++++++++++++++++++++++++
lib/plan.rb           |  4 +++
test/test_customer.rb | 55 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 106 insertions(+)

Detailed changes

lib/customer.rb 🔗

@@ -52,6 +52,14 @@ class Customer
 		)
 	end
 
+	def bill_plan
+		EM.promise_fiber do
+			DB.transaction do
+				charge_for_plan
+				add_one_month_to_current_plan unless activate_plan_starting_now
+			end
+		end
+	end
 
 	def payment_methods
 		@payment_methods ||=
@@ -72,4 +80,43 @@ class Customer
 			result&.respond_to?(:registered?) && result&.registered?
 		end
 	end
+
+protected
+
+	def charge_for_plan
+		params = [
+			@customer_id,
+			"#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
+			-@plan.monthly_price
+		]
+		DB.exec(<<~SQL, params)
+			INSERT INTO transactions
+				(customer_id, transaction_id, created_at, amount)
+			VALUES ($1, $2, LOCALTIMESTAMP, $3)
+		SQL
+	end
+
+	def activate_plan_starting_now
+		DB.exec(<<~SQL, [@customer_id, plan_name]).cmd_tuples.positive?
+			INSERT INTO plan_log
+				(customer_id, plan_name, date_range)
+			VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'))
+			ON CONFLICT DO NOTHING
+		SQL
+	end
+
+	def add_one_month_to_current_plan
+		DB.exec(<<~SQL, [@customer_id])
+			UPDATE plan_log SET date_range=range_merge(
+				date_range,
+				tsrange(
+					LOCALTIMESTAMP,
+					GREATEST(upper(date_range), LOCALTIMESTAMP) + '1 month'
+				)
+			)
+			WHERE
+				customer_id=$1 AND
+				date_range && tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month')
+		SQL
+	end
 end

lib/plan.rb 🔗

@@ -20,6 +20,10 @@ class Plan
 		@plan[:currency]
 	end
 
+	def monthly_price
+		BigDecimal.new(@plan[:monthly_price]) / 1000
+	end
+
 	def merchant_account
 		CONFIG[:braintree][:merchant_accounts].fetch(currency) do
 			raise "No merchant account for this currency"

test/test_customer.rb 🔗

@@ -47,4 +47,59 @@ class CustomerTest < Minitest::Test
 		assert_equal BigDecimal.new(0), customer.balance
 	end
 	em :test_for_customer_id_not_found
+
+	def test_bill_plan_activate
+		Customer::DB.expect(:transaction, nil) do |&block|
+			block.call
+			true
+		end
+		Customer::DB.expect(
+			:exec,
+			nil,
+			[
+				String,
+				Matching.new do |params|
+					params[0] == "test" &&
+					params[1].is_a?(String) &&
+					BigDecimal.new(-1) == params[2]
+				end
+			]
+		)
+		Customer::DB.expect(
+			:exec,
+			OpenStruct.new(cmd_tuples: 1),
+			[String, ["test", "test_usd"]]
+		)
+		Customer.new("test", plan_name: "test_usd").bill_plan.sync
+		Customer::DB.verify
+	end
+	em :test_bill_plan_activate
+
+	def test_bill_plan_update
+		Customer::DB.expect(:transaction, nil) do |&block|
+			block.call
+			true
+		end
+		Customer::DB.expect(
+			:exec,
+			nil,
+			[
+				String,
+				Matching.new do |params|
+					params[0] == "test" &&
+					params[1].is_a?(String) &&
+					BigDecimal.new(-1) == params[2]
+				end
+			]
+		)
+		Customer::DB.expect(
+			:exec,
+			OpenStruct.new(cmd_tuples: 0),
+			[String, ["test", "test_usd"]]
+		)
+		Customer::DB.expect(:exec, nil, [String, ["test"]])
+		Customer.new("test", plan_name: "test_usd").bill_plan.sync
+		Customer::DB.verify
+	end
+	em :test_bill_plan_update
 end