T
Tenanto
Documentation / Marketplace

Marketplace

Updated Jan 25, 2026

Marketplace & Distribution

This document describes the features for marketplace distribution of Tenanto.

Overview

Tenanto includes infrastructure for commercial distribution:

Demo Automation

Purpose

Allow potential customers to try Tenanto without manual intervention.

Components

Component Location Description
Configuration config/demo.php Demo feature settings
Service App\Domain\Demo\Services\DemoTenantService Demo tenant lifecycle management
Cleanup Job App\Domain\Demo\Jobs\CleanupExpiredDemosJob Scheduled cleanup of expired demos
Notification App\Domain\Demo\Notifications\DemoExpirationWarning Warning email before expiration
Events App\Domain\Demo\Events\* DemoTenantCreated, DemoTenantExpired, DemoTenantConverted
Commands App\Domain\Demo\Commands\* demo:create, demo:cleanup

Configuration

// config/demo.php
return [
    'enabled' => env('DEMO_ENABLED', true),
    'expiration_hours' => (int) env('DEMO_EXPIRATION_HOURS', 48),
    'warning_hours_before' => (int) env('DEMO_WARNING_HOURS', 24),
    'max_active_demos' => (int) env('DEMO_MAX_ACTIVE', 100),
    'seed_sample_data' => env('DEMO_SEED_DATA', true),
    'default_password' => env('DEMO_DEFAULT_PASSWORD', 'demo123'),
];

Usage

# Create a demo tenant
php artisan demo:create [email protected] --name="Demo Company"

# Manually trigger cleanup (normally scheduled hourly)
php artisan demo:cleanup --sync

Lifecycle

  1. Creation: DemoTenantService::createDemo() creates tenant + owner + sample data
  2. Warning: 24 hours before expiration, DemoExpirationWarning notification sent
  3. Expiration: CleanupExpiredDemosJob soft-deletes expired demos
  4. Conversion: DemoTenantService::convertToPaid() upgrades to full account

License System

Purpose

Protect the software with license keys and enable feature gating by tier.

Components

Component Location Description
Configuration config/licensing.php License system settings
Model App\Domain\Licensing\Models\License License Eloquent model
Key Generator App\Domain\Licensing\Services\LicenseKeyGenerator HMAC-based key generation
Validator App\Domain\Licensing\Services\LicenseValidator Offline/online validation
Service App\Domain\Licensing\Services\LicenseService License lifecycle management
Enums App\Domain\Licensing\Enums\* LicenseStatus, LicenseTier
Commands App\Domain\Licensing\Commands\* CLI management
Filament App\Filament\Admin\Resources\LicenseResource Admin panel UI

License Key Format

TENANTO-{TIER}-{HASH12}-{CHECKSUM4}
Example: TENANTO-PRO-A1B2C3D4E5F6-X7Y8

License Tiers

Tier Max Activations Features
Solo 1 Basic features, single domain
Pro 3 Multi-domain, priority support, webhooks
Team 10 All Pro features, team management
Unlimited All features, white-label support

Configuration

LICENSE_ENABLED=true
LICENSE_SECRET_KEY=your-very-long-secret-key-here
LICENSE_VALIDATION_MODE=offline
LICENSE_GRACE_DAYS=7

CLI Commands

# Generate a license
php artisan license:generate --tier=pro [email protected] --name="Customer Name"

# Validate a license
php artisan license:validate TENANTO-PRO-A1B2C3D4E5F6-X7Y8 --domain=example.com

# List licenses
php artisan license:list --tier=pro --status=active
php artisan license:list --stats
php artisan license:list --expiring

Validation Modes


Update System

Purpose

Notify administrators when new versions are available.

Components

Component Location Description
Configuration config/updates.php Update system settings
Service App\Domain\Updates\Services\VersionService Version checking
Value Object App\Domain\Updates\ValueObjects\VersionInfo Version data container
Event App\Domain\Updates\Events\UpdateAvailable Fired when update detected
Livewire App\Livewire\Updates\UpdateBanner Admin banner component

Configuration

// config/updates.php
return [
    'enabled' => env('UPDATES_ENABLED', true),
    'current_version' => env('APP_VERSION', '1.0.0'),
    'server' => [
        'url' => env('UPDATES_SERVER_URL', ''),
        'timeout' => 10,
    ],
    'check_interval_hours' => 24,
    'cache_duration' => 3600,
    'notifications' => [
        'show_banner' => true,
        'admin_only' => true,
        'email_security_updates' => true,
    ],
];

Usage

Add the update banner to your admin layout:

<livewire:updates.update-banner />

The banner will:


Onboarding Wizard

Purpose

Guide new tenant owners through initial setup.

Components

Component Location Description
Configuration config/onboarding.php Steps and settings
Model App\Domain\Onboarding\Models\OnboardingProgress Step completion tracking
Service App\Domain\Onboarding\Services\OnboardingService Business logic
Livewire App\Livewire\Onboarding\OnboardingWizard Wizard UI
Middleware App\Http\Middleware\EnsureOnboardingComplete Force completion
Controller App\Http\Controllers\OnboardingController Route handler

Default Steps

  1. Company Profile - Business name, industry, timezone
  2. Team Setup - Invite initial team members
  3. Preferences - Language, date format, notifications
  4. Billing - Select subscription plan

Configuration

// config/onboarding.php
return [
    'enabled' => env('ONBOARDING_ENABLED', true),
    'force_completion' => env('ONBOARDING_FORCE', true),
    'skip_for_demo' => true,
    'steps' => [
        'company_profile' => [
            'title' => 'Company Profile',
            'required' => true,
            'fields' => ['company_name', 'industry', 'size', 'timezone'],
        ],
        // ...
    ],
];

Routes

