Documentation
¶
Overview ¶
Package billingnotify delivers account-trash transitions (trash, restore) to the external billing service as durable River jobs.
A trash asks billing to cancel the subscription at period end; a restore asks it to revert that. Both used to be best-effort HTTP calls after commit, so a single billing 5xx on restore left a restored customer's subscription scheduled to cancel with no retry. The job is enqueued in the same transaction as the trash or restore, retried with River's backoff, and is convergent and serialized per account: the worker takes a transaction-scoped advisory lock on the account, re-reads its state and posts while still holding the lock, and skips a notice the account has since moved past (a trash notice for an account that was restored, a restore notice for one trashed again, either for an account already purged — purge has its own cancel call). Because the read and the post happen under one per-account lock, and every transition enqueues its own notice in its own transaction, the last post for an account always reflects its latest committed state — even when River runs a trash and a restore notice concurrently or out of order.
The job is deliberately NOT River-unique by account: River's uniqueness must include the running state, so a restore notice inserted while a trash notice is running would be dropped as a duplicate — the running trash post would land last and leave a live customer set to cancel. The advisory lock gives the one-at-a-time property without that loss.
Index ¶
Constants ¶
const ( ModeTrash = "trash" ModeRestore = "restore" )
Modes.
const MaxAttempts = 20
MaxAttempts bounds retries (River's exponential backoff spreads 20 attempts over roughly two weeks — longer than any billing outage we should survive silently).
Variables ¶
var ErrNotFound = errors.New("billingnotify: billing service has no account-state endpoint (404)")
ErrNotFound is what a Poster returns when the billing service does not know the endpoint (an older service answering 404). It is permanent: the job completes with a log line instead of retrying for two weeks.
Functions ¶
Types ¶
type Jobs ¶
type Jobs struct {
// contains filtered or unexported fields
}
Jobs is the registrar + enqueuer. Poster is late-bound (the agent API that owns the billing URLs is built after the River client starts).
func (*Jobs) EnqueueTx ¶
EnqueueTx inserts the notice in the caller's transaction (the trash or restore transaction), so the notice exists exactly when the transition commits.
func (*Jobs) RegisterJobs ¶
func (j *Jobs) RegisterJobs(w *river.Workers) []*river.PeriodicJob
RegisterJobs implements jobs.Registrar.
func (*Jobs) SetEnqueuer ¶
SetEnqueuer injects the shared River client.
type Worker ¶
type Worker struct {
river.WorkerDefaults[Args]
// contains filtered or unexported fields
}
Worker posts one notice.
func NewWorkerForTest ¶
NewWorkerForTest returns the worker RegisterJobs registers.