---
title: Viva Local Terminal SDK - Documentation
version: 1.0.0
language: TypeScript
package: "@qrcommunication/viva-local-terminal-sdk"
github: https://github.com/QrCommunication/sdk-js-viva-local-terminal
---

# Viva.com Local Terminal SDK — Documentation TypeScript

SDK TypeScript pour la **Local Terminal API** de Viva.com (`/pos/v1/`) — communication **peer-to-peer (P2P)** directe avec les terminaux de paiement EFT POS sur le réseau local. Couvre **3 ressources** : Transactions, Sessions et Device.

- **Version** : 1.0.0
- **Prérequis** : Node 18+ (ou tout runtime avec `fetch` global), TypeScript 5.x
- **Licence** : MIT
- **npm** : `@qrcommunication/viva-local-terminal-sdk`
- **Formats** : ESM + CJS + types `.d.ts`

---

## ⚠️ À lire en premier : ce n'est PAS l'API cloud Viva

Ce SDK **ne communique pas** avec `api.vivapayments.com`. La Local Terminal API est un **protocole peer-to-peer** : votre application parle **directement au terminal de carte** sur votre réseau local, en adressant **l'adresse IP et le port** du terminal lui-même.

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

> Extrait de la spec officielle : *« In a closed network environment, authentication is not required for peer-to-peer communication. This means that you can target the terminal without authentication, eliminating the need to include an Authorization tag in the header. »*

### Modèle asynchrone (polling)

Les opérations de transaction (`sale`, `refund`, `capturePreauth`, …) **retournent immédiatement** avec un `state` (typiquement `PROCESSING`) et un `sessionId`. **Vous générez vous-même le `sessionId`** (UUID), puis vous interrogez `sessions.get(sessionId)` jusqu'à ce que `state` vaille `SUCCESS` ou `FAILURE` et que `payloadData` soit renseigné.

### Prérequis réseau

