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

Viva Wallet ISV SDK

qrcommunication/viva-isv-sdk

PHP 8.2+, guzzlehttp/guzzle ^7.8

Quoi de neuf en v1.6.0 May 2026

Findings prod-testés + fallbacks ISV-only pour /platforms/v1 403. Breaking change sur la signature IsvWebhooks::create(). PHP 8.2+. GitHub Release v1.6.0 →
  • Nouveaux helpers IsvAccounts::getOnboardingUrl(), isVerified(), isAcquiringEnabled() — Répliquent les méthodes ConnectedAccounts via /isv/v1/ qui fonctionne sur les comptes ISV pure (vs /platforms/v1/ qui retourne 403 sans Marketplace API activée).
  • Fix breaking sur IsvWebhooks::create() — Signature passée de $eventType:string à $eventTypeId:int. L'API Viva exige l'ID numérique (1796/1797/1798/1799/8193/8194), pas un nom d'event en string.
  • Nouvelle méthode IsvWebhooks::verificationToken() — GET /isv/v1/webhooks/token pour récupérer la verification key requise avant tout enregistrement. Sert à signer les webhooks entrants HMAC-SHA256.
  • MerchantWebhookRegistrar — nouveau endpoint_unavailable statut — Distingue le HTTP 404 sur /api/messages/config (déprécié/restreint sur la plupart des comptes ISV en 2026) des autres échecs. Plus 3 helpers statiques : allSucceeded(), hasEndpointIssue(), allFailed().
  • Référence complète skill/references/prod-findings.md — Référence prod-testée des endpoints qui marchent / ne marchent PAS sur un vrai compte ISV, split OAuth ISV vs Smart Checkout, pièges UX onboarding, handshake signature webhook, tickets à ouvrir chez le support Viva.
  • Doc enrichie sur ConnectedAccounts — Avertissement HTTP 403 sur prod ISV + table de mapping vers IsvAccounts. Pareil pour IsvMessages (HTTP 404 + polling fallback), IsvAccounts::list() et IsvWebhooks::list() (HTTP 405 — tracer côté app).
📜 Antérieur — Quoi de neuf en v1.5.0 (1er mai 2026)
  • Nouvelle ressource IsvMessages — Enregistrement de webhooks au niveau marchand via Composite Basic Auth (/api/messages/config).
  • Nouveau helper MerchantWebhookRegistrar — Enregistrement idempotent des événements banking pour un marchand connecté.
  • Constante BANKING_EVENTS = [768, 769, 2054]
  • IsvConfig::isProduction() et isSandbox()

Installation

composer require qrcommunication/viva-isv-sdk

Prérequis : PHP 8.2+ avec les extensions ext-json et ext-curl.

Credentials requis — où les trouver dans le Dashboard Viva Wallet
Credential Emplacement dans le Dashboard
clientIdSettings > API Access > ISV OAuth Credentials > Client ID
clientSecretSettings > API Access > ISV OAuth Credentials > Client Secret
merchantIdSettings > API Access > Merchant ID
apiKeySettings > API Access > API Key
resellerIdSettings > API Access > Reseller Credentials > Reseller ID
resellerApiKeySettings > API Access > Reseller Credentials > Reseller API Key

Quick Start

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
clientIdstringClient ID OAuth2 ISV (*.apps.vivapayments.com)
clientSecretstringClient Secret OAuth2 ISV
merchantIdstringMerchant UUID ISV (pour ISV Basic Auth)
apiKeystringAPI Key ISV (pour ISV Basic Auth)
resellerIdstringReseller UUID (pour Composite Basic Auth)
resellerApiKeystringReseller API Key (pour Composite Basic Auth)
environmentstring|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)
├── $isvMessages       → IsvMessages             (3 méthodes — NOUVEAU v1.5.0)
└── merchantWebhookRegistrar() → MerchantWebhookRegistrar (helper — NOUVEAU v1.5.0)

39 méthodes au total — 12 ressources/helpers

Authentification

Le SDK gère 3 modes d'authentification automatiquement selon l'opération effectuée. Vous n'avez qu'à fournir les 6 credentials au constructeur.

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. Le SDK construit ce header automatiquement — vous passez uniquement le connectedMerchantId aux méthodes de $isv->transactions.

Token OAuth2 — refresh automatique

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

$isv->invalidateToken();

