# cPanel deployment guide

This is a deployment checklist for the Laravel API source in this directory. It does not replace a rehearsal on the actual hosting account. **Use a cPanel account with PHP 8.3+** (Laravel 13 requirement), Composer 2, MySQL/InnoDB, HTTPS, and preferably SSH/Terminal access. Confirm that the host enables PDO MySQL, OpenSSL, Mbstring, XML, Ctype, cURL, Fileinfo, tokenizer, PDO, and a supported PHP OPcache configuration.

## 1. Put application code outside the public web root

Preferred layout:

```text
/home/CPANEL_USER/kora-backend/       # this Laravel project, not web-accessible
/home/CPANEL_USER/public_html/        # only if the domain cannot target backend/public
```

In cPanel **Domains**, set the API domain's document root to `kora-backend/public` when the hosting plan permits it. The document root must never point to the project root. Keep `.env`, `vendor/`, `storage/`, `artisan`, migrations, and source code outside `public_html`.

If cPanel cannot set a domain document root to the Laravel `public/` folder, have the host configure a private app directory and copy only the contents of this project's `public/` directory to the domain root. Update the copied `index.php` paths to the private project location. Do not copy the whole application into `public_html`.

## 2. Composer dependencies and environment

From cPanel Terminal/SSH:

```bash
cd /home/CPANEL_USER/kora-backend
cp .env.example .env
composer install --no-dev --prefer-dist --optimize-autoloader
php artisan key:generate
```

Set the cPanel-selected PHP CLI binary explicitly if it differs from the web PHP version. Configure the domain's PHP version and CLI version to the same supported 8.3+ release. Create a MySQL database and a dedicated least-privilege database user in cPanel, then fill `.env` with production values:

```dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://api.example.com
APP_TIMEZONE=UTC
DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=CPANEL_DATABASE
DB_USERNAME=CPANEL_DB_USER
DB_PASSWORD=LONG_RANDOM_SECRET
CACHE_STORE=file
SESSION_DRIVER=file
QUEUE_CONNECTION=database
FILESYSTEM_DISK=local
MAIL_MAILER=smtp
AUTH_REQUIRE_EMAIL_VERIFICATION=false
AUTH_EMAIL_VERIFICATION_EXPIRE=60
PASSWORD_RESET_URL=https://app.example.com/reset-password
```

Use cPanel's secure environment editor if available. Never commit or email a real `.env`. Set `APP_DEBUG=false` before exposing the site. Configure mail transport, trusted proxy/HTTPS settings, CORS allowlist and rate limits for the production web/mobile origins. Signed verification URLs use `APP_URL`; registration sends a verification email after the workspace transaction commits, and `POST /api/v1/auth/email/verification-notification` can resend it. `AUTH_REQUIRE_EMAIL_VERIFICATION` defaults to false for upgrade safety; after verifying SMTP, signed links and recovery for all existing accounts, set it to true to gate business API routes. If mail delivery fails at registration, the account remains created and the authenticated resend route is available. The Paystack source integration now includes storefront/POS payments and platform subscriptions but is unverified in this workspace. The selected platform-owned Paystack model applies to subscription billing only; it does not approve marketplace, split, subaccount or tenant-sales settlement. A single global gateway/credential and the `PAYSTACK_ENABLED` / `PAYSTACK_PLATFORM_APPROVED` flags are currently shared by subscription and tenant POS/storefront payment endpoints. Turning them on for subscriptions also unlocks those merchant checkout routes. Keep both flags false and the key blank in production until the merchant settlement model is resolved or the code has separate per-flow gates, in addition to provider approval and staging tests. The platform-approval flag is an operator gate, not a substitute for Paystack approval.

### Paystack staging and webhook prerequisites

The source provides sale/storefront checkout, platform-owned subscription plan creation, server-side transaction verification, cancellation requests and HMAC-checked webhook handling for charge/subscription/invoice events, but it has not been run against Paystack or MySQL here. Use an isolated test-mode staging deployment to verify each applicable flow and ensure a staging configuration cannot capture live transactions. Configure the dashboard webhook URL as `https://YOUR_API_HOST/api/v1/webhooks/paystack`, keep the secret key only in server environment variables, and verify signed `charge.success`, subscription and invoice events over HTTPS. The server verifies charge transactions before applying them; browser redirects are not proof of payment. Subscription tiers are seeded disabled and without prices or entitlement values. Review unmatched/mismatched payments manually; automated Paystack refunds and settlement reconciliation are not implemented.

## 3. File permissions and migration

Make `storage/` and `bootstrap/cache/` writable by the account's PHP-FPM user (usually 775 directories and 664 files, subject to the host's ownership model). Do not use 777. Keep local/private files under `storage/app/private`; this disk is not linked into the public web root.

After confirming the configured database is the production database and taking a backup:

