openapi: 3.1.0
info:
  title: Viva Cloud Terminal SDK — Référence PHP
  version: 1.0.0
  description: |
    Documentation de référence du SDK PHP `qrcommunication/viva-cloud-terminal-sdk`.

    Ce document décrit les **classes**, **méthodes**, **paramètres** et **types de retour**
    du SDK — pas les endpoints HTTP sous-jacents.

    Le SDK pilote la **Cloud Terminal API marchand** (`/ecr/v1/`) de Viva Wallet :
    vente, capture de pré-autorisation, remboursements, rebate, sessions et polling.

    ## Installation

    ```bash
    composer require qrcommunication/viva-cloud-terminal-sdk
    ```

    ## Initialisation

    ```php
    use QrCommunication\VivaCloudTerminal\VivaCloudTerminalClient;

    $viva = new VivaCloudTerminalClient(
        clientId: 'your-client-id',
        clientSecret: 'your-client-secret',
        environment: 'demo', // ou 'production'
    );
    ```

    ## Ressources disponibles

    | Propriété | Classe | Description |
    |-----------|--------|-------------|
    | `$viva->devices` | `Devices` | Recherche des terminaux POS et statut |
    | `$viva->transactions` | `Transactions` | Vente, capture preauth, remboursements, rebate, actions |
    | `$viva->sessions` | `Sessions` | Récupération, listing, abort et polling des sessions |

    ## Montants

    Tous les montants sont en **centimes** (`int`). Exemple : `1170` = 11,70 EUR.
    `currencyCode` est le code ISO 4217 numérique en **string** (`'978'` = EUR).

    ## Authentification

    OAuth2 `client_credentials` → Bearer token. Le token est récupéré sur
    `accounts.vivapayments.com/connect/token` et utilisé contre
    `api.vivapayments.com/ecr/v1/...` (hôtes demo en sandbox). Tous les payloads
    `/ecr/v1/` sont en **camelCase**.

    ## Body vide sur succès

    La plupart des endpoints `/ecr/v1/transactions:*` répondent HTTP 200 sans body.
    Les méthodes `transactions` retournent `array{session_id, response}` : pollez la
    session via `sessions->get()` / `pollUntilComplete()` pour le résultat réel.

    ## Gestion d'erreurs

    | Exception | Quand |
    |-----------|-------|
    | `AuthenticationException` | Échec OAuth2 (identifiants invalides, 401) |
    | `ApiException` | Erreur HTTP retournée par l'API Viva (4xx, 5xx) |
    | `VivaException` | Classe de base pour toutes les exceptions SDK |
  contact:
    name: QrCommunication
    email: contact@qrcommunication.com
    url: https://qrcommunication.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

tags:
  - name: VivaCloudTerminalClient
    description: |
      Point d'entrée du SDK — instanciation, gestion du token, polling raccourci.

      ```php
      use QrCommunication\VivaCloudTerminal\VivaCloudTerminalClient;
      ```
  - name: Devices
    description: |
      `$viva->devices` — Découverte des terminaux POS (EFT POS) et de leur statut.

      ```php
      $devices = $viva->devices->search(statusId: 1);
      ```
  - name: Transactions
    description: |
      `$viva->transactions` — Vente, capture de pré-autorisation, remboursements,
      rebate et actions sur le terminal.

      ```php
      $sale = $viva->transactions->sale(terminalId: '16000010', amount: 1170, cashRegisterId: 'CR-01');
      ```
  - name: Sessions
    description: |
      `$viva->sessions` — Récupération, listing, abort et polling des sessions.

      ```php
      $result = $viva->sessions->pollUntilComplete($sale['session_id']);
      ```
  - name: Enums
    description: |
      Enums PHP fournis par le SDK.

      - `Environment` — `demo` | `production`
      - `EcrEventId` — IDs d'événement de session (0, 1003, 1006, 1016, 1020, 1099, 1100, 6000)
  - name: Exceptions
    description: |
      Hiérarchie d'exceptions du SDK.

      ```
      RuntimeException
      └── VivaException
          ├── ApiException
          └── AuthenticationException
      ```

