# frozen_string_literal: true
require "forwardable"
require_relative "em"
require_relative "plan"
class CustomerPlan
extend Forwardable
attr_reader :expires_at, :auto_top_up_amount, :monthly_overage_limit
def_delegator :@plan, :name, :plan_name
def_delegators :@plan, :currency, :merchant_account, :monthly_price,
:minute_limit, :message_limit
def self.for(customer_id, plan_name: nil, **kwargs)
new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
end
def initialize(
customer_id,
plan: nil,
expires_at: Time.now,
auto_top_up_amount: 0,
monthly_overage_limit: 0
)
@customer_id = customer_id
@plan = plan || OpenStruct.new
@expires_at = expires_at
@auto_top_up_amount = auto_top_up_amount || 0
@monthly_overage_limit = monthly_overage_limit || 0
end
def active?
plan_name && @expires_at > Time.now
end
def with_plan_name(plan_name)
self.class.new(
@customer_id,
plan: Plan.for(plan_name),
expires_at: @expires_at
)
end
def save_plan!
DB.exec_defer(<<~SQL, [@customer_id, plan_name])
INSERT INTO plan_log
(customer_id, plan_name, date_range)
VALUES (
$1,
$2,
tsrange(
LOCALTIMESTAMP - '2 seconds'::interval,
LOCALTIMESTAMP - '1 second'::interval
)
)
SQL
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
activated = 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
return false unless activated
DB.exec(<<~SQL, [@customer_id])
DELETE FROM plan_log WHERE customer_id=$1 AND date_range << '[now,now]'
AND upper(date_range) - lower(date_range) < '2 seconds'
SQL
end
def activation_date
dates = DB.query_defer(<<~SQL, [@customer_id])
SELECT
MIN(LOWER(date_range)) AS start_date
FROM plan_log WHERE customer_id = $1;
SQL
dates.then do |r|
r.first["start_date"]
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 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