# 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(note: nil) EM.promise_fiber do DB.transaction do charge_for_plan(note) 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 DB.query_one(<<~SQL, @customer_id).then { |r| r[:start_date] } SELECT MIN(LOWER(date_range)) AS start_date FROM plan_log WHERE customer_id = $1; SQL end protected def charge_for_plan(note) params = [ @customer_id, "#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}", -@plan.monthly_price, note ] DB.exec(<<~SQL, params) INSERT INTO transactions (customer_id, transaction_id, created_at, settled_after, amount, note) VALUES ($1, $2, LOCALTIMESTAMP, LOCALTIMESTAMP, $3, $4) 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