Shop It Docs
Developer ResourcesSubscription

Subscription Architecture

Current module structure, service responsibilities, entitlement flow, and access integration for subscriptions.

Subscription Architecture

Audience: backend developers Scope: current architecture in apps/api/src/modules/subscription, apps/api/src/modules/access, and packages/db/src/schema/subscription.ts

High-Level Shape

The current architecture has two related layers:

  1. subscription lifecycle
  2. access entitlement

Lifecycle lives in the subscription module. Runtime authorization lives in the access module.

Core Modules

SubscriptionModule

File: apps/api/src/modules/subscription/subscription.module.ts

Responsibilities:

  • provides SubscriptionService
  • provides AccessExpirationTask
  • exports SubscriptionService
  • imports ActivityModule

This module combines core subscription write logic with the scheduled expiry task.

SubscriptionMobileModule

File: apps/api/src/modules/subscription/mobile/subscription-mobile.module.ts

Responsibilities:

  • imports SubscriptionModule
  • exposes mobile subscription read endpoints
  • delegates trial start and detailed subscription lookup to SubscriptionService

Important current fact:

  • it does not import a payment module
  • it does not expose purchase or payment-verification routes

SubscriptionAdminAggregateModule

File: apps/api/src/modules/subscription/admin/subscription-admin-aggregate.module.ts

Responsibilities:

  • imports and re-exports all admin subscription submodules
  • wires controllers for:
    • modules
    • tiers
    • plans
    • plan prices
    • plan features
    • subscriptions

AccessModule

File: apps/api/src/modules/access/access.module.ts

Responsibilities:

  • globally exports AccessService
  • globally exports FeatureService

This is the read-side entitlement module used by route guards and other consumers.

Service Responsibilities

SubscriptionService

Primary write-side coordinator.

Key methods:

  • createPendingPurchase()
  • activateSubscription()
  • handlePaymentFailure()
  • startTrial()
  • adminGrant()
  • adminExtend()
  • cancel()
  • getActiveSubscriptions()
  • getSubscriptionById()
  • upsertUserAccess()

The most important architectural detail is upsertUserAccess(). Every grant path converges on it so that user_access remains the current entitlement source of truth.

AccessService

Primary module-level authorization service.

  • checks access by module slug
  • resolves the module first
  • grants access only when a non-revoked, non-expired user_access row exists

FeatureService

Primary feature-level entitlement service.

  • loads feature keys from active user_access -> plan -> features
  • supports boolean checks and value lookup

AccessExpirationTask

Scheduled cleanup.

  • runs every 5 minutes
  • revokes expired user_access
  • marks expired subscription rows as expired
  • invalidates related caches

Current Flow Boundaries

What the Subscription Module Does

  • manages catalog-backed subscription lifecycle
  • starts trials
  • grants and extends subscriptions
  • updates entitlement rows
  • exposes mobile read APIs for modules, plans, subscriptions, and access

What the Subscription Module Does Not Currently Expose

  • a public purchase initiation endpoint
  • a payment verification endpoint

The service layer still contains pending purchase and activation methods, but they are not exposed by the current mobile subscription controller.

Access Integration

The old SubscriptionAccessGuard naming is obsolete.

Current access integration uses:

  • AccessGuard + @RequiresAccess()
  • FeatureGuard + @RequiresFeature()

These guards depend on AccessService and FeatureService, not on a dedicated subscription guard.

Current File Layout

apps/api/src/
├── common/authorization/
│   ├── access.guard.ts
│   ├── feature.guard.ts
│   ├── requires-access.decorator.ts
│   └── requires-feature.decorator.ts

├── modules/
│   ├── access/
│   │   ├── access.module.ts
│   │   ├── access.service.ts
│   │   └── feature.service.ts
│   │
│   └── subscription/
│       ├── subscription.module.ts
│       ├── subscription.service.ts
│       ├── tasks/
│       │   └── access-expiration.task.ts
│       ├── admin/
│       │   ├── subscription-admin-aggregate.module.ts
│       │   ├── module/
│       │   ├── tier/
│       │   ├── plan/
│       │   ├── plan-price/
│       │   ├── plan-feature/
│       │   └── subscription/
│       └── mobile/
│           ├── subscription-mobile.module.ts
│           ├── subscription-mobile.controller.ts
│           └── subscription-mobile.service.ts

packages/db/src/schema/
└── subscription.ts

Architectural Summary

The current design is built around one core idea:

  • subscription tracks lifecycle
  • user_access tracks entitlement

That separation is why trials, admin grants, and paid subscriptions can all share the same authorization path while still keeping business history in subscription records.