1. Overview

The SaaS Billing module transforms Deprixa Plus from a single-tenant application into a fully managed multi-tenant platform. It enables the platform owner (Super Admin) to define subscription plans, manage organization wallets, and automate the entire billing lifecycle while organizations (Org Admins) subscribe to plans, recharge their wallets, and consume platform resources within their plan limits.

Core Business Model

The billing model follows a wallet-based subscription pattern:

Role Responsibilities

Role Responsibilities
Super AdminCreates and manages plans, assigns subscriptions to organizations, credits/debits wallets manually, views all invoices, configures payment gateways, monitors usage across all tenants.
Org AdminViews their current plan and usage, recharges their organization wallet via payment gateways, views their invoices and subscription history, enables/disables auto-renewal.

Edition Requirement

The SaaS Billing module is only available in the Plus edition. You must set APP_EDITION=Plus in your .env file and ensure saas_billing and saas_wallet are set to true in config/edition.php. Without these flags, all SaaS routes and UI elements are hidden.

2. SaaS Plans

Path: Admin > SaaS Dashboard > Plans

Route: /admin/billing/plans

Plans define the resource limits and feature access for each tier. The Super Admin creates, edits, and toggles plans from the SaaS Dashboard. Each plan can be activated or deactivated without deleting it — deactivated plans are hidden from the pricing page and cannot be assigned to new subscriptions, but existing subscribers remain on their plan until renewal.

Default Plans

Plan Monthly Price Max Shipments Max Users Max Branches Storage (GB) API Calls/Day
Free$050211100
Starter$295005251,000
Growth$792,000155205,000
Pro$19910,00050205025,000
Enterprise$499UnlimitedUnlimitedUnlimited200100,000

Plan Feature Flags

Beyond numeric limits, each plan includes boolean feature flags that gate access to premium capabilities:

Feature Free Starter Growth Pro Enterprise
api_accessNoNoYesYesYes
custom_brandingNoNoNoYesYes
priority_supportNoNoNoYesYes

Billing Cycles

Each plan supports four billing cycles. Longer cycles offer savings that can be configured per plan:

Cycle Duration Typical Discount
Monthly30 days0% (base price)
Quarterly90 days5-10%
Semiannual180 days10-15%
Annual365 days15-25%

Managing Plans

Plan Slug Convention

Each plan has a unique slug (e.g., free, starter, growth, pro, enterprise) used internally for plan identification. The slug cannot be changed after creation. Use lowercase alphanumeric characters and hyphens only.

3. Wallet System

Path (Admin): Admin > SaaS Dashboard > Wallets

Path (Org): My Billing > Recharge

Routes: /admin/billing/wallets (Admin), /my-billing/recharge (Org)