1. L'hôte qui exécute votre code et le terminal doivent être sur le **même LAN**.
2. Vous devez connaître **l'adresse IP et le port** du terminal (découvrables via l'app Viva.com Terminal, votre réseau, ou zeroconf/mDNS).
3. Le terminal sert du **HTTPS**, généralement avec un **certificat auto-signé**. Sur Node, passez `verifyTls: false` (ou fournissez un `dispatcher` undici personnalisé) — voir [TLS](#tls--certificats-auto-signés).

---

## Table des matières

1. [Installation](#installation)
2. [Démarrage rapide](#démarrage-rapide)
3. [Configuration](#configuration)
4. [TLS — certificats auto-signés](#tls--certificats-auto-signés)
5. [Ressources](#ressources)
   - [Transactions](#1-transactions--terminaltransactions)
   - [Sessions](#2-sessions--terminalsessions)
   - [Device](#3-device--terminaldevice)
6. [Enums](#enums)
7. [Gestion des erreurs](#gestion-des-erreurs)
8. [Variantes Merchant vs ISV](#variantes-merchant-vs-isv)
9. [Changelog](#changelog)

---

## Installation

```bash
npm install @qrcommunication/viva-local-terminal-sdk
# ou
pnpm add @qrcommunication/viva-local-terminal-sdk
```

Requiert Node 18+ (ou tout runtime exposant `fetch` global : navigateurs, Deno). Le package est publié en ESM + CJS avec types.

---

## Démarrage rapide

```ts
import { VivaLocalTerminalClient } from "@qrcommunication/viva-local-terminal-sdk";
import { randomUUID } from "node:crypto";

// 1. Pointer le client sur l'IP + port du terminal sur le LAN
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080", // IP + port du terminal
  verifyTls: false,                              // certificat auto-signé sur LAN fermé
});

// 2. Démarrer une vente (montant en centimes) — vous générez le sessionId
const sessionId = randomUUID();
const res = await terminal.transactions.sale({
  sessionId,
  amount: 1170,        // 11,70 EUR
  currencyCode: 978,   // EUR
  merchantReference: "order-123",
});
// res.state === "PROCESSING"

// 3. Interroger le résultat une fois le client ayant payé
const session = await terminal.sessions.get(sessionId);
// session.state === "SUCCESS" une fois la carte traitée
// session.payloadData contient le détail de la transaction

// 4. Contrôler le terminal
await terminal.device.screenControl(true);
await terminal.device.brightness(0.5);
```

---

## Configuration

### Constructeur `VivaLocalTerminalClient`

```ts
new VivaLocalTerminalClient(options: VivaLocalTerminalClientOptions)
```

`VivaLocalTerminalClientOptions` :

| Option | Type | Défaut | Description |
|--------|------|--------|-------------|
| `terminalBaseUrl` | `string` | **requis** | URL de base du terminal : scheme + IP + port, ex : `https://192.168.1.50:8080`. Le slash final est retiré. Le scheme (`https://`) est obligatoire. |
| `verifyTls` | `boolean` | `true` | Vérification du certificat TLS. `false` désactive la vérification (cas courant pour un cert auto-signé sur LAN fermé). |
| `useIsvEndpoints` | `boolean` | `false` | Cible les variantes ISV (chemins à slash final, ex : `/pos/v1/sale/`). |
| `timeout` | `number` | `30000` | Timeout par requête en millisecondes. |
| `fetch` | `typeof fetch` | global | Implémentation `fetch` personnalisée (surtout pour les tests). |
| `dispatcher` | `Dispatcher` | — | `Dispatcher` undici personnalisé. Omis + `verifyTls: false` sur Node → un `Agent` undici avec `rejectUnauthorized: false` est créé automatiquement. |

> **Aucune authentification.** Les options n'incluent ni clé API, ni client ID, ni secret. Le terminal fait confiance à tout appelant sur le réseau fermé.

### Méthode utilitaire du client

```ts
terminal.getConfig(): VivaLocalTerminalConfig   // Configuration courante (introspection)
```

### Propriétés publiques

```ts
terminal.transactions  // Transactions
terminal.sessions      // Sessions
terminal.device        // Device
```

---

## TLS — certificats auto-signés

Le terminal sert du HTTPS, le plus souvent avec un **certificat auto-signé**.

```ts
// 1. Vérification standard (CA système) — défaut. Échoue sur cert auto-signé.
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
});

// 2. Désactiver la vérification (modèle de confiance LAN-only)
// Sur Node, un Agent undici avec rejectUnauthorized: false est attaché automatiquement.
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
  verifyTls: false,
});

// 3. Contrôle fin : fournir votre propre Dispatcher undici (ex : CA pinné)
import { Agent } from "undici";
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
  dispatcher: new Agent({ connect: { ca: myTerminalCaPem } }),
});
```

> Sur les runtimes sans undici (navigateur), `verifyTls` dépend du comportement de `fetch` de la plateforme.

---

## Ressources

### 1. Transactions — `terminal.transactions`

Opérations de transaction sur la Local Terminal API. Chaque opération qui démarre une transaction **retourne immédiatement** une promesse résolue avec un `state` (typiquement `PROCESSING`) et le `sessionId`. Le résultat final s'obtient en interrogeant `sessions.get()`. Les montants sont **toujours des entiers en centimes**.

**Type de retour commun :** `Promise<TransactionResponse>` — `{ state?, sessionType?, sessionId?, [k: string]: unknown }`.

#### `sale(params)` — Initier une vente

```ts
sale(params: SaleParams): Promise<TransactionResponse>
```

Endpoint : `POST /pos/v1/sale`. Le terminal affiche le montant et attend la carte.

`SaleParams` :

| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| `sessionId` | `string` | **oui** | UUID de session généré par l'appelant |
| `amount` | `number` | **oui** | Montant à autoriser, en centimes |
| `merchantReference` | `string` | non | Référence libre côté marchand |
| `customerTrns` | `string` | non | Référence libre côté client |
| `preauth` | `boolean` | non | Le paiement est-il une pré-autorisation |
| `tipAmount` | `number` | non | Pourboire en centimes (incompatible avec `preauth`) |
| `paymentMethod` | `PaymentMethod \| string` | non | Méthode affichée en premier (ex : `CardPresent`) |
| `showTransactionResult` | `boolean` | non | Afficher le résultat à l'écran |
| `showReceipt` | `boolean` | non | Afficher le reçu + résultat |
| `currencyCode` | `number` | non | Code ISO 4217 numérique (devise marchand) |
| `skipSurcharge` | `boolean` | non | Désactiver le surcharge même sur cartes éligibles |
| `saleToAcquirerData` | `string` | non | JSON base64 de métadonnées acquéreur |
| `maxInstalments` | `number` | non | Versements max (marchands grecs uniquement) |
| `aadeProviderId` | `string` | non | ID provider AADE (Grèce) |
| `aadeProviderSignatureData` | `string` | non | Données de signature provider AADE (Grèce) |
| `aadeProviderSignature` | `string` | non | Signature provider AADE (Grèce) |
| `aadePreloaded` | `boolean` | non | Flag préchargé AADE (Grèce) |
| `aadePreloadedDuration` | `number` | non | Durée d'expiration préchargée AADE (Grèce) |
| `fiscalisationData` | `Record<string, unknown>` | non | Objet Viva Fiscalisation |
| `isvDetails` | `Record<string, unknown>` | non | Objet partenaire / fee / multi-marchand ISV (ISV uniquement) |

```ts
const res = await terminal.transactions.sale({
  sessionId: "4bdebe62-c211-4ca0-a994-b2fbea2061c5",
  amount: 1170,          // 11,70 EUR
  currencyCode: 978,     // EUR
  merchantReference: "order-123",
  customerTrns: "Consultation",
  showReceipt: true,
});

console.log(res.state);       // "PROCESSING"
console.log(res.sessionType); // "SALE"

// Puis polling :
const session = await terminal.sessions.get(res.sessionId!);
```

#### `capturePreauth(params)` — Capturer une pré-autorisation

```ts
capturePreauth(params: CapturePreauthParams): Promise<TransactionResponse>
```

Endpoint : `POST /pos/v1/preauth-completion`. Capture (complète) une vente précédemment pré-autorisée.

`CapturePreauthParams` :

| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| `sessionId` | `string` | **oui** | UUID de session généré par l'appelant |
| `amount` | `number` | **oui** | Montant à capturer, en centimes |
| `transactionId` | `string` | **oui** | ID de la transaction pré-autorisée initiale |
| `merchantReference` | `string` | non | Référence libre côté marchand |
| `customerTrns` | `string` | non | Référence libre côté client |
| `currencyCode` | `number` | non | Code ISO 4217 numérique |
| `saleToAcquirerData` | `string` | non | JSON base64 de métadonnées acquéreur |
| `aadeProviderId` … `aadePreloadedDuration` | `string`/`boolean`/`number` | non | Paramètres AADE (Grèce) |

```ts
const res = await terminal.transactions.capturePreauth({
  sessionId: crypto.randomUUID(),
  amount: 1500,          // 15,00 EUR
  transactionId: "preauth-transaction-id",
});
```

#### `refund(params)` — Rembourser (référencé)

```ts
refund(params: RefundParams): Promise<TransactionResponse>
```

Endpoint : `POST /pos/v1/refund`. Rembourse / annule une transaction par son ID d'origine.

`RefundParams` :

| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| `sessionId` | `string` | **oui** | UUID de session généré par l'appelant |
| `amount` | `number` | **oui** | Montant à rembourser, en centimes |
| `transactionId` | `string` | **oui** | ID de la transaction de vente d'origine |
| `orderCode` | `number` | non | Code d'ordre de la vente d'origine |
| `shortOrderCode` | `number` | non | Short order code (filtre) |
| `merchantReference` | `string` | non | Référence libre côté marchand |
| `customerTrns` | `string` | non | Référence libre côté client |
| `currencyCode` | `number` | non | Code ISO 4217 numérique |
| `showTransactionResult` | `boolean` | non | Afficher le résultat à l'écran |
| `showReceipt` | `boolean` | non | Afficher le reçu + résultat |
| `txnDateFrom` | `string` | non | Borne inférieure ISO 8601 pour localiser la transaction |
| `txnDateTo` | `string` | non | Borne supérieure ISO 8601 |
| `aadeProviderId` / `aadeProviderSignatureData` / `aadeProviderSignature` | `string` | non | Paramètres AADE (Grèce) |

```ts
const res = await terminal.transactions.refund({
  sessionId: crypto.randomUUID(),
  amount: 1170,
  transactionId: "original-sale-transaction-id",
});
```

#### `unreferencedRefund(params)` — Remboursement non référencé

```ts
unreferencedRefund(params: UnreferencedRefundParams): Promise<TransactionResponse>
```

Endpoint : `POST /pos/v1/unreferenced-refund`. Remboursement non lié à une transaction d'origine.

`UnreferencedRefundParams` :

| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| `sessionId` | `string` | **oui** | UUID de session généré par l'appelant |
| `amount` | `number` | **oui** | Montant à rembourser, en centimes |
| `merchantReference` | `string` | non | Référence libre côté marchand |
| `customerTrns` | `string` | non | Référence libre côté client |
| `showTransactionResult` | `boolean` | non | Afficher le résultat à l'écran |
| `showReceipt` | `boolean` | non | Afficher le reçu + résultat |
| `aadeProviderId` / `aadeProviderSignatureData` / `aadeProviderSignature` | `string` | non | Paramètres AADE (Grèce) |
| `fiscalisationData` | `Record<string, unknown>` | non | Objet Viva Fiscalisation |

```ts
const res = await terminal.transactions.unreferencedRefund({
  sessionId: crypto.randomUUID(),
  amount: 500, // 5,00 EUR
});
```

#### `abort(sessionId)` — Annuler une session de vente en cours

```ts
abort(sessionId: string): Promise<TransactionResponse>
```

Endpoint : `POST /pos/v1/abort`. Interrompt une session `SALE` en cours.

| Paramètre | Type | Requis | Description |
|-----------|------|--------|-------------|
| `sessionId` | `string` | **oui** | ID de la session SALE à interrompre |

```ts
await terminal.transactions.abort("4bdebe62-c211-4ca0-a994-b2fbea2061c5");
```

#### `aadeFimControl(token)` — Action de contrôle AADE FIM (Grèce)

```ts
aadeFimControl(token: string): Promise<AadeFimControlResponse>
// { status?: string; message?: string }
```

Endpoint : `POST /pos/v1/aade-fim-control`. Crée l'Echo Message (récupération de la Master Key) et le Control Message (création de la Session Key), valide la Master Key, le KCV et la Session Key chiffrée (fiscalisation AADE grecque).

| Paramètre | Type | Requis | Description |
|-----------|------|--------|-------------|
| `token` | `string` | **oui** | Token de contrôle FIMAS incluant le token AADE |

```ts
const res = await terminal.transactions.aadeFimControl(
  "ECR0210U/RCFB77000041/CMAC_K:1ED9F7AE0B2509281BBC2DE38EF2A12B:CC5FFF",
);
console.log(res.status);  // "success"
console.log(res.message); // "Fim control success"
```

#### `retrieve(params?)` — Lister les transactions historiques

```ts
retrieve(params?: RetrieveTransactionsParams): Promise<TransactionsListResponse>
// { isSuccess?: boolean; transactions?: Array<Record<string, unknown>> }
```

Endpoint : `GET /pos/v1/transactions`. Tous les filtres sont optionnels. `transactionTypes` accepte un id unique ou un tableau (envoyé en valeur séparée par virgules).

`RetrieveTransactionsParams` :

| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| `cardNumber` | `string` | non | Filtre par numéro de carte masqué |
| `orderCode` | `string` | non | Filtre par code d'ordre |
| `transactionTypes` | `TransactionTypeId \| number \| Array<...>` | non | Id(s) de type de transaction (5 = sale, 4 = refund, …) |
| `transactionStatus` | `string` | non | Filtre par statut |
| `startDate` | `string` | non | Borne inférieure ISO 8601 |
| `endDate` | `string` | non | Borne supérieure ISO 8601 |
| `isAadeAutonomouslyOnly` | `boolean` | non | Filtrer les transactions AADE autonomes uniquement |

```ts
import { TransactionTypeId } from "@qrcommunication/viva-local-terminal-sdk";

const result = await terminal.transactions.retrieve({
  transactionTypes: [TransactionTypeId.SALE, TransactionTypeId.REFUND],
  startDate: "2026-03-01T00:00:00Z",
  endDate: "2026-03-31T23:59:59Z",
});

for (const txn of result.transactions ?? []) {
  console.log(txn.orderCode);
}
```

---

### 2. Sessions — `terminal.sessions`

Récupération de session. Après qu'une opération de transaction a retourné `PROCESSING`, interrogez la session pour obtenir le résultat final. Le champ `payloadData` n'est renseigné qu'une fois la transaction terminée (`state` `SUCCESS` ou `FAILURE`).

#### `get(sessionId)` — Récupérer une session

```ts
get(sessionId: string): Promise<SessionResponse>
// { state?, sessionType?, sessionId?, payloadData?, [k: string]: unknown }
```

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

| Paramètre | Type | Requis | Description |
|-----------|------|--------|-------------|
| `sessionId` | `string` | **oui** | UUID de la session |

```ts
import { SessionState } from "@qrcommunication/viva-local-terminal-sdk";

// Boucle de polling typique
async function pollSession(terminal, sessionId) {
  for (;;) {
    const session = await terminal.sessions.get(sessionId);

    if (session.state === SessionState.SUCCESS) {
      return session; // session.payloadData contient le détail
    }
    if (session.state === SessionState.FAILURE) {
      throw new Error("Paiement échoué");
    }

    await new Promise((r) => setTimeout(r, 2000)); // attendre 2 s
  }
}
```

---

### 3. Device — `terminal.device`

Opérations de configuration du terminal physique : réveil/veille de l'écran et luminosité.

**Type de retour commun :** `Promise<DeviceResponse>` — `{ isSuccess?: boolean }`.

#### `screenControl(wakeUpLock)` — Contrôler l'écran / mode veille

```ts
screenControl(wakeUpLock: boolean): Promise<DeviceResponse>
```

Endpoint : `POST /pos/v1/awake-lock`.

| Paramètre | Type | Requis | Description |
|-----------|------|--------|-------------|
| `wakeUpLock` | `boolean` | **oui** | `true` : le terminal sort de veille et l'écran reste actif jusqu'au déverrouillage. `false` : retour au comportement de veille normal. |

```ts
const res = await terminal.device.screenControl(true);
console.log(res.isSuccess); // true
```

#### `brightness(brightness)` — Régler la luminosité

```ts
brightness(brightness: number): Promise<DeviceResponse>
```

Endpoint : `POST /pos/v1/brightness`. Lève un `RangeError` si la valeur n'est pas dans `(0, 1]`.

| Paramètre | Type | Requis | Description |
|-----------|------|--------|-------------|
| `brightness` | `number` | **oui** | Valeur dans `(0, 1]`, où `1.00` = luminosité maximale. La valeur `0` n'est **pas** autorisée. |

```ts
await terminal.device.brightness(0.5); // 50 %
await terminal.device.brightness(1.0); // maximum
// await terminal.device.brightness(0); // ❌ RangeError
```

---

## Enums

Tous les enums sont exportés depuis le point d'entrée du package.

### `SessionState`

```ts
enum SessionState {
  PROCESSING = "PROCESSING",
  BUSY_ERROR = "BUSY_ERROR",
  SERVER_ERROR = "SERVER_ERROR",
  SESSION_NOT_FOUND_ERROR = "SESSION_NOT_FOUND_ERROR",
  SUCCESS = "SUCCESS",
  FAILURE = "FAILURE",
}
```

| Valeur | Description |
|--------|-------------|
| `PROCESSING` | L'opération est en cours sur le terminal |
| `BUSY_ERROR` | Une autre transaction est déjà en cours sur le terminal |
| `SERVER_ERROR` | Le terminal a signalé une erreur côté serveur |
| `SESSION_NOT_FOUND_ERROR` | Le sessionId fourni est introuvable (abort) |
| `SUCCESS` | La transaction s'est terminée avec succès (résultat de session) |
| `FAILURE` | La transaction s'est terminée en erreur (résultat de session) |

### `SessionType`

```ts
enum SessionType {
  SALE = "SALE",
  CAPTURE_PREAUTH = "CAPTURE_PREAUTH",
  CANCEL = "CANCEL",
  UNREFERENCED_REFUND = "UNREFERENCED_REFUND",
  ABORT = "ABORT",
}
```

| Valeur | Description |
|--------|-------------|
| `SALE` | Transaction de vente |
| `CAPTURE_PREAUTH` | Capture (complétion) d'une vente pré-autorisée |
| `CANCEL` | Remboursement / annulation référençant une transaction d'origine |
| `UNREFERENCED_REFUND` | Remboursement non référencé |
| `ABORT` | Interruption d'une session SALE en cours |

### `TransactionTypeId`

```ts
enum TransactionTypeId {
  SALE = 5,
  REFUND = 4,
  REVERSAL = 7,
  IRIS_SALE = 60,
  IRIS_REFUND = 61,
  IRIS_VOID = 85,
}
```

| Constante | Valeur | Description |
|-----------|--------|-------------|
| `SALE` | 5 | Vente |
| `REFUND` | 4 | Remboursement |
| `REVERSAL` | 7 | Annulation (reversal) |
| `IRIS_SALE` | 60 | Vente IRIS |
| `IRIS_REFUND` | 61 | Remboursement IRIS |
| `IRIS_VOID` | 85 | Annulation IRIS |

### `PaymentMethod`

```ts
enum PaymentMethod {
  CARD_PRESENT = "CardPresent",
  IRIS = "Iris",
}
```

Accepté par le champ `paymentMethod` de `SaleParams` — spécifie la méthode affichée en premier. L'utilisateur peut toujours en choisir une autre sur le terminal.

---

## Gestion des erreurs

> ⚠️ **Erreurs 400 = chaînes brutes.** La Local Terminal API ne retourne **pas** de codes d'erreur structurés. Sur une 400, elle renvoie un **corps en chaîne de caractères brute** (ex : `"ZeroconfException error message Amount cannot be null"`). Le SDK préserve ce texte verbatim dans `ApiError.message`.

### Hiérarchie

```
Error
└── VivaError      // Classe de base de toute erreur du SDK
    └── ApiError   // Levée sur 4xx/5xx ET sur échec de transport (terminal injoignable)
```

### Propriétés et méthodes d'`ApiError`

| Membre | Type | Description |
|--------|------|-------------|
| `message` | `string` | Texte d'erreur — la chaîne brute du terminal verbatim sur une 400 |
| `httpStatus` | `number` | Code HTTP de la réponse (`0` si le terminal est injoignable) |
| `responseBody` | `VivaErrorBody \| null` | Corps d'erreur. Chaîne brute préservée sous `{ raw: "..." }` quand ce n'est pas du JSON |
| `getErrorText()` | `string \| null` | `message`, `error`, puis fallback sur `raw` |

### Exemple complet

```ts
import { ApiError } from "@qrcommunication/viva-local-terminal-sdk";

try {
  const res = await terminal.transactions.sale({
    sessionId,
    amount: 1170,
  });
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.httpStatus);            // 400, ou 0 si injoignable sur le LAN
    console.log(err.getErrorText());        // ex: "ZeroconfException error message Amount cannot be null"
    console.log(err.responseBody?.raw);     // chaîne brute du terminal (si 400 non-JSON)

    if (err.httpStatus === 0) {
      // Le terminal n'est pas joignable sur le LAN — vérifier IP/port/réseau
    }
  } else {
    throw err;
  }
}
```

> **Terminal injoignable :** si la requête ne peut pas atteindre le terminal (mauvaise IP, hors réseau, terminal éteint), une `ApiError` est levée avec `httpStatus === 0`.

---

## Variantes Merchant vs ISV

La spec expose chaque endpoint en deux variantes : **merchant** (sans slash final) et **ISV** (avec slash final, ex : `/pos/v1/sale/`). Plutôt que de dupliquer les méthodes, le SDK utilise **un seul jeu de méthodes** plus le toggle global `useIsvEndpoints`.

```ts
// Mode ISV : sale/refund acceptent le champ `isvDetails`,
// et toutes les requêtes ciblent les chemins à slash final (/pos/v1/sale/)
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
  useIsvEndpoints: true,
});

await terminal.transactions.sale({
  sessionId,
  amount: 1170,
  isvDetails: { /* objet partenaire / fee / multi-marchand ISV */ },
});
```

`isvDetails` est omis du corps quand il est `undefined`, il est donc inoffensif pour les appels merchant.

---

## Changelog

### v1.0.0

- Version initiale du SDK (jumeau TypeScript du SDK PHP `qrcommunication/viva-local-terminal-sdk`).
- Modèle **P2P sans authentification** : communication directe avec le terminal sur le LAN, aucun header `Authorization`.
- TLS configurable (`verifyTls: false` ou `dispatcher` undici personnalisé) pour les certificats auto-signés.
- Ressource **Transactions** : `sale()`, `capturePreauth()`, `refund()`, `unreferencedRefund()`, `abort()`, `aadeFimControl()`, `retrieve()`.
- Ressource **Sessions** : `get()` (polling du résultat).
- Ressource **Device** : `screenControl()`, `brightness()`.
- Enums : `SessionState`, `SessionType`, `TransactionTypeId`, `PaymentMethod`.
- Support des variantes Merchant et ISV via `useIsvEndpoints`.
- Préservation des erreurs 400 sous forme de chaîne brute ; `ApiError` avec `httpStatus === 0` sur terminal injoignable.
- TypeScript strict, ESM + CJS + `.d.ts`.
