# CLAUDE.md

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

## Project status: greenfield (not yet scaffolded)

As of 2026-05-21 this repo contains **only the design spec** — no Symfony application code exists yet. The authoritative design is:

- [docs/superpowers/specs/2026-05-21-cartecadeau-design.md](docs/superpowers/specs/2026-05-21-cartecadeau-design.md)

Read it before doing any work. The sections below summarize the parts that require cross-file understanding; the spec is the source of truth if they ever diverge.

The project owner communicates in **French** — respond in French.

## What this is

CARTECADEAU is a **Symfony** e-commerce site that resells gift cards (vouchers) and game top-ups. It sources products from the **Wupex** supplier API and collects customer payments via **CinetPay** (Mobile Money). Runs on WAMP (Windows / MySQL).

Two external integrations drive almost everything:
- **Wupex** (supplier) — Sandbox base URL `https://sandbox-service.wupex.com`. Auth via `x-api-key` header. All calls go through a single `WupexClient` service.
- **CinetPay** (payment) — Mobile Money. All calls go through a single `CinetPayClient` service.

## The one invariant that shapes the architecture

**A paid order must never be left undelivered or untraceable.** The customer pays *before* Wupex is called, so fulfillment must survive Wupex being slow/down at that moment. Consequences that any change must preserve:

1. **Fulfillment is asynchronous** (Symfony Messenger, Doctrine transport — no external broker). Payment confirmation dispatches a `FulfillOrder` message; the handler calls Wupex with retries.
2. **Idempotency:** `Order.reference` is unique and is reused as Wupex's `referenceId`. An order already `fulfilling`/`fulfilled` must never trigger a second Wupex purchase. The stored Wupex `orderName` is the dedup anchor.
3. **Never trust the browser return from CinetPay.** Payment is confirmed only via the server-side **webhook** + a `verifyPayment()` re-check. Only then does the order become `paid` and fulfillment dispatch.
4. Any `paid` order that fails fulfillment lands in `fulfillment_failed`, stays visible in admin, and is **manually relaunchable**.

Order state machine: `cart → pending_payment → paid → fulfilling → fulfilled`, with error branches `payment_failed` and `fulfillment_failed`.

## Domain rules that aren't obvious from the code

- **Currency:** Wupex prices are in **USD**; CinetPay charges in **XOF (FCFA)**. Sale price = `purchasePrice_USD × USD_XOF_RATE × (1 + MARGIN_PERCENT)`. Both `USD_XOF_RATE` and `MARGIN_PERCENT` are configurable (entered manually in v1 — no FX API).
- **Top-up `subType`** (Wupex product field) determines which account inputs to collect at checkout:
  - `1` = userId · `2` = userId + serverId (`POST /api/order/topup-listservers`) · `3` = userId + zoneId · `4` = userId + serverId + zoneId · `5` = attributes (`GET /api/order/topup/product/attribute`).
- **Catalog is synced into the local DB** (`Product` entities), not proxied live from Wupex on each page.
- Voucher `serialCode` is stored **encrypted at rest**.

## Wupex endpoint map (all via `WupexClient`)

| Purpose | Method / Endpoint |
|---|---|
| List products | `POST /api/product/merchant/invited/list` |
| Buy vouchers | `POST /api/order/pull-codes` (`?referenceId=&allowTakeAll=`) |
| Buy top-up | `POST /api/order/topup` |
| Top-up servers | `POST /api/order/topup-listservers?sku=` |
| Top-up attributes | `GET /api/order/topup/product/attribute?productId=` |
| Order list | `POST /api/order/list` |
| Order detail (serials/PIN) | `GET /api/order/detail?orderName=` |
| Customer balance | `GET /api/customer/balance` |

## Configuration & secrets

All secrets live in `.env.local` only (git-ignored), never committed:
`WUPEX_API_URL`, `WUPEX_API_KEY`, `WUPEX_MERCHANT_CODE`, `WUPEX_LANGUAGE`, CinetPay `apikey` + `site_id`.

> The Wupex API key was shared in plaintext during design — treat it as **exposed** and regenerate it in the Wupex portal before production.

## Commands (apply once the Symfony app is scaffolded)

The app does not exist yet, so these will only work after the initial scaffold (lot 1 of the plan):

```bash
composer install                                  # install dependencies
php bin/console doctrine:migrations:migrate        # apply DB migrations
symfony serve            # or configure the WAMP vhost to public/
php bin/console messenger:consume async -vv        # run the fulfillment worker (required for delivery)

# Tests
php bin/phpunit                                    # full suite
php bin/phpunit --filter testName path/to/Test.php # a single test
```

Implementation proceeds in lots (see spec §14): scaffold+auth → WupexClient+catalog → cart+checkout → CinetPay+webhook → async fulfillment+delivery → admin.
