SaaS Billing & Subscription Management
Multi-tenant subscription engine with wallet-based billing, automated plan enforcement, payment gateway integrations, and full lifecycle management for organizations.
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:
- Organizations subscribe to plans that define their resource limits (shipments, users, branches, storage, API calls).
- Organizations recharge their wallets using Stripe, PayPal, or Paystack. The wallet acts as a prepaid balance.
- The system auto-debits the wallet when subscriptions renew. If auto-renewal is enabled and the wallet has sufficient balance, the subscription renews automatically with no manual intervention.
- If insufficient balance, the organization enters a grace period, then moves to read-only mode until the wallet is recharged and the subscription is renewed.
Role Responsibilities
| Role | Responsibilities |
|---|---|
| Super Admin | Creates and manages plans, assigns subscriptions to organizations, credits/debits wallets manually, views all invoices, configures payment gateways, monitors usage across all tenants. |
| Org Admin | Views 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 | $0 | 50 | 2 | 1 | 1 | 100 |
| Starter | $29 | 500 | 5 | 2 | 5 | 1,000 |
| Growth | $79 | 2,000 | 15 | 5 | 20 | 5,000 |
| Pro | $199 | 10,000 | 50 | 20 | 50 | 25,000 |
| Enterprise | $499 | Unlimited | Unlimited | Unlimited | 200 | 100,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_access | No | No | Yes | Yes | Yes |
custom_branding | No | No | No | Yes | Yes |
priority_support | No | No | No | Yes | Yes |
Billing Cycles
Each plan supports four billing cycles. Longer cycles offer savings that can be configured per plan:
| Cycle | Duration | Typical Discount |
|---|---|---|
| Monthly | 30 days | 0% (base price) |
| Quarterly | 90 days | 5-10% |
| Semiannual | 180 days | 10-15% |
| Annual | 365 days | 15-25% |
Managing Plans
- Create: Click "New Plan" in the Plans section. Fill in name, slug, description, pricing per cycle, limits, and feature flags. Save to make the plan available.
- Edit: Click the edit icon on any plan row. Changes to limits apply to new subscriptions only; existing subscribers keep their current limits until renewal.
- Toggle: Use the active/inactive switch to hide a plan from the pricing page without deleting it. Existing subscriptions on a deactivated plan continue to function.
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
- Recharge: Organizations add funds via Stripe, PayPal, or Paystack. The payment gateway processes the charge and the system credits the wallet upon successful payment confirmation.
- Auto-Debit: When a subscription renews (either manually or via the daily cron), the subscription price is debited from the wallet balance.
- Manual Adjustments: Super Admins can manually credit or debit any organization's wallet from the admin panel (e.g., courtesy credits, corrections, refunds).
Wallet Transaction Types
All wallet movements are recorded in the saas_wallet_transactions table with full traceability:
| Type | Direction | Description |
|---|---|---|
recharge | Credit (+) | Funds added via payment gateway (Stripe, PayPal, Paystack) |
subscription_payment | Debit (-) | Automatic debit when subscription renews |
manual_credit | Credit (+) | Super Admin manually adds funds (courtesy, correction) |
manual_debit | Debit (-) | Super Admin manually removes funds (correction, penalty) |
refund | Credit (+) | Refund credited back to wallet |
Wallet Admin Panel
From /admin/billing/wallets, the Super Admin can:
- View all organization wallets with current balances
- Search and filter by organization name or balance range
- Click into a wallet to view the full transaction history
- Credit or debit a wallet with a reason/note (recorded in the transaction log)
- Export wallet transaction reports
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 |
|---|---|---|
trial | Organization is on a free trial period (configurable duration) | Full access to plan features. Trial badge shown in UI. |
active | Subscription is paid and current | Full access to all plan features and limits. |
grace_period | Subscription expired but within the grace window | Full access continues. Warning banner shown urging wallet recharge. |
read_only | Grace period expired, subscription is fully lapsed | Users can browse and view data but cannot create shipments, edit records, or perform any write operations. |
cancelled | Subscription was manually cancelled by Super Admin | Same 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:
- If
expires_athas passed andauto_renew = true: Checks the organization's wallet balance. If sufficient, debits the wallet for the plan price, generates a new invoice, sets a newexpires_atdate, and sends a renewal confirmation notification. - If
expires_athas passed and wallet balance is insufficient: Moves the subscription tograce_periodstatus. The grace period duration is configurable (default: 7 days). Sends an "insufficient balance" notification. - If in
grace_periodand grace days have expired: Moves the subscription toread_onlystatus. Sends a "subscription expired" notification. - If
auto_renew = falseandexpires_athas passed: Moves directly tograce_period, then toread_onlyafter grace days expire.
Read-Only Mode Restrictions
When a subscription is in read_only status:
- Users can log in and browse all existing data (shipments, invoices, reports, etc.)
- Users cannot create new shipments or any new records
- Users cannot edit, update, or delete any existing records
- Users can access billing pages to recharge their wallet and reactivate
- Users can access their profile and log out
- A persistent banner is displayed explaining the read-only state and linking to the billing page
Admin Subscription Management
From /admin/billing/subscriptions, the Super Admin can:
- Assign: Select an organization, choose a plan and billing cycle, set the start date. The wallet is debited immediately.
- Upgrade/Downgrade: Change the plan for an active subscription. Prorated credit is calculated for the remaining days on the current plan.
- Cancel: Immediately ends the subscription and moves the organization to read-only status.
- Extend: Manually extend the
expires_atdate without charging (courtesy extension).
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 |
|---|---|---|
subscription | Subscription renewal (auto or manual) | Invoice for the plan subscription payment debited from wallet. |
recharge | Wallet recharge via payment gateway | Invoice for the wallet top-up amount charged to the payment method. |
adjustment | Manual credit/debit by Super Admin | Invoice documenting a manual wallet adjustment with reason. |
refund | Refund processed by Super Admin | Credit note / refund invoice for returned funds. |
Invoice Statuses
| Status | Description |
|---|---|
draft | Invoice created but not yet finalized. Can be edited. |
issued | Invoice finalized and sent to the organization. Awaiting payment. |
paid | Payment confirmed (automatically via gateway or manually by Super Admin). |
overdue | Invoice past its due date without payment. Triggers reminders. |
cancelled | Invoice 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:
- Payment date
- Payment method (free text, e.g., "Wire Transfer", "Cash", "Check")
- Reference number (optional)
- Notes (optional)
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 Version | Checkout Sessions (Stripe Checkout v2) |
| Flow | Create Checkout Session → Redirect to Stripe → Callback on success → Credit wallet |
| Webhook URL | /my-billing/webhook/stripe |
| Webhook Security | Signature verification using STRIPE_WEBHOOK_SECRET |
| Required Config | STRIPE_KEY, STRIPE_SECRET, STRIPE_WEBHOOK_SECRET in .env |
PayPal Integration
| Detail | Value |
|---|---|
| API Version | Orders API v2 |
| Flow | Create Order → Redirect to PayPal → Capture payment on return → Credit wallet |
| Webhook URL | /my-billing/webhook/paypal |
| Webhook Security | Payload validation via PayPal API verification endpoint |
| Required Config | PAYPAL_CLIENT_ID, PAYPAL_SECRET, PAYPAL_MODE (sandbox/live) in .env |
Paystack Integration
| Detail | Value |
|---|---|
| API Version | Paystack Transaction API |
| Flow | Initialize transaction → Redirect to Paystack → Verify on callback → Credit wallet |
| Webhook URL | /my-billing/webhook/paystack |
| Webhook Security | HMAC SHA-512 signature verification using PAYSTACK_SECRET_KEY |
| Required Config | PAYSTACK_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 |
|---|---|---|
| Stripe | Signature header (Stripe-Signature) verified against webhook secret | STRIPE_WEBHOOK_SECRET |
| PayPal | Payload sent to PayPal verification API for authenticity check | Uses PAYPAL_CLIENT_ID + PAYPAL_SECRET |
| Paystack | HMAC SHA-512 of request body compared to X-Paystack-Signature header | PAYSTACK_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
- Shipment creation: Before a new shipment is saved, the system checks the organization's
max_shipmentslimit against their current usage count for the active billing cycle. If the limit is reached, the creation is blocked with a clear error message: "You have reached your plan's shipment limit (X/X). Please upgrade your plan to create more shipments." - User creation: Same pattern for
max_userswhen inviting or creating new users. - Branch creation: Same pattern for
max_brancheswhen adding new branches. - API calls: API rate limiting enforced per day based on
api_calls_daily. Returns HTTP 429 with a descriptive message when exceeded. - Storage: File uploads checked against
storage_gb. Upload rejected if total storage would exceed the plan limit.
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:
- Current shipment count vs. plan limit (with progress bar)
- Active users vs. plan limit
- Active branches vs. plan limit
- Storage used vs. plan limit
- API calls today vs. daily limit
# 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 / trial | Allowed | Allowed |
grace_period | Allowed | Allowed (with warning banner) |
read_only | Allowed | Blocked (HTTP 403) |
suspended | Allowed | Blocked (HTTP 403) |
cancelled | Allowed | Blocked (HTTP 403) |
Excluded Routes
The following routes are always accessible regardless of subscription status, even in read-only mode:
GET *— All read operations (browse, view, export, download)/my-billing/*— All billing pages (recharge wallet, view invoices, manage subscription)/profile/*— User profile management/logout— Logout action/notifications/*— View notifications
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 plan | saas.plan.created | Plan name, slug, pricing, limits, created by |
| Update plan | saas.plan.updated | Changed fields (before/after), updated by |
| Toggle plan | saas.plan.toggled | Plan name, new status (active/inactive), toggled by |
| Credit wallet | saas.wallet.credited | Organization, amount, reason, credited by |
| Debit wallet | saas.wallet.debited | Organization, amount, reason, debited by |
| Assign subscription | saas.subscription.assigned | Organization, plan, cycle, assigned by |
| Cancel subscription | saas.subscription.cancelled | Organization, plan, reason, cancelled by |
| Mark invoice paid | saas.invoice.marked_paid | Invoice 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 success | saas.subscription.auto_renewed | Organization, plan, amount debited, new expires_at |
| Grace period entered | saas.subscription.grace_period | Organization, plan, wallet balance, grace expiry date |
| Read-only activated | saas.subscription.read_only | Organization, 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
- Unauthenticated visitors: See all active plans with a "Get Started" button that links to the registration page.
- Authenticated users (Org Admin): See all active plans with their current plan highlighted (badge: "Current Plan"). Other plans show "Upgrade" or "Downgrade" buttons linking to
/my-billing. - Billing cycle toggle: Users can switch between Monthly, Quarterly, Semiannual, and Annual pricing views. Prices update dynamically.
- Feature comparison: A feature comparison table at the bottom shows all plan limits and feature flags side by side.
Customization
The pricing page content is driven entirely by the saas_plans table. To customize:
- Edit plan names, descriptions, and pricing from the Admin Plans page
- Toggle plans active/inactive to show or hide them
- Set a plan as "recommended" to highlight it with a special badge
- Customize the page heading and subheading from Settings
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.
| Column | Type | Description |
|---|---|---|
id | bigint (PK) | Auto-increment primary key |
name | string | Display name (e.g., "Growth") |
slug | string (unique) | URL-safe identifier (e.g., "growth") |
description | text | Plan description for pricing page |
price_monthly | decimal(10,2) | Monthly price in base currency |
price_quarterly | decimal(10,2) | Quarterly price |
price_semiannual | decimal(10,2) | Semiannual price |
price_annual | decimal(10,2) | Annual price |
max_shipments | integer | Max shipments per billing cycle (-1 = unlimited) |
max_users | integer | Max users per organization (-1 = unlimited) |
max_branches | integer | Max branches per organization (-1 = unlimited) |
storage_gb | integer | Max storage in gigabytes |
api_calls_daily | integer | Max API calls per day |
api_access | boolean | Whether API access is enabled |
custom_branding | boolean | Whether custom branding is allowed |
priority_support | boolean | Whether priority support is included |
is_active | boolean | Whether plan is visible/assignable |
is_recommended | boolean | Highlighted on pricing page |
sort_order | integer | Display order on pricing page |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
saas_subscriptions
Tracks each organization's subscription to a plan.
| Column | Type | Description |
|---|---|---|
id | bigint (PK) | Auto-increment primary key |
organization_id | bigint (FK) | References organizations table |
saas_plan_id | bigint (FK) | References saas_plans table |
billing_cycle | enum | monthly, quarterly, semiannual, annual |
status | enum | trial, active, grace_period, read_only, cancelled |
starts_at | timestamp | Subscription start date |
expires_at | timestamp | Subscription expiration date |
grace_ends_at | timestamp | Grace period end date (null if not in grace) |
auto_renew | boolean | Whether to auto-renew from wallet |
cancelled_at | timestamp | When the subscription was cancelled (null if active) |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
saas_wallets
One wallet per organization, holding the prepaid balance.
| Column | Type | Description |
|---|---|---|
id | bigint (PK) | Auto-increment primary key |
organization_id | bigint (FK, unique) | References organizations table (one-to-one) |
balance | decimal(12,2) | Current wallet balance |
currency | string(3) | ISO 4217 currency code (e.g., USD) |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
saas_wallet_transactions
Complete ledger of all wallet credits and debits.
| Column | Type | Description |
|---|---|---|
id | bigint (PK) | Auto-increment primary key |
saas_wallet_id | bigint (FK) | References saas_wallets table |
type | enum | recharge, subscription_payment, manual_credit, manual_debit, refund |
amount | decimal(12,2) | Transaction amount (positive for credits, negative for debits) |
balance_after | decimal(12,2) | Wallet balance after this transaction |
reference_id | string (unique) | Idempotency key / external reference |
gateway | string (nullable) | Payment gateway (stripe, paypal, paystack, manual) |
description | text | Human-readable description |
metadata | json (nullable) | Additional data (gateway response, admin notes) |
created_by | bigint (FK, nullable) | User who initiated (null for system/cron) |
created_at | timestamp | Transaction timestamp |
saas_invoices
Invoice records for all SaaS financial events.
| Column | Type | Description |
|---|---|---|
id | bigint (PK) | Auto-increment primary key |
organization_id | bigint (FK) | References organizations table |
invoice_number | string (unique) | Formatted invoice number (e.g., SAAS-INV-2026-00042) |
type | enum | subscription, recharge, adjustment, refund |
status | enum | draft, issued, paid, overdue, cancelled |
amount | decimal(12,2) | Invoice total amount |
currency | string(3) | ISO 4217 currency code |
description | text | Invoice description / line item summary |
saas_subscription_id | bigint (FK, nullable) | Related subscription (for subscription invoices) |
wallet_transaction_id | bigint (FK, nullable) | Related wallet transaction |
paid_at | timestamp (nullable) | When payment was confirmed |
due_at | timestamp (nullable) | Payment due date |
metadata | json (nullable) | Additional data (payment method, reference, notes) |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
Related Existing Tables
| Table | SaaS Usage |
|---|---|
organization_settings | Stores per-organization payment gateway credentials (Stripe keys, PayPal client ID/secret, Paystack keys). Keyed by organization_id + setting_key. |
notification_channels | Stores 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
- Set
APP_EDITION=Plusin.env - Set
saas_billingandsaas_wallettotrueinconfig/edition.php - Configure at least one payment gateway (Stripe, PayPal, or Paystack) in
.env - Run database migrations:
php artisan migrate - Run the SaaS seeder:
php artisan db:seed --class=SaasPlansSeeder - Register the cron job:
saas:check-subscriptionsdaily at 01:00 - Assign SaaS permissions to admin roles from Users & Roles settings
- Create or verify default plans exist in the Plans section
- Test the pricing page at
/pricingto confirm plans are visible - Test a wallet recharge flow end-to-end with the configured payment gateway