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, @parent_customer_id])
 75			INSERT INTO plan_log
 76				(customer_id, plan_name, parent_customer_id, date_range)
 77			VALUES (
 78				$1,
 79				$2,
 80				$3,
 81				tsrange(
 82					LOCALTIMESTAMP - '2 seconds'::interval,
 83					LOCALTIMESTAMP - '1 second'::interval
 84				)
 85			)
 86		SQL
 87	end
 88
 89	def bill_plan(note: nil)
 90		EMPromise.resolve(nil).then do
 91			DB.transaction do |db|
 92				next false unless !block_given? || yield(db)
 93
 94				charge_for_plan(note)
 95				extend_plan
 96				true
 97			end
 98		end
 99	end
100
101	def activate_plan_starting_now
102		activated = DB.exec(<<~SQL, [@customer_id, plan_name, @parent_customer_id])
103			INSERT INTO plan_log (customer_id, plan_name, date_range, parent_customer_id)
104			VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'), $3)
105			ON CONFLICT DO NOTHING
106		SQL
107		activated = activated.cmd_tuples.positive?
108		return false unless activated
109
110		DB.exec(<<~SQL, [@customer_id])
111			DELETE FROM plan_log WHERE customer_id=$1 AND date_range << '[now,now]'
112				AND upper(date_range) - lower(date_range) < '2 seconds'
113		SQL
114	end
115
116	def extend_plan
117		add_one_month_to_current_plan unless activate_plan_starting_now
118	end
119
120	def activation_date
121		DB.query_one(<<~SQL, @customer_id).then { |r| r[:start_date] }
122			SELECT
123				MIN(LOWER(date_range)) AS start_date
124			FROM plan_log WHERE customer_id = $1;
125		SQL
126	end
127
128protected
129
130	def charge_for_plan(note)
131		raise "No plan setup" unless @plan
132
133		params = [
134			@customer_id,
135			"#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
136			-@plan.monthly_price,
137			note
138		]
139		DB.exec(<<~SQL, params)
140			INSERT INTO transactions
141				(customer_id, transaction_id, created_at, settled_after, amount, note)
142			VALUES ($1, $2, LOCALTIMESTAMP, LOCALTIMESTAMP, $3, $4)
143		SQL
144	end
145
146	def add_one_month_to_current_plan
147		DB.exec(<<~SQL, [@customer_id])
148			UPDATE plan_log SET date_range=range_merge(
149				date_range,
150				tsrange(
151					LOCALTIMESTAMP,
152					GREATEST(upper(date_range), LOCALTIMESTAMP) + '1 month'
153				)
154			)
155			WHERE
156				customer_id=$1 AND
157				date_range && tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month')
158		SQL
159	end
160end