ADR-014: Subscription Plans, Feature Snapshots, and Manual Billing
Status
Accepted. Updates the subscription ownership and billing details in ADR-008 and extends the public-capability and route conventions in ADR-012 and ADR-009.
Context
Platform admins need to offer shared plans, assign organization subscriptions, customize feature limits, extend access, and enter received payments manually. Organizations need a consistent read-only view of their subscription and invoices. Changing a shared offering must not silently change an existing agreement, and correcting a payment must preserve financial history.
Decision
Ownership and permissions
Subscription owns shared plans, organization subscriptions, invoices, and payment records. Future tables are subscription_plans, subscription_subscriptions, subscription_invoices, and subscription_payments; shared offerings remain in this service rather than the admin service. No migrations are included yet.
Both platform admin and super_admin manage these resources through admin API use cases, which delegate through individually curated subscription/public capabilities. Organization admins are ordinary organization members, separate from platform admins, and can only read their organization's billing data. Active public plans are discoverable without authentication. Archived plans cannot be newly assigned but remain referenced by historical subscriptions.
Plan snapshots and feature limits
A subscription retains its assigned plan/feature snapshot and negotiated price. Editing a live plan affects future assignments only. Organization-specific feature overrides merge onto the assigned snapshot. Supplying an overrides object replaces the complete map; omitted keys revert to the snapshot, {} clears overrides, and omitting the whole field during PATCH preserves it.
Assigning a different plan takes a fresh snapshot, adopts that plan's price, and clears overrides before applying any explicit price/overrides in the same update.
Every plan requires max_members and max_doctors. Limits are nonnegative integers or null; null means unlimited and zero permits no numeric capacity. Additional features may be booleans, nonnegative integers, or null. Missing future keys grant no entitlement. Count active ordinary members and active doctor affiliations separately. Reject proposed effective limits below current active usage; do not remove members or affiliations as a side effect.
Subscription lifecycle
An organization has one current subscription and retains ended subscriptions in history. Creation requires explicit ordered start/end timestamps; status defaults to active. Trialing requires a trial end within that period. Statuses are trialing, active, past_due, canceled, and expired.
Future access checks require active/trialing status within the applicable dates; trial access ends at the trial end. Evaluate time-based expiry when reading and authorizing, rather than depending solely on a scheduler. Invoice balances never automatically change subscription status or access.
Organization and start are immutable. Extension uses a separate action with an end later than both the existing expiry and now. It neither creates an invoice nor reactivates an expired subscription. Expired subscriptions may be extended or reactivated only without a replacement current subscription. Canceled/deleted subscriptions cannot be extended. Deletion ends access immediately, records canceled/deleted timestamps, releases the current slot, and retains all history.
Invoices and manual payments
Currency is BDT and all amounts are integer minor units. Plans may be free; invoices and payments require positive amounts. Each invoice is one charge with description, covered period, invoice/due dates, and optional notes. Drafts can be edited, then issued as open; issued charge details are immutable.
Admins record received payments against open invoices with amount, received timestamp, method, optional reference, and optional notes. Methods are cash, bank_transfer, bkash, nagad, card, and other. Server-owned audit fields identify the platform admin recording or voiding an entry.
Allow multiple partial payments without overpayment. Non-voided payments determine paid total and remaining balance; full payment marks the invoice paid. Future recording must serialize balance checks/updates to prevent concurrent overpayment. Payment recording does not activate or extend subscriptions.
Payment corrections void the original with a reason and audit metadata, then record a replacement. Voiding recalculates invoice balance and reopens a paid invoice if necessary. Invoice voiding requires no non-voided payments. Preserve all originals; no destructive payment edit/delete or billing cascade deletion. uncollectible remains reserved for future invoice write-off functionality.
Gateways, refunds, credit balances, currency conversion, line items, tax, discounts, and automatic renewal invoicing are deferred.
Initial implementation boundary
The current deliverable is Huma/OpenAPI contracts, named payload examples, request validation, and one TODO use case per operation. Valid requests return 501 as application/problem+json; malformed JSON returns 400 and request-local schema/date validation returns 422. Authentication, audit actors, database state checks, limit enforcement, persistence, and canonical event publication are future behavior, not implemented by these stubs.
Public offering reads use /api/v1/subscriptions/plans. Platform management uses /api/v1/admin/subscription-plans, /admin/subscriptions, /admin/invoices, and /admin/payments under the same API version. Organization reads retain /api/v1/organizations/{organizationId}/subscription and /billing-history and reuse subscription-owned response schemas. Legacy subscription HTTP routes are no longer mounted. Detailed contracts are in Subscription API.
Rationale
Snapshots preserve negotiated terms, separate access management from bookkeeping, and prevent plan edits or payment entry from silently changing organization access. Retained invoice/payment history supports corrections without losing the original entry. Shared provider contracts prevent admin and organization from maintaining conflicting billing models.
Consequences
Positive
- Shared offerings and custom limits have one authoritative owner.
- Existing subscription agreements remain stable across catalog edits.
- Manual billing has explicit balances, transitions, and correction history.
- Frontends can use documented contracts before persistence is implemented.
Negative
- Admins manage access dates/status separately from billing and payment entry.
- Future implementation must enforce current-subscription uniqueness, usage checks, transactional balances, and trusted audit metadata.
- API stubs provide no operational billing or entitlement enforcement yet.
Related Decisions
- ADR-002: Table Prefixes
- ADR-003: Event-Driven Architecture
- ADR-007: Policy-Based Access Control
- ADR-008: Service Boundaries
- ADR-009: Route Naming
- ADR-011: One Use Case per File
- ADR-012: Explicit Public Service Contracts
- ADR-013: Doctor Service and Affiliations
Implementation clarification (2026-10-24)
The persistent implementation and current contracts are documented in Subscription workflows. The earlier contract-only/501 description above is historical. Billing now enforces active platform membership plus each operation's explicit Authorization catalog grant; legacy role labels and the allow-all placeholder grant no billing authority. Organization administrator ownership applies only within that organization.
DELETE cancellation is terminal; PATCH cancellation may explicitly reactivate. Extension normalizes elapsed state and never implicitly reactivates. Trial PATCH supports explicit null. Organization billing hides drafts and privileged fields. Creation of agreements, invoices, and payments requires an idempotency key. Implemented responses follow the repository's success envelope, including 200 archive/delete acknowledgments. Enforcement is staged through shadow/enforce configuration so real customer agreements precede production restrictions.