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)