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

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

    ## ⚠️ Modèle P2P sans authentification

    La Local Terminal API n'est PAS l'API cloud Viva. C'est un protocole
    **peer-to-peer (P2P)** : le client parle DIRECTEMENT au terminal EFT POS
    sur le réseau local (LAN).

    | | API cloud Viva | Local Terminal API (ce SDK) |
    |---|---|---|
    | Base URL | `https://api.vivapayments.com` | `https://<ip-terminal>:<port>` |
    | Réseau | Internet | LAN, P2P |
    | Auth | OAuth2 / Basic / Bearer | **Aucune** (pas de header Authorization) |
    | TLS | CA publique | Cert auto-signé du terminal |
    | Erreurs 400 | JSON | **Chaîne brute** verbatim |

    > Spec officielle : « In a closed network environment, authentication is not
    > required for peer-to-peer communication. [...] eliminating the need to
    > include an Authorization tag in the header. »

    ## Installation

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

    ## Initialisation

    ```php
    use QrCommunication\VivaLocalTerminal\VivaLocalTerminalClient;

    $terminal = new VivaLocalTerminalClient(
        terminalBaseUrl: 'https://192.168.1.50:8080', // IP + port du terminal sur le LAN
        verifyTls: false,                              // cert auto-signé sur LAN fermé
    );
    ```

    ## Ressources disponibles

    | Propriété | Classe | Description |
    |-----------|--------|-------------|
    | `$terminal->transactions` | `Transactions` | sale, capturePreauth, refund, unreferencedRefund, abort, aadeFimControl, retrieve |
    | `$terminal->sessions` | `Sessions` | get (polling du résultat) |
    | `$terminal->device` | `Device` | screenControl, brightness |

    ## Montants

    Tous les montants sont en **centimes** (`int`). Exemple : `1170` = 11,70 EUR.

    ## Modèle asynchrone (polling)

    Les opérations de transaction retournent immédiatement avec `state` (typiquement
    `PROCESSING`) et `sessionId`. Vous générez vous-même le `sessionId` (UUID), puis
    vous interrogez `sessions->get($sessionId)` jusqu'à `SUCCESS` ou `FAILURE`.

    ## Gestion d'erreurs

    Les erreurs 400 sont des **chaînes brutes**, pas du JSON. Le SDK lève des
    exceptions typées :

    | Exception | Quand |
    |-----------|-------|
    | `ApiException` | Erreur 4xx du terminal OU terminal injoignable (`httpStatus === 0`) |
    | `VivaException` | Classe de base pour toutes les exceptions SDK |

    ```php
    use QrCommunication\VivaLocalTerminal\Exceptions\ApiException;

    try {
        $resp = $terminal->transactions->sale(sessionId: $sessionId, amount: 1170);
    } catch (ApiException $e) {
        echo $e->httpStatus;          // 400, ou 0 si injoignable
        echo $e->getErrorText();      // chaîne brute du terminal
        echo $e->responseBody['raw']; // corps brut (si 400 non-JSON)
    }
    ```
  contact:
    name: QrCommunication
    email: contact@qrcommunication.com
    url: https://qrcommunication.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

tags:
  - name: VivaLocalTerminalClient
    description: |
      Point d'entrée du SDK — instanciation (P2P, sans auth) et introspection de la config.

      ```php
      use QrCommunication\VivaLocalTerminal\VivaLocalTerminalClient;

      $terminal = new VivaLocalTerminalClient(
          terminalBaseUrl: 'https://192.168.1.50:8080',
          verifyTls: false,
      );
      ```
  - name: Transactions
    description: |
      `$terminal->transactions` — Opérations de transaction sur la Local Terminal API (P2P).

      Chaque opération retourne immédiatement `state` + `sessionId`. Interroger
      `sessions->get()` pour le résultat final. Montants en centimes.

      ```php
      $resp = $terminal->transactions->sale(sessionId: $uuid, amount: 1170, currencyCode: 978);
      ```
  - name: Sessions
    description: |
      `$terminal->sessions` — Récupération de session (polling).

      `payloadData` n'est renseigné qu'une fois la transaction terminée (SUCCESS/FAILURE).
  - name: Device
    description: |
      `$terminal->device` — Contrôle du terminal physique : écran (awake-lock) et luminosité.
  - name: Enums
    description: |
      Enums PHP 8.1+ fournis par le SDK pour le typage strict.

      - `SessionState` — PROCESSING | BUSY_ERROR | SERVER_ERROR | SESSION_NOT_FOUND_ERROR | SUCCESS | FAILURE
      - `SessionType` — SALE | CAPTURE_PREAUTH | CANCEL | UNREFERENCED_REFUND | ABORT
      - `TransactionTypeId` — 5 (sale), 4 (refund), 7 (reversal), 60/61/85 (IRIS)
      - `PaymentMethod` — CardPresent | Iris
  - name: Exceptions
    description: |
      Hiérarchie d'exceptions du SDK.

      ```
      RuntimeException
      └── VivaException
          └── ApiException
      ```

