internal

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package internal is every implementation of the billing module. Nothing outside modules/billing can import it, which is the compiler enforcing idea 3: a consumer takes contracts.Service, and taking anything else does not build.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RefuseWhileSubscribed

func RefuseWhileSubscribed(token tenancy.SystemToken) func(context.Context, db.Tx[db.Tenant], *contracts.Plan) error

RefuseWhileSubscribed is the plan delete route's hook: a plan somebody is still on is not one the operator may remove. It is why the subscriptions table needs no foreign key — a key would refuse the delete of any plan a row had ever named, cancelled ones included, and would say so as a constraint name.

It counts under system access, and that is the consequence of the catalogue being shared: the plan being deleted is one every tenant can subscribe to, and the operator's own transaction sees only the operator's subscriptions. A tenant-scoped count here would have let the operator delete a plan half its customers were being billed for and reported nothing.

The count is in a transaction of its own, on a detached context, because a system transaction cannot nest inside a tenant one. The refusal still rolls the delete back: it is an error returned from a hook that runs inside the request's transaction.

func RegisterRoutes

func RegisterRoutes(api *httpx.API, svc contracts.Service)

RegisterRoutes mounts the two commands beside the singleton's read.

The read is rest.Singleton now — one row per tenant, mounted by ../module.go — and it carries no Write: a subscription is moved by subscribe and cancel, which are rules about the state it is in, and a PUT would be a customer writing its own period and its own price. The two commands are here because kit/rest's Command puts an id in the path and a singleton has none, which is the only thing a singleton changes about a command.

func Renew

func Renew(tenants jobs.TenantLister, svc contracts.Service, payments contracts.PaymentProvider, every time.Duration) jobs.Job

Renew is the module's periodic work: the one thing an outbox cannot express, because a period running out is not something that happened to anybody (docs/adr/0004). One instance in the cluster runs it per tick, kit/jobs taking an advisory lock named after the job.

Types

type Manual

type Manual struct{}

Manual is the PaymentProvider for an installation that takes money somewhere else: it records what is owed, in the log, and settles nothing.

It is not a stub. An unsettled receipt is a real answer — Settle marks the subscription past due and publishes billing.past_due — so a deployment with no payment processor still runs the whole lifecycle and still says, once per customer, that somebody owes money. A provider that lied and said Settled would be a deployment quietly giving everything away.

func NewManual

func NewManual() *Manual

NewManual returns the provider that moves no money. main wires it, so the choice is visible in the file that composes the application.

func (Manual) Charge

Charge records what is due and takes nothing. The reference is the charge's own idempotency key, so asking twice for one period is one reference: a renewal that runs again after a crash must not look like a second debt. That is what the interface asks of every implementation, and the one in this repository is where it is demonstrated.

type Service

type Service struct{}

Service is the subscription lifecycle. It has no fields: everything a command needs arrives with the transaction it is given, and the one thing it does not do is take money — that is the caller's, between two transactions.

func NewService

func NewService() *Service

NewService returns the lifecycle commands. It takes nothing, on purpose: see the type. module.go constructs it.

func (*Service) Cancel

func (s *Service) Cancel(ctx context.Context, tx db.Tx[db.Tenant], atPeriodEnd bool) (*contracts.Subscription, error)

Cancel ends the subscription, now or at the end of the period. See contracts.Service.

func (*Service) Current

func (s *Service) Current(_ context.Context, tx db.Tx[db.Tenant]) (*contracts.Subscription, error)

Current is the tenant's one subscription: a query rather than a crud.Get, because the caller has no id to give and row-level security has already narrowed the table to the one row there is.

func (*Service) Renew

Renew is the free half of a renewal. See contracts.Service.

func (*Service) Settle

Settle records what the provider said. See contracts.Service.

func (*Service) Subscribe

func (s *Service) Subscribe(ctx context.Context, tx db.Tx[db.Tenant], planID uuid.UUID) (*contracts.Subscription, error)

Subscribe enrolls the tenant in a plan. See contracts.Service.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL