~singpolyma/sgx-jmp

sgx-jmp/lib/customer_plan.rb -rw-r--r-- 3.2 KiB
94298f5dStephen Paul Weber Refactor alt top up to use FormTemplate 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# 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 self.extract(customer_id, **kwargs)
		self.for(
			customer_id,
			**kwargs.slice(
				:plan_name, :expires_at, :parent_customer_id,
				:auto_top_up_amount, :monthly_overage_limit
			)
		)
	end

	def initialize(
		customer_id,
		plan: nil,
		expires_at: Time.now,
		auto_top_up_amount: 0,
		monthly_overage_limit: 0,
		parent_customer_id: nil
	)
		@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
		@parent_customer_id = parent_customer_id
	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)
		EMPromise.resolve(nil).then do
			DB.transaction do |db|
				next false unless !block_given? || yield(db)

				charge_for_plan(note)
				add_one_month_to_current_plan unless activate_plan_starting_now
				true
			end
		end
	end

	def activate_plan_starting_now
		activated = DB.exec(<<~SQL, [@customer_id, plan_name, @parent_customer_id])
			INSERT INTO plan_log (customer_id, plan_name, date_range, parent_customer_id)
			VALUES ($1, $2, tsrange(LOCALTIMESTAMP, LOCALTIMESTAMP + '1 month'), $3)
			ON CONFLICT DO NOTHING
		SQL
		activated = activated.cmd_tuples.positive?
		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