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

Viva Wallet ISV SDK

qrcommunication/viva-isv-sdk

PHP 8.2+, guzzlehttp/guzzle ^7.8

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)

36 méthodes au total — 10 ressources

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
listlist()array
$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 $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
createcreate(string $url, string $eventType, ?string $description)array (avec webhookId)
listlist()array
updateupdate(string $webhookId, string $url, ?string $eventType)array
deletedelete(string $webhookId)array
// 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 $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';
}

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

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 (21)

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

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)