GET /onboarding  - Show wizard (name: onboarding.wizard)

Middleware

Add EnsureOnboardingComplete to routes that require completed onboarding:

Route::middleware(['auth', 'onboarding'])->group(function () {
    // Protected routes
});

Support Hooks

Purpose

Allow customers to submit support tickets through configurable providers.

Components

Component Location Description
Configuration config/support.php Provider settings
Contract App\Domain\Support\Contracts\SupportProviderContract Provider interface
Service App\Domain\Support\Services\SupportService Provider facade
Email Provider App\Domain\Support\Providers\EmailSupportProvider Default email-based provider
Value Objects App\Domain\Support\ValueObjects\* SupportTicket, TicketReply

Supported Providers

Provider Status Notes
Email ✅ Implemented Default provider, sends tickets via email
Freshdesk ⏳ Config only Integration available
Zendesk ⏳ Config only Integration available
HelpScout ⏳ Config only Integration available
Intercom ⏳ Config only Integration available

Configuration

// config/support.php
return [
    'enabled' => env('SUPPORT_ENABLED', true),
    'default' => env('SUPPORT_PROVIDER', 'email'),
    'email' => [
        'to' => env('SUPPORT_EMAIL_TO', '[email protected]'),
        'from_name' => env('SUPPORT_EMAIL_FROM', 'Support'),
        'subject_prefix' => '[Support]',
    ],
    'categories' => [
        'general' => 'General Inquiry',
        'technical' => 'Technical Issue',
        'billing' => 'Billing Question',
        'feature' => 'Feature Request',
    ],
    'priorities' => [
        'low' => 'Low',
        'normal' => 'Normal',
        'high' => 'High',
        'urgent' => 'Urgent',
    ],
];

Usage

use App\Domain\Support\Services\SupportService;
use App\Domain\Support\ValueObjects\SupportTicket;

$service = app(SupportService::class);

// Create ticket from array
$ticketId = $service->createTicket([
    'subject' => 'Issue with billing',
    'description' => 'Cannot update payment method...',
    'requester_email' => '[email protected]',
    'requester_name' => 'John Doe',
    'category' => 'billing',
    'priority' => 'high',
]);

// Or from object
$ticket = new SupportTicket(
    subject: 'Issue with billing',
    description: 'Cannot update payment method...',
    requesterEmail: '[email protected]',
    requesterName: 'John Doe',
    category: 'billing',
    priority: 'high',
);

$ticketId = $service->createTicketFromObject($ticket);

Custom Provider

Implement SupportProviderContract for custom integrations:

class FreshdeskSupportProvider implements SupportProviderContract
{
    public function createTicket(SupportTicket $ticket): string|int { /* ... */ }
    public function getTicket(string|int $ticketId): ?SupportTicket { /* ... */ }
    public function updateTicket(string|int $ticketId, SupportTicket $ticket): bool { /* ... */ }
    public function addReply(string|int $ticketId, TicketReply $reply): bool { /* ... */ }
    public function closeTicket(string|int $ticketId): bool { /* ... */ }
    public function getName(): string { return 'freshdesk'; }
    public function isConfigured(): bool { /* ... */ }
}

// Register in config/support.php
'providers' => [
    'freshdesk' => FreshdeskSupportProvider::class,
],

Environment Variables

Add these to .env.example:

# Demo Automation
DEMO_ENABLED=true
DEMO_EXPIRATION_HOURS=48
DEMO_WARNING_HOURS=24
DEMO_MAX_ACTIVE=100
DEMO_DEFAULT_PASSWORD=demo123

# Licensing
LICENSE_ENABLED=false
LICENSE_SECRET_KEY=your-secret-key-change-in-production
LICENSE_VALIDATION_MODE=offline
LICENSE_GRACE_DAYS=7

# Updates
UPDATES_ENABLED=true
APP_VERSION=1.0.0
UPDATES_SERVER_URL=
UPDATES_SHOW_BANNER=true

# Onboarding
ONBOARDING_ENABLED=true
ONBOARDING_FORCE=true

# Support
SUPPORT_ENABLED=true
SUPPORT_PROVIDER=email
[email protected]

Database Migrations

The marketplace features include the following migrations:

  1. 2025_11_29_210000_add_demo_columns_to_tenants_table.php

    • Adds is_demo, demo_expires_at, demo_warning_sent_at to tenants
  2. 2025_11_29_220000_create_licenses_table.php

    • Creates licenses table for license management
  3. 2025_11_29_230000_create_onboarding_progress_table.php

    • Creates onboarding_progress table
    • Adds onboarding_completed_at to tenants

Run migrations:

php artisan migrate

Testing

Tenanto includes comprehensive test coverage with 1,091 tests total:

PHP Unit & Feature Tests (607 tests)

php artisan test --filter="Licensing\|Updates\|Support"

Playwright E2E Tests (484 tests)

Full browser-based end-to-end testing covering all critical user journeys:

Category Tests Coverage
Authentication 45 Login, logout, registration, password reset
Admin Panel 120 Tenants, users, licenses CRUD operations
Tenant Panel 95 Projects, tasks, teams, settings
API Endpoints 65 REST API authentication and CRUD
Tenant Isolation 41 Cross-tenant security, permission enforcement
Navigation 48 Sidebar, breadcrumbs, responsive layout
Error Pages 25 403, 404, 500 error handling
Onboarding 20 Wizard flow, progress tracking
Profile & Settings 25 User profile management
# Run all E2E tests
npx playwright test

# Run specific category
npx playwright test e2e/admin/
npx playwright test e2e/isolation/

# Interactive mode
npx playwright test --ui

Test Configuration

E2E tests are configured in playwright.config.ts:


Security Considerations

License Keys

Email Security

Demo Tenants


Related Documentation


Future Enhancements