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
16 def self.for(customer_id, plan_name: nil, **kwargs)
17 new(customer_id, plan: plan_name&.then(&Plan.method(:for)), **kwargs)
18 end
19
20 def initialize(
21 customer_id,
22 plan: nil,
23 expires_at: Time.now,
24 auto_top_up_amount: 0,
25 monthly_overage_limit: 0
26 )
27 @customer_id = customer_id
28 @plan = plan || OpenStruct.new
29 @expires_at = expires_at
30 @auto_top_up_amount = auto_top_up_amount || 0
31 @monthly_overage_limit = monthly_overage_limit || 0
32 end
33
34 def active?
35 plan_name && @expires_at > Time.now
36 end
37
38 def with_plan_name(plan_name)
39 self.class.new(
40 @customer_id,
41 plan: Plan.for(plan_name),
42 expires_at: @expires_at
43 )
44 end
45
46 def bill_plan
47 EM.promise_fiber do
48 DB.transaction do
49 charge_for_plan
50 add_one_month_to_current_plan unless activate_plan_starting_now
51 end
52 end
53 end
54
55 def activate_plan_starting_now
56 DB.exec(<<~SQL, [@customer_id, plan_name]).cmd_tuples.positive?
57 INSERT INTO plan_log
58 (customer_id, plan_name, date_range)
59 VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'))
60 ON CONFLICT DO NOTHING
61 SQL
62 end
63
64 def activation_date
65 dates = DB.query_defer(<<~SQL, [@customer_id])
66 SELECT
67 MIN(LOWER(date_range)) AS start_date
68 FROM plan_log WHERE customer_id = $1;
69 SQL
70
71 dates.then do |r|
72 r.first["start_date"]
73 end
74 end
75
76protected
77
78 def charge_for_plan
79 params = [
80 @customer_id,
81 "#{@customer_id}-bill-#{plan_name}-at-#{Time.now.to_i}",
82 -@plan.monthly_price
83 ]
84 DB.exec(<<~SQL, params)
85 INSERT INTO transactions
86 (customer_id, transaction_id, created_at, amount)
87 VALUES ($1, $2, LOCALTIMESTAMP, $3)
88 SQL
89 end
90
91 def add_one_month_to_current_plan
92 DB.exec(<<~SQL, [@customer_id])
93 UPDATE plan_log SET date_range=range_merge(
94 date_range,
95 tsrange(
96 LOCALTIMESTAMP,
97 GREATEST(upper(date_range), LOCALTIMESTAMP) + '1 month'
98 )
99 )
100 WHERE
101 customer_id=$1 AND
102 date_range && tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month')
103 SQL
104 end
105end