Every organization has exactly one wallet. The wallet holds a monetary balance (in the platform's base currency) that is used to pay for subscriptions. The wallet is the central financial account for each tenant — all subscription payments are debited from the wallet, and all recharges credit the wallet.

How the Wallet Works

Wallet Transaction Types

All wallet movements are recorded in the saas_wallet_transactions table with full traceability:

Type Direction Description
rechargeCredit (+)Funds added via payment gateway (Stripe, PayPal, Paystack)
subscription_paymentDebit (-)Automatic debit when subscription renews
manual_creditCredit (+)Super Admin manually adds funds (courtesy, correction)
manual_debitDebit (-)Super Admin manually removes funds (correction, penalty)
refundCredit (+)Refund credited back to wallet

Wallet Admin Panel

From /admin/billing/wallets, the Super Admin can:

Wallet Balance Can Go Negative

Manual debits by Super Admins can push a wallet balance below zero. This is by design to allow corrections. However, auto-renewal will not proceed if the wallet balance is less than the subscription price — it requires sufficient funds.

4. Subscriptions

Path: Admin > SaaS Dashboard > Subscriptions

Route: /admin/billing/subscriptions

Subscriptions link organizations to plans with a defined billing cycle, start date, and expiration date. The Super Admin assigns, upgrades, downgrades, and cancels subscriptions from the admin panel.

Subscription Lifecycle

Every subscription moves through a well-defined lifecycle. The statuses are:

Status Description User Experience
trialOrganization is on a free trial period (configurable duration)Full access to plan features. Trial badge shown in UI.
activeSubscription is paid and currentFull access to all plan features and limits.
grace_periodSubscription expired but within the grace windowFull access continues. Warning banner shown urging wallet recharge.
read_onlyGrace period expired, subscription is fully lapsedUsers can browse and view data but cannot create shipments, edit records, or perform any write operations.
cancelledSubscription was manually cancelled by Super AdminSame restrictions as read_only until a new subscription is assigned.

Lifecycle Flow Diagram

Trial ──expires──▶ Active (if paid)
                       │
                   expires_at reached
                       │
              ┌────────┴────────┐
              │                 │
        auto_renew=true    auto_renew=false
              │                 │
     wallet has funds?     ──▶ grace_period
       │           │               │
      YES          NO         grace days expire
       │           │               │
    renew ──▶   grace_period  ──▶ read_only
   (new cycle)     │
              grace days expire
                   │
              read_only

Auto-Renewal Process

The system runs a daily cron job at 01:00 server time via the Artisan command:

php artisan saas:check-subscriptions

This command performs the following checks for every active subscription:

Read-Only Mode Restrictions

When a subscription is in read_only status:

Admin Subscription Management

From /admin/billing/subscriptions, the Super Admin can:

5. Invoices

Path: Admin > SaaS Dashboard > Invoices

Route: /admin/billing/invoices

SaaS invoices are automatically generated for every financial event — wallet recharges and subscription payments both produce invoices. The Super Admin can also generate manual adjustment and refund invoices.

Invoice Types

Type Trigger Description
subscriptionSubscription renewal (auto or manual)Invoice for the plan subscription payment debited from wallet.
rechargeWallet recharge via payment gatewayInvoice for the wallet top-up amount charged to the payment method.
adjustmentManual credit/debit by Super AdminInvoice documenting a manual wallet adjustment with reason.
refundRefund processed by Super AdminCredit note / refund invoice for returned funds.

Invoice Statuses

Status Description
draftInvoice created but not yet finalized. Can be edited.
issuedInvoice finalized and sent to the organization. Awaiting payment.
paidPayment confirmed (automatically via gateway or manually by Super Admin).
overdueInvoice past its due date without payment. Triggers reminders.
cancelledInvoice voided. No payment expected.

Manual Payment Recording

The Super Admin can mark any invoice as paid manually from the invoice detail view. This is useful for wire transfers, cash payments, or other offline payment methods. When marking as paid, the admin provides:

6. Payment Gateways (Wallet Recharge)

Organizations recharge their wallets through integrated payment gateways. Each gateway follows a redirect-based flow: the user initiates a recharge, is redirected to the gateway's hosted checkout, completes the payment, and is redirected back. The system verifies the payment via callback/webhook and credits the wallet.

Stripe Integration

Detail Value
API VersionCheckout Sessions (Stripe Checkout v2)
FlowCreate Checkout Session → Redirect to Stripe → Callback on success → Credit wallet
Webhook URL/my-billing/webhook/stripe
Webhook SecuritySignature verification using STRIPE_WEBHOOK_SECRET
Required ConfigSTRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET in .env

PayPal Integration

Detail Value
API VersionOrders API v2
FlowCreate Order → Redirect to PayPal → Capture payment on return → Credit wallet
Webhook URL/my-billing/webhook/paypal
Webhook SecurityPayload validation via PayPal API verification endpoint
Required ConfigPAYPAL_CLIENT_ID, PAYPAL_SECRET, PAYPAL_MODE (sandbox/live) in .env

Paystack Integration

Detail Value
API VersionPaystack Transaction API
FlowInitialize transaction → Redirect to Paystack → Verify on callback → Credit wallet
Webhook URL/my-billing/webhook/paystack
Webhook SecurityHMAC SHA-512 signature verification using PAYSTACK_SECRET_KEY
Required ConfigPAYSTACK_PUBLIC_KEY, PAYSTACK_SECRET_KEY in .env

Idempotency & Double-Credit Prevention

Every recharge transaction is assigned a unique reference ID before the user is redirected to the payment gateway. When the callback or webhook fires, the system checks whether a wallet transaction with that reference ID already exists. If it does, the credit is skipped. This prevents double-crediting in scenarios where both the redirect callback and the webhook fire for the same payment.

# Example reference ID format
saas_recharge_{org_id}_{timestamp}_{random}
# e.g., saas_recharge_42_1716825600_a1b2c3

Webhook Security Summary

Gateway Verification Method Secret Key
StripeSignature header (Stripe-Signature) verified against webhook secretSTRIPE_WEBHOOK_SECRET
PayPalPayload sent to PayPal verification API for authenticity checkUses PAYPAL_CLIENT_ID + PAYPAL_SECRET
PaystackHMAC SHA-512 of request body compared to X-Paystack-Signature headerPAYSTACK_SECRET_KEY

Gateway Credentials Per Organization

Payment gateway credentials can be configured globally (in .env) or per-organization (stored in organization_settings). Per-organization credentials take precedence, allowing each tenant to use their own Stripe/PayPal/Paystack account if desired.

7. Plan Limits Enforcement

Plan limits are enforced in real time whenever users attempt to create resources that are capped by their subscription plan. The enforcement is handled by the SaasUsageService class.

How Enforcement Works

Usage Reset Cycle

Shipment usage counters reset at the beginning of each billing cycle (based on the subscription's starts_at date). For example, if a subscription starts on January 15 with a monthly cycle, the shipment counter resets on February 15, March 15, and so on.

Usage Dashboard

The SaaS Dashboard shows a usage summary for each organization, including:

# SaasUsageService check example (pseudo-code)
$usage = SaasUsageService::check($organization, 'shipments');
# Returns: { allowed: true, current: 342, limit: 500, remaining: 158 }
# Or:      { allowed: false, current: 500, limit: 500, remaining: 0 }

8. Subscription Status Middleware

The CheckSubscriptionStatus middleware is applied to all authenticated routes (except excluded paths). It enforces subscription-based access control at the HTTP level.

Middleware Behavior

Subscription Status GET Requests POST/PUT/PATCH/DELETE
active / trialAllowedAllowed
grace_periodAllowedAllowed (with warning banner)
read_onlyAllowedBlocked (HTTP 403)
suspendedAllowedBlocked (HTTP 403)
cancelledAllowedBlocked (HTTP 403)

Excluded Routes

The following routes are always accessible regardless of subscription status, even in read-only mode:

Super Admin Bypass

Super Admin users bypass the CheckSubscriptionStatus middleware completely. They are never restricted by subscription status, as they are platform-level administrators, not organization-level users.

# Middleware registration in app/Http/Kernel.php
'check.subscription' => \App\Http\Middleware\CheckSubscriptionStatus::class,

# Applied to route groups
Route::middleware(['auth', 'check.subscription'])->group(function () {
    // All authenticated tenant routes
});

9. Notifications

The SaaS module sends automated notifications at every subscription lifecycle transition. Notifications are delivered via two channels: email (SMTP) and in-app (database notification with real-time push via WebSocket).

Notification Events

Event Trigger Notification Message Recipients
Subscription Renewed Auto-renewal succeeds (sufficient wallet balance) "Subscription renewed: {Plan Name} - Next renewal on {date}" Org Admin(s)
Insufficient Balance Auto-renewal fails due to low wallet balance "Action required: Insufficient balance to renew {Plan Name}. Please recharge your wallet within {X} days." Org Admin(s)
Subscription Expired Subscription expires_at reached without renewal "Subscription expired: {Plan Name}. Your account is now in grace period." Org Admin(s)
Read-Only Mode Grace period expires without renewal "Account restricted: read-only mode. Recharge your wallet and renew to restore full access." Org Admin(s) + all org users
Wallet Recharged Successful wallet recharge via payment gateway "Wallet recharged: ${amount} added. New balance: ${balance}" Org Admin(s)
Subscription Assigned Super Admin assigns a new subscription "New subscription: {Plan Name} ({Cycle}) activated until {date}" Org Admin(s)
Subscription Cancelled Super Admin cancels a subscription "Subscription cancelled: {Plan Name}. Contact support for assistance." Org Admin(s)

Email Templates

Each notification event has a corresponding email template using Laravel's Mailable system. Templates include the organization logo, plan details, action buttons (e.g., "Recharge Now"), and a direct link to the billing page.

Notification Channels

Additional notification channels (WhatsApp via UltraMsg, SMS via Twilio) can be configured in notification_channels table in the database. When enabled, subscription lifecycle notifications are also sent via these channels alongside email and in-app notifications.

10. Audit Logging

Every SaaS administrative operation is recorded in the audit_logs table, providing a complete audit trail for compliance and debugging purposes.

Logged Admin Operations

Operation Event Type Logged Data
Create plansaas.plan.createdPlan name, slug, pricing, limits, created by
Update plansaas.plan.updatedChanged fields (before/after), updated by
Toggle plansaas.plan.toggledPlan name, new status (active/inactive), toggled by
Credit walletsaas.wallet.creditedOrganization, amount, reason, credited by
Debit walletsaas.wallet.debitedOrganization, amount, reason, debited by
Assign subscriptionsaas.subscription.assignedOrganization, plan, cycle, assigned by
Cancel subscriptionsaas.subscription.cancelledOrganization, plan, reason, cancelled by
Mark invoice paidsaas.invoice.marked_paidInvoice ID, amount, payment method, marked by

Logged System Events (Cron)

The saas:check-subscriptions cron command also logs its actions with the actor recorded as system:

Event Event Type Logged Data
Auto-renewal successsaas.subscription.auto_renewedOrganization, plan, amount debited, new expires_at
Grace period enteredsaas.subscription.grace_periodOrganization, plan, wallet balance, grace expiry date
Read-only activatedsaas.subscription.read_onlyOrganization, plan, grace period duration exceeded
# Audit log entry structure
{
  "event_type": "saas.wallet.credited",
  "actor_type": "user",        // or "system" for cron
  "actor_id": 1,
  "organization_id": 42,
  "payload": {
    "amount": 100.00,
    "reason": "Courtesy credit for downtime",
    "new_balance": 329.50
  },
  "ip_address": "192.168.1.100",
  "created_at": "2026-05-10T14:30:00Z"
}

11. Pricing Page

Route: /pricing (public, no authentication required)

The pricing page is a public-facing page that displays all active SaaS plans with their features, limits, and pricing. It serves as the primary marketing and conversion tool for the platform.

Page Behavior

Customization

The pricing page content is driven entirely by the saas_plans table. To customize:

12. Database Tables

The SaaS module introduces five dedicated tables plus leverages two existing tables for configuration and notifications.

saas_plans

Stores all subscription plan definitions.

ColumnTypeDescription
idbigint (PK)Auto-increment primary key
namestringDisplay name (e.g., "Growth")
slugstring (unique)URL-safe identifier (e.g., "growth")
descriptiontextPlan description for pricing page
price_monthlydecimal(10,2)Monthly price in base currency
price_quarterlydecimal(10,2)Quarterly price
price_semiannualdecimal(10,2)Semiannual price
price_annualdecimal(10,2)Annual price
max_shipmentsintegerMax shipments per billing cycle (-1 = unlimited)
max_usersintegerMax users per organization (-1 = unlimited)
max_branchesintegerMax branches per organization (-1 = unlimited)
storage_gbintegerMax storage in gigabytes
api_calls_dailyintegerMax API calls per day
api_accessbooleanWhether API access is enabled
custom_brandingbooleanWhether custom branding is allowed
priority_supportbooleanWhether priority support is included
is_activebooleanWhether plan is visible/assignable
is_recommendedbooleanHighlighted on pricing page
sort_orderintegerDisplay order on pricing page
created_attimestampCreation timestamp
updated_attimestampLast update timestamp

saas_subscriptions

Tracks each organization's subscription to a plan.

ColumnTypeDescription
idbigint (PK)Auto-increment primary key
organization_idbigint (FK)References organizations table
saas_plan_idbigint (FK)References saas_plans table
billing_cycleenummonthly, quarterly, semiannual, annual
statusenumtrial, active, grace_period, read_only, cancelled
starts_attimestampSubscription start date
expires_attimestampSubscription expiration date
grace_ends_attimestampGrace period end date (null if not in grace)
auto_renewbooleanWhether to auto-renew from wallet
cancelled_attimestampWhen the subscription was cancelled (null if active)
created_attimestampCreation timestamp
updated_attimestampLast update timestamp

saas_wallets

One wallet per organization, holding the prepaid balance.

ColumnTypeDescription
idbigint (PK)Auto-increment primary key
organization_idbigint (FK, unique)References organizations table (one-to-one)
balancedecimal(12,2)Current wallet balance
currencystring(3)ISO 4217 currency code (e.g., USD)
created_attimestampCreation timestamp
updated_attimestampLast update timestamp

saas_wallet_transactions

Complete ledger of all wallet credits and debits.

ColumnTypeDescription
idbigint (PK)Auto-increment primary key
saas_wallet_idbigint (FK)References saas_wallets table
typeenumrecharge, subscription_payment, manual_credit, manual_debit, refund
amountdecimal(12,2)Transaction amount (positive for credits, negative for debits)
balance_afterdecimal(12,2)Wallet balance after this transaction
reference_idstring (unique)Idempotency key / external reference
gatewaystring (nullable)Payment gateway (stripe, paypal, paystack, manual)
descriptiontextHuman-readable description
metadatajson (nullable)Additional data (gateway response, admin notes)
created_bybigint (FK, nullable)User who initiated (null for system/cron)
created_attimestampTransaction timestamp

saas_invoices

Invoice records for all SaaS financial events.

ColumnTypeDescription
idbigint (PK)Auto-increment primary key
organization_idbigint (FK)References organizations table
invoice_numberstring (unique)Formatted invoice number (e.g., SAAS-INV-2026-00042)
typeenumsubscription, recharge, adjustment, refund
statusenumdraft, issued, paid, overdue, cancelled
amountdecimal(12,2)Invoice total amount
currencystring(3)ISO 4217 currency code
descriptiontextInvoice description / line item summary
saas_subscription_idbigint (FK, nullable)Related subscription (for subscription invoices)
wallet_transaction_idbigint (FK, nullable)Related wallet transaction
paid_attimestamp (nullable)When payment was confirmed
due_attimestamp (nullable)Payment due date
metadatajson (nullable)Additional data (payment method, reference, notes)
created_attimestampCreation timestamp
updated_attimestampLast update timestamp

Related Existing Tables

TableSaaS Usage
organization_settingsStores per-organization payment gateway credentials (Stripe keys, PayPal client ID/secret, Paystack keys). Keyed by organization_id + setting_key.
notification_channelsStores configuration for additional notification channels (UltraMsg for WhatsApp, Twilio for SMS, SMTP overrides). Used by SaaS notifications alongside the default email + in-app channels.

13. Permissions

The SaaS module defines five granular permissions that control access to different areas of the SaaS admin panel. These permissions are assigned to roles from the Users & Roles settings page.

SaaS Permission Matrix

Permission Slug Grants Access To
SaaS Billing Admin saas.billing.admin Full access to the SaaS Dashboard overview, analytics, and all sub-sections. This is the master permission — if a user has this, they can access everything in the SaaS module.
Manage Plans saas.plans.manage Create, edit, toggle, and delete SaaS plans. Access to /admin/billing/plans.
Manage Subscriptions saas.subscriptions.manage Assign, upgrade, downgrade, cancel, and extend subscriptions. Access to /admin/billing/subscriptions.
Manage Wallets saas.wallets.manage View wallet balances, credit/debit wallets, view transaction history. Access to /admin/billing/wallets.
Manage Invoices saas.invoices.manage View, issue, cancel, and mark invoices as paid. Access to /admin/billing/invoices.

Default Role Assignments

Role Permissions
Super Admin All 5 SaaS permissions (saas.billing.admin, saas.plans.manage, saas.subscriptions.manage, saas.wallets.manage, saas.invoices.manage)
Admin All 5 SaaS permissions
Manager None (SaaS management is admin-only by default)
Operator None

Permission Granularity

Permissions are additive. You can assign saas.wallets.manage to a support role without giving them access to plans or subscriptions. The saas.billing.admin permission is a superset — it grants access to all SaaS admin sections regardless of the other four permissions.

14. Configuration

The SaaS Billing module requires specific configuration in both the environment file and the edition configuration.

Environment Variables (.env)

# Edition (must be Plus for SaaS features)
APP_EDITION=Plus

# Stripe Configuration
STRIPE_KEY=pk_live_xxxxxxxxxxxx
STRIPE_SECRET=sk_live_xxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx

# PayPal Configuration
PAYPAL_CLIENT_ID=AxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxB
PAYPAL_SECRET=ExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxF
PAYPAL_MODE=live   # or "sandbox" for testing

# Paystack Configuration
PAYSTACK_PUBLIC_KEY=pk_live_xxxxxxxxxxxx
PAYSTACK_SECRET_KEY=sk_live_xxxxxxxxxxxx

# SaaS Settings
SAAS_GRACE_PERIOD_DAYS=7
SAAS_TRIAL_DAYS=14
SAAS_DEFAULT_PLAN=free

Edition Configuration (config/edition.php)

Both feature flags must be set to true for the SaaS module to be active:

// config/edition.php
return [
    // ... other edition flags ...
    'saas_billing' => true,   // Enables SaaS plans, subscriptions, invoices
    'saas_wallet'  => true,   // Enables wallet system and payment gateways
];

Cron Job Setup

The subscription lifecycle cron must be registered in your server's crontab or in Laravel's task scheduler:

# Option 1: Direct crontab entry
0 1 * * * cd /path-to-your-project && php artisan saas:check-subscriptions >> /dev/null 2>&1

# Option 2: Laravel Scheduler (in app/Console/Kernel.php)
$schedule->command('saas:check-subscriptions')->dailyAt('01:00');

# Then ensure the Laravel scheduler itself runs every minute:
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

Critical: Cron Must Be Running

Without the daily cron job, subscriptions will never auto-renew, grace periods will never trigger, and organizations will not transition to read-only mode. Verify the cron is running with php artisan saas:check-subscriptions --dry-run which simulates the process without making changes.

Configuration Checklist