Référence des ressources

1 ConnectedAccounts $isv->accounts

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

MéthodeSignatureRetour
createcreate(string $email, string $returnUrl, ?string $partnerName, ?string $logoUrl)array{accountId, invitation}
getget(string $accountId)array (détails du compte)
listlist()array (liste paginée)
isVerifiedisVerified(string $accountId)bool
onboardingUrlonboardingUrl(string $accountId)?string
updateupdate(string $accountId, array $attributes)array
deletedelete(string $accountId)array
// 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 $isv->isvAccounts

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

MéthodeSignatureRetour
createcreate(string $email, string $returnUrl, ?string $partnerName, ?string $primaryColor, ?string $logoUrl)array{accountId, invitation}
getget(string $accountId)array
getOnboardingUrl v1.6getOnboardingUrl(string $accountId)?string
isVerified v1.6isVerified(string $accountId)bool
isAcquiringEnabled v1.6isAcquiringEnabled(string $accountId)bool
listlist()array ⚠ HTTP 405 prod
Utiliser IsvAccounts sur les comptes ISV pure — L'API Marketplace (/platforms/v1/*) retourne HTTP 403 sauf si explicitement activée par Viva. Les méthodes getOnboardingUrl(), isVerified() et isAcquiringEnabled() (v1.6) répliquent les méthodes ConnectedAccounts via /isv/v1/ qui fonctionne toujours sur les comptes ISV.
$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']);

// v1.6 — helpers prod-tested (toujours fonctionnels sur ISV)
$onboardingUrl = $isv->isvAccounts->getOnboardingUrl($account['accountId']);
$isVerified    = $isv->isvAccounts->isVerified($account['accountId']);
$canAcquire    = $isv->isvAccounts->isAcquiringEnabled($account['accountId']);

// ⚠ list() retourne HTTP 405 en prod — tracer accountIds côté app
// $all = $isv->isvAccounts->list(); // ApiException 405

3 IsvOrders $isv->orders

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

MéthodeSignatureRetour
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
$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 récurrents
    preauth: false,
);

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

// 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 $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éthodeSignatureRetour
getget(string $transactionId, string $connectedMerchantId)array
listByDatelistByDate(string $connectedMerchantId, string $date)array
capturecapture(string $transactionId, string $connectedMerchantId, int $amount, ?int $isvAmount)array
recurringrecurring(string $initialTransactionId, string $connectedMerchantId, int $amount, ?int $isvAmount, ?string $sourceCode)array
cancelcancel(string $transactionId, string $connectedMerchantId, ?int $amount, ?string $sourceCode)array
// 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 &gt; API Access du compte ISV.

5 EcrTerminals $isv->terminals

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

MéthodeSignatureRetour
searchsearch(?string $merchantId, ?int $statusId, ?string $sourceCode)array
salesale(int $terminalId, int $amount, int $isvAmount, string $terminalMerchantId, string $cashRegisterId, ?string $merchantReference, int $currencyCode, ?string $sessionId)array{session_id, success}
getSessiongetSession(string $sessionId)array
listSessionslistSessions(string $date)array
abortabort(string $sessionId, string $cashRegisterId)array
pollUntilCompletepollUntilComplete(string $sessionId, int $timeoutSeconds, int $intervalMs)array
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 $isv->transfers

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

MéthodeSignatureRetour
sendsend(string $targetAccountId, int $amount, ?string $sourceWalletId, ?string $transactionId, ?string $description)array{transferId}
reversereverse(string $transferId, ?int $amount)array{transferId}
// 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 $isv->marketplace

Ordres marketplace avec transfert automatique vers le vendeur. La platform fee correspond à la différence entre amount et sellerAmount.

MéthodeSignatureRetour
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
// 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 $isv->nativeCheckout

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

MéthodeSignatureRetour
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().
// É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 $isv->isvWebhooks

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

