~singpolyma/sgx-jmp

dabca132291e2c3d1ca03e272b2cd556956c2e84 — Stephen Paul Weber 3 years ago e5b1edb
Break out CustomerPlan

We had Plan and Customer but the relationship between the two lived
entirely in Customer, which was growing quite large. Break that
relationship out into its own concept and give it a name.
3 files changed, 89 insertions(+), 65 deletions(-)

M lib/customer.rb
A lib/customer_plan.rb
M test/test_customer.rb
M lib/customer.rb => lib/customer.rb +10 -56
@@ 2,6 2,7 @@

require "forwardable"

require_relative "./customer_plan"
require_relative "./backend_sgx"
require_relative "./ibr"
require_relative "./payment_methods"


@@ 29,8 30,8 @@ class Customer
	extend Forwardable

	attr_reader :customer_id, :balance
	def_delegator :@plan, :name, :plan_name
	def_delegators :@plan, :currency, :merchant_account
	def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
	               :currency, :merchant_account, :plan_name
	def_delegators :@sgx, :register!, :registered?

	def initialize(


@@ 40,8 41,11 @@ class Customer
		balance: BigDecimal.new(0),
		sgx: BackendSgx.new(customer_id)
	)
		@plan = plan_name && Plan.for(plan_name)
		@expires_at = expires_at
		@plan = CustomerPlan.new(
			customer_id,
			plan: plan_name && Plan.for(plan_name),
			expires_at: expires_at
		)
		@customer_id = customer_id
		@balance = balance
		@sgx = sgx


@@ 51,29 55,11 @@ class Customer
		self.class.new(
			@customer_id,
			balance: @balance,
			expires_at: @expires_at,
			expires_at: expires_at,
			plan_name: plan_name
		)
	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 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 payment_methods
		@payment_methods ||=
			BRAINTREE


@@ 82,37 68,5 @@ class Customer
			.then(PaymentMethods.method(:for_braintree_customer))
	end

	def active?
		@plan && @expires_at > Time.now
	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 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
	protected def_delegator :@plan, :expires_at
end

A lib/customer_plan.rb => lib/customer_plan.rb +69 -0
@@ 0,0 1,69 @@
# frozen_string_literal: true

require "forwardable"

class CustomerPlan
	extend Forwardable

	attr_reader :expires_at
	def_delegator :@plan, :name, :plan_name
	def_delegators :@plan, :currency, :merchant_account

	def initialize(customer_id, plan: nil, expires_at: Time.now)
		@customer_id = customer_id
		@plan = plan
		@expires_at = expires_at
	end

	def active?
		@plan && @expires_at > Time.now
	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 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

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

M test/test_customer.rb => test/test_customer.rb +10 -9
@@ 5,6 5,7 @@ require "customer"

Customer::REDIS = Minitest::Mock.new
Customer::DB = Minitest::Mock.new
CustomerPlan::DB = Minitest::Mock.new

class CustomerTest < Minitest::Test
	def test_for_jid


@@ 49,11 50,11 @@ class CustomerTest < Minitest::Test
	em :test_for_customer_id_not_found

	def test_bill_plan_activate
		Customer::DB.expect(:transaction, nil) do |&block|
		CustomerPlan::DB.expect(:transaction, nil) do |&block|
			block.call
			true
		end
		Customer::DB.expect(
		CustomerPlan::DB.expect(
			:exec,
			nil,
			[


@@ 65,22 66,22 @@ class CustomerTest < Minitest::Test
				end
			]
		)
		Customer::DB.expect(
		CustomerPlan::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
		CustomerPlan::DB.verify
	end
	em :test_bill_plan_activate

	def test_bill_plan_update
		Customer::DB.expect(:transaction, nil) do |&block|
		CustomerPlan::DB.expect(:transaction, nil) do |&block|
			block.call
			true
		end
		Customer::DB.expect(
		CustomerPlan::DB.expect(
			:exec,
			nil,
			[


@@ 92,14 93,14 @@ class CustomerTest < Minitest::Test
				end
			]
		)
		Customer::DB.expect(
		CustomerPlan::DB.expect(
			:exec,
			OpenStruct.new(cmd_tuples: 0),
			[String, ["test", "test_usd"]]
		)
		Customer::DB.expect(:exec, nil, [String, ["test"]])
		CustomerPlan::DB.expect(:exec, nil, [String, ["test"]])
		Customer.new("test", plan_name: "test_usd").bill_plan.sync
		Customer::DB.verify
		CustomerPlan::DB.verify
	end
	em :test_bill_plan_update
end