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	            :parent_customer_id
 13
 14	def_delegator :@plan, :name, :plan_name
 15	def_delegators :@plan, :currency, :merchant_account, :monthly_price,
 16	               :minute_limit, :message_limit
 17
 18	def self.for(customer_id, plan_name: nil, **kwargs)
 19		new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
 20	end
 21
 22	def self.default(customer_id, jid)
 23		config = CONFIG[:parented_domains][Blather::JID.new(jid).domain]
 24		if config
 25			self.for(
 26				customer_id,
 27				plan_name: config[:plan_name],
 28				parent_customer_id: config[:customer_id]
 29			)
 30		else
 31			new(customer_id)
 32		end
 33	end
 34
 35	def self.extract(customer_id, **kwargs)
 36		self.for(
 37			customer_id,
 38			**kwargs.slice(
 39				:plan_name, :expires_at, :parent_customer_id,
 40				:auto_top_up_amount, :monthly_overage_limit
 41			)
 42		)
 43	end
 44
 45	def initialize(
 46		customer_id,
 47		plan: nil,
 48		expires_at: Time.now,
 49		auto_top_up_amount: 0,
 50		monthly_overage_limit: 0,
 51		parent_customer_id: nil
 52	)
 53		@customer_id = customer_id
 54		@plan = plan || OpenStruct.new
 55		@expires_at = expires_at
 56		@auto_top_up_amount = auto_top_up_amount || 0
 57		@monthly_overage_limit = monthly_overage_limit || 0
 58		@parent_customer_id = parent_customer_id
 59	end
 60
 61	def active?
 62		plan_name && @expires_at > Time.now
 63	end
 64
 65	def with_plan_name(plan_name)
 66		self.class.new(
 67			@customer_id,
 68			plan: Plan.for(plan_name),
 69			expires_at: @expires_at
 70		)
 71	end
 72
 73	def save_plan!
 74		DB.exec_defer(<<~SQL, [@customer_id, plan_name])
 75			INSERT INTO plan_log
 76				(customer_id, plan_name, date_range)
 77			VALUES (
 78				$1,
 79				$2,
 80				tsrange(
 81					LOCALTIMESTAMP - '2 seconds'::interval,
 82					LOCALTIMESTAMP - '1 second'::interval
 83				)
 84			)
 85		SQL
 86	end
 87
 88	def bill_plan(note: nil)
 89		EMPromise.resolve(nil).then do
 90			DB.transaction do |db|
 91				next false unless !block_given? || yield(db)
 92
 93				charge_for_plan(note)
 94				extend_plan
 95				true
 96			end
 97		end
 98	end
 99
100	def activate_plan_starting_now
101		activated = DB.exec(<<~SQL, [@customer_id, plan_name, @parent_customer_id])
102			INSERT INTO plan_log (customer_id, plan_name, date_range, parent_customer_id)
103			VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'), $3)
104			ON CONFLICT DO NOTHING
105		SQL
106		activated = activated.cmd_tuples.positive?
107		return false unless activated
108
109		DB.exec(<<~SQL, [@customer_id])
110			DELETE FROM plan_log WHERE customer_id=$1 AND date_range << '[now,now]'
111				AND upper(date_range) - lower(date_range) < '2 seconds'
112		SQL
113	end
114
115	def extend_plan
116		add_one_month_to_current_plan unless activate_plan_starting_now
117	end
118
119	def activation_date
120		DB.query_one(<<~SQL, @customer_id).then { |r| r[:start_date] }
121			SELECT
122				MIN(LOWER(date_range)) AS start_date
123			FROM plan_log WHERE customer_id = $1;
124		SQL
125	end
126
127protected
128
129	def charge_for_plan(note)
130		params = [
131			@customer_id,
132			"#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
133			-@plan.monthly_price,
134			note
135		]
136		DB.exec(<<~SQL, params)
137			INSERT INTO transactions
138				(customer_id, transaction_id, created_at, settled_after, amount, note)
139			VALUES ($1, $2, LOCALTIMESTAMP, LOCALTIMESTAMP, $3, $4)
140		SQL
141	end
142
143	def add_one_month_to_current_plan
144		DB.exec(<<~SQL, [@customer_id])
145			UPDATE plan_log SET date_range=range_merge(
146				date_range,
147				tsrange(
148					LOCALTIMESTAMP,
149					GREATEST(upper(date_range), LOCALTIMESTAMP) + '1 month'
150				)
151			)
152			WHERE
153				customer_id=$1 AND
154				date_range && tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month')
155		SQL
156	end
157end