MéthodeSignatureRetour
verificationToken v1.6verificationToken()array{Key: string}
create v1.6 BREAKINGcreate(string $url, int $eventTypeId)array
listlist()array ⚠ HTTP 405 prod
updateupdate(string $webhookId, string $url, ?int $eventTypeId)array
deletedelete(string $webhookId)array
Breaking change v1.6 sur create() — Signature passée de $eventType:string à $eventTypeId:int. L'API Viva rejette les noms d'event en string — seul l'ID numérique fonctionne (1796, 1797, 1798, 1799, 8193, 8194). Migration : remplacer 'transaction.payment.created' par 1796.
Handshake de verification obligatoire — Avant le 1er appel à create(), récupérer la verification key via verificationToken() et faire répondre votre URL webhook par {"Key": "<clé>"} sur les requêtes GET. Sans cela Viva refuse l'enregistrement. Utiliser la même clé pour vérifier les signatures HMAC-SHA256 des webhooks entrants.
// 1. Récupérer la verification key (handshake obligatoire)
$key = $isv->isvWebhooks->verificationToken()['Key'];
// → Persister app-side, utiliser pour signer les webhooks entrants
//   Configurer endpoint GET pour répondre {"Key": "$key"}

// 2. Créer un webhook (numeric eventTypeId, pas string)
$webhook = $isv->isvWebhooks->create(
    url: 'https://myapp.com/webhooks/viva',
    eventTypeId: 1796, // Transaction Payment Created
);

// Events ISV-level supportés :
//   1796 = Transaction Payment Created
//   1797 = Transaction Reversal Created (refund)
//   1798 = Transaction Failed
//   1799 = Transaction Price Calculated
//   8193 = Account Connected (KYB completion)
//   8194 = Account Verification Status Changed

foreach ([1796, 1797, 1798, 1799, 8193, 8194] as $eventId) {
    $isv->isvWebhooks->create($url, $eventId);
}

// ⚠ list() retourne HTTP 405 en prod — tracer registered EventTypeIds côté app

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

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

10 Webhooks $isv->webhooks

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

MéthodeSignatureRetour
verificationResponseverificationResponse(string $verificationKey)array{StatusCode, Key}
parseparse(string $rawBody)array{event_type, event_type_id, event_data}
isKnownEventWebhooks::isKnownEvent(int $eventTypeId) (statique)bool
// 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';
}

11 IsvMessages $isv->isvMessages New v1.5.0

Enregistrement d'abonnements webhook au niveau marchand via /api/messages/config. Utilise le Composite Basic Auth automatiquement — vous passez uniquement le connectedMerchantId.

Production gotcha (2026-05) : /api/messages/config retourne HTTP 404 — Cet endpoint semble déprécié/restreint sur la plupart des marchands ISV-managed. Tous les appels register/list/delete jettent ApiException 404. Workaround : poller les settlements via wallet API sur un job de reconciliation périodique. Les événements ne sont pas perdus — ils sont persistés dans le ledger wallet Viva.
MéthodeSignatureRetour
registerregister(string $connectedMerchantId, string $callbackUrl, int $eventTypeId)array
listlist(string $connectedMerchantId)array
deletedelete(string $connectedMerchantId, int $eventTypeId)array
use QrCommunication\VivaIsv\Helpers\MerchantWebhookRegistrar;

// Enregistrer un seul événement banking pour un marchand connecté
$isv->isvMessages->register(
    connectedMerchantId: 'merchant-uuid',
    callbackUrl: 'https://myapp.com/api/webhooks/viva',
    eventTypeId: 768, // Command Bank Transfer Created
);

// Lister les souscriptions actives du marchand
$subscriptions = $isv->isvMessages->list('merchant-uuid');

// Supprimer un abonnement
$isv->isvMessages->delete('merchant-uuid', 768);

12 MerchantWebhookRegistrar $isv->merchantWebhookRegistrar() New v1.5.0

Helper idempotent qui enregistre tous les événements banking pour un marchand connecté en un seul appel. Ignore les événements déjà enregistrés. Utilise la constante BANKING_EVENTS = [768, 769, 2054] par défaut.

MéthodeSignatureRetour
registerAll registerAll(string $connectedMerchantId, string $callbackUrl, ?array $events = null) array<int, array{event_id, status, message?}>
allSucceeded v1.6 staticallSucceeded(array $results)bool
hasEndpointIssue v1.6 statichasEndpointIssue(array $results)bool
allFailed v1.6 staticallFailed(array $results)bool
4 statuts depuis v1.6 — Les entrées de résultat ont 4 statuts distincts : created (succès), already_exists (duplicate idempotent), endpoint_unavailable (HTTP 404 — API Viva dépréciée/restreinte), failed (autre erreur). Les 3 helpers statiques permettent de brancher proprement sans parser le tableau manuellement.
use QrCommunication\VivaIsv\Helpers\MerchantWebhookRegistrar;