paths:
  # ============================================================
  # VivaLocalTerminalClient
  # ============================================================
  /VivaLocalTerminalClient/__construct:
    post:
      tags: [VivaLocalTerminalClient]
      operationId: VivaLocalTerminalClient__construct
      summary: new VivaLocalTerminalClient(...)
      description: |
        Instancie le client SDK. **Aucune authentification** — le terminal fait
        confiance à tout appelant sur le réseau fermé. Le `terminalBaseUrl` est
        l'adresse IP + port du terminal sur le LAN.

        ```php
        use QrCommunication\VivaLocalTerminal\VivaLocalTerminalClient;

        $terminal = new VivaLocalTerminalClient(
            terminalBaseUrl: 'https://192.168.1.50:8080',
            verifyTls: false,        // cert auto-signé ; true (défaut) ou chemin PEM
            useIsvEndpoints: false,  // true pour les variantes ISV (slash final)
            timeout: 30,
            connectTimeout: 10,
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClientConstructorParams'
      responses:
        '200':
          description: Instance `VivaLocalTerminalClient` créée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClientInstance'

  /VivaLocalTerminalClient/getConfig:
    get:
      tags: [VivaLocalTerminalClient]
      operationId: VivaLocalTerminalClient_getConfig
      summary: "$terminal->getConfig()"
      description: |
        Retourne l'objet `Config` pour introspection ou débogage.

        ```php
        $config = $terminal->getConfig();
        echo $config->baseUrl;          // 'https://192.168.1.50:8080'
        echo $config->verifyTls;        // false
        echo $config->useIsvEndpoints;  // false
        ```
      responses:
        '200':
          description: Objet `Config`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Config'

  # ============================================================
  # Transactions
  # ============================================================
  /Transactions/sale:
    post:
      tags: [Transactions]
      operationId: Transactions_sale
      summary: "$terminal->transactions->sale()"
      description: |
        Initie une vente — `POST /pos/v1/sale`. Le terminal affiche le montant et
        attend la carte. Retourne immédiatement `state = PROCESSING` + `sessionId`.
        Interroger `sessions->get($sessionId)` pour le résultat.

        ```php
        $resp = $terminal->transactions->sale(
            sessionId: '4bdebe62-c211-4ca0-a994-b2fbea2061c5', // UUID que vous générez
            amount: 1170,            // 11,70 EUR (centimes)
            currencyCode: 978,       // EUR
            merchantReference: 'order-123',
        );
        // $resp = ['state' => 'PROCESSING', 'sessionType' => 'SALE', 'sessionId' => ...]
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SaleParams'
      responses:
        '200':
          description: Vente initiée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResponse'
        '400':
          description: "`ApiException` — Erreur (corps en CHAÎNE BRUTE, ex: « Amount cannot be null »)"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'

  /Transactions/capturePreauth:
    post:
      tags: [Transactions]
      operationId: Transactions_capturePreauth
      summary: "$terminal->transactions->capturePreauth()"
      description: |
        Capture (complète) une vente pré-autorisée — `POST /pos/v1/preauth-completion`.

        ```php
        $resp = $terminal->transactions->capturePreauth(
            sessionId: 'a-new-uuid',
            amount: 1500,
            transactionId: 'preauth-transaction-id',
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CapturePreauthParams'
      responses:
        '200':
          description: Pré-autorisation capturée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResponse'
        '400':
          description: "`ApiException` (chaîne brute)"

  /Transactions/refund:
    post:
      tags: [Transactions]
      operationId: Transactions_refund
      summary: "$terminal->transactions->refund()"
      description: |
        Rembourse / annule une transaction par son ID d'origine — `POST /pos/v1/refund`.

        ```php
        $resp = $terminal->transactions->refund(
            sessionId: 'a-new-uuid',
            amount: 1170,
            transactionId: 'original-sale-transaction-id',
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefundParams'
      responses:
        '200':
          description: Remboursement initié
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResponse'
        '400':
          description: "`ApiException` (chaîne brute)"

  /Transactions/unreferencedRefund:
    post:
      tags: [Transactions]
      operationId: Transactions_unreferencedRefund
      summary: "$terminal->transactions->unreferencedRefund()"
      description: |
        Remboursement non lié à une transaction d'origine — `POST /pos/v1/unreferenced-refund`.

        ```php
        $resp = $terminal->transactions->unreferencedRefund(
            sessionId: 'a-new-uuid',
            amount: 500, // 5,00 EUR
        );
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UnreferencedRefundParams'
      responses:
        '200':
          description: Remboursement non référencé initié
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResponse'
        '400':
          description: "`ApiException` (chaîne brute)"

  /Transactions/abort:
    post:
      tags: [Transactions]
      operationId: Transactions_abort
      summary: "$terminal->transactions->abort()"
      description: |
        Interrompt une session SALE en cours — `POST /pos/v1/abort`.

        ```php
        $terminal->transactions->abort('4bdebe62-c211-4ca0-a994-b2fbea2061c5');
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sessionId]
              properties:
                sessionId:
                  type: string
                  format: uuid
                  description: ID de la session SALE à interrompre
      responses:
        '200':
          description: Session interrompue
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionResponse'

  /Transactions/aadeFimControl:
    post:
      tags: [Transactions]
      operationId: Transactions_aadeFimControl
      summary: "$terminal->transactions->aadeFimControl()"
      description: |
        Action de contrôle AADE FIM (fiscalisation grecque) — `POST /pos/v1/aade-fim-control`.

        Crée l'Echo Message + le Control Message, valide la Master Key, le KCV et
        la Session Key chiffrée.

        ```php
        $resp = $terminal->transactions->aadeFimControl(
            'ECR0210U/RCFB77000041/CMAC_K:1ED9F7AE0B2509281BBC2DE38EF2A12B:CC5FFF',
        );
        echo $resp['status'];  // 'success'
        echo $resp['message']; // 'Fim control success'
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token:
                  type: string
                  description: Token de contrôle FIMAS incluant le token AADE
      responses:
        '200':
          description: Contrôle AADE effectué
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AadeFimControlResponse'

  /Transactions/retrieve:
    get:
      tags: [Transactions]
      operationId: Transactions_retrieve
      summary: "$terminal->transactions->retrieve()"
      description: |
        Liste les transactions historiques avec filtres optionnels — `GET /pos/v1/transactions`.

        `transactionTypes` accepte un id unique ou une liste.

        ```php
        $result = $terminal->transactions->retrieve(
            transactionTypes: [5, 4], // ventes + remboursements
            startDate: '2026-03-01T00:00:00Z',
            endDate: '2026-03-31T23:59:59Z',
        );

        foreach ($result['transactions'] ?? [] as $txn) {
            echo $txn['orderCode'] . PHP_EOL;
        }
        ```
      parameters:
        - name: cardNumber
          in: query
          required: false
          schema:
            type: string
            nullable: true
          description: Filtre par numéro de carte masqué
        - name: orderCode
          in: query
          required: false
          schema:
            type: string
            nullable: true
          description: Filtre par code d'ordre
        - name: transactionTypes
          in: query
          required: false
          schema:
            oneOf:
              - type: integer
              - type: array
                items:
                  type: integer
          description: "Id(s) de type de transaction (5 = sale, 4 = refund, …)"
        - name: transactionStatus
          in: query
          required: false
          schema:
            type: string
            nullable: true
          description: Filtre par statut de transaction
        - name: startDate
          in: query
          required: false
          schema:
            type: string
            nullable: true
          description: Borne inférieure ISO 8601
        - name: endDate
          in: query
          required: false
          schema:
            type: string
            nullable: true
          description: Borne supérieure ISO 8601
        - name: isAadeAutonomouslyOnly
          in: query
          required: false
          schema:
            type: boolean
            nullable: true
          description: Filtrer les transactions AADE autonomes uniquement
      responses:
        '200':
          description: Liste des transactions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransactionsListResponse'

  # ============================================================
  # Sessions
  # ============================================================
  /Sessions/get:
    get:
      tags: [Sessions]
      operationId: Sessions_get
      summary: "$terminal->sessions->get()"
      description: |
        Récupère le détail complet d'une session par son id — `GET /pos/v1/sessions/{sessionId}`.

        `payloadData` n'est renseigné qu'une fois la transaction terminée (SUCCESS/FAILURE).

        ```php
        use QrCommunication\VivaLocalTerminal\Enums\SessionState;

        $session = $terminal->sessions->get($sessionId);
        if (($session['state'] ?? null) === SessionState::SUCCESS->value) {
            // Paiement réussi — $session['payloadData'] contient le détail
        }
        ```
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: UUID de la session
      responses:
        '200':
          description: Détail de la session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionResponse'

  # ============================================================
  # Device
  # ============================================================
  /Device/screenControl:
    post:
      tags: [Device]
      operationId: Device_screenControl
      summary: "$terminal->device->screenControl()"
      description: |
        Contrôle l'écran / mode veille du terminal — `POST /pos/v1/awake-lock`.

        ```php
        $resp = $terminal->device->screenControl(wakeUpLock: true);
        echo $resp['isSuccess']; // true
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [wakeUpLock]
              properties:
                wakeUpLock:
                  type: boolean
                  description: "true : réveil + écran actif jusqu'au déverrouillage ; false : veille normale"
      responses:
        '200':
          description: Commande appliquée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceResponse'

  /Device/brightness:
    post:
      tags: [Device]
      operationId: Device_brightness
      summary: "$terminal->device->brightness()"
      description: |
        Règle la luminosité du terminal — `POST /pos/v1/brightness`.

        La valeur doit être dans `(0, 1]` (0 interdit). Lève `\InvalidArgumentException`
        en dehors de cette plage.

        ```php
        $terminal->device->brightness(0.5); // 50 %
        ```
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [brightness]
              properties:
                brightness:
                  type: number
                  format: float
                  minimum: 0
                  exclusiveMinimum: true
                  maximum: 1
                  description: "Valeur dans (0, 1], 1.00 = max. 0 interdit."
      responses:
        '200':
          description: Luminosité appliquée
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceResponse'

