---
title: "SDK PHP Viva Wallet ISV — Référence complète"
description: "SDK PHP complet pour l'API Viva Wallet ISV Partner. 10 ressources, 36 méthodes, 3 modes d'authentification, terminaux Cloud POS, webhooks, Native Checkout."
package: "qrcommunication/viva-isv-sdk"
version: "1.3.5"
language: "PHP"
requires: "PHP 8.2+, ext-json, ext-curl"
license: "MIT"
github: "https://github.com/QrCommunication/sdk-php-viva-isv"
packagist: "https://packagist.org/packages/qrcommunication/viva-isv-sdk"
interactive_docs: "https://qrcommunication.github.io/sdk-php-viva-isv/"
locale: "fr"
generated_at: "2026-03-29"
---

# SDK PHP Viva Wallet ISV

SDK PHP complet pour l'API **Viva Wallet ISV Partner**. 10 ressources couvrant : comptes connectés, comptes ISV, ordres ISV avec commission, transactions (capture, récurrent, annulation), terminaux Cloud POS, transferts marketplace, ordres marketplace, Native Checkout ISV, webhooks ISV (CRUD) et parsing (21 événements).

> Ce SDK couvre les opérations ISV (comptes connectés, composite auth, commission). Pour les opérations marchands standard, voir `sdk-php-viva-merchant`.

---

