Subscription Workflow Overview
Current subscription workflow across catalog setup, entitlement, trials, admin grants, cancellation, and access expiry.
Subscription Workflow Overview
Audience: product, QA, backend, frontend Scope: current subscription lifecycle as implemented in
apps/apiandpackages/db
What Exists Today
The current implementation separates subscription lifecycle from runtime entitlement.
subscriptionrecords lifecycle and commercial stateuser_accessrecords whether a user currently has accesssubscription_historyanduser_access_historyprovide audit historyuser_trialenforces one trial per user per module
This means access checks are based on user_access, not directly on subscription.status.
Catalog Model
The catalog is built in five layers:
subscription_modulesubscription_tiersubscription_plansubscription_plan_pricesubscription_plan_feature
The important relationship is:
- one module has many tiers
- one tier has one plan
- one plan has many prices
- one plan has many features
Trials are configured on the plan via trialDays, not on the module.
Access Grant Sources
A user can receive access from three current paths:
- Trial start via
POST /subscriptions/trial/:planId - Admin grant via
POST /admin/subscriptions/grant - Paid activation via internal
SubscriptionServicemethods
The paid lifecycle still exists in service code, but the current SubscriptionMobileController does not expose purchase or payment-verification endpoints.
Current User-Facing Workflow
Browse
- public users can list modules via
GET /subscriptions/modules - authenticated users can list plans via
GET /subscriptions/plans
Start Trial
- user starts a trial against a plan, not a module
- the service validates:
- plan exists and is active
- plan has
trialDays > 0 - the user has not already consumed a trial for that module
- the user does not already have a live active/trial subscription for that module
- on success the system creates:
- a
subscriptionrow withstatus = "trial" - a
user_trialrow - a
subscription_historyrow withtrial_started - a
user_accessrow withgrantType = "trial"
- a
Paid Activation
The service supports a paid flow through:
createPendingPurchase(userId, planId, planPriceId)activateSubscription(pendingSubscriptionId)handlePaymentFailure(pendingSubscriptionId)
That flow is currently service-level. It is not exposed by the current mobile subscription controller. Any payment or order orchestration that activates subscriptions must call into SubscriptionService.
Cancel
Customer cancellation uses SubscriptionService.cancel(subscriptionId, userId).
Behavior:
subscription.statusbecomescancelledcancelledAtis set immediatelycancelsAtis set to the current subscription end date- matching
user_access.expiresAtis updated to the subscription end date
This is important: cancellation does not immediately revoke access. Access remains until the paid or trial period ends.
Admin Grant / Extend / Revoke
Admins can:
- grant access directly
- extend an existing subscription
- revoke immediately
Admin revoke is different from customer cancel:
- revoke sets the subscription to
cancelled cancelsAtis set tonow- matching
user_access.revokedAtis set immediately
Expiry Workflow
AccessExpirationTask runs every 5 minutes.
It performs two batches:
- revoke expired
user_accessrows - mark expired
subscriptionrows asexpired
It also invalidates the access-related cache keys after each expiry action.
Status vs Access
Access is not determined by subscription status alone.
| Subscription state | Typical access outcome |
|---|---|
pending_payment | no access |
trial | access exists until user_access.expiresAt passes |
active | access exists until user_access.expiresAt passes |
cancelled | may still have access until the retained end date |
expired | no access after scheduled revocation/expiry processing |
The practical rule is:
- authorization reads
user_access - lifecycle reporting reads
subscription
Workflow Summary
Technical Introduction
A modern full-stack TypeScript monorepo combining React 19, NestJS, TanStack Router, Drizzle ORM, and PostgreSQL - all managed with Turborepo and pnpm workspaces.
Paid Activation Flow
Current paid subscription lifecycle in the service layer, including pending purchase creation, activation, extension, and failure cleanup.