Flows

Subscription Flow

Plans, subscriptions and billing.

Store Subscription Flow

This document covers the end-to-end subscription lifecycle for stores: auto-provisioning, self-service upgrades via QiCard, admin management, and the plan limits that gate store operations.


Overview

Every store must have an active subscription to perform write operations. The system enforces plan limits via IStoreSubscriptionGuard, which is injected into all store-area services. A store without a valid subscription (expired, canceled, or missing) receives a 403 / 422 error before the business logic executes.

Subscription statuses

StatusMeaning
TrialingFree-trial period; full plan limits apply
ActivePaid and within the billing period
PastDuePayment overdue; write operations blocked
CanceledManually canceled; write operations blocked
ExpiredPeriod ended without renewal; write operations blocked

1. Auto-provisioning (trial subscription)

A free_trial subscription is automatically assigned in two scenarios:

  1. Store self-registration — after the owner completes OTP registration (AuthService.StoreRegisterAsync), EnsureInitialTrialSubscriptionAsync is called immediately.
  2. Admin-created store — when an admin directly creates a pre-approved store (StoreProvisioningService.CreateApprovedStoreWithOwnerAsync), EnsureInitialTrialSubscriptionAsync is called.

In both cases the method looks up the plan with Code == "free_trial", creates a StoreSubscription with Status = Trialing, and sets PeriodEnd = now + TrialDays (falls back to BillingPeriodDays if TrialDays == 0). Admin approval of a pending store does not create a subscription.

Store self-registers  ──►  AuthService.StoreRegisterAsync          ──►  EnsureInitialTrialSubscriptionAsync
                                                                               │
Admin creates store   ──►  StoreProvisioningService (pre-approved)  ──►  EnsureInitialTrialSubscriptionAsync
                                                                               │
                                                              StoreSubscription { Status = Trialing }

2. Store — view subscription and available plans

Main area: GET /api/v1/store

Requires permission: store.subscription.view

EndpointDescription
GET /api/v1/store/subscriptionReturns current subscription details (plan, status, period, usage snapshot)
GET /api/v1/store/subscription/plansLists all public plans the store can upgrade to

3. Store — self-service upgrade flow

Requires permission: store.subscription.upgrade

Step-by-step

  1. Browse plans — store calls GET /api/v1/store/subscription/plans to see available plans and pricing.
  2. Create upgrade request — store posts to POST /api/v1/store/subscription/upgrade-requests with target plan ID and return URL. The service:
    • Creates a StoreSubscriptionUpgradeRequest with Status = PendingPayment.
    • Initiates a QiCard hosted payment session.
    • Returns payment_url to redirect the user.
  3. User completes payment — user is redirected to QiCard's hosted page.
  4. QiCard webhook — on payment completion, QiCard calls POST /api/v1/payments/qicard/subscription-webhook. The service:
    • Matches the notification to the upgrade request via GatewayRequestId.
    • On success: creates a new StoreSubscription (status Active), sets ActivatedSubscriptionId, marks upgrade request Activated.
    • On failure: marks upgrade request Failed, stores FailureReason.
  5. Store verifies — store can poll or verify via POST /api/v1/store/subscription/upgrade-requests/{requestId}/verify after returning from the payment gateway.
  6. Check statusGET /api/v1/store/subscription/upgrade-requests/{requestId} shows full request state.
Store ──► POST /store/subscription/upgrade-requests
               │
               ▼
    StoreSubscriptionUpgradeRequest { Status = PendingPayment, payment_url }
               │
               ▼
    User redirected ──► QiCard payment page
               │
               ▼ (async)
    QiCard ──► POST /payments/qicard/subscription-webhook
               │
       ┌───────┴────────┐
    Success           Failure
       │                 │
  New StoreSubscription  UpgradeRequest { Status = Failed }
  { Status = Active }
  UpgradeRequest { Status = Activated }

4. Subscription guard — enforced limits

IStoreSubscriptionGuard is called before every write operation in the store area. It resolves the store's most recent active/trialing subscription and evaluates the plan's limits.

