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';
}
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éthode | Signature | Retour |
create | create(string $email, string $returnUrl, ?string $partnerName, ?string $logoUrl) | array{accountId, invitation} |
get | get(string $accountId) | array (détails du compte) |
list | list() | array (liste paginée) |
isVerified | isVerified(string $accountId) | bool |
onboardingUrl | onboardingUrl(string $accountId) | ?string |
update v1.7.0 PATCH | update(string $accountId, array $attributes) — PATCH /platforms/v1/accounts/{id} | array |
delete | delete(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 (PATCH /platforms/v1/accounts/{id} depuis v1.7.0)
$isv->accounts->update($account['accountId'], ['payouts' => ['enabled' => true]]);
// Supprimer
$isv->accounts->delete($account['accountId']);
v1.7.0 — verbe corrigé —
update() envoie désormais un PATCH sur /platforms/v1/accounts/{accountId} (auparavant POST), conforme au verbe documenté de la Marketplace API Viva. Le SDK choisit le verbe automatiquement.
2
IsvAccounts
$isv->isvAccounts
Comptes ISV via le namespace /isv/v1/ avec options de branding personnalisé (couleur primaire, logo).
| Méthode | Signature | Retour |
create | create(string $email, string $returnUrl, ?string $partnerName, ?string $primaryColor, ?string $logoUrl) | array{accountId, invitation} |
get | get(string $accountId) | array |
getOnboardingUrl v1.6 | getOnboardingUrl(string $accountId) | ?string |
isVerified v1.6 | isVerified(string $accountId) | bool |
isAcquiringEnabled v1.6 | isAcquiringEnabled(string $accountId) | bool |
list | list() | 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éthode | Signature | Retour |
create |
create(string $connectedMerchantId, int $amount, int $isvAmount, ?string $customerDescription, ?string $merchantReference, bool $allowRecurring, bool $preauth) |
array{order_code, checkout_url} |
retrieve v1.7.0 |
retrieve(int $orderCode, string $connectedMerchantId) — GET /api/orders/{code} |
array |
cancel v1.7.0 |
cancel(int $orderCode, string $connectedMerchantId) — DELETE /api/orders/{code} |
array |
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);
// v1.7.0 — Consulter un ordre d'un marchand connecté (Composite Basic Auth)
$details = $isv->orders->retrieve(1234567890, 'merchant-uuid');
// v1.7.0 — Annuler un ordre ouvert d'un marchand connecté
$result = $isv->orders->cancel(1234567890, 'merchant-uuid');
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éthode | Signature | Retour |
get | get(string $transactionId, string $connectedMerchantId) | array |
listByDate | listByDate(string $connectedMerchantId, string $date) | array |
listByClearanceDate v1.7.0 | listByClearanceDate(string $connectedMerchantId, string $clearanceDate) | array |
listByOrderCode v1.7.0 | listByOrderCode(string $connectedMerchantId, int $orderCode) | array |
listBySourceCode v1.7.0 | listBySourceCode(string $connectedMerchantId, string $sourceCode, string $date) | array |
capture | capture(string $transactionId, string $connectedMerchantId, int $amount, ?int $isvAmount) | array |
recurring | recurring(string $initialTransactionId, string $connectedMerchantId, int $amount, ?int $isvAmount, ?string $sourceCode) | array |
moto v1.7.0 | moto(string $connectedMerchantId, array $payload) — POST /api/transactions | array |
increasePreauth v1.7.0 | increasePreauth(string $transactionId, string $connectedMerchantId, int $amount, ?string $customerDescription, ?string $merchantReference, ?string $sourceCode, ?int $currencyCode, ?string $idempotencyKey) — POST /acquiring/v1/isv/transactions/{id}:increasepreauth | array |
cancel | cancel(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');
// v1.7.0 — Variantes de filtrage sur /api/transactions
$byClearance = $isv->transactions->listByClearanceDate('merchant-uuid', '2026-03-18');
$byOrder = $isv->transactions->listByOrderCode('merchant-uuid', 1234567890);
$bySource = $isv->transactions->listBySourceCode('merchant-uuid', 'Default', '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,
);
// v1.7.0 — Charge MOTO (Mail Order / Telephone Order), payload camelCase tel quel
$isv->transactions->moto('merchant-uuid', [
'amount' => 1500,
'orderCode' => 1234567890,
'creditcard' => [
'number' => '4111111111111111',
'expirationYear' => 2030,
'expirationMonth' => 12,
'cvc' => '111',
],
]);
// v1.7.0 — Augmenter une pré-autorisation existante (pré-auth incrémentale, Bearer New API)
$isv->transactions->increasePreauth(
transactionId: 'preauth-txn-uuid',
connectedMerchantId: 'merchant-uuid',
amount: 500,
idempotencyKey: 'preauth-inc-2026-001',
);
// Remboursement total
$isv->transactions->cancel('txn-uuid', 'merchant-uuid');
// Remboursement partiel (5,00 EUR)
$isv->transactions->cancel('txn-uuid', 'merchant-uuid', amount: 500);
Prérequis : L'option « Allow recurring payments and pre-auth captures via API » doit être activée dans Settings > API Access du compte ISV.
5
EcrTerminals
$isv->terminals
Terminaux de paiement Cloud POS ISV. Recherche, vente, polling de sessions, abort.
| Méthode | Signature | Retour |
search | search(?string $merchantId, ?int $statusId, ?string $sourceCode) | array |
sale | sale(int $terminalId, int $amount, int $isvAmount, string $terminalMerchantId, string $cashRegisterId, ?string $merchantReference, int $currencyCode, ?string $sessionId) | array{session_id, success} |
refund v1.7.0 | refund(string $sessionId, int $amount, ?string $merchantReference, int $terminalId, string $terminalMerchantId, string $cashRegisterId, int $currencyCode, ?string $refundSessionId) — POST /ecr/isv/v1/transactions:refund | array{session_id, success} |
createAction v1.7.0 | createAction(array $payload) — POST /ecr/isv/v1/actions | array |
getAction v1.7.0 | getAction(string $actionId) — GET /ecr/isv/v1/actions/{id} | array |
getSession | getSession(string $sessionId) | array |
listSessions | listSessions(string $date) | array |
abort | abort(string $sessionId, string $cashRegisterId) | array |
pollUntilComplete | pollUntilComplete(string $sessionId, int $timeoutSeconds, int $intervalMs) | array |
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();
}
// v1.7.0 — Remboursement référencé d'une vente POS (lié au parentSessionId)
$refund = $isv->terminals->refund(
sessionId: $session['session_id'], // session de la vente d'origine
amount: 1500,
terminalId: 16014231,
terminalMerchantId: 'merchant-uuid',
cashRegisterId: 'PratiConnect-CR1',
);
// v1.7.0 — Créer une action sur un terminal (ex. contrôle AADE-FIM)
$action = $isv->terminals->createAction([
'terminalId' => 16014231,
'cashRegisterId' => 'PratiConnect-CR1',
'isvDetails' => ['terminalMerchantId' => 'merchant-uuid'],
'request' => ['actionType' => 'aade-fim-control'],
]);
// v1.7.0 — Récupérer le résultat d'une action (HTTP 202 en cours → [])
$actionResult = $isv->terminals->getAction($action['actionId']);
// 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.
- v1.7.0 —
refund() est un remboursement référencé lié au parentSessionId de la vente d'origine ; un refundSessionId distinct est généré pour le remboursement.
- v1.7.0 —
getAction() renvoie [] tant que l'action est en cours (HTTP 202) ; répéter l'appel jusqu'au résultat.
6
Transfers
$isv->transfers
Envoi et annulation de transferts de fonds vers les comptes connectés.
| Méthode | Signature | Retour |
send | send(string $targetAccountId, int $amount, ?string $sourceWalletId, ?string $transactionId, ?string $description) | array{transferId} |
reverse | reverse(string $transferId, ?int $amount) | array{transferId} |
// 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éthode | Signature | Retour |
create |
create(int $amount, string $sellerAccountId, int $sellerAmount, ?string $customerDescription, ?string $merchantReference, ?string $sourceCode, bool $preauth) |
array{order_code, checkout_url, platform_fee} |
cancel |
cancel(string $transactionId, ?int $amount, bool $reverseTransfers, bool $refundPlatformFee) |
array |
// 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éthode | Signature | Retour |
createChargeToken |
createChargeToken(string $connectedMerchantId, int $amount, string $paymentData, int $paymentMethodId) |
array{chargeToken} |
createTransaction |
createTransaction(string $connectedMerchantId, string $chargeToken, int $amount, int $isvAmount, int $currencyCode, ?string $merchantTrns, ?string $customerTrns, bool $preauth) |
array{transactionId, statusId} |
Flux en 2 étapes :
- Le client collecte les données carte via le JS SDK Viva et obtient
paymentData (chiffré côté client).
- 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éthode | Signature | Retour |
verificationToken v1.6 | verificationToken() | array{Key: string} |
create v1.6 BREAKING | create(string $url, int $eventTypeId) | array |
list | list() | array ⚠ HTTP 405 prod |
update | update(string $webhookId, string $url, ?int $eventTypeId) | array |
delete | delete(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éthode | Signature | Retour |
verificationResponse | verificationResponse(string $verificationKey) | array{StatusCode, Key} |
parse | parse(string $rawBody) | array{event_type, event_type_id, event_data} |
isKnownEvent | Webhooks::isKnownEvent(int $eventTypeId) (statique) | bool |
// 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éthode | Signature | Retour |
register | register(string $connectedMerchantId, string $callbackUrl, int $eventTypeId) | array |
list | list(string $connectedMerchantId) | array |
delete | delete(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éthode | Signature | Retour |
registerAll |
registerAll(string $connectedMerchantId, string $callbackUrl, ?array $events = null) |
array<int, array{event_id, status, message?}> |
allSucceeded v1.6 static | allSucceeded(array $results) | bool |
hasEndpointIssue v1.6 static | hasEndpointIssue(array $results) | bool |
allFailed v1.6 static | allFailed(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.
13
IsvSources
$isv->sources
v1.7.2
Gestion des sources de paiement, sur le propre compte ISV (ISV Basic Auth) comme au nom d'un marchand connecté (Composite Basic Auth). Les sources définissent le regroupement des ventes d'un marchand et, pour l'e-commerce, les URLs de redirection utilisées par Smart Checkout. 4 méthodes.
Body camelCase. Le payload est transmis tel quel en camelCase (contrairement à la plupart des endpoints Legacy). Champs requis : name et sourceCode. Pour une source e-commerce, ajouter domain/isSecure/pathSuccess/pathFail ; pour une source présentielle (card-present), phone/address/walletId.
Viva ne fournit aucun endpoint API pour lister les sources — GET /api/sources n'existe pas dans la documentation Viva. La déduplication s'appuie sur la réponse documentée 409 — Source already exists with this source code, et non sur un listing. Pour consulter les sources existantes : Self Care / app bancaire Viva.
| Méthode | Signature | Auth | Retour |
create | create(array $payload): array — POST /api/sources | ISV Basic Auth | array |
createForMerchant | createForMerchant(string $connectedMerchantId, array $payload): array — POST /api/sources | Composite Basic Auth | array |
ensure | ensure(array $payload): array — idempotent (via 409) | ISV Basic Auth | array |
ensureForMerchant | ensureForMerchant(string $connectedMerchantId, array $payload): array — idempotent (via 409) | Composite Basic Auth | array |
create() / createForMerchant() — créer une source
create() vise le propre compte ISV (Legacy Basic Auth) ; createForMerchant() crée une source au nom d'un marchand connecté (Composite Basic Auth) — la méthode pour provisionner la boutique e-commerce d'un marchand via les champs camelCase name, sourceCode, domain, isSecure, pathSuccess, pathFail.
// Source e-commerce sur le compte ISV propre
$source = $isv->sources->create([
'name' => 'Boutique en ligne',
'sourceCode' => 'ECOM01',
'domain' => 'shop.example.com',
'isSecure' => true,
'pathSuccess' => 'https://shop.example.com/payment/success',
'pathFail' => 'https://shop.example.com/payment/fail',
]);
// Source présentielle (card-present)
$source = $isv->sources->create([
'name' => 'Boutique physique',
'sourceCode' => 'POS01',
'phone' => '+33123456789',
'address' => '1 rue de la Paix, Paris',
'walletId' => 'wallet-uuid',
]);
// Source d'un marchand connecté (Composite Basic Auth)
$source = $isv->sources->createForMerchant('merchant-uuid', [
'name' => 'Boutique du marchand',
'sourceCode' => '1234',
'domain' => 'boutique.fr',
'isSecure' => true,
'pathSuccess' => 'https://boutique.fr/success',
'pathFail' => 'https://boutique.fr/fail',
]);
ensure() / ensureForMerchant() — création idempotente (via 409)
Idempotent : tente la création et, si Viva répond 409 — Source already exists, retourne ['sourceCode' => …, 'status' => 'already_exists'] au lieu de lever. Aucun listing n'est utilisé (Viva n'expose pas de GET /api/sources). Idéal pour le provisioning — appelable à chaque onboarding sans créer de doublon.
$result = $isv->sources->ensureForMerchant('merchant-uuid', [
'name' => 'Boutique du marchand',
'sourceCode' => '1234',
'domain' => 'boutique.fr',
'isSecure' => true,
'pathSuccess' => 'https://boutique.fr/success',
'pathFail' => 'https://boutique.fr/fail',
]);
// créée, OU ['sourceCode' => '1234', 'status' => 'already_exists'] si elle existe déjà
// Variante compte ISV propre
$ownResult = $isv->sources->ensure([
'name' => 'Boutique en ligne',
'sourceCode' => 'ECOM01',
'domain' => 'shop.example.com',
'isSecure' => true,
'pathSuccess' => 'https://shop.example.com/payment/success',
'pathFail' => 'https://shop.example.com/payment/fail',
]);
14
Resellers
$isv->resellers
New v1.7.0
Flux de paiements cash & bill (validation → envoi OTP → encaissement) et création d'ordres scopés reseller. Utilise les endpoints /resellers/v1/ sur la New API avec Bearer ISV token (camelCase). Chaque méthode transmet son payload tel quel.
| Méthode | Signature | Endpoint |
validateCashPayment | validateCashPayment(array $payload) | POST /resellers/v1/transactions/cashPayments:validate |
validateBillPayment | validateBillPayment(array $payload) | POST /resellers/v1/transactions/billPayments:validate |
sendCashPaymentOtp | sendCashPaymentOtp(array $payload) | POST /resellers/v1/transactions/cashPayments:sendotp |
sendBillPaymentOtp | sendBillPaymentOtp(array $payload) | POST /resellers/v1/transactions/billPayments:sendotp |
cashPayment | cashPayment(array $payload) | POST /resellers/v1/transactions/cashPayments |
billPayment | billPayment(array $payload) | POST /resellers/v1/transactions/billPayments |
createOrder | createOrder(array $payload) | POST /resellers/v1/orders |
// 1. Valider un paiement cash avant encaissement
$validation = $isv->resellers->validateCashPayment([
'amount' => 1500,
'customerPhone' => '+33123456789',
]);
// 2. Envoyer un OTP au client
$isv->resellers->sendCashPaymentOtp(['reference' => $validation['reference']]);
// 3. Encaisser le paiement cash (avec l'OTP saisi par le client)
$payment = $isv->resellers->cashPayment([
'reference' => $validation['reference'],
'otp' => '123456',
]);
// Même flux pour les bill payments
$billValidation = $isv->resellers->validateBillPayment(['billCode' => 'BILL-001', 'amount' => 4200]);
$isv->resellers->sendBillPaymentOtp(['reference' => $billValidation['reference']]);
$isv->resellers->billPayment(['reference' => $billValidation['reference'], 'otp' => '654321']);
// Créer un ordre scopé reseller
$order = $isv->resellers->createOrder(['amount' => 1500, 'customerTrns' => 'Commande reseller']);
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.
| Valeur | Constante | Description |
0 | SUCCESS | Transaction réussie |
1003 | TERMINAL_TIMEOUT | Terminal hors délai |
1006 | DECLINED | Transaction refusée |
1016 | ABORTED | Transaction annulée |
1020 | INSUFFICIENT_FUNDS | Fonds insuffisants |
1099 | GENERIC_ERROR | Erreur générique |
1100 | IN_PROGRESS | En cours — continuer le polling |
6000 | BAD_PARAMS | Paramètres invalides |
TransactionEventId — codes de déclin détaillés
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)
| Valeur | Constante | Montant test (centimes) |
10001 | REFER_TO_ISSUER | — |
10003 | INVALID_MERCHANT | — |
10004 | PICKUP_CARD | — |
10005 | DO_NOT_HONOR | — |
10006 | GENERAL_ERROR | 9906 |
10012 | INVALID_TRANSACTION | — |
10013 | INVALID_AMOUNT | — |
10014 | INVALID_CARD | 9914 |
10030 | FORMAT_ERROR | — |
10041 | LOST_CARD | — |
10043 | STOLEN_CARD | 9920 |
10051 | INSUFFICIENT_FUNDS | 9951 |
10054 | EXPIRED_CARD | 9954 |
10055 | INCORRECT_PIN | — |
10057 | NOT_PERMITTED_CARDHOLDER | 9957 |
10058 | NOT_PERMITTED_TERMINAL | — |
10061 | WITHDRAWAL_LIMIT | 9961 |
10062 | RESTRICTED_CARD | — |
10063 | SECURITY_VIOLATION | — |
10065 | ACTIVITY_LIMIT | — |
10068 | LATE_RESPONSE | — |
10070 | CALL_ISSUER | — |
10075 | PIN_TRIES_EXCEEDED | — |
10200 | UNMAPPED | — |
Environment
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.