customer_plan.rb

  1# frozen_string_literal: true
  2
  3require "forwardable"
  4
  5require_relative "em"
  6require_relative "plan"
  7
  8class CustomerPlan
  9	extend Forwardable
 10
 11	attr_reader :expires_at, :auto_top_up_amount, :monthly_overage_limit
 12
 13	def_delegator :@plan, :name, :plan_name
 14	def_delegators :@plan, :currency, :merchant_account, :monthly_price,
 15	               :minute_limit, :message_limit
 16
 17	def self.for(customer_id, plan_name: nil, **kwargs)
 18		new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
 19	end
 20
 21	def initialize(
 22		customer_id,
 23		plan: nil,
 24		expires_at: Time.now,
 25		auto_top_up_amount: 0,
 26		monthly_overage_limit: 0
 27	)
 28		@customer_id = customer_id
 29		@plan = plan || OpenStruct.new
 30		@expires_at = expires_at
 31		@auto_top_up_amount = auto_top_up_amount || 0
 32		@monthly_overage_limit = monthly_overage_limit || 0
 33	end
 34
 35	def active?
 36		plan_name && @expires_at > Time.now
 37	end
 38
 39	def with_plan_name(plan_name)
 40		self.class.new(
 41			@customer_id,
 42			plan: Plan.for(plan_name),
 43			expires_at: @expires_at
 44		)
 45	end
 46
 47	def save_plan!
 48		DB.exec_defer(<<~SQL, [@customer_id, plan_name])
 49			INSERT INTO plan_log
 50				(customer_id, plan_name, date_range)
 51			VALUES (
 52				$1,
 53				$2,
 54				tsrange(
 55					LOCALTIMESTAMP - '2 seconds'::interval,
 56					LOCALTIMESTAMP - '1 second'::interval
 57				)
 58			)
 59		SQL
 60	end
 61
 62	def bill_plan(note: nil)
 63		EM.promise_fiber do
 64			DB.transaction do
 65				charge_for_plan(note)
 66				add_one_month_to_current_plan unless activate_plan_starting_now
 67			end
 68		end
 69	end
 70
 71	def activate_plan_starting_now
 72		activated = DB.exec(<<~SQL, [@customer_id, plan_name]).cmd_tuples.positive?
 73			INSERT INTO plan_log (customer_id, plan_name, date_range)
 74			VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'))
 75			ON CONFLICT DO NOTHING
 76		SQL
 77		return false unless activated
 78
 79		DB.exec(<<~SQL, [@customer_id])
 80			DELETE FROM plan_log WHERE customer_id=$1 AND date_range << '[now,now]'
 81				AND upper(date_range) - lower(date_range) < '2 seconds'
 82		SQL
 83	end
 84
 85	def activation_date
 86		dates = DB.query_defer(<<~SQL, [@customer_id])
 87			SELECT
 88				MIN(LOWER(date_range)) AS start_date
 89			FROM plan_log WHERE customer_id = $1;
 90		SQL
 91
 92		dates.then do |r|
 93			r.first["start_date"]
 94		end
 95	end
 96
 97protected
 98
 99	def charge_for_plan(note)
100		params = [
101			@customer_id,
102			"#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
103			-@plan.monthly_price,
104			note
105		]
106		DB.exec(<<~SQL, params)
107			INSERT INTO transactions
108				(customer_id, transaction_id, created_at, settled_after, amount, note)
109			VALUES ($1, $2, LOCALTIMESTAMP, LOCALTIMESTAMP, $3, $4)
110		SQL
111	end
112
113	def add_one_month_to_current_plan
114		DB.exec(<<~SQL, [@customer_id])
115			UPDATE plan_log SET date_range=range_merge(
116				date_range,
117				tsrange(
118					LOCALTIMESTAMP,
119					GREATEST(upper(date_range), LOCALTIMESTAMP) + '1 month'
120				)
121			)
122			WHERE
123				customer_id=$1 AND
124				date_range && tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month')
125		SQL
126	end
127end