// Enregistrer les 3 événements banking en un seul appel
$results = $isv->merchantWebhookRegistrar()->registerAll(
    connectedMerchantId: $merchantId,
    callbackUrl: 'https://app.example.com/api/webhooks/viva',
);

// Format v1.6 — chaque entrée :
// ['event_id' => 768, 'status' => 'created'|'already_exists'|'endpoint_unavailable'|'failed', 'message' => '...']

// Branching propre via les helpers statiques v1.6
if (MerchantWebhookRegistrar::allSucceeded($results)) {
    // Tout est OK (created OU already_exists)
} elseif (MerchantWebhookRegistrar::hasEndpointIssue($results)) {
    // L'API merchant-webhooks n'est pas dispo sur ce compte ISV (HTTP 404).
    // Fallback : job de reconciliation périodique via wallet API.
    Log::info('Viva merchant webhooks endpoint unavailable — using polling reconciliation');
} elseif (MerchantWebhookRegistrar::allFailed($results)) {
    // Erreur réseau / auth — retry plus tard
}

// Enregistrer uniquement un sous-ensemble d'événements
$results = $isv->merchantWebhookRegistrar()->registerAll(
    connectedMerchantId: $merchantId,
    callbackUrl: 'https://app.example.com/api/webhooks/viva',
    events: [768, 769],
);

// Consulter les IDs gérés par le helper
$bankingEvents = MerchantWebhookRegistrar::BANKING_EVENTS; // [768, 769, 2054]
Idempotent : Peut être appelé plusieurs fois sans risque — les événements déjà enregistrés sont en `already_exists`, pas re-enregistrés. À utiliser dans votre flux d'onboarding après la vérification KYB du marchand.

Enums

EcrEventId — codes résultat Cloud Terminal

use QrCommunication\VivaIsv\Enums\EcrEventId;

$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.
ValeurConstanteDescription
0SUCCESSTransaction réussie
1003TERMINAL_TIMEOUTTerminal hors délai
1006DECLINEDTransaction refusée
1016ABORTEDTransaction annulée
1020INSUFFICIENT_FUNDSFonds insuffisants
1099GENERIC_ERRORErreur générique
1100IN_PROGRESSEn cours — continuer le polling
6000BAD_PARAMSParamètres invalides

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

use QrCommunication\VivaIsv\Enums\TransactionEventId;

$decline = TransactionEventId::tryFrom($session['transactionEventId']);
echo $decline->label();       // 'Insufficient funds'
echo $decline->testAmount();  // 9951 (montant pour déclencher ce déclin en demo)
ValeurConstanteMontant test (centimes)
10001REFER_TO_ISSUER
10003INVALID_MERCHANT
10004PICKUP_CARD
10005DO_NOT_HONOR
10006GENERAL_ERROR9906
10012INVALID_TRANSACTION
10013INVALID_AMOUNT
10014INVALID_CARD9914
10030FORMAT_ERROR
10041LOST_CARD
10043STOLEN_CARD9920
10051INSUFFICIENT_FUNDS9951
10054EXPIRED_CARD9954
10055INCORRECT_PIN
10057NOT_PERMITTED_CARDHOLDER9957
10058NOT_PERMITTED_TERMINAL
10061WITHDRAWAL_LIMIT9961
10062RESTRICTED_CARD
10063SECURITY_VIOLATION
10065ACTIVITY_LIMIT
10068LATE_RESPONSE
10070CALL_ISSUER
10075PIN_TRIES_EXCEEDED
10200UNMAPPED

Environment

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

IsvConfig::isProduction() / isSandbox() New v1.5.0

Helpers booléens sur l'objet config — pratiques pour la logique conditionnelle sans comparaison de chaînes.

use QrCommunication\VivaIsv\IsvConfig;

$config = new IsvConfig(
    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:    'production',
);

$config->isProduction(); // true
$config->isSandbox();    // false

// Utilisation dans une logique conditionnelle
if ($config->isProduction()) {
    logger()->info('Exécution en production — utilisation des vrais credentials');
}

Gestion des erreurs

Le SDK définit 3 exceptions dans le namespace QrCommunication\VivaIsv\Exceptions :

