# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Critical context — read first

- **This is a live production site** (kapitano.shop — a Syrian e-commerce marketplace, Arabic-first, prices in Syrian Pounds/SYP). Never run destructive artisan commands (`migrate:fresh`, `db:wipe`, seeding) or anything that mutates production data.
- **Origin: CodeCanyon (Active eCommerce CMS).** The codebase has many pre-existing bugs and almost **no reusable code — the same logic is copy-pasted in multiple places**. A fix in one file usually does NOT fix the whole project. Before declaring any fix complete, grep for sibling copies of the same logic across:
  - `app/Http/Controllers/` (web/customer)
  - `app/Http/Controllers/Admin/`
  - `app/Http/Controllers/Api/V2/` (mobile apps)
  - `app/Http/Helpers.php`
  - Blade views under `resources/views/frontend/`, `backend/`, `seller/`
  - Example: a pricing fix must be checked in `Helpers.php`, `CartController`, `Api/V2/CartController`, `CheckoutController`, and the checkout/cart views.
- `kapitano.json` contains Firebase credentials — never paste its contents into code, commits, or chat output.
- The `create-laravel-feature` skill prescribes a repository+service architecture that this codebase does not use. Do not impose it here — match the existing (fat-controller + global-helper) style unless the user explicitly asks for a refactor.

## Commands

```bash
composer install
php artisan serve
```

Frontend assets (old laravel-mix / webpack, Bootstrap 4 + jQuery + Vue 2):

```bash
npm run dev      # one-off build
npm run watch    # rebuild on change
npm run prod     # production build
```

Tests (safe to run anywhere: `phpunit.xml` forces `APP_ENV=testing` with in-memory SQLite, so they never touch the real database):

```bash
vendor/bin/phpunit
vendor/bin/phpunit --filter TestName
```

The suite is small (`tests/Feature/` — checkout OTP verification and a null-regression test that builds its own minimal schema). There is no linter/formatter configured.

CLI PHP on this dev machine works (PHP 8.4 with `mbstring`, `curl`, `pdo_mysql`), so `vendor/bin/phpunit` and `php artisan tinker <script.php>` run fine. The local MySQL database `kapitano` is a stale copy of production (useful for realistic data, but recent production rows are absent — check the newest `created_at` before drawing conclusions from it).

## Architecture

Laravel 10, PHP 8.2. No repository/service abstraction as the primary pattern — most logic lives in controllers, Blade views, and global helpers.

### `app/Http/Helpers.php` is the architectural center

~215 global functions autoloaded via composer `files`. Almost everything routes through it:

- **Pricing**: `convert_price()`, `round_price()` / `round_system_price()` (rounds to nearest 500 SYP — recent bug fixes were exactly here), `format_price()`, `cart_product_price()`, `home_discounted_price()`, `cart_product_tax()`, coupon/discount helpers. Pricing bugs almost always involve these plus their duplicated callers.
- **Settings**: `get_setting()` reads the `business_settings` table (cached).
- **i18n**: `translate()` for UI strings.
- **Assets**: `uploaded_asset()` / `my_asset()` — files are referenced by upload ID, not path.

### Caching gotchas

Heavy use of long-lived caches: `Cache::rememberForever('verified_sellers_id')`, 86400s caches for products per category, `system_default_currency`, business settings. If a change to settings/products/currency "doesn't work", the cache is usually why — `php artisan cache:clear` (be deliberate on production).

### Routes are split by domain — 23 files in `routes/`

`web.php` (customer storefront), `admin.php`, `seller.php`, `api.php` + `api_seller.php` (mobile), plus feature files: `auction.php`, `pos.php`, `preorder.php`, `wholesale.php`, `delivery_boy.php`, `affiliate.php`, `club_points.php`, `otp.php`, `refund_request.php`, and per-gateway payment routes. When touching a feature, check whether it also has its own route file.

### API for mobile apps

`app/Http/Controllers/Api/V2/` mirrors much of the web controllers (its own `CartController`, `CheckoutController`, etc.) — the duplication warning applies most strongly here. Auth is Sanctum.

### Translations (i18n)

Models use a `*Translation` companion-model pattern (`ProductTranslation`, `CategoryTranslation`, …) with a `getTranslation('field', $lang)` method that falls back to the base model attribute. Store is Arabic-first; `translate()` handles UI strings.

### Database schema changes

Both `database/migrations/` and `sqlupdates/` (raw versioned SQL files, `v15.sql`…`v23.sql`, shipped by the CMS vendor) exist. Check both when reasoning about schema history; don't assume migrations tell the whole story.

### Other notable pieces

- `app/Services/` and `app/Utility/` both exist with overlapping roles (e.g. `OrderService` vs `CartUtility`, `SendSmsService` vs `SendSMSUtility`) — check both before adding new logic.
- Payments: many gateways bundled by the CMS, but the store actually uses **Cash on Delivery and Paymera** (`PaymeraService`, `PaymeraController`); OTP sign-in via phone/WhatsApp (`OtpService`, `app/Services/OTP/`).

### Paymera gateway (verified live 2026-08-30)

- API: `POST /api/create-payment`, `GET /api/get-payment-status/{paymentId}`, `POST /api/cancel-payment` on `config('paymera.base_url')` with HTTP basic auth. Responses are `{ErrorCode, ErrorMessage, Data}`; `ErrorCode == 0` is success. Payment status is `Data.status`: `A` accepted, `P` pending, `C` cancelled, `F` failed.
- **Amount unit**: the amount sent to Paymera is `grand_total (USD) × SYP exchange_rate` (the `currencies` table row for SYP, post-redenomination rate ≈ 138) — i.e. plain new Syrian Pounds. Do **NOT** divide by 100: real accepted payments (e.g. $32 order → 4416 SYP, status A) confirm the undivided amount is correct. A `÷100` existed briefly in the old web `rePayment` and was a bug.
- `App\Utility\PaymeraUtility::syncPaymentStatus()` is the single mark-paid path shared by web (`PaymeraController`) and mobile (`Api/V2/CheckoutController`). Both a browser `callback` (GET) and a server-to-server `trigger` (POST) hit it; trigger URLs must stay in `VerifyCsrfToken::$except`.
- Payment creation stores `payment_id` on `combined_orders`; syncing marks every `orders` row of that combined order paid. Beware: many `combined_orders` rows carry a `payment_id` but have **no `orders` rows** — paying those updates nothing.
- `performance-baselines/` holds Lighthouse configs/results used for performance work.
