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

# Viva Cloud Terminal SDK — Documentation TypeScript

SDK TypeScript pour la **Cloud Terminal API** de Viva Wallet — la variante
**marchand** (`/ecr/v1/`) de la solution EFT POS de Viva. Il permet à votre
back-office (ECR / caisse / application web) de piloter un terminal de paiement
physique via l'API REST de Viva : initier des ventes, capturer des
pré-autorisations, rembourser, et poller le résultat des sessions.

Isomorphe : fonctionne dans Node 18+, le navigateur, Deno, les edge runtimes et
React Native — il ne dépend que du `fetch` de la plateforme.

- **Version** : 1.0.0
- **Node** : 18.17+
- **Dépendances runtime** : aucune (utilise le `fetch` natif)
- **Licence** : MIT
- **npm** : `@qrcommunication/viva-cloud-terminal-sdk`
- **Auth** : OAuth2 `client_credentials` → Bearer token
- **Montants** : toujours en **centimes** (`number`) — `1170` = 11,70 EUR

> Ce SDK couvre **uniquement** les endpoints marchand `/ecr/v1/`. Pour la
> variante ISV / Partner (`/ecr/isv/v1/`, marchands connectés, composite auth),
> utiliser un SDK distinct.

> **Sécurité** : ne jamais embarquer votre `clientSecret` dans un bundle
> navigateur / public. Exécutez le SDK sur un serveur (ou edge) que vous
> contrôlez et proxifiez les requêtes depuis le client.

---

## Table des matières

