# Cheap Match Tickets

Single-vendor football ticket commerce application built with Laravel 13, PHP 8.3+, Blade, Tailwind CSS 4, Alpine.js, Vite, MySQL, queues, notifications, policies, private storage, and PHPUnit.

The product is deliberately positioned as an independent ticket service. It does not contain seller listings, marketplace flows, official tournament marks, or automatic payment-success logic.

## Core behavior

- Administrator-reviewed 104-match tournament structure, 48 teams, 16 host cities, and configurable stadium names.
- CSV/JSON fixture preview, validation, confirmation, error records, and rollback.
- Administrator-only inventory creation and verification with complete movement history.
- Server-priced one-match cart, row-locked checkout, timed reservations, and overselling prevention.
- Order numbers in `CMT-YYYY-000001` format and signed pending-payment links.
- HTTPS live-chat allowlist, safe prefilled messages, delayed redirect, and redirect audit logs.
- Manual payment submission, private proof storage, partial/full approval, rejection, and idempotency fields.
- Reserved inventory becomes sold only after full administrator-approved payment.
- Ticket assignment and mailbox-delivery transfer tracking without external account passwords or public QR codes.
- Customer orders, support requests, refund requests, administrator dashboards, legal content, SEO metadata, sitemap, CSP, role permissions, audit/security tables, queued notifications, and scheduled maintenance commands.

## Requirements

- PHP 8.3 or newer with `bcmath`, `curl`, `fileinfo`, `intl`, `mbstring`, `openssl`, `pdo_mysql`, `sodium`, and `zip`
- MySQL 8+ or MariaDB 10.6+
- Composer 2.8+
- Node.js 22+ and NPM 10+
- A process supervisor for queue workers and the scheduler

## Local installation

```bash
composer install
copy .env.example .env
php artisan key:generate
```

Set local database credentials in `.env`, then run:

```bash
php artisan migrate --seed
npm install
npm run build
php artisan storage:link
php artisan test
```

For a quick local SQLite setup, set `DB_CONNECTION=sqlite`, create `database/database.sqlite`, and remove the MySQL variables.

## Development accounts

Seeded accounts use `DEMO_ADMIN_PASSWORD`. If it is empty, the development-only fallback is `ChangeMe!2026`.

| Role | Email |
|---|---|
| Super administrator | `admin@cheapmatchtickets.test` |
| Inventory manager | `inventory@cheapmatchtickets.test` |
| Order manager | `orders@cheapmatchtickets.test` |
| Customer support | `support@cheapmatchtickets.test` |
| Customer | `customer@cheapmatchtickets.test` |

Never seed these accounts in production without setting a unique secret and restricting access.

## Fixture imports

The seeder provides a complete structural 104-match dataset for development. Before production sales, replace it with an administrator-reviewed current schedule import. The application never scrapes during customer requests.

Required CSV headers or JSON keys:

```text
match_number,stage,group_code,home_team_code,away_team_code,home_placeholder,away_placeholder,kickoff_at_utc,stadium_slug,status,featured,sales_enabled,sales_close_at
```

Use ISO-8601 UTC timestamps. Team codes and stadium slugs must already exist.

```bash
php artisan matches:import storage/app/private/imports/fixtures/schedule.csv --source="Reviewed schedule 2026-06-13"
php artisan matches:update-status
php artisan matches:close-sales
php artisan reservations:expire
```

## Queue setup

All customer email notifications implement `ShouldQueue`. Production must run a worker:

```bash
php artisan queue:work --sleep=3 --tries=3 --timeout=120 --max-time=3600
```

Configure Supervisor or systemd to restart the worker. After deployment run `php artisan queue:restart`.

## Scheduler setup

Run Laravel's scheduler every minute:

```cron
* * * * * cd /var/www/cheapmatchtickets && php artisan schedule:run >> /dev/null 2>&1
```

The schedule expires reservations every minute and updates sales/match statuses every five minutes.

## Private storage

Payment proofs, support attachments, refund evidence, imports, and exports use the non-public `private` disk at `storage/app/private`. Do not expose that directory through the web server. Public branding assets belong on the public disk; sensitive ticket files do not.

## Production deployment

1. Point the web server document root at `public/` and enforce HTTPS.
2. Set `APP_ENV=production`, `APP_DEBUG=false`, the canonical `APP_URL`, encrypted sessions, unique database credentials, and real mail/queue configuration.
3. Run `composer install --no-dev --optimize-autoloader` and `npm ci && npm run build`.
4. Run `php artisan migrate --force`.
5. Run `php artisan config:cache`, `php artisan event:cache`, `php artisan route:cache`, and `php artisan view:cache`.
6. Start queue workers and install the scheduler cron entry.
7. Configure log rotation, uptime monitoring, failed-job alerts, database backups, and TLS renewal.
8. Review live-chat domains, payment methods, support details, legal content, fixture data, inventory, and all seeded users before enabling sales.

## Backups

Back up the database and private storage together so records and files remain consistent.

```bash
mysqldump --single-transaction --routines --triggers cheap_match_tickets | gzip > cmt-$(date +%F-%H%M).sql.gz
tar -czf cmt-private-$(date +%F-%H%M).tar.gz storage/app/private
```

Encrypt backups, keep off-site copies, define retention, and test restoration regularly. Never commit backups or `.env` files.

## Security checklist

- Rotate `APP_KEY` only with a migration plan for encrypted data.
- Use least-privilege database and administrator accounts; require 2FA for administrators.
- Keep live-chat domains allowlisted and use HTTPS only.
- Keep payment instructions inside authorized channels and verify payments independently.
- Configure a real malware scanner behind the file-scanning interface before accepting production uploads.
- Verify gateway webhook signatures and idempotency keys before enabling automated gateways.
- Review CSP additions before adding third-party scripts.
- Keep PHP, Composer, NPM dependencies, the OS, and the database patched.
- Monitor login failures, administrator activity, payment changes, inventory movement, and live-chat redirects.
- Never store ticket-account passwords, public working barcodes, QR codes, or production credentials in source control.

## Verification commands

```bash
php artisan migrate:fresh --seed
php artisan test
vendor/bin/pint --test
npm run build
php artisan route:cache
```