Guard methodTriggered byBlocks when
EnsureCanWriteAsyncAll update/delete/cancel operationsSubscription expired, canceled, or missing
EnsureCanCreateOrdersAsyncCreate orderMaxOrdersPerPeriod quota reached
EnsureCanUseWarehouseFulfillmentAsyncCreate/update order with FulfillmentType = WarehousePlan does not allow warehouse fulfillment
EnsureCanCreateProductAsyncCreate productMaxProducts limit reached
EnsureCanCreateWarehouseAsyncCreate warehouseMaxWarehouses limit reached
EnsureCanCreateStoreUserAsyncCreate store-scoped user (RBAC)MaxUsers limit reached
EnsureCanCreateApiKeyAsyncCreate API keyAllowsApiIntegrations = false or MaxApiKeys reached
EnsureCanManageWebhookAsyncCreate webhook / rotate secretAllowsWebhooks = false or MaxWebhookSubscriptions reached
EnsureCanUseIntegrationsAsync(called by API key check)AllowsApiIntegrations = false

Quota errors return HTTP 422; access/status errors return HTTP 403.


5. Subscription plan features

A SubscriptionPlan defines both numeric limits and feature flags.

FieldTypeMeaning
MaxOrdersPerPeriodint?Max orders creatable within the subscription period (null = unlimited)
MaxProductsint?Max active products
MaxWarehousesint?Max active store-owned warehouses
MaxUsersint?Max active store users
MaxApiKeysint?Max active API keys
MaxWebhookSubscriptionsint?Max webhook subscriptions
AllowsApiIntegrationsboolEnables API key creation and integration endpoints
AllowsWebhooksboolEnables webhook management
AllowsWarehouseFulfillmentboolEnables warehouse-type order fulfillment
AllowsAdvancedReportsboolReserved for advanced reporting features
AllowsAiAutomationboolReserved for AI/automation features
TrialDaysintTrial duration for auto-provisioned subscriptions
BillingPeriodDaysintDefault billing period (used as trial fallback)
MonthlyPriceIqddecimalDisplay price in IQD

6. Admin — subscription management

Main area: /api/v1/admin

Requires permission: admin.subscriptions.view or admin.subscriptions.manage

EndpointPermissionDescription
GET /api/v1/admin/subscription-plansviewList all plans (optionally including inactive)
POST /api/v1/admin/subscription-plansmanageCreate a new plan
PUT /api/v1/admin/subscription-plans/{planId}manageUpdate a plan
GET /api/v1/admin/stores/{storeId}/subscriptionviewView a specific store's current subscription
POST /api/v1/admin/stores/{storeId}/subscriptionmanageManually assign a plan to a store (bypasses payment)
POST /api/v1/admin/stores/{storeId}/subscription/cancelmanageCancel a store's active subscription
GET /api/v1/admin/subscription-upgrade-requestsviewPaginated list of all upgrade requests (filterable)
GET /api/v1/admin/subscription-upgrade-requests/{requestId}viewView a single upgrade request

7. New permissions

PermissionScopeDescription
admin.subscriptions.viewadminView subscription plans and store subscriptions
admin.subscriptions.manageadminManage subscription plans and store subscriptions
store.subscription.viewstoreView own subscription status and available plans
store.subscription.upgradestoreRequest and pay for subscription upgrades

store.subscription.view is included in both store_owner and store_staff default permission sets. store.subscription.upgrade is included in store_owner only.


8. Payment webhook — /api/v1/payments/qicard/subscription-webhook

This public endpoint (no auth) is called by QiCard on subscription payment events. It is handled by IStoreSubscriptionUpgradeService.TryHandleQiCardWebhookAsync. The handler:

  1. Parses and validates the QiCard notification JSON.
  2. Matches GatewayRequestId to an existing StoreSubscriptionUpgradeRequest.
  3. On confirmed payment:
    • Creates a new StoreSubscription for the target plan, starting from now.
    • Updates the upgrade request with Status = Activated, PaidAt, ActivatedAt, ActivatedSubscriptionId.
  4. On failed payment:
    • Updates the upgrade request with Status = Failed, FailureReason.

Source: DOCS/flows/store-subscription-flow.md