# Technical Architecture — AnimFX Store

High-level and low-level design documentation for developers.

---

## 🎯 System Overview

```
┌─────────────────────────────────────────────────────────────────┐
│                        Client Browser                           │
└──────────────────────────┬──────────────────────────────────────┘
                           │ HTTPS
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Nginx / Apache (Static + Proxy)             │
└──────────────────────────┬──────────────────────────────────────┘
                           │ FastCGI / mod_php
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│                    PHP-FPM (CodeIgniter 4)                      │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│  │   Routing   │ │  Filters    │ │ Controllers │ │   Views   │ │
│  │  (Routes.php)│ │(Auth/Admin) │ │  (HTTP)     │ │  (Parser) │ │
│  └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └───────────┘ │
│         │               │               │                       │
│         ▼               ▼               ▼                       │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                    Libraries / Services                   │  │
│  │  ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐  │  │
│  │  │PaymentGateway│ │  (Future)    │ │   (Future)       │  │  │
│  │  │  (Paddle,    │ │  Email,      │ │   Storage,       │  │  │
│  │  │   Binance,   │ │  Queue,      │ │   Search,        │  │  │
│  │  │   PayPal)    │ │  Notification│ │   Analytics      │  │  │
│  │  └──────────────┘ └──────────────┘ └──────────────────┘  │  │
│  └──────────────────────────────────────────────────────────┘  │
│         │               │               │                       │
│         ▼               ▼               ▼                       │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                       Models                               │  │
│  │  User, Product, Order, OrderItem, Download, Setting, ...  │  │
│  └──────────────────────────────────────────────────────────┘  │
│         │                                                       │
│         ▼                                                       │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │                    Database Layer                          │  │
│  │  Query Builder → PDO → MySQL / SQLite                     │  │
│  └──────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │  Paddle  │ │ Binance  │ │  PayPal  │
        │  API     │ │  Pay API │ │  API v2  │
        └──────────┘ └──────────┘ └──────────┘
```

---

## 🗄 Database Schema (ERD)

```
users
├── id (PK)
├── name
├── email (UNIQUE)
├── password (bcrypt)
├── role (ENUM: 'user', 'admin')
├── is_active (BOOL)
├── avatar (nullable)
├── created_at
└── updated_at

categories
├── id (PK)
├── name
├── slug (UNIQUE)
├── icon
├── description
├── sort_order
├── is_active
├── created_at
└── updated_at

products
├── id (PK)
├── category_id (FK → categories.id)
├── name
├── slug (UNIQUE)
├── description
├── price (DECIMAL 10,2)
├── preview_image
├── preview_gif
├── file_path
├── file_type (SVGA/LOTTIE/GIF/WEBM)
├── file_size
├── tags (JSON)
├── downloads (counter)
├── is_featured
├── is_active
├── paddle_price_id (nullable, FK to Paddle catalog)
├── created_at
└── updated_at

orders
├── id (PK)
├── user_id (FK → users.id)
├── order_code (UNIQUE, e.g. INV-AB12CD)
├── total_amount (DECIMAL 10,2)
├── status (ENUM: 'pending', 'paid', 'cancelled', 'rejected')
├── payment_method (ENUM: 'bank_transfer', 'paddle', 'binance', 'paypal')
├── payment_proof (filename, nullable)
├── payment_txn_id (provider transaction ID, nullable)
├── notes (nullable)
├── admin_notes (nullable)
├── confirmed_at (nullable)
├── created_at
└── updated_at

order_items
├── id (PK)
├── order_id (FK → orders.id)
├── product_id (FK → products.id)
├── product_name (denormalized for history)
├── price (DECIMAL 10,2)
└── created_at

downloads
├── id (PK)
├── user_id (FK → users.id)
├── product_id (FK → products.id)
├── order_id (FK → orders.id, nullable for free)
├── downloaded_at
└── UNIQUE(user_id, product_id)  -- one download per user per product

settings
├── id (PK)
├── key (UNIQUE)
├── value (TEXT)
└── updated_at

banners
├── id (PK)
├── title
├── subtitle
├── image
├── link
├── sort_order
├── is_active
├── created_at
└── updated_at

migrations (CI4 internal)
├── id (PK)
├── version
├── class
├── group
├── namespace
├── time
└── batch
```

---

## 🔄 Request Lifecycle

### 1. Public Route (e.g., `GET /products`)
```
Request
  → Routes.php (match)
  → AuthFilter (skip for public)
  → Products::index()
      → ProductModel->getActiveWithCategory()
      → View: products/index.php
  → Response (HTML)
```

### 2. Authenticated Route (e.g., `POST /orders/place`)
```
Request
  → Routes.php (match)
  → AuthFilter (redirect to /login if no session)
  → Orders::place()
      → Validate CSRF + input
      → OrderModel->insert()
      → OrderItemModel->insertBatch()
      → PaymentGateway->paddleCheckoutUrl() / binanceCheckoutUrl() / paypalCheckoutUrl()
      → Redirect to provider checkout URL
  → Response (302 Redirect)
```

