"> Aller au contenu principal
PHP PHP PHP v1.5.3 MIT

Viva Wallet Merchant SDK

qrcommunication/viva-merchant-sdk

PHP 8.2+, guzzlehttp/guzzle ^7.8

Quoi de neuf en v1.5.3 What's new in v1.5.3 May 5, 2026

🐛 Bug fix critique — Wallets::list() retournait silencieusement 0 EUR au lieu du vrai solde. — Critical bug fix: Wallets::list() silently returned 0 EUR instead of the real balance. GitHub Release v1.5.3 →
  • Wallets::list() — fix unwrap réponse Legacy — L'endpoint /api/wallets peut retourner trois formes ({Wallets: [...]}, liste directe [...], objet seul {...}). Le fallback précédent $result['Wallets'] ?? $result['wallets'] ?? [$result] wrappait la liste déjà-plate dans un array supplémentaire — balance() itérait alors sur 1 élément qui était lui-même un array et tous les soldes évaluaient silencieusement à 0. Reproduit en prod sur merchant 0119432c-… (vrai Available: 17.89 EUR, balance() pré-v1.5.3: 0 EUR). — Fix Wallets::list() unwrap nested response. Pre-v1.5.3 silently returned 0 EUR.
  • Drop-in fix — Aucun changement d'API publique. Update via composer require qrcommunication/viva-merchant-sdk:^1.5.3. — Drop-in fix, no public API change.
📜 Antérieur — Quoi de neuf en v1.5.2 (5 mai 2026) 📜 Earlier — What's new in v1.5.2 (May 5, 2026)
  • Wallets::list(), Account::wallets(), Account::info() — fix routage Legacy API — Les trois méthodes appelaient HttpClient::get() au lieu de legacyGet(). Conséquence : /api/wallets et /api/accounts/{id} étaient routés vers api.vivapayments.com avec Bearer OAuth2 au lieu de www.vivapayments.com avec Basic Auth → HTTP 404 systématique. — Fix Legacy API routing: 3 methods now correctly hit www.vivapayments.com with Basic Auth.
  • Drop-in fix, aucun changement d'API publique. — Drop-in fix, no public API change.