RuntimeException
└── VivaException         (base — httpStatus, responseBody, getErrorCode(), getErrorText())
    ├── ApiException      (erreurs API 4xx/5xx)
    └── AuthenticationException  (erreurs OAuth2 — httpStatus = 401)
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 (24)

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

Événements banking — niveau marchand (v1.5.0) New

Ces 3 événements s'enregistrent par marchand connecté via /api/messages/config (pas via /isv/v1/webhooks). Utiliser le helper MerchantWebhookRegistrar pour les enregistrer en un seul appel.

IDTypeDescription
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
Constante BANKING_EVENTS : Le SDK expose MerchantWebhookRegistrar::BANKING_EVENTS = [768, 769, 2054] pour éviter de coder ces IDs en dur dans votre application.

Test en sandbox

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

Carte de test

ChampValeur
Numéro4111 1111 1111 1111
ExpirationToute date future
CVV111

Montants qui déclenchent des déclin en mode demo

use QrCommunication\VivaIsv\Enums\TransactionEventId;

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

$order = $isv->orders->create('merchant-uuid', $amount);
Montant (centimes)Déclin déclenché
9951Fonds insuffisants (INSUFFICIENT_FUNDS)
9954Carte expirée (EXPIRED_CARD)
9920Carte volée (STOLEN_CARD)
9957Carte non autorisée (NOT_PERMITTED_CARDHOLDER)
9961Limite de retrait (WITHDRAWAL_LIMIT)
9906Erreur générale (GENERAL_ERROR)
9914Carte invalide (INVALID_CARD)

Pièges en production (testés 2026-05) v1.6.0

Ces comportements ont été observés sur un vrai compte ISV production (Merchant 0119432c-...) lors d'une session de debug. Ils ne sont PAS dans la doc Viva officielle.

Endpoints qui ne fonctionnent PAS sur les comptes ISV pure

EndpointHTTP StatusWorkaround
GET /isv/v1/accounts (isvAccounts->list())405Tracer les accountIds côté app
DELETE /isv/v1/accounts/{id}405Pas de cleanup possible
GET /isv/v1/webhooks (isvWebhooks->list())405Tracer les EventTypeIds côté app
/platforms/v1/accounts/* (Marketplace API)403Utiliser IsvAccounts (/isv/v1/) à la place
POST /api/messages/config (isvMessages->register())404Reconciliation par polling via wallet API
POST /api/sources (composite auth)400Pas besoin (Source par défaut Viva)

OAuth split — ISV vs Smart Checkout (obligatoire)

Les credentials ISV ne supportent que le grant client_credentials. Le flow Authorization Code (login OAuth utilisateur final pour connecter un compte business existant) requiert des credentials Smart Checkout séparées créées dans Viva → Settings → API Access → Smart Checkout. Tenter /connect/authorize avec les credentials ISV redirige toujours vers accounts.vivapayments.com/home/error?errorId=CfDJ8... (errorId chiffré .NET DataProtection — impossible à décoder).

Pièges UX d'onboarding

  1. Email d'invitation déjà lié à un business Viva — L'écran d'invitation Viva affiche un sélecteur "Connect existing" / "Create new business account". Cliquer "Connect existing" plante avec "Failed to connect with PartnerName" si le redirect URI Smart Checkout n'est pas whitelisté côté Viva. Forcer un KYB propre en utilisant un email frais (ex: alias +tag).
  2. accountId peut finir lié à un autre email business — Cas observé : invitation pour userA@example.com → écran sélecteur → Continue → erreur OAuth → l'accountId côté Viva finit lié à userB@otherdomain.com (multi-business Viva owner). Toujours vérifier merchantId et legalName après KYB avant de considérer l'onboarding réussi.
  3. Pings sans signature — Viva fait des appels périodiques (au moins toutes les heures) 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 considère le webhook comme cassé et le désactive.

Tickets support à ouvrir chez Viva

ItemDemande
Activation rôle ISV"Activate ISV Partner Program on merchant {ID}"
App Smart Checkout séparée"Create Smart Checkout OAuth app for our platform"
Whitelist redirect URI"Add https://app.example.com/callback to redirect URIs of OAuth app {client_id}"
Activation /api/messages/config"Enable merchant-level webhook registration on our ISV (events 768/769/2054)" — souvent refusé
Activation /platforms/v1/*"Enable Marketplace API on our ISV" — souvent refusé

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