1. [Quoi de neuf en v1.0.0](#changelog)
2. [Installation](#installation)
3. [Démarrage rapide](#démarrage-rapide)
4. [Configuration](#configuration)
5. [Authentification](#authentification)
6. [Ressources](#ressources)
   - [Devices](#1-devices--vivadevices)
   - [Transactions](#2-transactions--vivatransactions)
   - [Sessions](#3-sessions--vivasessions)
   - [Polling](#4-polling--polluntilcomplete)
7. [Enums & helpers](#enums--helpers)
8. [Gestion des erreurs](#gestion-des-erreurs)
9. [Carte de test](#carte-de-test)

---

## Quoi de neuf en v1.0.0

Première version stable du SDK Cloud Terminal (marchand `/ecr/v1/`).

- **Client** : `VivaCloudTerminalClient` — point d'entrée unique avec auth OAuth2
  automatique (Bearer token caché et rafraîchi 60s avant expiration).
- **`devices`** — `search()` (`POST /ecr/v1/devices:search`).
- **`transactions`** — `sale()`, `capturePreauth()`, `refund()`,
  `unreferencedRefund()`, `fastRefund()`, `rebate()`, `createAction()`,
  `getAction()`.
- **`sessions`** — `get()`, `listByDate()`, `abort()`, `pollUntilComplete()`.
- **`pollUntilComplete()`** — disponible aussi comme raccourci sur le client.
- **Constantes `EcrEventId`** + helpers (`ecrEventIdFrom`, `isSuccessfulEvent`,
  `isInProgress`, `ecrEventLabel`).
- Isomorphe (Node / browser / Deno / edge / React Native), zéro dépendance.
- Montants en **centimes**, payloads en **camelCase**.

---

## Installation

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

---

## Démarrage rapide

```ts
import { VivaCloudTerminalClient } from "@qrcommunication/viva-cloud-terminal-sdk";

const viva = new VivaCloudTerminalClient({
  clientId: "your-client-id",
  clientSecret: "your-client-secret",
  environment: "demo", // "demo" (sandbox) ou "production"
});

// 1. (recommandé) confirmer que le terminal est Live avant de transiger
const devices = await viva.devices.search({ statusId: 1 });

// 2. initier une vente sur le terminal (montant en centimes)
const sale = await viva.transactions.sale({
  terminalId: "16000010",
  amount: 1170, // 11,70 EUR
  cashRegisterId: "CR-01",
  merchantReference: "order-42",
});

// 3. poller jusqu'à ce que le client termine (ou décline) sur le terminal
const result = await viva.pollUntilComplete(sale.sessionId);

if (result.success === true) {
  // result.transactionId, result.amount, ...
}
```

---

## Configuration

### Options du constructeur `VivaCloudTerminalClient`

```ts
new VivaCloudTerminalClient(options: VivaCloudTerminalOptions)

interface VivaCloudTerminalOptions {
  clientId: string;
  clientSecret: string;
  environment?: Environment;   // défaut: "demo"
  timeoutMs?: number;          // défaut: 30000
  fetch?: typeof fetch;        // défaut: fetch global
}
```

| Option | Type | Requis | Description |
|--------|------|--------|-------------|
| `clientId` | `string` | Oui | Client ID OAuth2 (grant `client_credentials`) |
| `clientSecret` | `string` | Oui | Secret OAuth2 |
| `environment` | `Environment` | Non | `"demo"` (défaut, sandbox) ou `"production"` |
| `timeoutMs` | `number` | Non | Timeout des requêtes en ms (défaut : 30000) |
| `fetch` | `typeof fetch` | Non | Implémentation `fetch` custom (polyfill / mock) |

### Méthodes utilitaires du client

```ts
viva.getConfig(): CloudTerminalConfig         // Configuration résolue (hôtes, env, timeout)
viva.invalidateToken(): void                  // Forcer un nouveau handshake OAuth
viva.pollUntilComplete(sessionId, options?): Promise<Session> // Raccourci
```

### Propriétés publiques

```ts
viva.devices        // DevicesResource
viva.transactions   // TransactionsResource
viva.sessions       // SessionsResource
```

Chaque méthode `transactions.*` retourne un `TransactionResult` :

```ts
interface TransactionResult {
  sessionId: string;                  // pollez-le pour le résultat
  response: Record<string, unknown>;  // body API brut (souvent vide en succès)
}
```

---

## Authentification

La Cloud Terminal API utilise un **seul** mode d'authentification :

| Étape | Hôte | Auth |
|-------|------|------|
| Token | `accounts.vivapayments.com/connect/token` (demo : `demo-accounts...`) | Basic `client_id:client_secret`, `grant_type=client_credentials` |
| Appels API | `api.vivapayments.com/ecr/v1/...` (demo : `demo-api...`) | `Authorization: Bearer {token}` |

Le SDK récupère et met en cache le Bearer token automatiquement (rafraîchi 60s
avant expiration). Appeler `viva.invalidateToken()` pour forcer un nouveau
handshake.

> **CRITIQUE** : tous les payloads `/ecr/v1/` sont en **camelCase**.

---

## Ressources

### 1. Devices — `viva.devices`

Découverte des terminaux POS (EFT POS) et de leur statut. Recommandé en
pré-flight avant toute transaction, surtout sur les connexions WAN, pour
confirmer que le terminal est `Live` (`statusId` 1).

#### `search()` — Rechercher les terminaux POS

```ts
search(params?: DeviceSearchParams): Promise<JsonObject>

interface DeviceSearchParams {
  statusId?: number;
  sourceCode?: string;
}
```

Endpoint : `POST /ecr/v1/devices:search`.

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `statusId` | `number` | — | Filtrer par statut du terminal |
| `sourceCode` | `string` | — | Code d'identification personnalisé assigné au terminal par le marchand |

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

**Retourne :** une liste de terminaux. Chaque entrée contient : `terminalId`
(string), `statusId` (number), `sourceCode` (string), `virtualTerminalId`
(string).

```ts
const devices = (await viva.devices.search({ statusId: 1 })) as unknown as Device[];

for (const device of devices) {
  console.log(`${device.terminalId} — statut ${device.statusId}`);
  // 16000010 — statut 1
}
```

---

### 2. Transactions — `viva.transactions`

Opérations transactionnelles marchand : vente, capture de pré-autorisation,
remboursements, rebate et actions sur le terminal. Les champs optionnels
partagés sont définis par `TransactionCommonParams` :

```ts
interface TransactionCommonParams {
  merchantReference?: string;       // défaut: valeur "SDK-..." générée
  currencyCode?: string;            // défaut: "978" (EUR)
  customerTrns?: string;
  showTransactionResult?: boolean;
  showReceipt?: boolean;
  sessionId?: string;               // auto-généré (UUID v4) si omis
  extra?: Record<string, unknown>;  // champs API additionnels fusionnés tels quels
}
```

> **Body vide sur succès** : la plupart des endpoints `/ecr/v1/transactions:*`
> répondent HTTP 200 **sans body** — c'est le succès du *dispatch* vers le
> terminal, pas le résultat du paiement. Chaque méthode retourne donc un
> `TransactionResult` : pollez `sessionId` pour le résultat réel.

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

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

interface SaleParams extends TransactionCommonParams {
  terminalId: string | number;
  amount: number;
  cashRegisterId: string;
  tipAmount?: number;
  preauth?: boolean;
  paymentMethod?: string;
  skipSurcharge?: boolean;
}
```

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

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `terminalId` | `string \| number` | **requis** | ID du terminal cible (ex : `"16000010"`) |
| `amount` | `number` | **requis** | Montant à autoriser, en centimes |
| `cashRegisterId` | `string` | **requis** | Identification de la caisse (défini par le marchand) |
| `tipAmount` | `number` | `0` | Pourboire en centimes (incompatible avec `preauth`) |
| `preauth` | `boolean` | — | Flag de pré-autorisation (nécessite activation Viva) |
| `paymentMethod` | `string` | — | Méthode de paiement affichée par défaut (ex : `"CardPresent"`) |
| `skipSurcharge` | `boolean` | — | Désactiver la surcharge pour cette transaction |
| `merchantReference` | `string` | `"SDK-{sessionId}"` | Référence marchand (texte libre) |
| `currencyCode` | `string` | `"978"` | Code ISO 4217 numérique en string |
| `customerTrns` | `string` | — | Référence client (texte libre) |
| `showTransactionResult` | `boolean` | — | Afficher le résultat sur le terminal |
| `showReceipt` | `boolean` | — | Afficher le reçu + résultat sur le terminal |
| `sessionId` | `string` | auto (UUID) | UUID de session |
| `extra` | `object` | `{}` | Champs API additionnels (ex : `fiscalisationData`) |

**Retourne :** `Promise<TransactionResult>`

```ts
const sale = await viva.transactions.sale({
  terminalId: "16000010",
  amount: 2599, // 25,99 EUR
  cashRegisterId: "CR-01",
  customerTrns: "Table 12",
  showReceipt: true,
});

const final = await viva.pollUntilComplete(sale.sessionId, { timeoutSeconds: 90 });
```

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

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

interface CapturePreauthParams extends TransactionCommonParams {
  parentSessionId: string;
  terminalId: string | number;
  amount: number;
  cashRegisterId: string;
}
```

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

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `parentSessionId` | `string` | **requis** | UUID de session de la pré-autorisation à capturer |
| `terminalId` | `string \| number` | **requis** | ID du terminal cible |
| `amount` | `number` | **requis** | Montant à capturer, en centimes |
| `cashRegisterId` | `string` | **requis** | Identification de la caisse |
| `merchantReference` | `string` | `"SDK-capture-{sessionId}"` | Référence marchand |
| `currencyCode` | `string` | `"978"` | Code ISO 4217 numérique en string |
| `customerTrns` | `string` | — | Référence client |
| `showTransactionResult` | `boolean` | — | Afficher le résultat sur le terminal |
| `showReceipt` | `boolean` | — | Afficher le reçu + résultat sur le terminal |
| `sessionId` | `string` | auto (UUID) | UUID de session pour la capture |
| `extra` | `object` | `{}` | Champs API additionnels |

**Retourne :** `Promise<TransactionResult>`

```ts
// Pré-autoriser (nécessite preauth activé sur le compte Viva)
const preauth = await viva.transactions.sale({
  terminalId: "16000010",
  amount: 5000,
  cashRegisterId: "CR-01",
  preauth: true,
});
await viva.pollUntilComplete(preauth.sessionId);

// Plus tard, capturer le montant final (peut être inférieur à l'autorisé)
const capture = await viva.transactions.capturePreauth({
  parentSessionId: preauth.sessionId,
  terminalId: "16000010",
  amount: 4200,
  cashRegisterId: "CR-01",
});
await viva.pollUntilComplete(capture.sessionId);
```

#### `refund()` — Rembourser une vente (référencé)

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

interface RefundParams extends TransactionCommonParams {
  parentSessionId: string;
  terminalId: string | number;
  amount: number;
  cashRegisterId: string;
}
```

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

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `parentSessionId` | `string` | **requis** | UUID de session de la vente originale à rembourser |
| `terminalId` | `string \| number` | **requis** | ID du terminal cible |
| `amount` | `number` | **requis** | Montant à rembourser, en centimes |
| `cashRegisterId` | `string` | **requis** | Identification de la caisse |
| `merchantReference` | `string` | `"SDK-refund-{sessionId}"` | Référence marchand |
| `currencyCode` | `string` | `"978"` | Code ISO 4217 numérique en string |
| `customerTrns` | `string` | — | Référence client |
| `showTransactionResult` | `boolean` | — | Afficher le résultat sur le terminal |
| `showReceipt` | `boolean` | — | Afficher le reçu + résultat sur le terminal |
| `sessionId` | `string` | auto (UUID) | UUID de session pour le remboursement |
| `extra` | `object` | `{}` | Champs API additionnels |

**Retourne :** `Promise<TransactionResult>`

```ts
const refund = await viva.transactions.refund({
  parentSessionId: sale.sessionId,
  terminalId: "16000010",
  amount: 1170,
  cashRegisterId: "CR-01",
});
await viva.pollUntilComplete(refund.sessionId);
```

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

```ts
unreferencedRefund(params: StandaloneRefundParams): Promise<TransactionResult>

interface StandaloneRefundParams extends TransactionCommonParams {
  terminalId: string | number;
  amount: number;
  cashRegisterId: string;
}
```

Endpoint : `POST /ecr/v1/transactions:unreferenced-refund`. Remboursement
autonome, non lié à une vente originale.

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `terminalId` | `string \| number` | **requis** | ID du terminal cible |
| `amount` | `number` | **requis** | Montant à rembourser, en centimes |
| `cashRegisterId` | `string` | **requis** | Identification de la caisse |
| `merchantReference` | `string` | `"SDK-unref-refund-{sessionId}"` | Référence marchand |
| `currencyCode` | `string` | `"978"` | Code ISO 4217 numérique en string |
| `customerTrns` | `string` | — | Référence client |
| `showTransactionResult` | `boolean` | — | Afficher le résultat sur le terminal |
| `showReceipt` | `boolean` | — | Afficher le reçu + résultat sur le terminal |
| `sessionId` | `string` | auto (UUID) | UUID de session |
| `extra` | `object` | `{}` | Champs API additionnels (ex : `fiscalisationData`) |

**Retourne :** `Promise<TransactionResult>`

```ts
const refund = await viva.transactions.unreferencedRefund({
  terminalId: "16000010",
  amount: 1500,
  cashRegisterId: "CR-01",
});
await viva.pollUntilComplete(refund.sessionId);
```

#### `fastRefund()` — Remboursement rapide (Visa/MC/Maestro)

```ts
fastRefund(params: StandaloneRefundParams): Promise<TransactionResult>
```

Endpoint : `POST /ecr/v1/transactions:fast-refund`. Remboursement swift sur
Visa / Mastercard / Maestro. Mêmes paramètres (`StandaloneRefundParams`) que
`unreferencedRefund()`. Référence par défaut : `"SDK-fast-refund-{sessionId}"`.

**Retourne :** `Promise<TransactionResult>`

```ts
const refund = await viva.transactions.fastRefund({
  terminalId: "16000010",
  amount: 1170,
  cashRegisterId: "CR-01",
});
await viva.pollUntilComplete(refund.sessionId);
```

#### `rebate()` — Rabais sur carte

```ts
rebate(params: StandaloneRefundParams): Promise<TransactionResult>
```

Endpoint : `POST /ecr/v1/transactions:rebate`. Rabais vers une carte
Visa / Mastercard / Maestro. Mêmes paramètres (`StandaloneRefundParams`) que
`unreferencedRefund()`. Référence par défaut : `"SDK-rebate-{sessionId}"`.

**Retourne :** `Promise<TransactionResult>`

```ts
const rebate = await viva.transactions.rebate({
  terminalId: "16000010",
  amount: 500,
  cashRegisterId: "CR-01",
});
await viva.pollUntilComplete(rebate.sessionId);
```

#### `createAction()` — Créer une action sur le terminal

```ts
createAction(payload: CreateActionParams): Promise<JsonObject>

type CreateActionParams = Record<string, unknown>;
```

Endpoint : `POST /ecr/v1/actions`. Crée une action à invoquer sur le terminal
(ex : `aade-fim-control`). Le payload est transmis tel quel (camelCase) et doit
inclure `terminalId`, `cashRegisterId`, `actionType` et `request`.

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `payload` | `object` | **requis** | Corps de la requête d'action |

**Retourne :** `Promise<JsonObject>` — notamment `actionId` à passer à
`getAction()`.

```ts
const action = await viva.transactions.createAction({
  terminalId: "16000010",
  cashRegisterId: "XDE384678UY",
  actionType: "aade-fim-control",
  request: {
    token: "ECR0210U/RCFB77000041/CMAC_K:299BFD51...:CC5FFF",
  },
});

console.log(action.actionId); // f7287549-e93a-4d33-b936-000027d326d0
```

#### `getAction()` — Récupérer le résultat d'une action

```ts
getAction(actionId: string): Promise<JsonObject>
```

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

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `actionId` | `string` | **requis** | ID de l'action (retourné par `createAction()`) |

**Retourne :** `Promise<JsonObject>` (`successfullyProcessed`, `response`, ...).

```ts
const result = await viva.transactions.getAction(
  "f7287549-e93a-4d33-b936-000027d326d0",
);

if (result.successfullyProcessed === true) {
  console.log(result.response); // { status: "success", message: "..." }
}
```

---

### 3. Sessions — `viva.sessions`

Récupération, listing et abort des sessions Cloud Terminal. Une session
représente le cycle de vie d'une transaction sur un terminal. Après avoir initié
une transaction, pollez la session par son id pour observer son résultat
(`eventId`, `success`, `transactionId`, ...).

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

```ts
get(sessionId: string): Promise<Session>

interface Session extends Record<string, unknown> {
  eventId?: number;
  success?: boolean;
  transactionId?: string;
  amount?: number;
}
```

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

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `sessionId` | `string` | **requis** | UUID de la session |

**Retourne :** `Promise<Session>` (`success`, `eventId`, `transactionId`,
`amount`, ...).

```ts
const session = await viva.sessions.get(sale.sessionId);

console.log(session.eventId);       // 0 = succès
console.log(session.transactionId); // UUID de la transaction Viva
```

#### `listByDate()` — Lister les sessions d'une date

```ts
listByDate(params: ListSessionsParams): Promise<JsonObject>

interface ListSessionsParams {
  date: string;                   // format YYYY-MM-DD
  aadeAutonomouslyOnly?: boolean; // Grèce uniquement
}
```

Endpoint : `GET /ecr/v1/sessions?date=YYYY-MM-DD`.

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `date` | `string` | **requis** | Date au format `YYYY-MM-DD` |
| `aadeAutonomouslyOnly` | `boolean` | — | Filtrer aux sessions AADE-autonomes uniquement (Grèce) |

**Retourne :** une liste de sessions.

```ts
const sessions = await viva.sessions.listByDate({ date: "2026-06-06" });
```

#### `abort()` — Annuler une session active

```ts
abort(sessionId: string, cashRegisterId: string): Promise<JsonObject>
```

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

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `sessionId` | `string` | **requis** | UUID de la session à annuler |
| `cashRegisterId` | `string` | **requis** | Identification de la caisse |

**Retourne :** `Promise<JsonObject>`

```ts
await viva.sessions.abort(sale.sessionId, "CR-01");
```

---

### 4. Polling — `pollUntilComplete()`

```ts
pollUntilComplete(sessionId: string, options?: PollOptions): Promise<Session>

interface PollOptions {
  timeoutSeconds?: number; // défaut: 120
  intervalMs?: number;     // défaut: 3000
}
```

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).

Disponible sur la ressource `sessions` **et** comme raccourci sur le client.

| Paramètre | Type | Défaut | Description |
|-----------|------|--------|-------------|
| `sessionId` | `string` | **requis** | UUID de la session à poller |
| `options.timeoutSeconds` | `number` | `120` | Temps d'attente total maximum (secondes) |
| `options.intervalMs` | `number` | `3000` | Délai entre deux tentatives (millisecondes) |

**Retourne :** l'état final de la session. Si le timeout est atteint sans
réponse, retourne `{ success: false, eventId: 1003, message: "SDK poll timeout" }`.

```ts
import { ecrEventIdFrom, isSuccessfulEvent } from "@qrcommunication/viva-cloud-terminal-sdk";

const result = await viva.pollUntilComplete(sale.sessionId, { timeoutSeconds: 90 });

const event = ecrEventIdFrom(result.eventId);
if (event !== null && isSuccessfulEvent(event)) {
  // Paiement confirmé
}
```

---

## Enums & helpers

### `EcrEventId`

```ts
import {
  EcrEventId,
  ecrEventIdFrom,
  isSuccessfulEvent,
  isInProgress,
  isTerminalEvent,
  ecrEventLabel,
} from "@qrcommunication/viva-cloud-terminal-sdk";

const EcrEventId = {
  SUCCESS: 0,
  TERMINAL_TIMEOUT: 1003,
  DECLINED: 1006,
  ABORTED: 1016,
  INSUFFICIENT_FUNDS: 1020,
  GENERIC_ERROR: 1099,
  IN_PROGRESS: 1100,
  BAD_PARAMS: 6000,
} as const;
```

| eventId | Constante | Sens |
|---------|-----------|------|
| 0 | `SUCCESS` | Transaction réussie |
| 1003 | `TERMINAL_TIMEOUT` | Timeout terminal |
| 1006 | `DECLINED` | Refusée |
| 1016 | `ABORTED` | Annulée |
| 1020 | `INSUFFICIENT_FUNDS` | Fonds insuffisants |
| 1099 | `GENERIC_ERROR` | Erreur générique |
| 1100 | `IN_PROGRESS` | En cours (continuer à poller) |
| 6000 | `BAD_PARAMS` | Paramètres incorrects |

| Helper | Signature | Description |
|--------|-----------|-------------|
| `ecrEventIdFrom` | `(value: number \| null \| undefined) => EcrEventId \| null` | Retourne l'événement connu ou `null` si non reconnu |
| `isSuccessfulEvent` | `(eventId: EcrEventId) => boolean` | `true` si succès |
| `isInProgress` | `(eventId: EcrEventId) => boolean` | `true` tant que `IN_PROGRESS` (1100) |
| `isTerminalEvent` | `(eventId: EcrEventId) => boolean` | `true` si état final (≠ `IN_PROGRESS`) |
| `ecrEventLabel` | `(eventId: EcrEventId) => string` | Libellé lisible |

```ts
const event = ecrEventIdFrom(result.eventId);
if (event !== null) {
  isSuccessfulEvent(event); // true en cas de succès
  isInProgress(event);      // true tant que IN_PROGRESS (1100)
  ecrEventLabel(event);     // libellé lisible
}
```

### `Environment`

```ts
import { Environment } from "@qrcommunication/viva-cloud-terminal-sdk";

const Environment = {
  DEMO: "demo",
  PRODUCTION: "production",
} as const;
```

| Constante | Hôte OAuth (token) | Hôte API (`/ecr/v1/`) |
|-----------|--------------------|------------------------|
| `DEMO` | `https://demo-accounts.vivapayments.com` | `https://demo-api.vivapayments.com` |
| `PRODUCTION` | `https://accounts.vivapayments.com` | `https://api.vivapayments.com` |

---

## Gestion des erreurs

Toutes les erreurs héritent de `VivaError`.

### Hiérarchie

```
Error
└── VivaError                  // Classe de base
    ├── ApiError               // Erreurs HTTP 4xx / 5xx
    └── AuthenticationError    // OAuth2 invalide (401)
```

### Méthodes / propriétés de `ApiError`

| Membre | Type | Description |
|--------|------|-------------|
| `httpStatus` | `number` | Code HTTP de la réponse |
| `getErrorText()` | `string \| null` | Message d'erreur Viva lisible |
| `getErrorCode()` | `number \| null` | Code d'erreur Viva, si présent |

### Exemple complet

```ts
import {
  ApiError,
  AuthenticationError,
  VivaError,
} from "@qrcommunication/viva-cloud-terminal-sdk";

try {
  await viva.transactions.sale({
    terminalId: "16000010",
    amount: 1170,
    cashRegisterId: "CR-01",
  });
} catch (error) {
  if (error instanceof AuthenticationError) {
    // Handshake OAuth échoué (client id/secret invalides)
  } else if (error instanceof ApiError) {
    error.httpStatus;     // ex : 409
    error.getErrorText(); // message Viva lisible
    error.getErrorCode(); // code Viva, si présent
  } else if (error instanceof VivaError) {
    // Classe de base pour toutes les erreurs SDK
  }
}
```

---

## Carte de test

Environnement `demo` (sandbox).

| Champ | Valeur |
|-------|--------|
| Numéro de carte | `4111 1111 1111 1111` |
| CVV | `111` |
| Mot de passe 3DS | `Secret!33` |

Montants de déclin courants : `9951` (fonds insuffisants), `9954` (carte
expirée), `9920` (carte volée), `9957` (non autorisé), `9961` (limite de
retrait).
