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';
}
Resource Reference
1
ConnectedAccounts
$isv->accounts
Management of merchant accounts connected to the ISV platform. Creation, retrieval, KYB onboarding, verification, update and deletion.
| Method | Signature | Return |
create | create(string $email, string $returnUrl, ?string $partnerName, ?string $logoUrl) | array{accountId, invitation} |
get | get(string $accountId) | array (account details) |
list | list() | array (paginated list) |
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 — verb fixed —
update() now issues a PATCH on /platforms/v1/accounts/{accountId} (was POST), matching Viva's documented Marketplace API verb. The SDK picks the verb automatically.
2
IsvAccounts
$isv->isvAccounts
ISV accounts via the /isv/v1/ namespace with custom branding options (primary color, logo).
| Method | Signature | Return |
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 |
Use IsvAccounts on pure-ISV accounts —
The Marketplace API (/platforms/v1/*) returns HTTP 403 unless explicitly enabled by Viva. The methods getOnboardingUrl(), isVerified() and isAcquiringEnabled() (v1.6) replicate ConnectedAccounts methods on /isv/v1/ which always works on ISV accounts.
$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
Creation of Smart Checkout payment orders for connected merchants with ISV commission.
| Method | Signature | Return |
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');
Rules for isvAmount :
isvAmount must be <= amount — otherwise InvalidArgumentException.
- The connected merchant uses their default payment source — never pass a
sourceCode.
4
IsvTransactions
$isv->transactions
Operations on transactions of connected merchants. The SDK automatically uses Composite Basic Auth for all calls to this resource.
| Method | Signature | Return |
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);
Prerequisite: The option "Allow recurring payments and pre-auth captures via API" must be enabled in Settings > API Access of the ISV account.
5
EcrTerminals
$isv->terminals
ISV Cloud POS payment terminals. Search, sale, session polling, abort.
| Method | Signature | Return |
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');
Important notes:
- Pre-authorization is not supported via ISV Cloud Terminal — use Smart Checkout with
preauth: true.
- Abort uses
GET (not DELETE) — an undocumented quirk of the Viva API. The SDK handles this automatically.
- The
sessionId is auto-generated if not provided.
- The SDK builds
isvDetails automatically.
- v1.7.0 —
refund() is a referenced refund tied to the original sale parentSessionId; a fresh refundSessionId is generated for the refund itself.
- v1.7.0 —
getAction() returns [] while the action is still processing (HTTP 202); poll until the result is available.
6
Transfers
$isv->transfers
Sending and reversing fund transfers to connected accounts.
| Method | Signature | Return |
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
Marketplace orders with automatic transfer to the seller. The platform fee is the difference between amount and sellerAmount.
| Method | Signature | Return |
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
Native server-to-server payment for connected merchants, without Smart Checkout redirection.
| Method | Signature | Return |
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} |
2-step flow:
- The client collects card data via the Viva JS SDK and obtains
paymentData (encrypted client-side).
- Your server creates a charge token via
createChargeToken(), then executes the 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
Creation, listing, update and deletion of ISV webhook subscriptions.
| Method | Signature | Return |
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 on create() —
Signature changed from $eventType:string to $eventTypeId:int. Viva's API rejects string event names — only the numeric ID works (1796, 1797, 1798, 1799, 8193, 8194). Migration: replace 'transaction.payment.created' with 1796.
Verification handshake mandatory —
Before calling create() the first time, fetch the verification key via verificationToken() and have your webhook URL respond {"Key": "<key>"} on GET requests. Without it Viva refuses registration. Use the same key for HMAC-SHA256 verification of incoming webhooks.
// 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
Verification of the initial Viva Wallet GET request and parsing of POST payloads (21 event types).
| Method | Signature | Return |
verificationResponse | verificationResponse(string $verificationKey) | array{StatusCode, Key} |
parse | parse(string $rawBody) | array{event_type, event_type_id, event_data} |
isKnownEvent | Webhooks::isKnownEvent(int $eventTypeId) (static) | 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
Registration of merchant-level webhook subscriptions via /api/messages/config. Uses Composite Basic Auth automatically — you only pass the connectedMerchantId.
Production gotcha (2026-05): /api/messages/config returns HTTP 404 —
This endpoint appears deprecated/restricted on most ISV-managed merchants. All register/list/delete calls throw ApiException 404. Workaround: poll settlements via wallet API on a periodic reconciliation job. The events themselves are not lost — they are persisted in Viva's wallet ledger.
| Method | Signature | Return |
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
Idempotent helper that registers all banking events for a connected merchant in one call. Skips events already registered. Uses the constant BANKING_EVENTS = [768, 769, 2054] by default.
| Method | Signature | Return |
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 statuses since v1.6 —
The result entries now include 4 distinct statuses: created (success), already_exists (idempotent duplicate), endpoint_unavailable (HTTP 404 — Viva API deprecated/restricted), failed (other error). The 3 static helpers let you branch cleanly without parsing the array manually.
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:
Safe to call multiple times — events already registered are listed as `already_exists`, not re-registered. Use this helper in your onboarding flow after merchant KYB verification.
13
IsvSources
$isv->sources
v1.7.2
Payment-source management, on the ISV's own account (ISV Basic Auth) as well as on behalf of a connected merchant (Composite Basic Auth). Sources define how a merchant's sales are grouped and, for e-commerce, the redirect URLs used by Smart Checkout. 4 methods.
camelCase body. The payload is forwarded as-is in camelCase (unlike most Legacy endpoints). Required fields: name and sourceCode. For an e-commerce source add domain/isSecure/pathSuccess/pathFail; for a card-present source add phone/address/walletId.
Viva provides no API endpoint to list sources — GET /api/sources does not exist in the Viva documentation. Deduplication relies on the documented 409 — Source already exists with this source code response, not on a listing. To review existing sources: Self Care / Viva banking app.
| Method | Signature | Auth | Return |
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() — create a source
create() targets the ISV's own account (Legacy Basic Auth); createForMerchant() creates a source on behalf of a connected merchant (Composite Basic Auth) — the method to provision a merchant's e-commerce store via the camelCase fields 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() — idempotent create (via 409)
Idempotent: attempt the create and, if Viva responds 409 — Source already exists, return ['sourceCode' => …, 'status' => 'already_exists'] instead of throwing. No listing is used (Viva exposes no GET /api/sources). Ideal for provisioning — callable on every onboarding without creating duplicates.
$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
Cash & bill payment flow (validate → send OTP → charge) and reseller-scoped order creation. Uses the /resellers/v1/ endpoints on the New API with Bearer ISV token (camelCase). Each method forwards its payload as-is.
| Method | 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 — Cloud Terminal result codes
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.
| Value | Constant | Description |
0 | SUCCESS | Transaction successful |
1003 | TERMINAL_TIMEOUT | Terminal timed out |
1006 | DECLINED | Transaction declined |
1016 | ABORTED | Transaction aborted |
1020 | INSUFFICIENT_FUNDS | Insufficient funds |
1099 | GENERIC_ERROR | Generic error |
1100 | IN_PROGRESS | In progress — continue polling |
6000 | BAD_PARAMS | Invalid parameters |
TransactionEventId — detailed decline codes
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)
| Value | Constant | Test amount (cents) |
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;
// Pass as string or enum — both are accepted
$isv = new VivaIsvClient(..., environment: 'demo');
$isv = new VivaIsvClient(..., environment: Environment::PRODUCTION);
IsvConfig::isProduction() / isSandbox()
New v1.5.0
Boolean helpers on the config object — convenient for conditional logic without string comparison.
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
// Use in conditional logic
if ($config->isProduction()) {
logger()->info('Running in production — using real credentials');
}
Error Handling
The SDK defines 3 exceptions in the QrCommunication\VivaIsv\Exceptions namespace:
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
}
The capture() and recurring() methods throw ApiException if ErrorCode !== 0 in the Viva Wallet response.