diff --git a/bin/billing_monthly_cronjob b/bin/billing_monthly_cronjob new file mode 100755 index 0000000000000000000000000000000000000000..3cb91af392d1acfac4f364832848e99a639f452a --- /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") +)