Shop It Docs
WorkflowsSubscription

Paid Activation Flow

Current paid subscription lifecycle in the service layer, including pending purchase creation, activation, extension, and failure cleanup.

Paid Activation Flow

Audience: backend, QA Scope: current paid lifecycle in SubscriptionService

Current State

The subscription domain still contains a paid purchase lifecycle, but the current SubscriptionMobileController does not expose purchase or payment-verification routes.

Today, paid subscription activation is a service-level workflow built around:

  • createPendingPurchase(userId, planId, planPriceId)
  • activateSubscription(pendingSubscriptionId)
  • handlePaymentFailure(pendingSubscriptionId)

Any order or payment integration must call these methods explicitly.

Step 1: Create Pending Purchase

createPendingPurchase():

  1. loads the target plan and price
  2. derives the module through plan -> tier -> module
  3. checks for an existing live subscription for the same user and module
  4. checks for an existing pending subscription for the same user and module
  5. either:
    • updates the existing pending row, or
    • creates a new subscription row with status = "pending_payment"

Notes:

  • startDate and endDate are initialized to now in the current implementation
  • priceSnapshotNpr is copied from the chosen plan price
  • a subscription_history row with created is inserted only when a new pending row is created

Step 2: Activate Pending Purchase

activateSubscription() handles three cases.

Case A: Trial Conversion

If a live trial exists for the same user and module:

  • the existing trial subscription is updated to active
  • planId, planPriceId, priceSnapshotNpr, startDate, and endDate are updated
  • the user_trial row gets convertedAt
  • a trial_converted history row is written
  • the pending row is deleted
  • user_access is updated with grantType = "subscription"

Case B: Extension / Repurchase

If a live active subscription exists for the same user and module:

  • the existing active subscription is extended
  • the base date is max(existing.endDate, now)
  • newEndDate = baseDate + durationDays
  • the pending row is deleted
  • user_access expiry is extended
  • a subscription history row with extended is written

Case C: First-Time Activation

If no live active/trial subscription exists:

  • the pending row becomes the active subscription
  • status = "active"
  • startDate = now
  • endDate = now + durationDays
  • user_access is created or updated
  • a subscription history row with activated is written

After all three cases, user access caches are invalidated.

Step 3: Failure Cleanup

handlePaymentFailure() only acts on pending_payment subscriptions.

If the pending row still exists:

  • it is deleted
  • an activity record is written

No access row is granted during pending state, so failed payment cleanup does not need entitlement rollback.

Access Side Effects

Paid activation always flows through upsertUserAccess().

That method guarantees:

  • current entitlement is stored in user_access
  • every change gets a user_access_history row
  • repeated grants for the same userId + moduleId update the active row rather than creating a new current row

Operational Notes

  • pending_payment exists in the schema and analytics, but there is no current mobile controller endpoint to create it
  • the current implementation supports paid lifecycle orchestration from service code
  • documentation should not assume the old payment routes still exist under the subscription controller

Flow Summary