Shop It Docs
WorkflowsSubscription

Trial Flow

Current plan-based trial workflow, eligibility rules, entitlement creation, and conversion behavior.

Trial Flow

Audience: backend, QA, frontend Scope: POST /subscriptions/trial/:planId and related lifecycle behavior

Key Change From Older Docs

Trials are now configured on the plan, not on the module.

  • old assumption: module-level trialDurationDays
  • current implementation: plan-level trialDays and trialRequiresCc

The trial endpoint also takes a planId, not a moduleId.

Endpoint

POST /subscriptions/trial/:planId

Authentication:

  • requires JwtAuthGuard

Eligibility Checks

SubscriptionService.startTrial() performs these checks:

  1. the plan exists and is active
  2. plan.trialDays > 0
  3. the user has no user_trial record for the same module
  4. the user has no live active or trial subscription for the same module

The module is derived from the selected plan's tier.

What Gets Written

On success, one transaction creates:

  1. subscription

    • status = "trial"
    • planId = selected plan
    • planPriceId = null
    • priceSnapshotNpr = 0
    • startDate = now
    • endDate = now + plan.trialDays
  2. user_trial

    • one row per userId + moduleId
    • stores startedAt
    • later stores convertedAt if the trial becomes paid
  3. subscription_history

    • action trial_started
  4. user_access

    • grantType = "trial"
    • expiresAt = endDate

Why Trial Uses user_access

The trial subscription row is not used directly for authorization.

Authorization depends on the user_access row that is created during trial start. That keeps trials aligned with admin grants and paid access.

Trial Conversion

If a pending paid purchase is activated while a live trial exists for the same module, activateSubscription() converts the trial.

Conversion behavior:

  • existing subscription changes from trial to active
  • planId and planPriceId are updated
  • startDate is reset to now
  • endDate is recalculated from the selected plan price
  • user_trial.convertedAt is set
  • user_access changes from grantType = "trial" to grantType = "subscription"
  • a trial_converted history row is recorded

Cancellation and Expiry

If a user cancels during trial:

  • subscription status becomes cancelled
  • access is retained only until the current endDate

If the trial reaches its end date:

  • AccessExpirationTask revokes the expired user_access
  • the corresponding subscription is marked expired

Flow Summary