billing_monthly_cronjob

 1#!/usr/bin/ruby
 2# frozen_string_literal: true
 3
 4# Usage: ./billing_monthly_cronjob \
 5#      '{ healthchecks_url = "https://hc-ping.com/...", plans = ./plans.dhall }'
 6
 7require "bigdecimal"
 8require "date"
 9require "dhall"
10require "net/http"
11require "pg"
12
13CONFIG = Dhall.load(ARGV[0]).sync
14
15Net::HTTP.post_form(URI("#{CONFIG[:healthchecks_url]}/start"), {})
16
17db = PG.connect(dbname: "jmp")
18db.type_map_for_results = PG::BasicTypeMapForResults.new(db)
19db.type_map_for_queries = PG::BasicTypeMapForQueries.new(db)
20
21not_renewed = 0
22renewed = 0
23revenue = BigDecimal.new(0)
24
25RENEW_UNTIL = Date.today >> 1
26
27class Plan
28	def self.from_name(plan_name)
29		plan = CONFIG[:plans].find { |p| p[:name].to_s == plan_name }
30		new(plan) if plan
31	end
32
33	def initialize(plan)
34		@plan = plan
35	end
36
37	def price
38		BigDecimal.new(@plan["monthly_price"].to_i) * 0.0001
39	end
40
41	def bill_customer(db, customer_id)
42		transaction_id = "#{customer_id}-renew-until-#{RENEW_UNTIL}"
43		db.exec_params(<<-SQL, [customer_id, transaction_id, -price])
44			INSERT INTO transactions
45				(customer_id, transaction_id, amount, note)
46			VALUES
47				($1, $2, $3, 'Renew account plan')
48		SQL
49	end
50
51	def renew(db, customer_id, expires_at)
52		bill_customer(db, customer_id)
53
54		params = [RENEW_UNTIL, customer_id, expires_at]
55		db.exec_params(<<-SQL, params)
56		  UPDATE plan_log SET expires_at=$1
57		  WHERE customer_id=$2 AND expires_at=$3
58		SQL
59	end
60end
61
62db.transaction do
63	db.exec(
64		<<-SQL
65		SELECT customer_id, plan_name, expires_at, balance
66		FROM customer_plans INNER JOIN balances USING (customer_id)
67		WHERE expires_at <= NOW()
68		SQL
69	).each do |expired_customer|
70		plan = Plan.from_name(expired_customer["plan_name"])
71
72		if expired_customer["balance"] < plan.price
73			not_renewed += 1
74			next
75		end
76
77		plan.renew(
78			db,
79			expired_customer["customer_id"],
80			expired_customer["expires_at"]
81		)
82
83		renewed += 1
84		revenue += plan.price
85	end
86end
87
88Net::HTTP.post_form(
89	URI(CONFIG[:healthchecks_url].to_s),
90	renewed: renewed,
91	not_renewed: not_renewed,
92	revenue: revenue.to_s("F")
93)