```bash
php artisan migrate --force
php artisan db:seed --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

Before live data, rehearse on a staging copy. This source has not yet been migrated or executed in this workspace; review any migration/runtime errors before putting real data into it. Never run `migrate:fresh` against production.

## Platform administration and subscription setup

After migrations and seeders have completed, create a dedicated, email-verified platform administrator account and grant server-side access from cPanel Terminal:

```bash
php artisan platform:staff admin@example.com admin
# or grant a support-only account:
php artisan platform:staff support@example.com support
# revoke a platform account and close its support sessions:
php artisan platform:staff support@example.com --revoke
```

Do not use a tenant/workspace account as a support identity. Configure the disabled Starter, Business and Enterprise rows through the authenticated platform-admin API at `/api/v1/platform/subscription-plans/{code}` with a validated NGN amount in subunits, Paystack interval, feature keys and limits; then call `/api/v1/platform/subscription-plans/{code}/sync-paystack`. Live checkout stays blocked until the plan is active, fully configured, synced and Paystack credentials/operator gates are enabled. A tenant owner selects a tier for a seven-day cardless trial; the trial never auto-converts and requires explicit paid checkout. Failed recurring charges receive a seven-day application access grace period; no provider retry behavior is assumed.

Owner-approved support impersonation uses a per-request approval, owner email notice at request and activation, a scoped 60-minute token with no extension, owner revocation, blocked membership/role/billing/permanent-delete writes, and session-linked audit records. Confirm SMTP before enabling this workflow. The support request/approval/activation routes are documented in `README.md` and `docs/05-api-and-openapi.yaml`.

## 4. Queue and scheduler on shared hosting

This app uses a database queue for portability. The Laravel scheduler runs `expenses:generate-recurring` daily at 00:15, `exports:expire` daily at 01:30, and `storefront-orders:expire` every five minutes. Expiration verifies pending Paystack attempts before releasing stock; unresolved provider/reconciliation states—including ambiguous checkout-initialization outcomes—retain reservations. Do not manually release such stock or create another payment attempt until the original provider reference is resolved. Create the queue tables through migrations, then use cPanel **Cron Jobs** (every minute) to process a bounded batch and run the scheduler:

```cron
* * * * * cd /home/CPANEL_USER/kora-backend && /usr/local/bin/php artisan schedule:run >> /dev/null 2>&1
* * * * * cd /home/CPANEL_USER/kora-backend && /usr/local/bin/php artisan queue:work database --stop-when-empty --sleep=2 --tries=3 --timeout=90 >> /dev/null 2>&1
```

Replace `/usr/local/bin/php` with the actual PHP 8.3+ CLI binary shown by the host. Do not start a long-lived worker from a web request. For a shared plan without shell/cron access, do not enable background-dependent features; use a supported managed queue or change plans.

## 5. HTTPS, backups and health checks

- Force HTTPS at cPanel, set `SESSION_SECURE_COOKIE=true`, and test that Authorization headers reach PHP through Apache.
- Restrict database access to the application database/user; retain encrypted off-host backups and test a restore.
- Protect `/up` with host monitoring and inspect Laravel logs without exposing them publicly.
- Configure outbound SMTP, `PASSWORD_RESET_URL` and `INVITATION_ACCEPT_URL` (links must be handled by the web/universal/deep-link client); verify password-reset, invitation and signed email-verification flows. Invitation mail is synchronous: the API returns no token, and a failed send revokes the pending invitation so the inviter can safely issue a new one. Email verification mail is sent after registration commits; failed delivery does not delete the account, and the authenticated resend route supports recovery.
- Ensure `/storage` is not a public route for attachments; private downloads must pass tenant, branch and permission checks.
- Use staging and a rollback/backup plan for every migration. Add uptime, error-rate, disk-space, queue-age and database-capacity monitoring.

## Current application boundary

The backend has source for tenant identity and branch access, membership/role administration, catalogue and tax rates, customer ledger, inventory transfers, supplier purchase orders/receipts/payables, POS sales and returns/voids, manual and Paystack payment flows, expenses and recurring schedules, reports, audit logs and queued CSV exports. This source remains **unverified**: PHP/Composer/MySQL are unavailable in the authoring environment, and migrations, tests, queue/scheduler behavior, SMTP, provider staging and cPanel deployment have not been run. Storefront orders support collection and local delivery with merchant-defined flat-fee zones, encrypted delivery-address snapshots, nontaxable delivery charges in prepaid Paystack totals, a zero-COGS non-inventory sale line, manual sale-return refunds, and audited `paid → processing → dispatched → delivered` transitions. Zone coverage descriptions are not geocoded or validated service areas. Keep Paystack disabled until the provider/platform operating model and staging checks are complete. Subscription billing/entitlements and support impersonation now have source and migrations, but remain unverified: no PHP lint, Laravel tests, migrations, Paystack staging, email-notice delivery or cPanel deployment has run here. Plans are disabled/unpriced until a platform admin configures them. The selected policy is Starter/Business/Enterprise, seven-day cardless tier-selected trial with explicit paid checkout, seven-day overdue grace, and owner-approved support sessions capped at 60 minutes without extension. Support receives a scoped token; the owner is emailed; security/account/billing/permanent-delete writes are blocked; the owner can revoke. Automatic Paystack refunds, shipment tracking, broad accounting/tax workflows and a production web/mobile interface also remain outstanding. Feature tests have been authored but not executed. The existing browser prototype is not connected to this API. This document is a deployment checklist, not a production-readiness certification.