paths:
  # ============================================================
  # VivaCloudTerminalClient
  # ============================================================
  /VivaCloudTerminalClient/__construct:
    post:
      tags: [VivaCloudTerminalClient]
      operationId: VivaCloudTerminalClient__construct
      summary: new VivaCloudTerminalClient(...)
      description: |
        Instancie le client SDK. L'authentification OAuth2 est automatique au premier appel API.

        ```php
        use QrCommunication\VivaCloudTerminal\VivaCloudTerminalClient;

        $viva = new VivaCloudTerminalClient(
            clientId: 'your-client-id',
            clientSecret: 'your-client-secret',
            environment: 'demo', // string ou Environment enum
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClientConstructorParams'
      responses:
        '200':
          description: Instance `VivaCloudTerminalClient` créée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClientInstance'

  /VivaCloudTerminalClient/invalidateToken:
    post:
      tags: [VivaCloudTerminalClient]
      operationId: VivaCloudTerminalClient_invalidateToken
      summary: "$viva->invalidateToken()"
      description: |
        Supprime le token OAuth caché, forçant un nouveau handshake au prochain appel.

        ```php
        $viva->invalidateToken();
        ```
      responses:
        '200':
          description: "`void` — Le token est invalidé"

  /VivaCloudTerminalClient/pollUntilComplete:
    get:
      tags: [VivaCloudTerminalClient]
      operationId: VivaCloudTerminalClient_pollUntilComplete
      summary: "$viva->pollUntilComplete()"
      description: |
        Raccourci vers `Sessions::pollUntilComplete()`. Poll une session jusqu'à
        un état terminal ou expiration du timeout.

        ```php
        $result = $viva->pollUntilComplete($sale['session_id'], timeoutSeconds: 90);
        ```
      parameters:
        - name: sessionId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: UUID de la session à poller
        - name: timeoutSeconds
          in: query
          required: false
          schema:
            type: integer
            default: 120
          description: Temps d'attente total maximum (secondes)
        - name: intervalMs
          in: query
          required: false
          schema:
            type: integer
            default: 3000
          description: Délai entre deux tentatives (millisecondes)
      responses:
        '200':
          description: État final de la session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'

  /VivaCloudTerminalClient/getConfig:
    get:
      tags: [VivaCloudTerminalClient]
      operationId: VivaCloudTerminalClient_getConfig
      summary: "$viva->getConfig()"
      description: |
        Retourne l'objet `CloudTerminalConfig` (hôtes, environnement).

        ```php
        $config = $viva->getConfig();
        echo $config->apiUrl();      // 'https://demo-api.vivapayments.com'
        echo $config->accountsUrl(); // 'https://demo-accounts.vivapayments.com'
        ```
      responses:
        '200':
          description: Objet `CloudTerminalConfig`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Config'

  # ============================================================
  # Devices
  # ============================================================
  /Devices/search:
    post:
      tags: [Devices]
      operationId: Devices_search
      summary: "$viva->devices->search()"
      description: |
        Recherche les terminaux POS, filtrage optionnel par statut et/ou source code.

        Endpoint : `POST /ecr/v1/devices:search`. Recommandé en pré-flight avant
        toute transaction (confirmer que le terminal est `Live`, `statusId` 1).

        Valeurs `statusId` : 0 = WareHouse, 1 = Live, 2 = Ready To Ship,
        3 = In Stock, 4 = Pending Key Injection, 5 = Lost, 6 = Broken.

        ```php
        $devices = $viva->devices->search(statusId: 1);

        foreach ($devices as $device) {
            echo $device['terminalId'] . ' — statut ' . $device['statusId'] . "\n";
        }
        ```
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                statusId:
                  type: integer
                  nullable: true
                  description: Filtrer par statut du terminal
                sourceCode:
                  type: string
                  nullable: true
                  description: Code d'identification personnalisé assigné au terminal
      responses:
        '200':
          description: Liste des terminaux
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Device'
                description: "array<int|string, mixed>"
        '401':
          description: "`AuthenticationException` — OAuth2 invalide"

  # ============================================================
  # Transactions
  # ============================================================
  /Transactions/sale:
    post:
      tags: [Transactions]
      operationId: Transactions_sale
      summary: "$viva->transactions->sale()"
      description: |
        Initie une vente sur un terminal POS.

        Endpoint : `POST /ecr/v1/transactions:sale`. Retourne `array{session_id, response}` ;
        le résultat réel s'obtient en pollant la session.

        ```php
        $sale = $viva->transactions->sale(
            terminalId: '16000010',
            amount: 2599,          // 25,99 EUR (centimes)
            cashRegisterId: 'CR-01',
            customerTrns: 'Table 12',
            showReceipt: true,
        );

        $final = $viva->pollUntilComplete($sale['session_id'], timeoutSeconds: 90);
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaleParams'
      responses:
        '200':
          description: Vente dispatchée vers le terminal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResult'
        '409':
          description: "`ApiException` — Session avec cet ID existe déjà"

  /Transactions/capturePreauth:
    post:
      tags: [Transactions]
      operationId: Transactions_capturePreauth
      summary: "$viva->transactions->capturePreauth()"
      description: |
        Capture une transaction pré-autorisée.

        Endpoint : `POST /ecr/v1/transactions:preauth-completion`. Capture le montant
        retenu par la pré-autorisation identifiée par `parentSessionId`.

        ```php
        $capture = $viva->transactions->capturePreauth(
            parentSessionId: $preauth['session_id'],
            terminalId: '16000010',
            amount: 4200,
            cashRegisterId: 'CR-01',
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapturePreauthParams'
      responses:
        '200':
          description: Capture dispatchée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResult'

  /Transactions/refund:
    post:
      tags: [Transactions]
      operationId: Transactions_refund
      summary: "$viva->transactions->refund()"
      description: |
        Rembourse une vente, en référençant la session originale.

        Endpoint : `POST /ecr/v1/transactions:refund`. Un nouveau `sessionId` est
        généré pour le remboursement ; `parentSessionId` référence la vente originale.

        ```php
        $refund = $viva->transactions->refund(
            parentSessionId: $sale['session_id'],
            terminalId: '16000010',
            amount: 1170,
            cashRegisterId: 'CR-01',
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundParams'
      responses:
        '200':
          description: Remboursement dispatché
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResult'

  /Transactions/unreferencedRefund:
    post:
      tags: [Transactions]
      operationId: Transactions_unreferencedRefund
      summary: "$viva->transactions->unreferencedRefund()"
      description: |
        Remboursement autonome (non référencé), non lié à une vente originale.

        Endpoint : `POST /ecr/v1/transactions:unreferenced-refund`.

        ```php
        $refund = $viva->transactions->unreferencedRefund(
            terminalId: '16000010',
            amount: 1500,
            cashRegisterId: 'CR-01',
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StandaloneRefundParams'
      responses:
        '200':
          description: Remboursement autonome dispatché
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResult'

  /Transactions/fastRefund:
    post:
      tags: [Transactions]
      operationId: Transactions_fastRefund
      summary: "$viva->transactions->fastRefund()"
      description: |
        Remboursement rapide (swift) sur Visa / Mastercard / Maestro.

        Endpoint : `POST /ecr/v1/transactions:fast-refund`.

        ```php
        $refund = $viva->transactions->fastRefund(
            terminalId: '16000010',
            amount: 1170,
            cashRegisterId: 'CR-01',
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StandaloneRefundParams'
      responses:
        '200':
          description: Remboursement rapide dispatché
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResult'

  /Transactions/rebate:
    post:
      tags: [Transactions]
      operationId: Transactions_rebate
      summary: "$viva->transactions->rebate()"
      description: |
        Rabais vers une carte Visa / Mastercard / Maestro.

        Endpoint : `POST /ecr/v1/transactions:rebate`.

        ```php
        $rebate = $viva->transactions->rebate(
            terminalId: '16000010',
            amount: 500,
            cashRegisterId: 'CR-01',
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StandaloneRefundParams'
      responses:
        '200':
          description: Rabais dispatché
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResult'

  /Transactions/createAction:
    post:
      tags: [Transactions]
      operationId: Transactions_createAction
      summary: "$viva->transactions->createAction()"
      description: |
        Crée une action à invoquer sur un terminal POS (ex : `aade-fim-control`).

        Endpoint : `POST /ecr/v1/actions`. Le payload est transmis tel quel
        (camelCase) et doit inclure `terminalId`, `cashRegisterId`, `actionType`
        et `request`.

        ```php
        $action = $viva->transactions->createAction([
            'terminalId' => '16000010',
            'cashRegisterId' => 'XDE384678UY',
            'actionType' => 'aade-fim-control',
            'request' => ['token' => 'ECR0210U/RCFB77000041/CMAC_K:...:CC5FFF'],
        ]);
        echo $action['actionId'];
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [terminalId, cashRegisterId, actionType, request]
              properties:
                terminalId:
                  type: string
                cashRegisterId:
                  type: string
                actionType:
                  type: string
                  enum: [aade-fim-control]
                request:
                  type: object
        description: "Corps de la requête d'action (passé tel quel, camelCase)"
      responses:
        '200':
          description: Action créée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActionResult'

  /Transactions/getAction:
    get:
      tags: [Transactions]
      operationId: Transactions_getAction
      summary: "$viva->transactions->getAction()"
      description: |
        Récupère le résultat d'une action précédemment créée.

        Endpoint : `GET /ecr/v1/actions/{actionId}`. Retourne HTTP 202 (body vide → `[]`)
        tant que l'action est en cours, ou le résultat une fois terminée.

        ```php
        $result = $viva->transactions->getAction('f7287549-e93a-4d33-b936-000027d326d0');

        if (($result['successfullyProcessed'] ?? false) === true) {
            echo $result['response']['status'];
        }
        ```
      parameters:
        - name: actionId
          in: query
          required: true
          schema:
            type: string
          description: ID de l'action (retourné par `createAction()`)
      responses:
        '200':
          description: Résultat de l'action
          content:
            application/json:
              schema:
                type: object
                description: "array<string, mixed>"
        '202':
          description: "Traitement de l'action non terminé (body vide → [])"
        '404':
          description: "`ApiException` — Action introuvable"

  # ============================================================
  # Sessions
  # ============================================================
  /Sessions/get:
    get:
      tags: [Sessions]
      operationId: Sessions_get
      summary: "$viva->sessions->get()"
      description: |
        Récupère une session par son id.

        Endpoint : `GET /ecr/v1/sessions/{sessionId}`.

        ```php
        $session = $viva->sessions->get($sale['session_id']);
        echo $session['eventId'];       // 0 = succès
        echo $session['transactionId']; // UUID de la transaction Viva
        ```
      parameters:
        - name: sessionId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: UUID de la session
      responses:
        '200':
          description: Données de la session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'

  /Sessions/listByDate:
    get:
      tags: [Sessions]
      operationId: Sessions_listByDate
      summary: "$viva->sessions->listByDate()"
      description: |
        Liste toutes les sessions effectuées une date donnée.

        Endpoint : `GET /ecr/v1/sessions?date=Y-m-d`.

        ```php
        $sessions = $viva->sessions->listByDate('2026-06-06');
        ```
      parameters:
        - name: date
          in: query
          required: true
          schema:
            type: string
            format: date
          description: "Date au format `Y-m-d`"
        - name: aadeAutonomouslyOnly
          in: query
          required: false
          schema:
            type: boolean
            nullable: true
          description: Filtrer aux sessions AADE-autonomes uniquement (Grèce)
      responses:
        '200':
          description: Liste de sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                description: "array<int|string, mixed>"

  /Sessions/abort:
    delete:
      tags: [Sessions]
      operationId: Sessions_abort
      summary: "$viva->sessions->abort()"
      description: |
        Annule une session active.

        Endpoint : `DELETE /ecr/v1/sessions/{sessionId}?cashRegisterId=...`.
        Seule la caisse ayant créé la transaction peut l'annuler.

        ```php
        $viva->sessions->abort($sale['session_id'], cashRegisterId: 'CR-01');
        ```
      parameters:
        - name: sessionId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: UUID de la session à annuler
        - name: cashRegisterId
          in: query
          required: true
          schema:
            type: string
          description: Identification de la caisse
      responses:
        '200':
          description: Session annulée
          content:
            application/json:
              schema:
                type: object

  /Sessions/pollUntilComplete:
    get:
      tags: [Sessions]
      operationId: Sessions_pollUntilComplete
      summary: "$viva->sessions->pollUntilComplete()"
      description: |
        Poll une session jusqu'à un état terminal ou expiration du timeout.

        Continue de poller tant que `eventId` vaut `IN_PROGRESS` (1100), en dormant
        `intervalMs` entre les tentatives. S'arrête aussi sur un `eventId` inconnu
        (fail-safe). En cas de timeout sans réponse, retourne
        `['success' => false, 'eventId' => 1003, 'message' => 'SDK poll timeout']`.

        ```php
        $result = $viva->sessions->pollUntilComplete($sale['session_id'], 90, 2000);
        ```
      parameters:
        - name: sessionId
          in: query
          required: true
          schema:
            type: string
            format: uuid
          description: UUID de la session à poller
        - name: timeoutSeconds
          in: query
          required: false
          schema:
            type: integer
            default: 120
          description: Temps d'attente total maximum (secondes)
        - name: intervalMs
          in: query
          required: false
          schema:
            type: integer
            default: 3000
          description: Délai entre deux tentatives (millisecondes)
      responses:
        '200':
          description: État final de la session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'

components:
  schemas:
    ClientConstructorParams:
      type: object
      required: [clientId, clientSecret]
      properties:
        clientId:
          type: string
          description: Client ID OAuth2 (grant client_credentials)
        clientSecret:
          type: string
          description: Secret OAuth2
        environment:
          type: string
          enum: [demo, production]
          default: demo
          description: "Environnement (string ou Environment enum)"

    ClientInstance:
      type: object
      description: Instance VivaCloudTerminalClient
      properties:
        devices:
          type: object
          description: "Devices — POST /ecr/v1/devices:search"
        transactions:
          type: object
          description: "Transactions — /ecr/v1/transactions:* + /ecr/v1/actions"
        sessions:
          type: object
          description: "Sessions — GET/DELETE /ecr/v1/sessions..."

    Config:
      type: object
      description: CloudTerminalConfig
      properties:
        clientId:
          type: string
        environment:
          type: string
          enum: [demo, production]
        accountsUrl:
          type: string
          example: https://demo-accounts.vivapayments.com
        apiUrl:
          type: string
          example: https://demo-api.vivapayments.com

    Device:
      type: object
      properties:
        terminalId:
          type: string
          example: "16000010"
        statusId:
          type: integer
          example: 1
        sourceCode:
          type: string
          example: "543679"
        virtualTerminalId:
          type: string
          example: cUOIpgY5RqKF5SC0BV6ZCl

    SaleParams:
      type: object
      required: [terminalId, amount, cashRegisterId]
      properties:
        terminalId:
          oneOf:
            - type: string
            - type: integer
          description: ID du terminal cible
        amount:
          type: integer
          description: Montant à autoriser, en centimes
        cashRegisterId:
          type: string
          description: Identification de la caisse
        merchantReference:
          type: string
          nullable: true
          description: "Référence marchand (défaut: SDK-{sessionId})"
        currencyCode:
          type: string
          default: "978"
          description: "Code ISO 4217 numérique en string ('978' = EUR)"
        tipAmount:
          type: integer
          default: 0
          description: Pourboire en centimes (incompatible avec preauth)
        preauth:
          type: boolean
          nullable: true
          description: Flag de pré-autorisation
        customerTrns:
          type: string
          nullable: true
          description: Référence client
        paymentMethod:
          type: string
          nullable: true
          description: "Méthode de paiement affichée (ex: CardPresent)"
        skipSurcharge:
          type: boolean
          nullable: true
        showTransactionResult:
          type: boolean
          nullable: true
        showReceipt:
          type: boolean
          nullable: true
        sessionId:
          type: string
          format: uuid
          nullable: true
          description: UUID de session (auto-généré si null)
        extra:
          type: object
          description: "Champs API additionnels (ex: fiscalisationData)"

    CapturePreauthParams:
      type: object
      required: [parentSessionId, terminalId, amount, cashRegisterId]
      properties:
        parentSessionId:
          type: string
          format: uuid
          description: UUID de session de la pré-autorisation à capturer
        terminalId:
          oneOf:
            - type: string
            - type: integer
        amount:
          type: integer
          description: Montant à capturer, en centimes
        cashRegisterId:
          type: string
        merchantReference:
          type: string
          nullable: true
        currencyCode:
          type: string
          default: "978"
        customerTrns:
          type: string
          nullable: true
        showTransactionResult:
          type: boolean
          nullable: true
        showReceipt:
          type: boolean
          nullable: true
        sessionId:
          type: string
          format: uuid
          nullable: true
        extra:
          type: object

    RefundParams:
      type: object
      required: [parentSessionId, terminalId, amount, cashRegisterId]
      properties:
        parentSessionId:
          type: string
          format: uuid
          description: UUID de session de la vente originale à rembourser
        terminalId:
          oneOf:
            - type: string
            - type: integer
        amount:
          type: integer
          description: Montant à rembourser, en centimes
        cashRegisterId:
          type: string
        merchantReference:
          type: string
          nullable: true
        currencyCode:
          type: string
          default: "978"
        customerTrns:
          type: string
          nullable: true
        showTransactionResult:
          type: boolean
          nullable: true
        showReceipt:
          type: boolean
          nullable: true
        sessionId:
          type: string
          format: uuid
          nullable: true
        extra:
          type: object

    StandaloneRefundParams:
      type: object
      required: [terminalId, amount, cashRegisterId]
      description: "Paramètres pour unreferencedRefund(), fastRefund() et rebate()"
      properties:
        terminalId:
          oneOf:
            - type: string
            - type: integer
        amount:
          type: integer
          description: Montant, en centimes
        cashRegisterId:
          type: string
        merchantReference:
          type: string
          nullable: true
        currencyCode:
          type: string
          default: "978"
        customerTrns:
          type: string
          nullable: true
        showTransactionResult:
          type: boolean
          nullable: true
        showReceipt:
          type: boolean
          nullable: true
        sessionId:
          type: string
          format: uuid
          nullable: true
        extra:
          type: object

    TransactionResult:
      type: object
      description: "array{session_id, response} — pollez la session pour le résultat réel"
      properties:
        session_id:
          type: string
          format: uuid
          description: UUID de session utilisé (à poller)
        response:
          type: object
          description: "Body API brut (souvent vide en succès)"

    ActionResult:
      type: object
      properties:
        terminalId:
          type: string
        cashRegisterId:
          type: string
        actionType:
          type: string
          enum: [aade-fim-control]
        actionId:
          type: string
          format: uuid
          example: f7287549-e93a-4d33-b936-000027d326d0

    Session:
      type: object
      description: "État d'une session (champs dynamiques selon l'API)"
      properties:
        eventId:
          type: integer
          description: "ID d'événement (voir EcrEventId)"
        success:
          type: boolean
        transactionId:
          type: string
          format: uuid
        amount:
          type: integer
          description: Montant en centimes

    ApiError:
      type: object
      description: "ApiException — getHttpStatus(), getErrorText(), getErrorCode()"
      properties:
        httpStatus:
          type: integer
        errorText:
          type: string
          nullable: true
        errorCode:
          type: integer
          nullable: true
