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