## Table des matières

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [Configuration](#configuration)
4. [Authentification (3 modes)](#authentification)
5. [Ressources](#ressources)
   - [1. ConnectedAccounts](#1-connectedaccounts)
   - [2. IsvAccounts](#2-isvaccounts)
   - [3. IsvOrders](#3-isvorders)
   - [4. IsvTransactions](#4-isvtransactions)
   - [5. EcrTerminals](#5-ecrterminals)
   - [6. Transfers](#6-transfers)
   - [7. MarketplaceOrders](#7-marketplaceorders)
   - [8. NativeCheckoutIsv](#8-nativecheckoutisv)
   - [9. IsvWebhooks](#9-isvwebhooks)
   - [10. Webhooks](#10-webhooks)
6. [Enums](#enums)
7. [Gestion des erreurs](#gestion-des-erreurs)
8. [Événements webhook (21)](#événements-webhook-21)
9. [Test en sandbox](#test-en-sandbox)

---

## Installation

```bash
composer require qrcommunication/viva-isv-sdk
```

**Prérequis :** PHP 8.2+ avec `ext-json` et `ext-curl`.

### Où trouver les credentials

| Credential       | Emplacement dans le Dashboard Viva                                     |
|------------------|------------------------------------------------------------------------|
| `clientId`       | Settings > API Access > ISV OAuth Credentials > Client ID              |
| `clientSecret`   | Settings > API Access > ISV OAuth Credentials > Client Secret          |
| `merchantId`     | Settings > API Access > Merchant ID                                    |
| `apiKey`         | Settings > API Access > API Key                                        |
| `resellerId`     | Settings > API Access > Reseller Credentials > Reseller ID             |
| `resellerApiKey` | Settings > API Access > Reseller Credentials > Reseller API Key        |

---

## Quick Start

```php
use QrCommunication\VivaIsv\VivaIsvClient;

// 1. Instancier le client avec les 6 credentials
$isv = new VivaIsvClient(
    clientId:       'isv-client-id.apps.vivapayments.com',
    clientSecret:   'isv-client-secret',
    merchantId:     'isv-merchant-uuid',
    apiKey:         'isv-api-key',
    resellerId:     'reseller-uuid',
    resellerApiKey: 'reseller-api-key',
    environment:    'demo', // 'demo' ou 'production'
);

// 2. Créer un compte marchand connecté
$account = $isv->accounts->create(
    email: 'merchant@example.com',
    returnUrl: 'https://myapp.com/onboarding/complete',
);
// => ['accountId' => 'uuid', 'invitation' => ['redirectUrl' => 'https://...']]

// 3. Créer un ordre avec commission ISV
$order = $isv->orders->create(
    connectedMerchantId: $account['accountId'],
    amount: 1500,    // 15,00 EUR
    isvAmount: 100,  // 1,00 EUR de commission
);
// => ['order_code' => 1234567890, 'checkout_url' => 'https://...']

// 4. Rediriger le client vers le checkout
header('Location: ' . $order['checkout_url']);

// 5. Capturer une pré-autorisation
$isv->transactions->capture('preauth-txn-uuid', 'merchant-uuid', amount: 1500);

// 6. Vente sur terminal POS
$session = $isv->terminals->sale(
    terminalId: 16014231,
    amount: 1500,
    isvAmount: 100,
    terminalMerchantId: 'merchant-uuid',
    cashRegisterId: 'POS-CR1',
);
$result = $isv->terminals->pollUntilComplete($session['session_id']);

// 7. Rembourser
$isv->transactions->cancel('txn-uuid', 'merchant-uuid', amount: 500);

// Test de connexion
if ($isv->testConnection()) {
    echo 'Connexion ISV OK';
}
```

---

## Configuration

### Constructeur `VivaIsvClient`

| Paramètre        | Type              | Description                                                         |
|------------------|-------------------|---------------------------------------------------------------------|
| `clientId`       | `string`          | Client ID OAuth2 ISV (`*.apps.vivapayments.com`)                   |
| `clientSecret`   | `string`          | Client Secret OAuth2 ISV                                            |
| `merchantId`     | `string`          | Merchant UUID ISV (pour ISV Basic Auth)                             |
| `apiKey`         | `string`          | API Key ISV (pour ISV Basic Auth)                                   |
| `resellerId`     | `string`          | Reseller UUID (pour Composite Basic Auth)                           |
| `resellerApiKey` | `string`          | Reseller API Key (pour Composite Basic Auth)                        |
| `environment`    | `string\|Environment` | `'demo'` ou `'production'` (défaut : `'demo'`)                 |

### Architecture des ressources

```
VivaIsvClient
├── $accounts          → ConnectedAccounts   (7 méthodes)
├── $isvAccounts       → IsvAccounts         (3 méthodes)
├── $orders            → IsvOrders           (2 méthodes)
├── $transactions      → IsvTransactions     (5 méthodes)
├── $terminals         → EcrTerminals        (6 méthodes)
├── $transfers         → Transfers           (2 méthodes)
├── $marketplace       → MarketplaceOrders   (2 méthodes)
├── $nativeCheckout    → NativeCheckoutIsv   (2 méthodes)
├── $isvWebhooks       → IsvWebhooks         (4 méthodes)
└── $webhooks          → Webhooks            (3 méthodes)

36 méthodes au total — 10 ressources
```

---

## Authentification

Le SDK gère **3 modes d'authentification** automatiquement. Vous fournissez les 6 credentials au constructeur et le SDK choisit le bon mode pour chaque appel.

| Mode                        | Username                              | Password           | Utilisé pour                                                                              |
|-----------------------------|---------------------------------------|--------------------|-------------------------------------------------------------------------------------------|
| **ISV OAuth2 (Bearer)**     | `clientId`                            | `clientSecret`     | Comptes, ordres ISV, terminaux, transferts, marketplace, native checkout, webhooks ISV     |
| **ISV Basic Auth**          | `merchantId`                          | `apiKey`           | Opérations legacy sur le propre compte ISV                                                |
| **Composite Basic Auth**    | `{resellerId}:{connectedMerchantId}`  | `resellerApiKey`   | Transactions des marchands connectés (capture, récurrent, annulation)                     |

### Composite Basic Auth — format non documenté par Viva Wallet

Le format `{ResellerID}:{ConnectedMerchantID}` comme username avec `{ResellerAPIKey}` comme password a été découvert empiriquement lors de la certification ISV. Ce format n'est pas dans la documentation officielle. Le SDK construit ce header automatiquement — vous passez uniquement le `connectedMerchantId` aux méthodes de `$isv->transactions`.

### Refresh du token OAuth2

Le token Bearer est obtenu automatiquement et rafraîchi avant expiration (marge de 60 secondes). Pour forcer un refresh manuel :

```php
$isv->invalidateToken();
```

---

## Ressources

### 1. ConnectedAccounts

**Propriété :** `$isv->accounts`

Gestion des comptes marchands connectés à la plateforme ISV. Création, consultation, onboarding KYB, vérification, mise à jour et suppression.

| Méthode          | Signature                                                                  | Retour                        |
|------------------|----------------------------------------------------------------------------|-------------------------------|
| `create`         | `create(string $email, string $returnUrl, ?string $partnerName, ?string $logoUrl)` | `array{accountId, invitation}` |
| `get`            | `get(string $accountId)`                                                   | `array`                       |
| `list`           | `list()`                                                                   | `array`                       |
| `isVerified`     | `isVerified(string $accountId)`                                            | `bool`                        |
| `onboardingUrl`  | `onboardingUrl(string $accountId)`                                         | `?string`                     |
| `update`         | `update(string $accountId, array $attributes)`                             | `array`                       |
| `delete`         | `delete(string $accountId)`                                                | `array`                       |

```php
// Créer un compte connecté avec branding
$account = $isv->accounts->create(
    email: 'merchant@example.com',
    returnUrl: 'https://myapp.com/onboarding/complete',
    partnerName: 'Ma Plateforme',
    logoUrl: 'https://myapp.com/logo.png',
);

// Rediriger le marchand vers l'onboarding KYB
header('Location: ' . $account['invitation']['redirectUrl']);

// Vérifier le statut KYB
if ($isv->accounts->isVerified($account['accountId'])) {
    echo 'Le marchand peut recevoir des paiements';
}

// Récupérer l'URL d'onboarding (null si déjà vérifié)
$url = $isv->accounts->onboardingUrl($account['accountId']);

// Lister tous les comptes connectés
$accounts = $isv->accounts->list();

// Consulter un compte
$details = $isv->accounts->get($account['accountId']);

// Mettre à jour
$isv->accounts->update($account['accountId'], ['email' => 'new@example.com']);

// Supprimer
$isv->accounts->delete($account['accountId']);
```

---

### 2. IsvAccounts

**Propriété :** `$isv->isvAccounts`

Comptes ISV via le namespace `/isv/v1/` avec options de branding personnalisé (couleur primaire, logo).

| Méthode  | Signature                                                                                          | Retour                        |
|----------|----------------------------------------------------------------------------------------------------|-------------------------------|
| `create` | `create(string $email, string $returnUrl, ?string $partnerName, ?string $primaryColor, ?string $logoUrl)` | `array{accountId, invitation}` |
| `get`    | `get(string $accountId)`                                                                           | `array`                       |
| `list`   | `list()`                                                                                           | `array`                       |

```php
$account = $isv->isvAccounts->create(
    email: 'merchant@example.com',
    returnUrl: 'https://myapp.com/onboarding/done',
    partnerName: 'Ma Plateforme',
    primaryColor: '#0052FF',
    logoUrl: 'https://myapp.com/logo.png',
);

$details = $isv->isvAccounts->get($account['accountId']);
$all     = $isv->isvAccounts->list();
```

---

### 3. IsvOrders

**Propriété :** `$isv->orders`

Création d'ordres de paiement Smart Checkout pour les marchands connectés avec commission ISV.

| Méthode        | Signature                                                                                                                                    | Retour                          |
|----------------|----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|
| `create`       | `create(string $connectedMerchantId, int $amount, int $isvAmount, ?string $customerDescription, ?string $merchantReference, bool $allowRecurring, bool $preauth)` | `array{order_code, checkout_url}` |
| `checkoutUrl`  | `checkoutUrl(int $orderCode)`                                                                                                                | `string`                        |

```php
$order = $isv->orders->create(
    connectedMerchantId: 'merchant-uuid',
    amount: 1500,                    // 15,00 EUR
    isvAmount: 100,                  // 1,00 EUR de commission
    customerDescription: 'Consultation bien-être',
    merchantReference: 'INV-2026-001',
    allowRecurring: true,            // Tokeniser la carte pour les paiements récurrents
    preauth: false,
);

echo $order['order_code'];    // 1234567890
echo $order['checkout_url'];  // https://demo.vivapayments.com/web/checkout?ref=1234567890

// Reconstruire l'URL à partir d'un code existant
$url = $isv->orders->checkoutUrl(1234567890);
```

**Règles `isvAmount` :**
- `isvAmount` doit être `<= amount` — sinon `InvalidArgumentException`.
- Le marchand connecté utilise sa source de paiement par défaut — ne jamais passer de `sourceCode`.

---

### 4. IsvTransactions

**Propriété :** `$isv->transactions`

Opérations sur les transactions des marchands connectés. Le SDK utilise automatiquement le **Composite Basic Auth** pour tous les appels de cette ressource.

| Méthode       | Signature                                                                                                   | Retour   |
|---------------|-------------------------------------------------------------------------------------------------------------|----------|
| `get`         | `get(string $transactionId, string $connectedMerchantId)`                                                   | `array`  |
| `listByDate`  | `listByDate(string $connectedMerchantId, string $date)`                                                     | `array`  |
| `capture`     | `capture(string $transactionId, string $connectedMerchantId, int $amount, ?int $isvAmount)`                 | `array`  |
| `recurring`   | `recurring(string $initialTransactionId, string $connectedMerchantId, int $amount, ?int $isvAmount, ?string $sourceCode)` | `array`  |
| `cancel`      | `cancel(string $transactionId, string $connectedMerchantId, ?int $amount, ?string $sourceCode)`             | `array`  |

```php
// Consulter une transaction
$txn = $isv->transactions->get('txn-uuid', 'merchant-uuid');

// Lister les transactions d'une journée
$transactions = $isv->transactions->listByDate('merchant-uuid', '2026-03-18');

// Capturer une pré-autorisation
$isv->transactions->capture(
    transactionId: 'preauth-txn-uuid',
    connectedMerchantId: 'merchant-uuid',
    amount: 1500,
    isvAmount: 100,
);

// Paiement récurrent (à partir d'une transaction initiale tokenisée)
$isv->transactions->recurring(
    initialTransactionId: 'initial-txn-uuid',
    connectedMerchantId: 'merchant-uuid',
    amount: 1500,
    isvAmount: 100,
);

// Remboursement total
$isv->transactions->cancel('txn-uuid', 'merchant-uuid');

// Remboursement partiel (5,00 EUR)
$isv->transactions->cancel('txn-uuid', 'merchant-uuid', amount: 500);
```

> **Prérequis :** L'option « Allow recurring payments and pre-auth captures via API » doit être activée dans Settings > API Access du compte ISV.

---

### 5. EcrTerminals

**Propriété :** `$isv->terminals`

Terminaux de paiement Cloud POS ISV. Recherche, vente, polling de sessions, abort.

| Méthode              | Signature                                                                                                                                  | Retour                    |
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|
| `search`             | `search(?string $merchantId, ?int $statusId, ?string $sourceCode)`                                                                        | `array`                   |
| `sale`               | `sale(int $terminalId, int $amount, int $isvAmount, string $terminalMerchantId, string $cashRegisterId, ?string $merchantReference, int $currencyCode, ?string $sessionId)` | `array{session_id, success}` |
| `getSession`         | `getSession(string $sessionId)`                                                                                                            | `array`                   |
| `listSessions`       | `listSessions(string $date)`                                                                                                               | `array`                   |
| `abort`              | `abort(string $sessionId, string $cashRegisterId)`                                                                                         | `array`                   |
| `pollUntilComplete`  | `pollUntilComplete(string $sessionId, int $timeoutSeconds, int $intervalMs)`                                                               | `array`                   |

```php
use QrCommunication\VivaIsv\Enums\EcrEventId;

// Rechercher les terminaux d'un marchand
$terminals = $isv->terminals->search(merchantId: 'merchant-uuid');

// Vente POS ISV
$session = $isv->terminals->sale(
    terminalId: 16014231,
    amount: 1500,
    isvAmount: 100,
    terminalMerchantId: 'merchant-uuid',
    cashRegisterId: 'PratiConnect-CR1',
    merchantReference: 'INV-2026-001',
);

echo $session['session_id']; // UUID de session

// Polling jusqu'au résultat (défaut : 120s timeout, 3s intervalle)
$result = $isv->terminals->pollUntilComplete($session['session_id']);

// Interpréter le résultat avec l'enum
$eventId = EcrEventId::tryFrom($result['eventId']);
if ($eventId?->isSuccessful()) {
    echo 'Transaction réussie : ' . $result['transactionId'];
} else {
    echo 'Échec : ' . $eventId?->label();
}

// Annuler une session active
$isv->terminals->abort('session-uuid', 'PratiConnect-CR1');

// Consulter une session
$session = $isv->terminals->getSession('session-uuid');

// Lister les sessions d'une journée
$sessions = $isv->terminals->listSessions('2026-03-18');
```

**Notes importantes :**
- La pré-autorisation n'est **pas** supportée via Cloud Terminal ISV — utiliser Smart Checkout avec `preauth: true`.
- L'abort utilise `GET` (pas `DELETE`) — particularité non documentée de l'API Viva. Le SDK gère cela automatiquement.
- Le `sessionId` est auto-généré si non fourni.
- Le SDK construit `isvDetails` automatiquement.

---

### 6. Transfers

**Propriété :** `$isv->transfers`

Envoi et annulation de transferts de fonds vers les comptes connectés.

| Méthode    | Signature                                                                                              | Retour               |
|------------|--------------------------------------------------------------------------------------------------------|----------------------|
| `send`     | `send(string $targetAccountId, int $amount, ?string $sourceWalletId, ?string $transactionId, ?string $description)` | `array{transferId}` |
| `reverse`  | `reverse(string $transferId, ?int $amount)`                                                            | `array{transferId}` |

```php
// Envoyer des fonds à un vendeur
$transfer = $isv->transfers->send(
    targetAccountId: 'seller-account-uuid',
    amount: 1000,                    // 10,00 EUR
    transactionId: 'txn-uuid',       // Lier à une transaction existante
    description: 'Commission mars 2026',
);

echo $transfer['transferId'];

// Annuler totalement un transfert
$isv->transfers->reverse('transfer-uuid');

// Annuler partiellement (5,00 EUR)
$isv->transfers->reverse('transfer-uuid', amount: 500);
```

---

### 7. MarketplaceOrders

**Propriété :** `$isv->marketplace`

Ordres marketplace avec transfert automatique vers le vendeur. La `platform_fee` correspond à `amount - sellerAmount`.

| Méthode  | Signature                                                                                                                              | Retour                                      |
|----------|----------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|
| `create` | `create(int $amount, string $sellerAccountId, int $sellerAmount, ?string $customerDescription, ?string $merchantReference, ?string $sourceCode, bool $preauth)` | `array{order_code, checkout_url, platform_fee}` |
| `cancel` | `cancel(string $transactionId, ?int $amount, bool $reverseTransfers, bool $refundPlatformFee)`                                        | `array`                                     |

```php
// Créer un ordre marketplace
$order = $isv->marketplace->create(
    amount: 1500,                    // 15,00 EUR total
    sellerAccountId: 'seller-uuid',
    sellerAmount: 1200,              // 12,00 EUR au vendeur
    customerDescription: 'Achat marketplace',
    merchantReference: 'MP-2026-001',
);

echo $order['order_code'];
echo $order['checkout_url'];
echo $order['platform_fee'];  // 300 = 3,00 EUR de commission plateforme

// Remboursement total avec reversal des transferts
$isv->marketplace->cancel('txn-uuid');

// Remboursement partiel sans rembourser la commission plateforme
$isv->marketplace->cancel(
    transactionId: 'txn-uuid',
    amount: 500,
    reverseTransfers: true,
    refundPlatformFee: false,
);
```

---

### 8. NativeCheckoutIsv

**Propriété :** `$isv->nativeCheckout`

Paiement natif server-to-server pour les marchands connectés, sans redirection Smart Checkout.

| Méthode               | Signature                                                                                                                                        | Retour                          |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------|
| `createChargeToken`   | `createChargeToken(string $connectedMerchantId, int $amount, string $paymentData, int $paymentMethodId)`                                         | `array{chargeToken}`            |
| `createTransaction`   | `createTransaction(string $connectedMerchantId, string $chargeToken, int $amount, int $isvAmount, int $currencyCode, ?string $merchantTrns, ?string $customerTrns, bool $preauth)` | `array{transactionId, statusId}` |

**Flux en 2 étapes :**
1. Le client collecte les données carte via le JS SDK Viva et obtient `paymentData` (chiffré côté client).
2. Votre serveur crée un charge token via `createChargeToken()`, puis exécute la transaction via `createTransaction()`.

```php
// Étape 1 : créer un charge token
$token = $isv->nativeCheckout->createChargeToken(
    connectedMerchantId: 'merchant-uuid',
    amount: 1500,
    paymentData: $encryptedCardData,  // Du JS SDK Viva
    paymentMethodId: 0,               // 0 = carte par défaut
);

// Étape 2 : exécuter la transaction
$txn = $isv->nativeCheckout->createTransaction(
    connectedMerchantId: 'merchant-uuid',
    chargeToken: $token['chargeToken'],
    amount: 1500,
    isvAmount: 100,
    currencyCode: 978,                // EUR
    merchantTrns: 'INV-2026-001',
    customerTrns: 'Consultation bien-être',
);

echo $txn['transactionId'];
echo $txn['statusId'];
```

---

### 9. IsvWebhooks

**Propriété :** `$isv->isvWebhooks`

Création, listing, mise à jour et suppression d'abonnements webhook ISV.

| Méthode  | Signature                                                   | Retour                        |
|----------|-------------------------------------------------------------|-------------------------------|
| `create` | `create(string $url, string $eventType, ?string $description)` | `array` (avec `webhookId`)   |
| `list`   | `list()`                                                    | `array`                       |
| `update` | `update(string $webhookId, string $url, ?string $eventType)` | `array`                       |
| `delete` | `delete(string $webhookId)`                                 | `array`                       |

```php
// Créer un webhook
$webhook = $isv->isvWebhooks->create(
    url: 'https://myapp.com/webhooks/viva',
    eventType: 'transaction.payment.created',
    description: 'Notifications de paiement',
);

// Lister
$webhooks = $isv->isvWebhooks->list();

// Modifier
$isv->isvWebhooks->update(
    webhookId: $webhook['webhookId'],
    url: 'https://myapp.com/webhooks/viva-v2',
    eventType: 'transaction.refund.created',
);

// Supprimer
$isv->isvWebhooks->delete($webhook['webhookId']);
```

---

### 10. Webhooks

**Propriété :** `$isv->webhooks`

Vérification de la requête GET initiale de Viva Wallet et parsing des payloads POST (21 types d'événements).

| Méthode                | Signature                                        | Retour                                        |
|------------------------|--------------------------------------------------|-----------------------------------------------|
| `verificationResponse` | `verificationResponse(string $verificationKey)`  | `array{StatusCode, Key}`                      |
| `parse`                | `parse(string $rawBody)`                         | `array{event_type, event_type_id, event_data}` |
| `isKnownEvent`         | `Webhooks::isKnownEvent(int $eventTypeId)` (statique) | `bool`                                   |

```php
// GET — vérification initiale
$verificationKey = config('services.viva.verification_key');
return response()->json(
    $isv->webhooks->verificationResponse($verificationKey)
);
// => {"StatusCode": 0, "Key": "votre-cle"}

// POST — parsing des événements
$event = $isv->webhooks->parse(file_get_contents('php://input'));

echo $event['event_type'];     // 'transaction.payment.created'
echo $event['event_type_id'];  // 1796
$data = $event['event_data'];

// Pattern Laravel Controller
match ($event['event_type']) {
    'transaction.payment.created' => $this->handlePayment($event['event_data']),
    'transaction.refund.created'  => $this->handleRefund($event['event_data']),
    'account.connected'           => $this->handleNewAccount($event['event_data']),
    default => null,
};

// Vérifier si un eventTypeId est connu (méthode statique)
use QrCommunication\VivaIsv\Resources\Webhooks;

if (Webhooks::isKnownEvent(1796)) {
    echo 'Événement reconnu';
}
```

---

## Enums

### `EcrEventId` — codes résultat Cloud Terminal

Namespace : `QrCommunication\VivaIsv\Enums\EcrEventId`

```php
$event = EcrEventId::tryFrom($session['eventId']);
$event->isSuccessful(); // true si SUCCESS (0)
$event->isTerminal();   // true si état final (pas IN_PROGRESS)
$event->shouldPoll();   // true si IN_PROGRESS (1100)
$event->label();        // 'Transaction successful', 'Declined', etc.
```

| Valeur | Constante            | Description                         |
|--------|----------------------|-------------------------------------|
| `0`    | `SUCCESS`            | Transaction réussie                 |
| `1003` | `TERMINAL_TIMEOUT`   | Terminal hors délai                 |
| `1006` | `DECLINED`           | Transaction refusée                 |
| `1016` | `ABORTED`            | Transaction annulée                 |
| `1020` | `INSUFFICIENT_FUNDS` | Fonds insuffisants                  |
| `1099` | `GENERIC_ERROR`      | Erreur générique                    |
| `1100` | `IN_PROGRESS`        | En cours — continuer le polling     |
| `6000` | `BAD_PARAMS`         | Paramètres invalides                |

### `TransactionEventId` — codes de déclin détaillés

Namespace : `QrCommunication\VivaIsv\Enums\TransactionEventId`

```php
$decline = TransactionEventId::tryFrom($session['transactionEventId']);
echo $decline->label();       // 'Insufficient funds'
echo $decline->testAmount();  // 9951 (montant pour déclencher ce déclin en demo)
```

| Valeur  | Constante                   | Montant test |
|---------|-----------------------------|--------------|
| `10001` | `REFER_TO_ISSUER`           | —            |
| `10003` | `INVALID_MERCHANT`          | —            |
| `10004` | `PICKUP_CARD`               | —            |
| `10005` | `DO_NOT_HONOR`              | —            |
| `10006` | `GENERAL_ERROR`             | `9906`       |
| `10012` | `INVALID_TRANSACTION`       | —            |
| `10013` | `INVALID_AMOUNT`            | —            |
| `10014` | `INVALID_CARD`              | `9914`       |
| `10030` | `FORMAT_ERROR`              | —            |
| `10041` | `LOST_CARD`                 | —            |
| `10043` | `STOLEN_CARD`               | `9920`       |
| `10051` | `INSUFFICIENT_FUNDS`        | `9951`       |
| `10054` | `EXPIRED_CARD`              | `9954`       |
| `10055` | `INCORRECT_PIN`             | —            |
| `10057` | `NOT_PERMITTED_CARDHOLDER`  | `9957`       |
| `10058` | `NOT_PERMITTED_TERMINAL`    | —            |
| `10061` | `WITHDRAWAL_LIMIT`          | `9961`       |
| `10062` | `RESTRICTED_CARD`           | —            |
| `10063` | `SECURITY_VIOLATION`        | —            |
| `10065` | `ACTIVITY_LIMIT`            | —            |
| `10068` | `LATE_RESPONSE`             | —            |
| `10070` | `CALL_ISSUER`               | —            |
| `10075` | `PIN_TRIES_EXCEEDED`        | —            |
| `10200` | `UNMAPPED`                  | —            |

### `Environment`

```php
use QrCommunication\VivaIsv\Enums\Environment;

// Passer en string ou en enum — les deux sont acceptés
$isv = new VivaIsvClient(..., environment: 'demo');
$isv = new VivaIsvClient(..., environment: Environment::PRODUCTION);
```

---

## Gestion des erreurs

### Hiérarchie des exceptions

```
RuntimeException
└── VivaException         (base — httpStatus, responseBody, getErrorCode(), getErrorText())
    ├── ApiException      (erreurs API 4xx/5xx)
    └── AuthenticationException  (erreurs OAuth2 — httpStatus = 401)
```

Namespace : `QrCommunication\VivaIsv\Exceptions`

### Exemple complet

```php
use QrCommunication\VivaIsv\Exceptions\ApiException;
use QrCommunication\VivaIsv\Exceptions\AuthenticationException;

try {
    $order = $isv->orders->create('merchant-uuid', 1500, isvAmount: 100);
} catch (AuthenticationException $e) {
    // Credentials ISV invalides
    echo $e->getMessage(); // 'ISV OAuth2 authentication failed: ...'
} catch (ApiException $e) {
    // Erreur API (400, 404, 500, etc.)
    echo $e->getMessage();       // Message d'erreur
    echo $e->httpStatus;         // Code HTTP
    echo $e->getErrorCode();     // Code Viva (ErrorCode)
    echo $e->getErrorText();     // Texte Viva (ErrorText)
    print_r($e->responseBody);   // Body JSON complet
}
```

> Les méthodes `capture()` et `recurring()` lancent `ApiException` si `ErrorCode !== 0` dans la réponse Viva Wallet.

---

## Événements webhook (21)

| ID     | Type                                      |
|--------|-------------------------------------------|
| `1796` | `transaction.payment.created`             |
| `1797` | `transaction.refund.created`              |
| `1798` | `transaction.payment.cancelled`           |
| `1799` | `transaction.reversal.created`            |
| `1800` | `transaction.preauth.created`             |
| `1801` | `transaction.preauth.completed`           |
| `1802` | `transaction.preauth.cancelled`           |
| `1810` | `pos.session.created`                     |
| `1811` | `pos.session.failed`                      |
| `1812` | `transaction.price.calculated`            |
| `1813` | `transaction.failed`                      |
| `1819` | `account.connected`                       |
| `1820` | `account.verification.status.changed`     |
| `1821` | `account.transaction.created`             |
| `1822` | `command.bank.transfer.created`           |
| `1823` | `command.bank.transfer.executed`          |
| `1824` | `transfer.created`                        |
| `1825` | `obligation.created`                      |
| `1826` | `obligation.captured`                     |
| `1827` | `order.updated`                           |
| `1828` | `sale.transactions.file`                  |

---

## Test en sandbox

Utiliser `environment: 'demo'` pour tester sans transactions réelles.

### Carte de test

| Champ      | Valeur                    |
|------------|---------------------------|
| Numéro     | `4111 1111 1111 1111`     |
| Expiration | Toute date future         |
| CVV        | `111`                     |

### Montants qui déclenchent des déclins en mode demo

```php
use QrCommunication\VivaIsv\Enums\TransactionEventId;

// Obtenir le montant de test pour un type de déclin spécifique
$amount = TransactionEventId::INSUFFICIENT_FUNDS->testAmount(); // 9951

$order = $isv->orders->create('merchant-uuid', $amount);
```

| Montant (centimes) | Déclin déclenché                                  |
|--------------------|---------------------------------------------------|
| `9951`             | Fonds insuffisants (`INSUFFICIENT_FUNDS`)          |
| `9954`             | Carte expirée (`EXPIRED_CARD`)                     |
| `9920`             | Carte volée (`STOLEN_CARD`)                        |
| `9957`             | Carte non autorisée (`NOT_PERMITTED_CARDHOLDER`)   |
| `9961`             | Limite de retrait (`WITHDRAWAL_LIMIT`)             |
| `9906`             | Erreur générale (`GENERAL_ERROR`)                  |
| `9914`             | Carte invalide (`INVALID_CARD`)                    |

---

## Pièges évités par le SDK

Ces 9 pièges ont été découverts lors de la certification ISV. Le SDK les gère automatiquement.

| # | Piège                                                                | Ce que le SDK fait pour vous                                                  |
|---|----------------------------------------------------------------------|-------------------------------------------------------------------------------|
| 1 | **Composite Basic Auth** non documenté (`ResellerID:MerchantID / ResellerAPIKey`) | Le `HttpClient` construit automatiquement le header composite pour chaque appel sur un marchand connecté |
| 2 | **camelCase vs PascalCase** — New API en camelCase, Legacy API en PascalCase | Chaque ressource envoie les paramètres dans la bonne casse             |
| 3 | **Pas de `sourceCode`** dans les ordres ISV                          | `IsvOrders::create()` n'envoie jamais de `sourceCode`                        |
| 4 | **`isvDetails`** obligatoire pour les ventes POS ISV                 | `EcrTerminals::sale()` construit `isvDetails` automatiquement                |
| 5 | **Abort POS** utilise GET (pas DELETE) avec `cashRegisterId` en query param | `EcrTerminals::abort()` utilise GET                                    |
| 6 | **Endpoints ISV** différents des endpoints marchands (`/ecr/isv/v1/` vs `/ecr/v1/`) | Chaque ressource utilise le bon préfixe                              |
| 7 | **Token OAuth2** expire silencieusement                              | Le `HttpClient` rafraîchit le token automatiquement avec une marge de 60s    |
| 8 | **Réponses vides** (200 sans body) sur certains endpoints POS        | Le `HttpClient` retourne `[]` au lieu de crash JSON                          |
| 9 | **`MerchantId`** en query string pour les ordres et native checkout ISV | Les méthodes `create()` ajoutent automatiquement `?MerchantId=`          |