📜 Antérieur — Quoi de neuf en v1.5.1 (4 mai 2026) 📜 Earlier — What's new in v1.5.1 (May 4, 2026)
Documentation-only release — aucun breaking change. Findings prod-testés + audit complet de la doc Viva. GitHub Release v1.5.1 →
  • Référence skill/references/prod-findings.md — Confirmation que TOUS les endpoints du SDK fonctionnent en production avec Basic Auth simple (testé sur compte ISV propre QR Communication). — Confirmation that ALL SDK endpoints work in production with simple Basic Auth.
  • Section "Production gotchas" dans SKILL.md — Référence rapide pour agents IA : endpoints testés, quand utiliser ce SDK vs viva-isv-sdk, handshake webhook, IPs Azure sans signature, codes d'erreur. — Quick reference for AI agents.
  • EventTypeIds étendus documentés — Au-delà des 21 trackés actuellement : 1802/1803 (POS ECR), 4865 (Order Updated), 5632/5633 (DEPRECATED Marketplace), 8448 (Transfer Created) + Sale Transactions signed. — Extended EventTypeIds beyond the current 21 tracked.
  • SubTypeIds 2054 complets — Incluant 183 IsvAcquiringCommission, 200-203 TransfersPlatform* (settlements marketplace), full ranges PayIn/PayOut/Clearance/Wallet/Obligations. — Full SubTypeId table for event 2054.
  • 5 paires de credentials Viva distinctes documentées — Smart Checkout / ISV / Reseller API / POS APIs / Account Transactions. Source de confusion fréquente. — 5 distinct Viva credential pairs documented.
  • Test amounts pour décliner — Table complète .9905-.9996 → EventIds 10005-10096 pour tests E2E (insufficient funds, expired card, pickup, etc.). — Full test amounts table for E2E decline testing.
  • Roadmap endpoints non couverts — Data Services Search, Acquiring v1 cards/refunds, POS Cloud Terminal /ecr/v1/* self, RF Code (Greek), Issuing API. — Roadmap of yet-uncovered Viva endpoints.
📜 Antérieur — Quoi de neuf en v1.5.0 (1er mai 2026) 📜 Earlier — What's new in v1.5.0 (May 1, 2026)
  • Nouvelle ressource Messages — Enregistrement de webhooks au niveau marchand via /api/messages/config (Basic Auth simple merchantId:apiKey).
  • Nouveau helper WebhookRegistrar — Enregistrement idempotent des événements banking en un seul appel (registerAll).
  • Constante BANKING_EVENTS = [768, 769, 2054]
  • Contracts HttpClientInterface + MessagesInterface — Extraction des interfaces pour le mocking (Dependency Inversion Principle).
  • Alignement avec sdk-php-viva-isv — Signature registerAll($callbackUrl, ?$events) identique.

Installation

Installez le SDK via Composer. Requiert PHP 8.2+ avec les extensions ext-json et ext-curl.

Install the SDK via Composer. Requires PHP 8.2+ with ext-json and ext-curl extensions.

composer require qrcommunication/viva-merchant-sdk

Compatible avec Laravel, Symfony, ou tout projet PHP standard.

Démarrage rapide Quick Start

<?php

use QrCommunication\VivaMerchant\VivaClient;

$viva = new VivaClient(
    merchantId:   'votre-merchant-uuid',
    apiKey:       'votre-api-key',
    clientId:     'xxx.apps.vivapayments.com',
    clientSecret: 'votre-client-secret',
    environment:  'demo', // ou 'production'
);

// Créer un ordre de paiement — Create a payment order
$order = $viva->orders->create(
    amount: 1500,
    customerDescription: 'Consultation',
);
// Rediriger le client vers $order['checkout_url']

// Vérifier une transaction après paiement — Verify a transaction after payment
$txn = $viva->transactions->getV2('transaction-uuid');

// Rembourser (partiel) — Partial refund
$viva->transactions->cancel('transaction-uuid', amount: 500);

// Capturer une pré-autorisation — Capture a pre-authorization
$viva->transactions->capture('preauth-uuid', amount: 1500);

// Charge récurrente — Recurring charge
$viva->transactions->recurring('initial-txn-uuid', amount: 1500);

// Apple Pay / Google Pay (NativeCheckout)
$token = $viva->nativeCheckout->createChargeToken(1500, $applePayData);
$txn   = $viva->nativeCheckout->createTransaction($token['chargeToken'], 1500);

// Tester la connexion — Test connection
$ok = $viva->testConnection(); // true ou false

Configuration

Le constructeur VivaClient accepte 5 paramètres. L'authentification OAuth2 est gérée automatiquement — le token est mis en cache et rafraîchi avant expiration.

The VivaClient constructor accepts 5 parameters. OAuth2 authentication is handled automatically — the token is cached in memory and refreshed before expiry.

Paramètre Type Requis Description
merchantId string Oui UUID du marchand Viva Wallet
apiKey string Oui Clé API (Legacy API — Basic Auth)
clientId string Oui Client ID OAuth2 (format xxx.apps.vivapayments.com)
clientSecret string Oui Secret OAuth2
environment string|Environment Non 'demo' (défaut) ou 'production'

Architecture d'authentification

Le SDK utilise deux mécanismes d'authentification selon la ressource utilisée :

API Méthode Ressources
Legacy API Basic Auth (merchantId:apiKey) Orders, Transactions, Sources
New API Bearer OAuth2 (auto-refresh) Wallets, BankAccounts, NativeCheckout, DataServices, Account

Ressources

1 Orders $viva->orders

Ordres de paiement Smart Checkout. Crée des liens de paiement hébergés sur les serveurs Viva Wallet.

Smart Checkout payment orders. Creates hosted payment links on Viva Wallet's servers.

create()

Créer un ordre de paiement.

Paramètre Type Défaut Description
amount int requis Montant en centimes (ex : 1500 = 15,00 EUR)
customerDescription ?string null Texte affiché au client sur la page de paiement
merchantReference ?string null Référence interne (visible dans les exports)
sourceCode ?string null Code source de paiement (null = source par défaut)
allowRecurring bool false Tokeniser la carte pour les charges futures
preauth bool false Pré-autorisation uniquement (capture séparée)
maxInstallments int 0 Nombre max de versements (0 = désactivé)

Retourne : array{order_code: int, checkout_url: string}

$order = $viva->orders->create(
    amount:              1500,          // 15,00 EUR
    customerDescription: 'Consultation',
    merchantReference:   'session_123',
    allowRecurring:      true,
    preauth:             false,
    maxInstallments:     3,
);

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

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

get(int $orderCode): array

Récupérer le statut d'un ordre.

$order = $viva->orders->get(orderCode: 1234567890);

cancel(int $orderCode): array

Annuler un ordre non payé.

$viva->orders->cancel(orderCode: 1234567890);

checkoutUrl(int $orderCode): string

Générer l'URL de checkout à partir d'un code de commande existant. Aucun appel API — calcul local.

$url = $viva->orders->checkoutUrl(orderCode: 1234567890);
// 'https://demo.vivapayments.com/web/checkout?ref=1234567890'

2 Transactions $viva->transactions

Consultation, remboursement, capture de pré-autorisation et paiements récurrents.

Transaction retrieval, refunds, pre-authorization captures, and recurring payments.

get(string $transactionId): array

Détails complets via Legacy API (Basic Auth). Retourne des clés PascalCase incluant frais, commission, infos carte.

$txn = $viva->transactions->get('transaction-uuid');

echo $txn['Transactions'][0]['Amount'];   // en centimes
echo $txn['Transactions'][0]['StatusId']; // 'F', 'A', 'C', etc.
echo $txn['Transactions'][0]['Fee'];
echo $txn['Transactions'][0]['CreditCard']['Number']; // masqué

getV2(string $transactionId): array

Détails allégés via New API (Bearer OAuth). Clés camelCase. Recommandé pour vérifier les paiements Smart Checkout.

$txn = $viva->transactions->getV2('transaction-uuid');

echo $txn['email'];              // email acheteur
echo $txn['amount'];             // en EUR (pas en centimes)
echo $txn['statusId'];           // 'F' = finalisée
echo $txn['orderCode'];          // code de commande
echo $txn['cardNumber'];         // numéro masqué
echo $txn['currencyCode'];       // ex: 978 (EUR)
echo $txn['recurringSupport'];   // true si tokenisée

listByDate(string $date): array

Lister les transactions pour une date (format Y-m-d).

Retourne : array<int, array<string, mixed>>

$transactions = $viva->transactions->listByDate('2026-03-18');

foreach ($transactions as $txn) {
    echo $txn['TransactionId'] . ' — ' . $txn['Amount'] . "\n";
}

cancel(string $transactionId, ?int $amount = null, ?string $sourceCode = null): array

Annuler ou rembourser une transaction. Même jour = annulation (void). Jour passé = remboursement (refund).

Paramètre Type Défaut Description
transactionId string requis UUID de la transaction
amount ?int null Centimes (null = remboursement total)
sourceCode ?string null Code source de paiement

Retourne : array{TransactionId: string}

// Remboursement total — Full refund
$result = $viva->transactions->cancel('transaction-uuid');

// Remboursement partiel (5,00 EUR) — Partial refund
$result = $viva->transactions->cancel('transaction-uuid', amount: 500);

echo $result['TransactionId']; // UUID du remboursement

capture(string $transactionId, int $amount): array

Capturer une pré-autorisation. Lève ApiException si la capture échoue.

$result = $viva->transactions->capture(
    transactionId: 'preauth-uuid',
    amount:        1500, // 15,00 EUR
);

recurring(string $initialTransactionId, int $amount, ?string $sourceCode = null): array

Effectuer une charge récurrente sur une carte tokenisée. Prérequis : l'ordre initial doit avoir été créé avec allowRecurring: true.

$result = $viva->transactions->recurring(
    initialTransactionId: 'initial-txn-uuid',
    amount:               1500,
    sourceCode:           '1234', // optionnel
);

3 Sources $viva->sources

Gestion des sources de paiement : domaines autorisés et URLs de redirection après paiement.

Payment sources management: authorized domains and post-payment redirect URLs.

list(): array

Lister toutes les sources de paiement du compte.

$sources = $viva->sources->list();

foreach ($sources as $source) {
    echo $source['Name'] . ' — ' . $source['SourceCode'] . "\n";
}

create(string $name, string $sourceCode, ?string $domain, ?string $pathSuccess, ?string $pathFail): array

Créer une nouvelle source de paiement.

Paramètre Type Défaut Description
name string requis Nom d'affichage de la source
sourceCode string requis Code à 4 chiffres
domain ?string null Domaine du site (ex : www.example.com)
pathSuccess ?string null Chemin de redirection après paiement réussi
pathFail ?string null Chemin de redirection après échec
$source = $viva->sources->create(
    name:        'Mon site web',
    sourceCode:  '1234',
    domain:      'www.example.com',
    pathSuccess: '/paiement/succes',
    pathFail:    '/paiement/echec',
);

4 Wallets $viva->wallets

Gestion des portefeuilles (sous-comptes), soldes et transferts internes.

Wallet management (sub-accounts), balances, and internal transfers.

list(): array

Lister les portefeuilles du compte (Legacy API /api/wallets, Basic Auth).

Retourne : list<array{Iban, WalletId, IsPrimary, Amount, Available, Overdraft, FriendlyName, CurrencyCode}>

Fix v1.5.3 — Déballage correct des 3 formes de réponse possibles. Auparavant un double-nesting silencieux faisait retourner 0 EUR à balance() même avec des fonds réels.
$wallets = $viva->wallets->list();
foreach ($wallets as $w) {
    echo $w['Iban'] . ' — Available: ' . $w['Available'] . ' ' . $w['CurrencyCode'] . PHP_EOL;
}
// GR7205700000005273617183734 — Available: 17.89 978
// GR7205700000004781377813871 — Available: 0 978

⚠️ CurrencyCode est en ISO 4217 numérique en string ("978" = EUR, "840" = USD, "826" = GBP). Convertir côté application si besoin.

balance(): array

Solde agrégé de tous les portefeuilles (itère list() et somme).

Retourne : array{available: float, pending: float, reserved: float, currency: string}

$balance = $viva->wallets->balance();
echo $balance['available'];  // 17.89 (somme des Available)
echo $balance['pending'];    // 0.00
echo $balance['reserved'];   // 0.00
echo $balance['currency'];   // '978' (ISO numérique brut — convertir si besoin)

💡 Pour le total ledger (Available + Pending + Reserved + Overdraft), itérer list() et sommer le champ Amount.

transfer(int $amount, string $sourceWalletId, string $targetWalletId, ?string $description = null): array

Transfert entre deux portefeuilles du même compte. Prérequis : activer « Allow transfers between accounts » dans Settings > API Access.

$viva->wallets->transfer(
    amount:         5000, // 50,00 EUR
    sourceWalletId: 'source-uuid',
    targetWalletId: 'target-uuid',
    description:    'Transfert mensuel',
);

listDetailed(): array

Liste enrichie avec IBAN, SWIFT, solde et indicateur de portefeuille principal.

$wallets = $viva->wallets->listDetailed();

foreach ($wallets as $wallet) {
    echo $wallet['iban'] . ' — ' . $wallet['amount'] . "\n";
    echo $wallet['isPrimary'] ? 'Principal' : 'Secondaire';
}

create(string $friendlyName, string $currencyCode = 'EUR'): array

Créer un nouveau portefeuille.

$viva->wallets->create(
    friendlyName: 'Compte secondaire',
    currencyCode: 'EUR',
);

update(int $walletId, string $friendlyName): array

Renommer un portefeuille existant.

$viva->wallets->update(walletId: 12345, friendlyName: 'Nouveau nom');

searchTransactions(array $params): array

Rechercher les transactions d'un portefeuille sur une plage de dates.

$transactions = $viva->wallets->searchTransactions([
    'date_from' => '2026-03-01',
    'date_to'   => '2026-03-31',
    'walletId'  => 12345,
]);

getTransaction(string $transactionId): array

Détails d'une transaction de compte (wallet transaction).

$txn = $viva->wallets->getTransaction('transaction-uuid');

5 BankAccounts $viva->bankAccounts

Gestion des comptes bancaires IBAN et virements SEPA (standard et instantané).

IBAN bank accounts management and SEPA transfers (standard and instant).

link(string $iban, string $beneficiaryName, ?string $friendlyName = null): array

Lier un IBAN externe au compte Viva Wallet.

$result = $viva->bankAccounts->link(
    iban:            'FR7630006000011234567890189',
    beneficiaryName: 'Jean Dupont',
    friendlyName:    'Compte principal',
);

echo $result['bankAccountId']; // UUID du compte lié
echo $result['isVivaIban'];    // false (IBAN externe)

transferOptions(string $bankAccountId): array

Récupérer les options de transfert disponibles pour un compte lié.

$options = $viva->bankAccounts->transferOptions('bank-account-uuid');

feeCommand(string $bankAccountId, int $amount, string $walletId, bool $isInstant = false, string $instructionType = 'SHA'): array

Calculer les frais avant d'exécuter un virement. Le bankCommandId retourné doit être passé à send().

$fees = $viva->bankAccounts->feeCommand(
    bankAccountId:   'bank-account-uuid',
    amount:          10000, // 100,00 EUR
    walletId:        'source-wallet-uuid',
    isInstant:       true,  // SEPA instantané
    instructionType: 'SHA', // frais partagés
);

echo $fees['bankCommandId']; // à passer à send()
echo $fees['fee'];           // frais en centimes

send(string $bankAccountId, int $amount, string $walletId, ?string $bankCommandId = null, ?string $description = null): array

Exécuter un virement SEPA vers un compte lié.

$result = $viva->bankAccounts->send(
    bankAccountId: 'bank-account-uuid',
    amount:        10000,
    walletId:      'source-wallet-uuid',
    bankCommandId: 'fee-command-uuid', // optionnel
    description:   'Virement mensuel',
);

echo $result['commandId']; // UUID du virement
echo $result['isInstant']; // true/false
echo $result['fee'];       // frais en centimes

list(): array

Lister les comptes bancaires liés au compte.

$accounts = $viva->bankAccounts->list();

get(string $bankAccountId): array

Détails d'un compte bancaire lié.

$account = $viva->bankAccounts->get('bank-account-uuid');

6 NativeCheckout $viva->nativeCheckout

Paiements natifs Apple Pay et Google Pay directement depuis votre application mobile ou web.

Native Apple Pay and Google Pay payments directly from your mobile or web application.

Le flux NativeCheckout se déroule en deux étapes : d'abord générer un chargeToken à partir des données de paiement mobile, puis créer la transaction avec ce token.

createChargeToken()

Générer un token de charge à partir des données Apple Pay ou Google Pay.

Paramètre Type Défaut Description
amount int requis Montant en centimes
paymentData string requis Données Apple Pay ou Google Pay (JSON sérialisé)
paymentMethod string 'applepay' 'applepay' ou 'googlepay'
sourceCode ?string null Code source de paiement
dynamicDescriptor ?string null Descripteur dynamique sur le relevé bancaire

Retourne : array{chargeToken: string, redirectToACSForm: ?string}

$token = $viva->nativeCheckout->createChargeToken(
    amount:        1500,
    paymentData:   $applePayPaymentDataString, // JSON string
    paymentMethod: 'applepay', // ou 'googlepay'
    sourceCode:    '1234',
);

echo $token['chargeToken'];       // à passer à createTransaction()
echo $token['redirectToACSForm']; // formulaire 3DS (si applicable)

createTransaction()

Finaliser la transaction à partir du token de charge.

Paramètre Type Défaut Description
chargeToken string requis Token issu de createChargeToken()
amount int requis Montant en centimes
currencyCode int 978 Code ISO 4217 numérique (978 = EUR)
sourceCode ?string null Code source de paiement
merchantTrns ?string null Référence interne marchand
customerTrns ?string null Description visible par le client
preauth bool false Pré-autorisation uniquement
tipAmount int 0 Pourboire en centimes
installments ?int null Nombre de versements

Retourne : array{transactionId: string, statusId: string, amount: int, orderCode: int}

$txn = $viva->nativeCheckout->createTransaction(
    chargeToken:   $token['chargeToken'],
    amount:        1500,
    currencyCode:  978,           // EUR (ISO 4217 numérique)
    merchantTrns:  'ref_123',
    customerTrns:  'Consultation',
);

echo $txn['transactionId']; // UUID
echo $txn['statusId'];      // 'F' = finalisée
echo $txn['amount'];        // 1500
echo $txn['orderCode'];     // code de commande

7 DataServices $viva->dataServices

Rapports MT940 et souscriptions webhook pour la génération de fichiers de données.

MT940 reports and webhook subscriptions for data file generation.

mt940(string $date): array

Récupérer le rapport MT940 pour une date donnée (format Y-m-d).

$report = $viva->dataServices->mt940('2026-03-18');

createSubscription(string $url, string $eventType): array

Créer une souscription webhook pour recevoir les notifications de fichiers.

$sub = $viva->dataServices->createSubscription(
    url:       'https://example.com/webhooks/viva-files',
    eventType: 'SaleTransactionsFileGenerated',
);
echo $sub['subscriptionId'];

updateSubscription(string $subscriptionId, ?string $url, ?string $eventType): array

Mettre à jour une souscription existante (null = conserver la valeur actuelle).

$viva->dataServices->updateSubscription(
    subscriptionId: 'sub-uuid',
    url:            'https://example.com/webhooks/new-url',
    eventType:      null, // conserver l'eventType actuel
);

deleteSubscription(string $subscriptionId): void

Supprimer une souscription webhook.

$viva->dataServices->deleteSubscription('sub-uuid');

listSubscriptions(): array

Lister toutes les souscriptions webhook actives.

$subs = $viva->dataServices->listSubscriptions();

foreach ($subs as $sub) {
    echo $sub['subscriptionId'] . ' → ' . $sub['url'] . "\n";
}

requestFile(string $date): void

Déclencher la génération asynchrone d'un fichier pour une date donnée. Utilisez une souscription webhook pour être notifié de la disponibilité du fichier.

$viva->dataServices->requestFile('2026-03-18');
// Génération asynchrone — attendre le webhook 'sale.transactions.file'

8 Webhooks $viva->webhooks

Vérification et parsing des webhooks Viva Wallet. Aucun appel API — traitement entièrement local.

Viva Wallet webhook verification and parsing. No API calls — fully local processing.

verificationResponse(string $verificationKey): array

Répondre à la requête GET de vérification initiale de Viva Wallet.

Retourne : array{StatusCode: int, Key: string}

// Route GET /webhooks/viva
public function verify()
{
    return response()->json(
        $viva->webhooks->verificationResponse('votre-verification-key')
    );
    // Réponse : {"StatusCode": 0, "Key": "votre-verification-key"}
}

parse(string $rawBody): array

Parser le payload JSON d'un webhook POST. Lève \InvalidArgumentException si le JSON est invalide.

Retourne : array{event_type: string, event_type_id: int, event_data: array}

// Route POST /webhooks/viva
public function handle(Request $request)
{
    $event = $viva->webhooks->parse($request->getContent());

    echo $event['event_type'];    // 'transaction.payment.created'
    echo $event['event_type_id']; // 1796
    // $event['event_data']       // données de l'événement

    match ($event['event_type']) {
        'transaction.payment.created'   => $this->onPayment($event['event_data']),
        'transaction.refund.created'    => $this->onRefund($event['event_data']),
        'transaction.preauth.created'   => $this->onPreauth($event['event_data']),
        'transaction.preauth.completed' => $this->onCapture($event['event_data']),
        default => logger()->info('Webhook ignoré : ' . $event['event_type']),
    };

    return response()->json(['status' => 'ok']);
}

isKnownEvent(int $eventTypeId): bool

Vérifier si un ID d'événement est reconnu par le SDK.

$viva->webhooks->isKnownEvent(1796); // true
$viva->webhooks->isKnownEvent(9999); // false

eventTypeIds(): array

Retourner la liste de tous les IDs d'événements connus.

$ids = $viva->webhooks->eventTypeIds();
// [1796, 1797, 1798, ..., 1828]

24 types d'événements supportés

ID Type d'événement Description
1796transaction.payment.createdPaiement créé / reçu
1797transaction.refund.createdRemboursement créé
1798transaction.payment.cancelledPaiement annulé
1799transaction.reversal.createdAnnulation (void) créée
1800transaction.preauth.createdPré-autorisation créée
1801transaction.preauth.completedPré-autorisation capturée
1802transaction.preauth.cancelledPré-autorisation annulée
1810pos.session.createdSession POS créée
1811pos.session.failedSession POS échouée
1812transaction.price.calculatedPrix calculé (frais dynamiques)
1813transaction.failedTransaction échouée
1819account.connectedCompte connecté
1820account.verification.status.changedStatut de vérification du compte modifié
1821account.transaction.createdTransaction de compte (wallet) créée
1822command.bank.transfer.createdVirement bancaire initié
1823command.bank.transfer.executedVirement bancaire exécuté
1824transfer.createdTransfert entre wallets créé
1825obligation.createdObligation créée
1826obligation.capturedObligation capturée
1827order.updatedOrdre mis à jour
1828sale.transactions.fileFichier de transactions disponible

Événements banking merchant-level — enregistrement via Messages New v1.5.0

Ces 3 événements s'enregistrent via $viva->messages->register() ou via le helper WebhookRegistrar::registerAll(). Ils utilisent l'endpoint /api/messages/config (Basic Auth), différent de l'endpoint ISV-level.

These 3 events are registered via $viva->messages->register() or the WebhookRegistrar::registerAll() helper.

ID Type d'événement Description
768command.bank.transfer.createdOrdre de virement bancaire créé
769command.bank.transfer.executedOrdre de virement bancaire exécuté
2054account.transaction.createdTransaction de compte (wallet) créée

9 Account $viva->account

Informations et portefeuilles du compte marchand.

Merchant account information and wallets.

info(): array

Informations générales du compte marchand.

$info = $viva->account->info();
echo $info['merchantId'];
echo $info['businessName'];
echo $info['email'];

wallets(): array

Portefeuilles associés au compte marchand.

$wallets = $viva->account->wallets();

10 Messages $viva->messages New v1.5.0

Enregistrement de webhooks au niveau marchand via /api/messages/config. Utilise le Basic Auth (merchantId:apiKey) — différent des webhooks ISV-level.

Merchant-level webhook registration via /api/messages/config. Uses Basic Auth (merchantId:apiKey) — distinct from ISV-level webhooks.

Méthode Signature Retour
registerregister(string $callbackUrl, int $eventTypeId)array
listlist()array
deletedelete(int $eventTypeId)array
// Enregistrer un événement banking
$viva->messages->register(
    callbackUrl: 'https://myapp.com/api/webhooks/viva',
    eventTypeId: 768, // Command Bank Transfer Created
);

// Lister les souscriptions actives
$subscriptions = $viva->messages->list();

// Supprimer un abonnement
$viva->messages->delete(768);

11 WebhookRegistrar $viva->webhookRegistrar() New v1.5.0

Helper idempotent qui enregistre les événements banking en un seul appel. Les événements déjà enregistrés sont ignorés sans erreur.

Idempotent helper that registers banking events in a single call. Already-registered events are skipped without error.

Méthode Signature Retour
registerAll registerAll(string $callbackUrl, ?array $events = null) array{registered: int[], skipped: int[], failed: int[]}
use QrCommunication\VivaMerchant\Helpers\WebhookRegistrar;

// Enregistrer les 3 événements banking en un seul appel
$results = $viva->webhookRegistrar()->registerAll(
    callbackUrl: 'https://app.example.com/api/webhooks/viva',
);
// [
//   'registered' => [768, 769, 2054],
//   'skipped'    => [],
//   'failed'     => [],
// ]

// Enregistrer un sous-ensemble
$results = $viva->webhookRegistrar()->registerAll(
    callbackUrl: 'https://app.example.com/api/webhooks/viva',
    events: [768],
);

// Consulter les IDs gérés
$bankingEvents = WebhookRegistrar::BANKING_EVENTS; // [768, 769, 2054]
Idempotent : Sûr d'être appelé plusieurs fois (ex. : dans votre pipeline de déploiement ou lors de la création d'un compte). Safe to call multiple times — e.g. in your deploy pipeline or account setup flow.

Contracts New v1.5.0

Deux interfaces extraites du SDK pour faciliter le mocking dans les tests (Dependency Inversion Principle).

Two interfaces extracted for testability — inject them instead of concrete classes in your services.

Interface Namespace Description
HttpClientInterface QrCommunication\VivaMerchant\Contracts Contrat pour le client HTTP interne. — Contract for the internal HTTP client.
MessagesInterface QrCommunication\VivaMerchant\Contracts Contrat pour la resource Messages — permet de mocker l'enregistrement webhook. — Contract for the Messages resource — enables mocking webhook registration.
use QrCommunication\VivaMerchant\Contracts\MessagesInterface;

// Injection dans un service — testable sans appel HTTP réel
class MyOnboardingService
{
    public function __construct(
        private readonly MessagesInterface $messages,
    ) {}

    public function registerWebhooks(string $callbackUrl): void
    {
        $this->messages->register($callbackUrl, 768);
        $this->messages->register($callbackUrl, 769);
    }
}

// Dans un test PHPUnit
$mockMessages = $this->createMock(MessagesInterface::class);
$mockMessages->expects($this->exactly(2))->method('register');

$service = new MyOnboardingService($mockMessages);
$service->registerWebhooks('https://example.com/webhooks/viva');

Enums

Environment

Environnement Viva Wallet. Accepté en string ou en instance d'enum.

Constante Valeur Description
Environment::DEMO 'demo' Environnement de test
Environment::PRODUCTION 'production' Environnement de production
use QrCommunication\VivaMerchant\Enums\Environment;

$env = Environment::DEMO;
$env = Environment::from('demo');

$env->value;         // 'demo'
$env->apiUrl();      // 'https://demo-api.vivapayments.com'
$env->legacyUrl();   // 'https://demo.vivapayments.com'
$env->checkoutUrl(); // 'https://demo.vivapayments.com/web/checkout'
$env->accountsUrl(); // 'https://demo-accounts.vivapayments.com'

Currency

Codes ISO 4217 numériques pour les 12 devises supportées.

Constante Valeur ISO numérique Code ISO
Currency::EUR978EUR
Currency::GBP826GBP
Currency::USD840USD
Currency::PLN985PLN
Currency::RON946RON
Currency::BGN975BGN
Currency::CZK203CZK
Currency::HRK191HRK
Currency::HUF348HUF
Currency::DKK208DKK
Currency::SEK752SEK
Currency::NOK578NOK
use QrCommunication\VivaMerchant\Enums\Currency;

Currency::EUR->value;      // 978
Currency::EUR->iso();      // 'EUR'
Currency::fromIso('GBP');  // Currency::GBP (826)

TransactionStatus

Statuts de transaction Viva Wallet avec méthodes helpers.

Valeur Constante isSuccessful() isPending() isFailed() label()
'F'FINALIZEDouiFinalized
'A'PENDINGouiPending
'C'CLEARINGouiClearing
'E'ERRORouiError
'M'MANUALLY_REVERSEDouiReversed
'X'REQUIRES_ACTIONRequires Action
'R'REFUNDEDRefunded
use QrCommunication\VivaMerchant\Enums\TransactionStatus;

$status = TransactionStatus::from('F');
$status->isSuccessful();  // true
$status->isPending();     // false
$status->isFailed();      // false
$status->label();         // 'Finalized'

// Usage typique
$txn = $viva->transactions->getV2('transaction-uuid');
$status = TransactionStatus::from($txn['statusId']);
if ($status->isSuccessful()) {
    // Paiement confirmé
}

Gestion des erreurs Error Handling

Toutes les exceptions héritent de VivaException (extends RuntimeException).

RuntimeException
└── VivaException               (base — httpStatus, responseBody, getErrorCode(), getErrorText())
    ├── ApiException             (erreurs 4xx / 5xx générales)
    ├── AuthenticationException  (401 — identifiants invalides)
    └── ValidationException      (422 — erreurs de validation)

Propriétés et méthodes

Membre Type Description
$httpStatus int Code HTTP de la réponse
$responseBody ?array Corps JSON décodé de la réponse Viva
getErrorCode() ?int Code d'erreur Viva (ErrorCode)
getErrorText() ?string Message d'erreur Viva (ErrorText, message, ou detail)
$errors (ValidationException uniquement) array<string, string[]> Erreurs de validation par champ
use QrCommunication\VivaMerchant\Exceptions\ApiException;
use QrCommunication\VivaMerchant\Exceptions\AuthenticationException;
use QrCommunication\VivaMerchant\Exceptions\ValidationException;
use QrCommunication\VivaMerchant\Exceptions\VivaException;

try {
    $order = $viva->orders->create(amount: 1500);
} catch (AuthenticationException $e) {
    // Identifiants invalides — httpStatus = 401
    echo $e->getMessage();

} catch (ValidationException $e) {
    // Erreur de validation — httpStatus = 422
    foreach ($e->errors as $field => $messages) {
        echo "$field : " . implode(', ', $messages) . "\n";
    }

} catch (ApiException $e) {
    // Erreur API générale
    echo $e->httpStatus;         // ex: 400, 404, 500
    echo $e->getErrorCode();     // code Viva
    echo $e->getErrorText();     // message Viva
    print_r($e->responseBody);   // corps JSON complet

} catch (VivaException $e) {
    // Toute autre erreur SDK
    echo $e->getMessage();
}

Guide d'intégration Webhooks Webhooks Integration Guide

  1. 1. Configurer dans le Dashboard Viva

    Aller dans Settings > API Access > Webhooks, ajouter l'URL de votre endpoint et noter la clé de vérification.

  2. 2. Gérer la vérification (GET)

    Viva envoie une requête GET pour vérifier la disponibilité de l'endpoint.

    // Route GET /webhooks/viva
    public function verify()
    {
        return response()->json(
            $viva->webhooks->verificationResponse('votre-clé-de-vérification')
        );
    }
  3. 3. Recevoir les événements (POST)

    // Route POST /webhooks/viva
    public function handle(Request $request)
    {
        $event = $viva->webhooks->parse($request->getContent());
    
        match ($event['event_type']) {
            'transaction.payment.created'   => $this->onPayment($event['event_data']),
            'transaction.refund.created'    => $this->onRefund($event['event_data']),
            'transaction.preauth.created'   => $this->onPreauth($event['event_data']),
            'transaction.preauth.completed' => $this->onCapture($event['event_data']),
            default => logger()->info('Webhook ignoré : ' . $event['event_type']),
        };
    
        return response()->json(['status' => 'ok']);
    }

Laravel : Exclure l'URL webhook du middleware VerifyCsrfToken dans bootstrap/app.php.

Carte de test Test Card

Pour l'environnement demo uniquement. Aucun 3DS n'est requis en mode démo.

Champ Valeur
Numéro de carte 4111 1111 1111 1111
Expiration Toute date future
CVV 111
3DS Non requis en mode demo

Pièges en production (testés 2026-05) Production gotchas (prod-tested 2026-05) v1.5.1

Ces comportements ont été observés sur un compte propre Viva production réel (Merchant ISV propre QR Communication, 0119432c-...) et complètent la doc Viva officielle. These behaviors were observed on a real production Viva account and supplement the official Viva docs.

Endpoints confirmés fonctionnels en prod (Basic Auth simple)

EndpointMéthode SDKNotes
POST /api/orders$viva->orders->create()OrderCode retourné, success: true
GET /api/orders/{orderCode}$viva->orders->get()Statut ordre
POST /api/transactions/{txnId}$viva->transactions->capture()Capture preauth
DELETE /api/transactions/{txnId}$viva->transactions->cancel()Refund/void selon date
POST /api/transactions/{initialTxnId}$viva->transactions->recurring()Charge récurrent
GET /api/wallets$viva->wallets->list()IBAN, soldes, currency code
POST /api/sources$viva->sources->create()Création Source Smart Checkout
POST /api/messages/config$viva->messages->register()Webhooks merchant-level
⚠ Distinction importante avec viva-isv-sdk — Sur les comptes connectés ISV (sub-merchants opérés via composite auth), POST /api/messages/config retourne 404. Ce SDK fonctionne avec Basic Auth simple sur le compte propre uniquement. Pour les sub-merchants ISV, voir qrcommunication/viva-isv-sdk.

EventTypeIds étendus (au-delà des 21 actuellement trackés)

EventTypeIdNameNiveauNotes
768Command Bank Transfer CreatedmerchantVirement SEPA out créé
769Command Bank Transfer ExecutedmerchantVirement SEPA out exécuté
1796-1799Transaction Payment* (Created/Reversal/Failed/PriceCalculated)merchant + ISVPaiements
1802Transaction POS ECR Session CreatedmerchantECR Cloud Terminal succès
1803Transaction POS ECR Session FailedmerchantECR Cloud Terminal échec/abort/timeout
2054Account Transaction CreatedmerchantMvt wallet/bank (avec SubTypeIds détaillés ↓)
4865Order UpdatedmerchantOrder cancelled (Smart Checkout cancel button OU API DELETE)
5632/5633Obligation Created/Capturedmarketplace (DEPRECATED)Marketplace seller obligation — anciennement
8193Account ConnectedISV/MarketplaceKYB completion (EventData: PersonId, WalletId, ConnectedAccountId)
8194Account Verification Status ChangedISV/MarketplaceVerification status updated
8448Transfer CreatedmarketplaceMarketplace transfer made (manual ou auto)
Sale Transactions(no ID, signed HMAC)merchantAsync file generation done — Viva-Signature / Viva-Signature-256

SubTypeIds importants pour event 2054 (Account Transaction Created)

  • 183 IsvAcquiringCommission — commission ISV créditée
  • 200 TransfersPlatformAmountSettlement — settlement marketplace
  • 201 TransfersPlatformFeeSettlement
  • 202 TransfersPlatformAmountClearance
  • 203 TransfersPlatformFeeClearance
  • 20-25 PayIn* — top-ups (Cash, Dias, Card, Voucher, SmartMoney, Iban)
  • 30-32 PayOut* — payouts (Iban, Card, DirectDebit)
  • 80-87 Clearance* — clearance / settlement
  • 140-160 Wallet* — opérations internes wallet

5 paires de credentials Viva distinctes

Source de confusion fréquente — les credentials NE sont PAS interchangeables :

CredentialsUsage
Smart Checkout (client_id/secret)/checkout/v2/orders, /acquiring/v1/*, /walletaccounts/v1/*, /transfers/v1/*
ISV/checkout/v2/isv/orders, /isv/v1/accounts, /isv/v1/webhooks, /nativecheckout/v2/isv/*, /ecr/isv/v1/*
Reseller APIAPI resellers (différent des ISV credentials)
POS APIsCloud Terminal /ecr/v1/*
Account Transactions/banktransfers/v1/*, /dataservices/v1/*

OAuth scopes documentés

3 scopes nommés dans la doc Viva publique :

  • urn:viva:payments:core:api:redirectcheckout — Smart Checkout
  • urn:viva:payments:biservices:publicapi — Account Transactions Search (Data Services)
  • urn:viva:payments:biservices:internalapi — Data Services internal
⚠ Ne jamais passer scope=... dans /connect/token — Viva dérive les scopes des credentials utilisées. Tenter d'envoyer un scope explicite retourne invalid_scope.

Webhook signature handshake

Avant messages->register(), votre endpoint webhook DOIT répondre à un GET avec : Before messages->register(), your webhook endpoint MUST respond to GET with:

{ "Key": "<verification-key>" }

Récupérable depuis Viva → Settings → API Access → Webhooks. La même clé sert à vérifier les signatures HMAC-SHA256 des webhooks entrants :

$expected = hash_hmac('sha256', $rawPayload, $verificationKey);
if (! hash_equals($expected, $request->header('X-Viva-Signature'))) {
    return response()->json(['error' => 'Invalid signature'], 401);
}
⚠ Pings sans signature de Viva — Viva fait des appels périodiques sur l'URL webhook sans X-Viva-Signature depuis IPs Azure (51.138.x.x, 20.54.x.x). Renvoyer 200 sur ces calls (logger en warning) sinon Viva désactive le webhook.

Roadmap des endpoints non encore couverts

Audit de la doc Viva (483 pages crawlées, 3/2026) — endpoints existants côté Viva mais pas encore exposés par ce SDK :

  • Data Services Search (/dataservices/v1/accounttransactions/Search) — idéal pour reconcilier sans dépendre des webhooks 768/769/2054
  • Acquiring v1 cards/refunds (/acquiring/v1/cards/tokens, :rebate, :fastrefund)
  • POS Cloud Terminal self-account (/ecr/v1/* avec credentials POS APIs séparées)
  • RF Code Greek (POST /web2/checkout/v2/paymentsessions)
  • MB Reference / MB Way Portugal
  • Issuing API (white-label cards, gated)

Référence complète dans le SDK : skill/references/prod-findings.md .