From 37b09a44d8b9d9d85e45810cf5006b2968856965 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 2 Mar 2021 10:46:16 -0500 Subject: [PATCH] Create initial monthly billing cronjob Renews all expired plans where there is enough balance to do so. Sets expires_at on all renewed plans to one month from today, so even if they were very expired they are up to date the moment they pay. --- bin/billing_monthly_cronjob | 93 +++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100755 bin/billing_monthly_cronjob diff --git a/bin/billing_monthly_cronjob b/bin/billing_monthly_cronjob new file mode 100755 index 0000000..3cb91af --- /dev/null +++ b/bin/billing_monthly_cronjob @@ -0,0 +1,93 @@ +#!/usr/bin/ruby +# frozen_string_literal: true + +# Usage: ./billing_monthly_cronjob \ +# '{ healthchecks_url = "https://hc-ping.com/...", plans = ./plans.dhall }' + +require "bigdecimal" +require "date" +require "dhall" +require "net/http" +require "pg" + +CONFIG = Dhall.load(ARGV[0]).sync + +Net::HTTP.post_form(URI("#{CONFIG[:healthchecks_url]}/start"), {}) + +db = PG.connect(dbname: "jmp") +db.type_map_for_results = PG::BasicTypeMapForResults.new(db) +db.type_map_for_queries = PG::BasicTypeMapForQueries.new(db) + +not_renewed = 0 +renewed = 0 +revenue = BigDecimal.new(0) + +RENEW_UNTIL = Date.today >> 1 + +class Plan + def self.from_name(plan_name) + plan = CONFIG[:plans].find { |p| p[:name].to_s == plan_name } + new(plan) if plan + end + + def initialize(plan) + @plan = plan + end + + def price + BigDecimal.new(@plan["monthly_price"].to_i) * 0.0001 + end + + def bill_customer(db, customer_id) + transaction_id = "#{customer_id}-renew-until-#{RENEW_UNTIL}" + db.exec_params(<<-SQL, [customer_id, transaction_id, -price]) + INSERT INTO transactions + (customer_id, transaction_id, amount, note) + VALUES + ($1, $2, $3, 'Renew account plan') + SQL + end + + def renew(db, customer_id, expires_at) + bill_customer(db, customer_id) + + params = [RENEW_UNTIL, customer_id, expires_at] + db.exec_params(<<-SQL, params) + UPDATE plan_log SET expires_at=$1 + WHERE customer_id=$2 AND expires_at=$3 + SQL + end +end + +db.transaction do + db.exec( + <<-SQL + SELECT customer_id, plan_name, expires_at, balance + FROM customer_plans INNER JOIN balances USING (customer_id) + WHERE expires_at <= NOW() + SQL + ).each do |expired_customer| + plan = Plan.from_name(expired_customer["plan_name"]) + + if expired_customer["balance"] < plan.price + not_renewed += 1 + next + end + + plan.renew( + db, + expired_customer["customer_id"], + expired_customer["expires_at"] + ) + + renewed += 1 + revenue += plan.price + end +end + +Net::HTTP.post_form( + URI(CONFIG[:healthchecks_url].to_s), + renewed: renewed, + not_renewed: not_renewed, + revenue: revenue.to_s("F") +) -- 2.38.5