Marketplace & Distribution
This document describes the features for marketplace distribution of Tenanto.
Overview
Tenanto includes infrastructure for commercial distribution:
- Demo Automation - Time-limited demo tenants with automatic cleanup
- License System - Secure license key generation and validation
- Update System - Version checking and update notifications
- Onboarding Wizard - Guided setup for new tenants
- Support Hooks - Extensible support ticket system
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
- Creation:
DemoTenantService::createDemo()creates tenant + owner + sample data - Warning: 24 hours before expiration,
DemoExpirationWarningnotification sent - Expiration:
CleanupExpiredDemosJobsoft-deletes expired demos - 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
- Prefix: Product identifier
- Tier: SOLO, PRO, TEAM, UNLIMITED
- Hash: 12-character random string
- Checksum: 4-character HMAC verification
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
- Offline: Validates against local database (default, recommended)
- Online: Validates against remote API endpoint
- Disabled: All licenses pass validation (development only)
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:
- Show when updates are available
- Highlight security updates in red
- Allow dismissal for 7 days
- Link to changelog/download
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
- Company Profile - Business name, industry, timezone
- Team Setup - Invite initial team members
- Preferences - Language, date format, notifications
- 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 |
|---|---|---|
| ✅ 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:
-
2025_11_29_210000_add_demo_columns_to_tenants_table.php- Adds
is_demo,demo_expires_at,demo_warning_sent_atto tenants
- Adds
-
2025_11_29_220000_create_licenses_table.php- Creates
licensestable for license management
- Creates
-
2025_11_29_230000_create_onboarding_progress_table.php- Creates
onboarding_progresstable - Adds
onboarding_completed_atto tenants
- Creates
Run migrations:
php artisan migrate
Testing
Tenanto includes comprehensive test coverage with 1,091 tests total:
PHP Unit & Feature Tests (607 tests)
LicenseKeyGeneratorTest- Key generation and validationLicenseServiceTest- License lifecycle operationsVersionInfoTest- Version value objectVersionServiceTest- Update checkingSupportServiceTest- Support ticket operations- Plus 600+ additional tests for all core features
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:
- 5 browser projects (Chromium, Firefox, WebKit, Mobile Chrome, Mobile Safari)
- Automatic retries for flaky tests
- HTML reports with screenshots and videos on failure
Security Considerations
License Keys
- HMAC-SHA256 checksums prevent tampering
- Secret key must be kept secure
- Grace period limits unauthorized use
Email Security
- All user content sanitized before email inclusion
- Prevents header injection attacks
- In-memory storage is NOT production-ready
Demo Tenants
- Automatic cleanup prevents resource exhaustion
- Rate limiting recommended on creation endpoint
- Sample data isolated to demo tenant
Related Documentation
- Multi-Tenancy Architecture - Tenant model and isolation
- Deployment Guide - Production configuration
- Security Guide - Security best practices
Future Enhancements
- Database-backed support ticket storage
- Freshdesk/Zendesk provider implementations
- License usage analytics
- Automated demo-to-paid conversion flow
- Multi-language onboarding steps