components:
  schemas:
    # ---- Client ----
    ClientConstructorParams:
      type: object
      required: [terminalBaseUrl]
      properties:
        terminalBaseUrl:
          type: string
          example: "https://192.168.1.50:8080"
          description: "URL de base du terminal (scheme + IP + port). Scheme obligatoire."
        verifyTls:
          oneOf:
            - type: boolean
            - type: string
          default: true
          description: "true (défaut) | chemin PEM (CA pinné) | false (LAN-only)"
        useIsvEndpoints:
          type: boolean
          default: false
          description: "Cible les variantes ISV (slash final, ex: /pos/v1/sale/)"
        timeout:
          type: integer
          default: 30
          description: Timeout par requête (secondes)
        connectTimeout:
          type: integer
          default: 10
          description: Timeout de connexion TCP (secondes)

    ClientInstance:
      type: object
      description: "Instance VivaLocalTerminalClient (propriétés: transactions, sessions, device)"

    Config:
      type: object
      properties:
        baseUrl:
          type: string
          description: URL de base normalisée (sans slash final)
        verifyTls:
          oneOf:
            - type: boolean
            - type: string
        useIsvEndpoints:
          type: boolean
        timeout:
          type: integer
        connectTimeout:
          type: integer

    # ---- Transactions params ----
    SaleParams:
      type: object
      required: [sessionId, amount]
      properties:
        sessionId:
          type: string
          format: uuid
          description: UUID de session généré par l'appelant
        amount:
          type: integer
          description: Montant à autoriser, en centimes
        merchantReference:
          type: string
        customerTrns:
          type: string
        preauth:
          type: boolean
        tipAmount:
          type: integer
          description: Pourboire en centimes (incompatible avec preauth)
        paymentMethod:
          type: string
          description: "CardPresent | Iris (méthode affichée en premier)"
        showTransactionResult:
          type: boolean
        showReceipt:
          type: boolean
        currencyCode:
          type: integer
          description: Code ISO 4217 numérique (devise marchand)
        skipSurcharge:
          type: boolean
        saleToAcquirerData:
          type: string
          description: JSON base64 de métadonnées acquéreur
        maxInstalments:
          type: integer
          description: Versements max (marchands grecs)
        aadeProviderId:
          type: string
        aadeProviderSignatureData:
          type: string
        aadeProviderSignature:
          type: string
        aadePreloaded:
          type: boolean
        aadePreloadedDuration:
          type: integer
        fiscalisationData:
          type: object
          description: Objet Viva Fiscalisation
        isvDetails:
          type: object
          description: Objet partenaire / fee / multi-marchand (ISV uniquement)

    CapturePreauthParams:
      type: object
      required: [sessionId, amount, transactionId]
      properties:
        sessionId:
          type: string
          format: uuid
        amount:
          type: integer
          description: Montant à capturer, en centimes
        transactionId:
          type: string
          description: ID de la transaction pré-autorisée initiale
        merchantReference:
          type: string
        customerTrns:
          type: string
        currencyCode:
          type: integer
        saleToAcquirerData:
          type: string
        aadeProviderId:
          type: string
        aadeProviderSignatureData:
          type: string
        aadeProviderSignature:
          type: string
        aadePreloaded:
          type: boolean
        aadePreloadedDuration:
          type: integer

    RefundParams:
      type: object
      required: [sessionId, amount, transactionId]
      properties:
        sessionId:
          type: string
          format: uuid
        amount:
          type: integer
          description: Montant à rembourser, en centimes
        transactionId:
          type: string
          description: ID de la transaction de vente d'origine
        orderCode:
          type: integer
        shortOrderCode:
          type: integer
        merchantReference:
          type: string
        customerTrns:
          type: string
        currencyCode:
          type: integer
        showTransactionResult:
          type: boolean
        showReceipt:
          type: boolean
        txnDateFrom:
          type: string
          description: Borne inférieure ISO 8601 pour localiser la transaction
        txnDateTo:
          type: string
          description: Borne supérieure ISO 8601
        aadeProviderId:
          type: string
        aadeProviderSignatureData:
          type: string
        aadeProviderSignature:
          type: string

    UnreferencedRefundParams:
      type: object
      required: [sessionId, amount]
      properties:
        sessionId:
          type: string
          format: uuid
        amount:
          type: integer
          description: Montant à rembourser, en centimes
        merchantReference:
          type: string
        customerTrns:
          type: string
        showTransactionResult:
          type: boolean
        showReceipt:
          type: boolean
        aadeProviderId:
          type: string
        aadeProviderSignatureData:
          type: string
        aadeProviderSignature:
          type: string
        fiscalisationData:
          type: object
          description: Objet Viva Fiscalisation

    # ---- Responses ----
    TransactionResponse:
      type: object
      description: "Réponse immédiate des opérations de transaction"
      properties:
        state:
          $ref: '#/components/schemas/SessionStateEnum'
        sessionType:
          $ref: '#/components/schemas/SessionTypeEnum'
        sessionId:
          type: string
          format: uuid

    SessionResponse:
      type: object
      description: "Détail complet d'une session (GET /pos/v1/sessions/{id})"
      properties:
        state:
          $ref: '#/components/schemas/SessionStateEnum'
        sessionType:
          $ref: '#/components/schemas/SessionTypeEnum'
        sessionId:
          type: string
          format: uuid
        payloadData:
          type: object
          description: "Payload final, présent une fois la transaction terminée"

    AadeFimControlResponse:
      type: object
      properties:
        status:
          type: string
          example: success
        message:
          type: string
          example: Fim control success

    TransactionsListResponse:
      type: object
      properties:
        isSuccess:
          type: boolean
        transactions:
          type: array
          items:
            type: object

    DeviceResponse:
      type: object
      properties:
        isSuccess:
          type: boolean

    # ---- Enums ----
    SessionStateEnum:
      type: string
      enum: [PROCESSING, BUSY_ERROR, SERVER_ERROR, SESSION_NOT_FOUND_ERROR, SUCCESS, FAILURE]
      description: |
        `QrCommunication\VivaLocalTerminal\Enums\SessionState`

        Backed enum PHP 8.1+ (valeurs string).

    SessionTypeEnum:
      type: string
      enum: [SALE, CAPTURE_PREAUTH, CANCEL, UNREFERENCED_REFUND, ABORT]
      description: |
        `QrCommunication\VivaLocalTerminal\Enums\SessionType`

        Backed enum PHP 8.1+ (valeurs string).

    TransactionTypeIdEnum:
      type: integer
      enum: [5, 4, 7, 60, 61, 85]
      description: |
        `QrCommunication\VivaLocalTerminal\Enums\TransactionTypeId`

        5 = sale, 4 = refund, 7 = reversal, 60 = IRIS sale, 61 = IRIS refund, 85 = IRIS void.

    PaymentMethodEnum:
      type: string
      enum: [CardPresent, Iris]
      description: |
        `QrCommunication\VivaLocalTerminal\Enums\PaymentMethod`

    # ---- Errors ----
    ApiError:
      type: object
      description: |
        `ApiException` — levée sur 4xx OU sur terminal injoignable (`httpStatus === 0`).
        Sur une 400, le corps du terminal est une CHAÎNE BRUTE préservée sous `responseBody.raw`.
      properties:
        message:
          type: string
          description: "Texte d'erreur (chaîne brute du terminal sur 400)"
        httpStatus:
          type: integer
          description: "Code HTTP (0 si le terminal est injoignable sur le LAN)"
        responseBody:
          type: object
          nullable: true
          properties:
            raw:
              type: string
              description: "Corps brut non-JSON du terminal"