### 3. Webhook (e.g., `POST /payment/webhook/paypal`)
```
Request (from PayPal servers)
  → Routes.php (match, no filter)
  → Payment::webhookPaypal()
      → PaymentGateway->handlePaypalWebhook()
          → Verify signature (verify-webhook-signature API)
          → Parse event (PAYMENT.CAPTURE.COMPLETED)
          → confirmPaypalCapture() → OrderModel->update(status=paid)
          → DownloadModel->record() for each item
  → Response (200 OK / 400 Bad Request)
```

---

## 💳 Payment Gateway Architecture

### PaymentGateway Class (Single Provider-Agnostic Service)

```php
class PaymentGateway {
    // Constants
    const MANUAL  = 'bank_transfer';
    const PADDLE  = 'paddle';
    const BINANCE = 'binance';
    const PAYPAL  = 'paypal';

    // Availability
    public function paddleEnabled(array $settings): bool
    public function binanceEnabled(array $settings): bool
    public function paypalEnabled(array $settings): bool

    // Paddle
    public function paddleEnsurePrice(array $settings, array $product): ?string
    public function paddleCheckoutUrl(array $settings, array $orderData): array
    public function handlePaddleWebhook(IncomingRequest $request, array $settings): bool

    // Binance
    public function binanceCheckoutUrl(array $settings, string $orderCode, float $amount): string
    public function handleBinanceWebhook(IncomingRequest $request, array $settings): bool

    // PayPal
    public function paypalCheckoutUrl(array $settings, array $orderData): array
    public function paypalCapture(array $settings, string $paypalOrderId): array
    public function confirmPaypalCapture(array $settings, array $order, float $amount, string $currency, string $txnId): bool
    public function handlePaypalWebhook(IncomingRequest $request, array $settings): bool

    // Shared
    public static function markOrderPaid(int $orderId, string $txnId = ''): bool
```

### Design Principles
1. **Provider-agnostic order flow** — `Orders::place()` calls the same interface for all gateways
2. **Idempotent confirmation** — `markOrderPaid()` checks `status !== 'paid'` before updating
3. **Single validation point** — `confirmPaypalCapture()` used by both return URL and webhook
4. **Settings-driven** — All credentials in DB (`settings` table), not code
5. **Sandbox support** — Each gateway has `*_sandbox` setting for test mode

### Webhook Security

| Provider | Verification Method |
|----------|---------------------|
| **Paddle** | Ed25519 signature over raw JSON body. Public key stored in `paddle_public_key` setting. Uses `paragonie/sodium_compat` for cross-platform compatibility. |
| **Binance Pay** | HMAC-SHA256 over `timestamp\nnonce\nbody\n`. Secret in `binance_api_secret`. Validates `BinancePay-Certificate-SN` matches `binance_api_key`. |
| **PayPal** | Calls PayPal `verify-webhook-signature` API with transmission headers (`PAYPAL-AUTH-ALGO`, `PAYPAL-CERT-URL`, `PAYPAL-TRANSMISSION-ID`, `PAYPAL-TRANSMISSION-SIG`, `PAYPAL-TRANSMISSION-TIME`) + webhook event JSON. Requires `paypal_webhook_id` setting. Cert URL host must be `*.paypal.com`. |

---

## 🔐 Authentication & Authorization

### Session-Based (CodeIgniter Native)
- **Driver**: File (`writable/session/`)
- **Cookie**: `ci_session` (HttpOnly, SameSite=Lax, Secure in prod)
- **Data stored**: `user_id`, `user_name`, `user_email`, `user_role`, `cart`

### Filters
| Filter | Applied To | Logic |
|--------|------------|-------|
| `auth` | `/cart/*`, `/orders/*`, `/downloads/*`, `/account/*` | Redirect to `/login` if no `user_id` in session |
| `admin` | `/admin/*` | Redirect to `/` if `session('user_role') !== 'admin'` |

### Role-Based Access
| Role | Permissions |
|------|-------------|
| `user` | Browse, cart, checkout, own orders, downloads, profile |
| `admin` | All user permissions + `/admin/*` (dashboard, products, orders, users, settings, banners, categories) |

---

## 📁 Key Directories

