qrcommunication / viva-merchant-sdk
PHP SDK for Viva Wallet Merchant API — orders, transactions, sources, webhooks
Package info
github.com/QrCommunication/sdk-php-viva-merchant
pkg:composer/qrcommunication/viva-merchant-sdk
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
README
SDK PHP complet pour l'intégration Viva Wallet. Couvre 10 ressources : Orders, Transactions, Sources, Wallets, BankAccounts, NativeCheckout, DataServices, Account, Webhooks et Messages (abonnements webhook).
PHP 8.2+ requis. Compatible Laravel, Symfony, ou tout projet PHP.
Ce SDK couvre les opérations marchands standard. Pour les opérations ISV (comptes connectés, composite auth), voir
sdk-php-viva-isv.
Table des matières
- Installation
- Démarrage rapide
- Référence des ressources
- Enregistrement des webhooks banking
- Architecture
- Enums
- Gestion d'erreurs
- Webhooks — Guide d'intégration
- Carte de test
- Documentation interactive
- Intégration IA
- Licence
Installation
composer require qrcommunication/viva-merchant-sdk
Prérequis : PHP 8.2+ avec ext-json et ext-curl.
Démarrage rapide
use QrCommunication\VivaMerchant\VivaClient; $viva = new VivaClient( merchantId: 'votre-merchant-uuid', apiKey: 'votre-api-key', clientId: 'xxx.apps.vivapayments.com', clientSecret: 'votre-client-secret', environment: 'demo', // ou 'production' ); // Créer un ordre de paiement $order = $viva->orders->create(amount: 1500, customerDescription: 'Consultation'); // Rediriger le client vers $order['checkout_url'] // Vérifier une transaction après paiement $txn = $viva->transactions->getV2('transaction-uuid'); // Rembourser $viva->transactions->cancel('transaction-uuid', amount: 500); // Capturer une pré-autorisation $viva->transactions->capture('preauth-uuid', amount: 1500); // Charge récurrente $viva->transactions->recurring('initial-txn-uuid', amount: 1500); // Apple Pay / Google Pay $token = $viva->nativeCheckout->createChargeToken(1500, $applePayData); $txn = $viva->nativeCheckout->createTransaction($token['chargeToken'], 1500); // Tester la connexion $viva->testConnection(); // true ou false
Référence des ressources
1. Orders — $viva->orders
Ordres de paiement Smart Checkout.
create() — Créer un ordre
$order = $viva->orders->create( amount: 1500, // Centimes (15,00 EUR) customerDescription: 'Consultation', // Affiché au client merchantReference: 'session_123', // Référence interne allowRecurring: true, // Tokeniser la carte preauth: false, // Pré-autorisation ? maxInstallments: 3, // Paiement en 3x ); echo $order['order_code']; // 1234567890 echo $order['checkout_url']; // https://demo.vivapayments.com/web/checkout?ref=1234567890
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
amount |
int |
requis | Montant en centimes |
customerDescription |
?string |
null |
Texte affiché au client |
merchantReference |
?string |
null |
Référence interne (exports) |
sourceCode |
?string |
null |
Source de paiement |
allowRecurring |
bool |
false |
Autoriser les charges récurrentes |
preauth |
bool |
false |
Pré-autorisation uniquement |
maxInstallments |
int |
0 |
Nombre max de versements |
Retour : array{order_code: int, checkout_url: string}
get() — Statut d'un ordre
$order = $viva->orders->get(orderCode: 1234567890);
cancel() — Annuler un ordre non payé
$viva->orders->cancel(orderCode: 1234567890);
checkoutUrl() — URL de checkout (sans appel API)
$url = $viva->orders->checkoutUrl(orderCode: 1234567890); // 'https://demo.vivapayments.com/web/checkout?ref=1234567890'
2. Transactions — $viva->transactions
Consultation, remboursement, capture et paiements récurrents.
get() — Détails complets (Legacy API, PascalCase)
$txn = $viva->transactions->get('transaction-uuid'); echo $txn['Transactions'][0]['Amount']; echo $txn['Transactions'][0]['StatusId'];
getV2() — Détails légers (New API, camelCase)
$txn = $viva->transactions->getV2('transaction-uuid'); echo $txn['email']; echo $txn['amount']; // En EUR (pas en centimes) echo $txn['statusId']; // 'F' echo $txn['orderCode'];
Recommandé pour vérifier les paiements Smart Checkout.
listByDate() — Lister par date
$transactions = $viva->transactions->listByDate('2026-03-18'); foreach ($transactions as $txn) { echo $txn['TransactionId'] . ' — ' . $txn['Amount'] . "\n"; }
Retour : array<int, array<string, mixed>>
cancel() — Annuler / Rembourser
// Remboursement total $result = $viva->transactions->cancel('transaction-uuid'); // Remboursement partiel (5,00 EUR) $result = $viva->transactions->cancel('transaction-uuid', amount: 500); echo $result['TransactionId']; // UUID du remboursement
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
transactionId |
string |
requis | UUID de la transaction |
amount |
?int |
null |
Centimes (null = total) |
sourceCode |
?string |
null |
Source de paiement |
Même jour = annulation (void). Jour passé = remboursement (refund).
capture() — Capturer une pré-autorisation
$result = $viva->transactions->capture('preauth-uuid', amount: 1500);
Lève ApiException si la capture échoue.
recurring() — Charge récurrente
$result = $viva->transactions->recurring( initialTransactionId: 'initial-txn-uuid', amount: 1500, sourceCode: '1234', // optionnel );
Prérequis : l'ordre initial doit avoir été créé avec allowRecurring: true.
3. Sources — $viva->sources
Gestion des sources de paiement (domaines autorisés, URLs de redirection).
list() — Lister les sources
$sources = $viva->sources->list(); foreach ($sources as $source) { echo $source['Name'] . ' — ' . $source['SourceCode'] . "\n"; }
create() — Créer une source
$source = $viva->sources->create( name: 'Mon site web', sourceCode: '1234', domain: 'www.example.com', pathSuccess: '/paiement/succes', pathFail: '/paiement/echec', );
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
name |
string |
requis | Nom d'affichage |
sourceCode |
string |
requis | Code à 4 chiffres |
domain |
?string |
null |
Domaine du site |
pathSuccess |
?string |
null |
Redirection succès |
pathFail |
?string |
null |
Redirection échec |
4. Wallets — $viva->wallets
Portefeuilles (sous-comptes), soldes et transferts.
list() — Lister les portefeuilles
$wallets = $viva->wallets->list();
balance() — Solde agrégé
$balance = $viva->wallets->balance(); echo $balance['available']; // 150.50 echo $balance['pending']; // 25.00 echo $balance['reserved']; // 0.00 echo $balance['currency']; // 'EUR'
Retour : array{available: float, pending: float, reserved: float, currency: string}
transfer() — Transfert entre wallets
$viva->wallets->transfer( amount: 5000, // 50,00 EUR sourceWalletId: 'source-uuid', targetWalletId: 'target-uuid', description: 'Transfert mensuel', );
Prérequis : « Allow transfers between accounts » dans Settings > API Access.
listDetailed() — Liste enrichie (IBAN, SWIFT)
$wallets = $viva->wallets->listDetailed(); foreach ($wallets as $wallet) { echo $wallet['iban'] . ' — ' . $wallet['amount'] . "\n"; echo $wallet['isPrimary'] ? 'Principal' : 'Secondaire'; }
create() — Créer un portefeuille
$viva->wallets->create(friendlyName: 'Compte secondaire', currencyCode: 'EUR');
update() — Renommer un portefeuille
$viva->wallets->update(walletId: 12345, friendlyName: 'Nouveau nom');
searchTransactions() — Rechercher les transactions de compte
$transactions = $viva->wallets->searchTransactions([ 'date_from' => '2026-03-01', 'date_to' => '2026-03-18', 'walletId' => 12345, ]);
getTransaction() — Détails d'une transaction de compte
$txn = $viva->wallets->getTransaction('transaction-uuid');
5. BankAccounts — $viva->bankAccounts
Comptes bancaires IBAN et virements SEPA.
link() — Lier un IBAN
$result = $viva->bankAccounts->link( iban: 'FR7630006000011234567890189', beneficiaryName: 'Jean Dupont', friendlyName: 'Compte principal', ); echo $result['bankAccountId']; // UUID echo $result['isVivaIban']; // false
transferOptions() — Options de transfert
$options = $viva->bankAccounts->transferOptions('bank-account-uuid');
feeCommand() — Calculer les frais avant virement
$fees = $viva->bankAccounts->feeCommand( bankAccountId: 'bank-account-uuid', amount: 10000, // 100,00 EUR walletId: 'source-wallet-uuid', isInstant: true, // SEPA instantané instructionType: 'SHA', // Frais partagés ); echo $fees['bankCommandId']; // À passer à send() echo $fees['fee']; // Frais en centimes
send() — Exécuter un virement SEPA
$result = $viva->bankAccounts->send( bankAccountId: 'bank-account-uuid', amount: 10000, walletId: 'source-wallet-uuid', bankCommandId: 'fee-command-uuid', // Optionnel description: 'Virement mensuel', ); echo $result['commandId']; // UUID du virement echo $result['isInstant']; // true/false echo $result['fee']; // Frais en centimes
list() — Lister les comptes liés
$accounts = $viva->bankAccounts->list();
get() — Détails d'un compte lié
$account = $viva->bankAccounts->get('bank-account-uuid');
6. NativeCheckout — $viva->nativeCheckout
Paiements Apple Pay et Google Pay.
createChargeToken() — Générer un token de charge
$token = $viva->nativeCheckout->createChargeToken( amount: 1500, paymentData: $applePayPaymentDataString, paymentMethod: 'applepay', // ou 'googlepay' sourceCode: '1234', ); echo $token['chargeToken']; // Token à passer à createTransaction() echo $token['redirectToACSForm']; // Formulaire 3DS (si applicable)
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
amount |
int |
requis | Montant en centimes |
paymentData |
string |
requis | Données Apple Pay / Google Pay |
paymentMethod |
string |
'applepay' |
'applepay' ou 'googlepay' |
sourceCode |
?string |
null |
Source de paiement |
dynamicDescriptor |
?string |
null |
Descripteur dynamique |
createTransaction() — Exécuter la transaction
$txn = $viva->nativeCheckout->createTransaction( chargeToken: $token['chargeToken'], amount: 1500, currencyCode: 978, // EUR (ISO 4217 numérique) merchantTrns: 'ref_123', customerTrns: 'Consultation', ); echo $txn['transactionId']; // UUID echo $txn['statusId']; // 'F' = finalisée echo $txn['amount']; // 1500 echo $txn['orderCode']; // Code de l'ordre
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
chargeToken |
string |
requis | Token de createChargeToken() |
amount |
int |
requis | Montant en centimes |
currencyCode |
int |
978 |
ISO 4217 numérique |
sourceCode |
?string |
null |
Source de paiement |
merchantTrns |
?string |
null |
Référence interne |
customerTrns |
?string |
null |
Description client |
preauth |
bool |
false |
Pré-autorisation ? |
tipAmount |
int |
0 |
Pourboire en centimes |
installments |
?int |
null |
Nombre de versements |
7. DataServices — $viva->dataServices
Rapports MT940 et souscriptions webhook pour les fichiers de données.
mt940() — Rapport MT940
$report = $viva->dataServices->mt940('2026-03-18');
createSubscription() — Créer une souscription webhook
$sub = $viva->dataServices->createSubscription( url: 'https://example.com/webhooks/viva-files', eventType: 'SaleTransactionsFileGenerated', ); echo $sub['subscriptionId'];
updateSubscription() — Mettre à jour
$viva->dataServices->updateSubscription( subscriptionId: 'sub-uuid', url: 'https://example.com/webhooks/new-url', eventType: null, // null = conserver l'actuel );
deleteSubscription() — Supprimer
$viva->dataServices->deleteSubscription('sub-uuid');
listSubscriptions() — Lister
$subs = $viva->dataServices->listSubscriptions(); foreach ($subs as $sub) { echo $sub['subscriptionId'] . ' → ' . $sub['url'] . "\n"; }
requestFile() — Demander la génération d'un fichier
$viva->dataServices->requestFile('2026-03-18');
Déclenche la génération asynchrone. Utilisez une souscription webhook pour être notifié.
8. Webhooks — $viva->webhooks
Vérification et parsing des webhooks Viva Wallet. Aucun appel API — tout est local.
verificationResponse() — Répondre au GET de vérification
public function verify() { return response()->json( $viva->webhooks->verificationResponse('votre-verification-key') ); // => {"StatusCode": 0, "Key": "votre-verification-key"} }
parse() — Parser un webhook POST
public function handle(Request $request) { $event = $viva->webhooks->parse($request->getContent()); echo $event['event_type']; // 'transaction.payment.created' echo $event['event_type_id']; // 1796 echo $event['event_data']; // Données de l'événement match ($event['event_type']) { 'transaction.payment.created' => $this->handlePayment($event['event_data']), 'transaction.refund.created' => $this->handleRefund($event['event_data']), default => null, }; }
Retour : array{event_type: string, event_type_id: int, event_data: array<string, mixed>}
Lève InvalidArgumentException si le JSON est invalide.
isKnownEvent() — Vérifier un ID d'événement
$viva->webhooks->isKnownEvent(1796); // true $viva->webhooks->isKnownEvent(9999); // false
eventTypeIds() — Lister les IDs connus
$ids = $viva->webhooks->eventTypeIds(); // [1796, 1797, 1798, ..., 1828]
21 types d'événements supportés
| ID | Type |
|---|---|
| 1796 | transaction.payment.created |
| 1797 | transaction.refund.created |
| 1798 | transaction.payment.cancelled |
| 1799 | transaction.reversal.created |
| 1800 | transaction.preauth.created |
| 1801 | transaction.preauth.completed |
| 1802 | transaction.preauth.cancelled |
| 1810 | pos.session.created |
| 1811 | pos.session.failed |
| 1812 | transaction.price.calculated |
| 1813 | transaction.failed |
| 1819 | account.connected |
| 1820 | account.verification.status.changed |
| 1821 | account.transaction.created |
| 1822 | command.bank.transfer.created |
| 1823 | command.bank.transfer.executed |
| 1824 | transfer.created |
| 1825 | obligation.created |
| 1826 | obligation.captured |
| 1827 | order.updated |
| 1828 | sale.transactions.file |
9. Account — $viva->account
Informations du compte marchand.
info() — Informations du compte
$info = $viva->account->info(); echo $info['merchantId']; echo $info['businessName']; echo $info['email'];
wallets() — Portefeuilles du compte
$wallets = $viva->account->wallets();
10. Messages — $viva->messages()
Gestion des abonnements webhook via /api/messages/config (Legacy API, Basic Auth).
Utilisez
$viva->webhookRegistrar()->registerAll(...)pour l'enregistrement idempotent des events banking.messages()donne un accès bas niveau si besoin.
register() — Créer un abonnement webhook
$sub = $viva->messages()->register( eventTypeId: 768, // Bank Transfer Created callbackUrl: 'https://example.com/webhooks/viva', ); // => ['Id' => 'sub-uuid', 'Active' => true]
list() — Lister les abonnements
$subscriptions = $viva->messages()->list(); foreach ($subscriptions as $sub) { echo $sub['Id'] . ' → EventTypeId: ' . $sub['EventTypeId']; }
delete() — Supprimer un abonnement
$viva->messages()->delete('sub-uuid-here');
Enregistrement des webhooks banking
Les events 768 (Bank Transfer Created), 769 (Bank Transfer Executed) et 2054 (Account Transaction Created) doivent être enregistrés par chaque merchant via /api/messages/config (pas via les webhooks ISV).
Utilisez WebhookRegistrar pour un enregistrement idempotent : les erreurs "duplicate" sont silencieusement transformées en already_exists.
// Enregistrer les 3 events banking en une ligne $results = $viva->webhookRegistrar()->registerAll( callbackUrl: 'https://example.com/webhooks/viva', ); // => ['768' => 'registered', '769' => 'registered', '2054' => 'already_exists'] // Ou un sous-ensemble d'events $results = $viva->webhookRegistrar()->registerAll( callbackUrl: 'https://example.com/webhooks/viva', events: [768, 769], );
Statuts possibles dans le tableau retourné :
| Statut | Signification |
|---|---|
registered |
Abonnement créé avec succès |
already_exists |
Déjà enregistré (Viva a retourné 400 duplicate) |
error:{message} |
Échec inattendu (503, réseau, etc.) |
Convention cross-SDK :
WebhookRegistrar::BANKING_EVENTSetregisterAll()ont les mêmes noms danssdk-php-viva-isv. La différence est l'absence de$connectedMerchantId— ce SDK parle au nom du merchant lui-même.
Architecture
src/
├── VivaClient.php # Point d'entrée — instancie les 10 ressources + 1 helper
├── Config.php # Configuration (URLs par environnement)
├── HttpClient.php # Client HTTP Guzzle (OAuth2 auto, Basic Auth)
├── Contracts/
│ ├── HttpClientInterface.php # Interface pour HttpClient (mocking)
│ └── MessagesInterface.php # Interface pour Messages (mocking)
├── Enums/
│ ├── Environment.php # demo | production
│ ├── Currency.php # Codes ISO 4217 numériques
│ └── TransactionStatus.php # F, A, C, E, M, X, R
├── Exceptions/
│ ├── VivaException.php # Classe de base (RuntimeException)
│ ├── ApiException.php # Erreur API (4xx, 5xx)
│ ├── AuthenticationException.php # Échec OAuth2 (401)
│ └── ValidationException.php # Validation (422)
├── Helpers/
│ └── WebhookRegistrar.php # Enregistrement idempotent des events banking
└── Resources/
├── Orders.php # Smart Checkout
├── Transactions.php # Get, list, cancel, capture, recurring
├── Sources.php # Sources de paiement
├── Wallets.php # Portefeuilles, soldes, transferts
├── BankAccounts.php # IBAN, virements SEPA
├── NativeCheckout.php # Apple Pay, Google Pay
├── DataServices.php # MT940, souscriptions webhook
├── Webhooks.php # Vérification et parsing local
├── Account.php # Infos du compte
└── Messages.php # Abonnements webhook (/api/messages/config)
L'authentification est gérée automatiquement par HttpClient :
- Legacy API (Basic Auth) — utilisée par Orders, Transactions, Sources
- New API (Bearer OAuth2) — utilisée par Wallets, BankAccounts, NativeCheckout, DataServices, Account
Le token OAuth2 est mis en cache en mémoire et rafraîchi automatiquement avant expiration.
Enums
Environment
use QrCommunication\VivaMerchant\Enums\Environment; $env = Environment::DEMO; $env = Environment::PRODUCTION; $env = Environment::from('demo'); $env->value; // 'demo' $env->apiUrl(); // 'https://demo-api.vivapayments.com' $env->legacyUrl(); // 'https://demo.vivapayments.com' $env->checkoutUrl(); // 'https://demo.vivapayments.com/web/checkout' $env->accountsUrl(); // 'https://demo-accounts.vivapayments.com'
Currency
use QrCommunication\VivaMerchant\Enums\Currency; Currency::EUR->value; // 978 Currency::EUR->iso(); // 'EUR' Currency::fromIso('GBP'); // Currency::GBP (826)
Devises supportées : EUR (978), GBP (826), USD (840), PLN (985), RON (946), BGN (975), CZK (203), HRK (191), HUF (348), DKK (208), SEK (752), NOK (578).
TransactionStatus
use QrCommunication\VivaMerchant\Enums\TransactionStatus; $status = TransactionStatus::from('F'); $status->isSuccessful(); // true $status->isPending(); // false $status->isFailed(); // false $status->label(); // 'Finalized'
| Valeur | Constante | isSuccessful() |
isPending() |
isFailed() |
|---|---|---|---|---|
F |
FINALIZED |
oui | ||
A |
PENDING |
oui | ||
C |
CLEARING |
oui | ||
E |
ERROR |
oui | ||
M |
MANUALLY_REVERSED |
oui | ||
X |
REQUIRES_ACTION |
|||
R |
REFUNDED |
Gestion d'erreurs
Toutes les exceptions héritent de VivaException qui étend RuntimeException.
RuntimeException
└── VivaException
├── ApiException
├── AuthenticationException
└── ValidationException
use QrCommunication\VivaMerchant\Exceptions\ApiException; use QrCommunication\VivaMerchant\Exceptions\AuthenticationException; use QrCommunication\VivaMerchant\Exceptions\ValidationException; use QrCommunication\VivaMerchant\Exceptions\VivaException; try { $order = $viva->orders->create(amount: 1500); } catch (AuthenticationException $e) { // Identifiants invalides — httpStatus = 401 echo $e->getMessage(); } catch (ValidationException $e) { // Erreur de validation — httpStatus = 422 foreach ($e->errors as $field => $messages) { echo "$field: " . implode(', ', $messages); } } catch (ApiException $e) { // Erreur API générale — 400, 404, 500, etc. echo $e->httpStatus; echo $e->getErrorCode(); echo $e->getErrorText(); print_r($e->responseBody); } catch (VivaException $e) { // Toute autre erreur SDK }
Propriétés et méthodes de VivaException
| Membre | Type | Description |
|---|---|---|
$httpStatus |
int |
Code HTTP de la réponse |
$responseBody |
?array |
Corps JSON décodé de la réponse |
getErrorCode() |
?int |
Code d'erreur Viva (ErrorCode) |
getErrorText() |
?string |
Message d'erreur Viva (ErrorText, message, ou detail) |
Webhooks — Guide d'intégration
1. Configurer le webhook dans le Dashboard Viva
- Aller dans Settings > API Access > Webhooks
- Ajouter l'URL de votre endpoint
- Noter la clé de vérification
2. Gérer la vérification (GET)
// Route GET /webhooks/viva public function verify() { return response()->json( $viva->webhooks->verificationResponse('votre-clé') ); }
3. Recevoir les événements (POST)
// Route POST /webhooks/viva public function handle(Request $request) { $event = $viva->webhooks->parse($request->getContent()); match ($event['event_type']) { 'transaction.payment.created' => $this->onPayment($event['event_data']), 'transaction.refund.created' => $this->onRefund($event['event_data']), 'transaction.preauth.created' => $this->onPreauth($event['event_data']), 'transaction.preauth.completed' => $this->onCapture($event['event_data']), default => logger()->info('Webhook ignoré : ' . $event['event_type']), }; return response()->json(['status' => 'ok']); }
4. Enregistrement des webhooks banking par API (optionnel)
Les events 768, 769 et 2054 (virements et transactions de compte) ne peuvent pas être configurés depuis le Dashboard Viva pour les marchands standard. Ils doivent être enregistrés par API via /api/messages/config.
// Enregistrement idempotent au démarrage de l'application $results = $viva->webhookRegistrar()->registerAll( callbackUrl: config('services.viva.webhook_url'), ); // $results = ['768' => 'registered', '769' => 'registered', '2054' => 'already_exists'] // Les 'already_exists' sont silencieux — safe à relancer à chaque boot.
Distinction ISV vs merchant : dans le SDK ISV (
sdk-php-viva-isv), ces events sont enregistrés au niveau de l'ISV pour unconnectedMerchantId. Ici, ils s'appliquent directement au compte marchand authentifié.
Carte de test
Pour l'environnement demo :
| Champ | Valeur |
|---|---|
| Numéro de carte | 4111 1111 1111 1111 |
| Expiration | Toute date future |
| CVV | 111 |
| 3DS | Pas de 3DS en demo |
Documentation interactive
La documentation interactive (ReDoc) est disponible en ligne :
https://qrcommunication.github.io/sdk-php-viva-merchant/
Elle détaille chaque classe, méthode, paramètre et type de retour du SDK.
Intégration IA
Ce SDK inclut un skill détaillé (skill/SKILL.md) automatiquement détecté par les assistants IA. Il fournit la référence complète des 9 resources, 34+ méthodes, enums, exceptions et patterns d'implémentation.
| Outil | Fichier | Détection |
|---|---|---|
| Claude Code | CLAUDE.md + skill/SKILL.md |
Automatique |
| Cursor | .cursorrules |
Automatique |
| GitHub Copilot | .github/copilot-instructions.md |
Automatique |
| OpenAI Codex | AGENTS.md |
Automatique |
// Un agent IA peut construire cet appel à partir de : // "Crée un paiement de 25 EUR pour une consultation" $order = $viva->orders->create( amount: 2500, customerDescription: 'Consultation', );
Licence
MIT — QrCommunication