Billing job

A walkthrough of a job on humus that bills a month of subscriptions: it issues invoices in PostgreSQL and charges each one through a payments API, once, however often it runs. These pages build it step by step.

The billing job runs once a day, as a Kubernetes CronJob. Each run:

  1. issues an invoice for every active subscription, for last month;
  2. charges every invoice still pending, from any month, through a payments API;
  3. records each answer, and exits 0 when no invoice is left pending, or 1 when any is.

The next run charges whatever a failed run left pending. A run that is retried, or that overlaps another, charges nothing twice, because each invoice’s ID is its charge’s idempotency key.

The files it ends with

Its code is a module of its own, at example/job/billing:

example/job/billing/
├── main.go          humus.Main(app.Init, app.Options()...)
├── app/             Init, the settings, Faults, the telemetry Builder and Options
├── billing/         the run: issue, charge, settle, and what it returns
├── postgres/        the invoices in PostgreSQL, and schema.sql
├── payments/        the payments API's client: the request, the answers and Classify
├── cases/           the test cases the container tests and kind share
├── functional/      the cases against kind
├── deploy/          the CronJob, its alerts and dashboard, and the kind overlay
└── Dockerfile       the job's image

These pages build postgres, payments, billing, then app and main.


The invoices

The billing job’s tables in schema.sql, and the postgres package that issues a month of invoices, reads the pending ones and settles each one once.

Charging

The billing job’s payments package: the charge request and its idempotency key, what each answer from the payments API means for the invoice, and the Classify that decides which answers are retried.

The run

The billing job’s run: it issues a month of invoices, charges every pending one with a bounded number in flight, settles each answer, stops early when every further charge would fail, and returns what the exit status reports.

Init

The billing job’s Init: its settings, sourced and checked; its secrets, read from files; the pgx pool and the payments client; Faults and the telemetry Builder in Options; and main.

Testing

Testing the billing job: the run as a unit, against a store in memory and a payments API of the test’s own; the shared cases and their Target, run in containers and on kind; and the failure injection cases.

Deploying

Deploying the billing job: its image, its CronJob and the kustomization; the kind overlay with PostgreSQL, WireMock and the LGTM stack; and running it on kind, a backfill, and a rehearsal.

Observing

Observing the billing job: a run’s trace, records and metrics in Grafana, its dashboard, and what each of its alerts means and asks of you.

Production-ready

The billing job’s production-ready checklist: each item, and how the job meets it, to check a job of your own against.