| Path | Purpose |
|------|---------|
| `app/Config/` | Routes, Database, Filters, Validation, Services, Boot |
| `app/Controllers/` | HTTP handlers (Auth, Cart, Orders, Payment, Products, Admin/*) |
| `app/Libraries/` | Reusable services (`PaymentGateway`) |
| `app/Models/` | Data layer (User, Product, Order, etc.) |
| `app/Views/` | Templates (CI4 parser, `esc()` by default) |
| `app/Database/Migrations/` | Schema versioning |
| `app/Database/Seeds/` | Reference data |
| `public/` | Document root (`index.php`, `.htaccess`, assets) |
| `writable/` | Runtime (cache, logs, session, uploads, SQLite) |
| `tests/manual/` | E2E integration tests (run against live dev server) |

---

## 🌐 Configuration System

### Priority (highest wins)
1. **Environment variables** (server-level, Docker, `.env`)
2. **`.env` file** (project root)
3. **Config classes** (`app/Config/*.php`)
4. **Framework defaults**

### Key Config Classes
| Class | Purpose |
|-------|---------|
| `App` | Base URL, CSP, CSRF, locale, timezone |
| `Database` | Connections (default, tests) |
| `Routes` | All route definitions |
| `Filters` | Filter aliases + route mappings |
| `Validation` | Custom rules |
| `Services` | Service container bindings |

### Settings Table (Runtime Config)
- **Table**: `settings` (key-value)
- **Model**: `SettingModel` → `get()`, `setSetting()`, `getAll()`
- **Loaded**: In `BaseController::initController()` → `$this->settings`
- **Editable**: `/admin/settings` (admin only)
- **Keys**: `store_*`, `bank_*`, `paddle_*`, `binance_*`, `paypal_*`, `maintenance_mode`, `allow_registration`, etc.

---

## 🧪 Testing Strategy

### Manual E2E Tests (`tests/manual/`)
- **Purpose**: Full integration test against running dev server
- **Coverage**: Checkout flow, webhook verification, capture validation, idempotency
- **Run**: `php tests/manual/_paddle_e2e_test.php` (requires `php spark serve`)

### Why Not PHPUnit?
- Payment gateways require live HTTP endpoints (webhooks, redirects)
- SQLite file locking issues in parallel tests
- Manual scripts are faster to write and debug for this use case

### Adding Tests
1. Copy `_paddle_e2e_test.php` as template
2. Implement provider-specific flow
3. Use `check()` helper for assertions
4. Clean up test data in `finally` block

---

## 📦 Deployment Architecture

### Shared Hosting
```
public_html/
├── .htaccess (rewrite to public/)
├── public/ (document root)
│   ├── index.php
│   └── assets/
├── app/
├── vendor/
├── writable/
└── .env
```

### VPS (Nginx + PHP-FPM)
```
Request → Nginx (80/443) → PHP-FPM (unix socket) → CI4 → MySQL
                │
                ├─ Static files (direct)
                └─ SSL termination (Let's Encrypt)
```

### Docker
```
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Nginx     │────▶│  PHP-FPM    │────▶│  MariaDB    │
│  (Port 80/  │     │  (Port 9000)│     │  (Port 3306)│
│   443)      │     │             │     │             │
└─────────────┘     └─────────────┘     └─────────────┘
       │                   │                    │
       ▼                   ▼                    ▼
  Static files       App code              Persistent
  + SSL termination  + vendor/             volume
```

---

## 🔧 Extensibility Points

### Adding New Payment Gateway
1. Add constant + `*Enabled()` method in `PaymentGateway`
2. Implement `*CheckoutUrl()` returning `{ok, url, error?}`
3. Implement `handle*Webhook()` returning `bool`
4. Add `confirm*Capture()` for shared validation
5. Register webhook route in `Routes.php`
6. Add admin settings + view option

### Adding Admin Module (e.g., Reports)
1. `php spark make:controller Admin/Reports`
2. Add routes in `Routes.php` (admin group)
3. Add sidebar link in `app/Views/layouts/admin.php`
4. Create views in `app/Views/admin/reports/`
5. Add model if new table needed

### Adding Customer-Facing Feature
1. Controller in `app/Controllers/` (not `Admin/`)
2. Routes in public section of `Routes.php`
3. Views in `app/Views/` (not `admin/`)
4. Apply `auth` filter if login required

---

## 📊 Performance Considerations

| Area | Optimization |
|------|--------------|
| **Database** | Indexes on `orders.user_id`, `orders.order_code`, `downloads(user_id, product_id)`, `products.category_id` |
| **Queries** | `ProductModel->getActiveWithCategory()` uses join, not N+1 |
| **Session** | File driver (fast enough for low-mid traffic); Redis for scale |
| **Cache** | `php spark optimize` caches config + routes; view parser caches parsed templates |
| **Assets** | Nginx serves static directly; gzip + long expiry for hashed assets |
| **Webhooks** | Respond 200 within 5s; heavy processing (download grants) done inline but fast |

---

## 🚀 Scaling Path

| Trigger | Action |
|---------|--------|
| > 100 req/s | Move session to Redis (`app.sessionDriver = 'redis'`) |
| > 1000 orders/day | Read replica for reporting queries |
| Webhook latency | Queue webhook processing (Redis + worker) |
| Large file downloads | CDN (Cloudflare R2 / S3) + signed URLs |
| Multi-region | Database replication + DNS failover |

---

## 📚 Related Docs

- **README.md** — Quick start, features, config
- **DEPLOYMENT.md** — Hosting-specific guides
- **CONTRIBUTING.md** — Code standards, workflow
- **Inline PHPDoc** — All public methods in `PaymentGateway`, Models, Controllers

---

*Architecture version: 1.0 (2026-09-27)*