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

Viva Wallet ISV SDK

qrcommunication/viva-isv-sdk

PHP 8.2+, guzzlehttp/guzzle ^7.8

Quoi de neuf en v1.7.2 June 2026

Correctif sources : list()/find() retirées (elles reposaient sur GET /api/sources, inexistant côté Viva). IsvSources expose désormais 4 méthodes ; ensure()/ensureForMerchant() dédupliquent via la réponse documentée 409 « Source already exists » de Viva. PHP 8.2+. GitHub Release v1.7.2 →
  • IsvSources ($isv->sources) — list/find retirées, ensure via 409 — list()/listForMerchant()/find()/findForMerchant() sont retirées — elles reposaient sur GET /api/sources, non documenté par Viva (seul POST /api/sources existe). ensure()/ensureForMerchant() sont réimplémentées sur le mécanisme documenté : elles tentent la création et, sur 409 « Source already exists », retournent ['sourceCode' => …, 'status' => 'already_exists'] au lieu de lever. create()/createForMerchant() inchangées. Pour lister les sources existantes : Self Care / app bancaire Viva.
📜 Antérieur — Quoi de neuf en v1.7.1 (6 juin 2026)
  • IsvSources ($isv->sources) — gestion des sources de paiement — Ajout de createForMerchant() (création au nom d'un marchand connecté, Composite Basic Auth) et des variantes idempotentes ensure()/ensureForMerchant(). Note : les méthodes list()/find() introduites ici reposaient sur GET /api/sources (inexistant côté Viva) et ont été retirées en v1.7.2.
📜 Antérieur — Quoi de neuf en v1.7.0 (6 juin 2026)
  • Nouvelle ressource IsvSources ($isv->sources) — create() enregistre une source de paiement sur le propre compte ISV via POST /api/sources (Legacy ISV Basic Auth, body camelCase). Étendu ensuite avec createForMerchant() + ensure[ForMerchant]() — voir v1.7.2.
  • Nouvelle ressource Resellers ($isv->resellers) — validateCashPayment(), validateBillPayment(), sendCashPaymentOtp(), sendBillPaymentOtp(), cashPayment(), billPayment(), createOrder() — flux cash & bill payment + ordres reseller via /resellers/v1/ (Bearer New API).
  • EcrTerminals — nouvelles méthodes refund(), createAction(), getAction() — Remboursement référencé lié au parentSessionId de la vente d'origine (POST /ecr/isv/v1/transactions:refund), plus requêtes d'action terminal (POST /ecr/isv/v1/actions, ex. aade-fim-control) et polling du résultat (GET /ecr/isv/v1/actions/{id}, HTTP 202 → []).
  • IsvOrders — nouvelles méthodes retrieve(), cancel() — GET / DELETE /api/orders/{code} pour les marchands connectés (Composite Basic Auth).
  • IsvTransactions — nouvelles méthodes listByClearanceDate(), listByOrderCode(), listBySourceCode(), moto(), increasePreauth() — Variantes de filtrage sur GET /api/transactions, charge MOTO (POST /api/transactions, force moto:true), et pré-auth incrémentale (POST /acquiring/v1/isv/transactions/{id}:increasepreauth, Bearer New API).
  • Correctif de verbe sur ConnectedAccounts::update() (POST → PATCH) — Utilise désormais PATCH /platforms/v1/accounts/{accountId}, conforme au verbe documenté de la Marketplace API Viva. HttpClient gagne aussi patch() et legacyPatch().
  • Note — domaine Platform déjà couvert — /platforms/v1/accounts et /platforms/v1/transfers étaient déjà couverts par ConnectedAccounts et Transfers — aucune ressource Platforms n'a été ajoutée pour éviter la duplication. Seul le verbe de mise à jour a été corrigé.
📜 Antérieur — Quoi de neuf en v1.6.0 (4 mai 2026)
  • Nouveaux helpers IsvAccounts::getOnboardingUrl(), isVerified(), isAcquiringEnabled() — Répliquent les méthodes ConnectedAccounts via /isv/v1/ qui fonctionne sur les comptes ISV pure (vs /platforms/v1/ qui retourne 403 sans Marketplace API activée).
  • Fix breaking sur IsvWebhooks::create() — Signature passée de $eventType:string à $eventTypeId:int. L'API Viva exige l'ID numérique (1796/1797/1798/1799/8193/8194), pas un nom d'event en string.
  • Nouvelle méthode IsvWebhooks::verificationToken() — GET /isv/v1/webhooks/token pour récupérer la verification key requise avant tout enregistrement. Sert à signer les webhooks entrants HMAC-SHA256.
  • MerchantWebhookRegistrar — nouveau endpoint_unavailable statut — Distingue le HTTP 404 sur /api/messages/config des autres échecs. Plus 3 helpers statiques : allSucceeded(), hasEndpointIssue(), allFailed().
📜 Antérieur — Quoi de neuf en v1.5.0 (1er mai 2026)
  • Nouvelle ressource IsvMessages — Enregistrement de webhooks au niveau marchand via Composite Basic Auth (/api/messages/config).
  • Nouveau helper MerchantWebhookRegistrar — Enregistrement idempotent des événements banking pour un marchand connecté.
  • Constante BANKING_EVENTS = [768, 769, 2054]
  • IsvConfig::isProduction() et isSandbox()

Installation

composer require qrcommunication/viva-isv-sdk

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

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

Quick Start

use QrCommunication\VivaIsv\VivaIsvClient;

// 1. Instancier le client avec les 6 credentials
$isv = new VivaIsvClient(
    clientId:       'isv-client-id.apps.vivapayments.com',
    clientSecret:   'isv-client-secret',
    merchantId:     'isv-merchant-uuid',
    apiKey:         'isv-api-key',
    resellerId:     'reseller-uuid',
    resellerApiKey: 'reseller-api-key',
    environment:    'demo', // 'demo' ou 'production'
);

// 2. Créer un compte marchand connecté
$account = $isv->accounts->create(
    email: 'merchant@example.com',
    returnUrl: 'https://myapp.com/onboarding/complete',
);
// => ['accountId' => 'uuid', 'invitation' => ['redirectUrl' => 'https://...']]

// 3. Créer un ordre avec commission ISV
$order = $isv->orders->create(
    connectedMerchantId: $account['accountId'],
    amount: 1500,    // 15,00 EUR
    isvAmount: 100,  // 1,00 EUR de commission
);
// => ['order_code' => 1234567890, 'checkout_url' => 'https://...']

// 4. Rediriger le client vers le checkout
header('Location: ' . $order['checkout_url']);

// 5. Capturer une pré-autorisation
$isv->transactions->capture('preauth-txn-uuid', 'merchant-uuid', amount: 1500);

// 6. Vente sur terminal POS
$session = $isv->terminals->sale(
    terminalId: 16014231,
    amount: 1500,
    isvAmount: 100,
    terminalMerchantId: 'merchant-uuid',
    cashRegisterId: 'POS-CR1',
);
$result = $isv->terminals->pollUntilComplete($session['session_id']);

// 7. Rembourser
$isv->transactions->cancel('txn-uuid', 'merchant-uuid', amount: 500);

// Test de connexion
if ($isv->testConnection()) {
    echo 'Connexion ISV OK';
}

Configuration

Constructeur VivaIsvClient

Paramètre Type Description
clientIdstringClient ID OAuth2 ISV (*.apps.vivapayments.com)
clientSecretstringClient Secret OAuth2 ISV
merchantIdstringMerchant UUID ISV (pour ISV Basic Auth)
apiKeystringAPI Key ISV (pour ISV Basic Auth)
resellerIdstringReseller UUID (pour Composite Basic Auth)
resellerApiKeystringReseller API Key (pour Composite Basic Auth)
environmentstring|Environment'demo' ou 'production' (défaut : 'demo')

Architecture des ressources

VivaIsvClient
├── $accounts          → ConnectedAccounts   (7 méthodes)
├── $isvAccounts       → IsvAccounts         (3 méthodes)
├── $orders            → IsvOrders           (4 méthodes)
├── $transactions      → IsvTransactions     (10 méthodes)
├── $terminals         → EcrTerminals        (9 méthodes)
├── $transfers         → Transfers           (2 méthodes)
├── $marketplace       → MarketplaceOrders   (2 méthodes)
├── $nativeCheckout    → NativeCheckoutIsv   (2 méthodes)
├── $isvWebhooks       → IsvWebhooks             (4 méthodes)
├── $webhooks          → Webhooks                (3 méthodes)
├── $isvMessages       → IsvMessages             (3 méthodes — NOUVEAU v1.5.0)
├── $sources           → IsvSources              (4 méthodes)
├── $resellers         → Resellers               (7 méthodes — NOUVEAU v1.7.0)
└── merchantWebhookRegistrar() → MerchantWebhookRegistrar (helper — NOUVEAU v1.5.0)

14 ressources/helpers

Authentification

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

Mode Username Password Utilisé pour
ISV OAuth2 (Bearer) clientId clientSecret Comptes, ordres ISV, terminaux, transferts, marketplace, native checkout, webhooks ISV
ISV Basic Auth merchantId apiKey Opérations legacy sur le propre compte ISV
Composite Basic Auth {resellerId}:{connectedMerchantId} resellerApiKey Transactions des marchands connectés (capture, récurrent, annulation)
Composite Basic Auth — format non documenté par Viva Wallet
Le format {ResellerID}:{ConnectedMerchantID} comme username avec {ResellerAPIKey} comme password a été découvert empiriquement lors de la certification ISV. Le SDK construit ce header automatiquement — vous passez uniquement le connectedMerchantId aux méthodes de $isv->transactions.

Token OAuth2 — refresh automatique

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

$isv->invalidateToken();

Référence des ressources

1 ConnectedAccounts $isv->accounts

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

MéthodeSignatureRetour
createcreate(string $email, string $returnUrl, ?string $partnerName, ?string $logoUrl)array{accountId, invitation}
getget(string $accountId)array (détails du compte)
listlist()array (liste paginée)
isVerifiedisVerified(string $accountId)bool
onboardingUrlonboardingUrl(string $accountId)?string
update v1.7.0 PATCHupdate(string $accountId, array $attributes)PATCH /platforms/v1/accounts/{id}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 (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éthodeSignatureRetour
createcreate(string $email, string $returnUrl, ?string $partnerName, ?string $primaryColor, ?string $logoUrl)array{accountId, invitation}
getget(string $accountId)array
getOnboardingUrl v1.6getOnboardingUrl(string $accountId)?string
isVerified v1.6isVerified(string $accountId)bool
isAcquiringEnabled v1.6isAcquiringEnabled(string $accountId)bool
listlist()array ⚠ HTTP 405 prod
Utiliser IsvAccounts sur les comptes ISV pure — L'API Marketplace (/platforms/v1/*) retourne HTTP 403 sauf si explicitement activée par Viva. Les méthodes getOnboardingUrl(), isVerified() et isAcquiringEnabled() (v1.6) répliquent les méthodes ConnectedAccounts via /isv/v1/ qui fonctionne toujours sur les comptes ISV.
$account = $isv->isvAccounts->create(
    email: 'merchant@example.com',
    returnUrl: 'https://myapp.com/onboarding/done',
    partnerName: 'Ma Plateforme',
    primaryColor: '#0052FF',
    logoUrl: 'https://myapp.com/logo.png',
);

$details = $isv->isvAccounts->get($account['accountId']);

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

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

3 IsvOrders $isv->orders

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

MéthodeSignatureRetour
create create(string $connectedMerchantId, int $amount, int $isvAmount, ?string $customerDescription, ?string $merchantReference, bool $allowRecurring, bool $preauth) array{order_code, checkout_url}
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éthodeSignatureRetour
getget(string $transactionId, string $connectedMerchantId)array
listByDatelistByDate(string $connectedMerchantId, string $date)array
listByClearanceDate v1.7.0listByClearanceDate(string $connectedMerchantId, string $clearanceDate)array
listByOrderCode v1.7.0listByOrderCode(string $connectedMerchantId, int $orderCode)array
listBySourceCode v1.7.0listBySourceCode(string $connectedMerchantId, string $sourceCode, 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
moto v1.7.0moto(string $connectedMerchantId, array $payload)POST /api/transactionsarray
increasePreauth v1.7.0increasePreauth(string $transactionId, string $connectedMerchantId, int $amount, ?string $customerDescription, ?string $merchantReference, ?string $sourceCode, ?int $currencyCode, ?string $idempotencyKey)POST /acquiring/v1/isv/transactions/{id}:increasepreautharray
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');

// 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 &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}
refund v1.7.0refund(string $sessionId, int $amount, ?string $merchantReference, int $terminalId, string $terminalMerchantId, string $cashRegisterId, int $currencyCode, ?string $refundSessionId)POST /ecr/isv/v1/transactions:refundarray{session_id, success}
createAction v1.7.0createAction(array $payload)POST /ecr/isv/v1/actionsarray
getAction v1.7.0getAction(string $actionId)GET /ecr/isv/v1/actions/{id}array
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();
}

// 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.0refund() 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.0getAction() 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éthodeSignatureRetour
sendsend(string $targetAccountId, int $amount, ?string $sourceWalletId, ?string $transactionId, ?string $description)array{transferId}
reversereverse(string $transferId, ?int $amount)array{transferId}
// Envoyer des fonds à un vendeur
$transfer = $isv->transfers->send(
    targetAccountId: 'seller-account-uuid',
    amount: 1000,                    // 10,00 EUR
    transactionId: 'txn-uuid',       // Lier à une transaction existante
    description: 'Commission mars 2026',
);

echo $transfer['transferId'];

// Annuler totalement un transfert
$isv->transfers->reverse('transfer-uuid');

// Annuler partiellement (5,00 EUR)
$isv->transfers->reverse('transfer-uuid', amount: 500);

7 MarketplaceOrders $isv->marketplace

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

MéthodeSignatureRetour
create create(int $amount, string $sellerAccountId, int $sellerAmount, ?string $customerDescription, ?string $merchantReference, ?string $sourceCode, bool $preauth) array{order_code, checkout_url, platform_fee}
cancel cancel(string $transactionId, ?int $amount, bool $reverseTransfers, bool $refundPlatformFee) array
// Créer un ordre marketplace
$order = $isv->marketplace->create(
    amount: 1500,                    // 15,00 EUR total
    sellerAccountId: 'seller-uuid',
    sellerAmount: 1200,              // 12,00 EUR au vendeur
    customerDescription: 'Achat marketplace',
    merchantReference: 'MP-2026-001',
);

echo $order['order_code'];
echo $order['checkout_url'];
echo $order['platform_fee'];  // 300 = 3,00 EUR de commission plateforme

// Remboursement total avec reversal des transferts
$isv->marketplace->cancel('txn-uuid');

// Remboursement partiel sans rembourser la commission plateforme
$isv->marketplace->cancel(
    transactionId: 'txn-uuid',
    amount: 500,
    reverseTransfers: true,
    refundPlatformFee: false,
);

8 NativeCheckoutIsv $isv->nativeCheckout

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

MéthodeSignatureRetour
createChargeToken createChargeToken(string $connectedMerchantId, int $amount, string $paymentData, int $paymentMethodId) array{chargeToken}
createTransaction createTransaction(string $connectedMerchantId, string $chargeToken, int $amount, int $isvAmount, int $currencyCode, ?string $merchantTrns, ?string $customerTrns, bool $preauth) array{transactionId, statusId}

Flux en 2 étapes :

  1. Le client collecte les données carte via le JS SDK Viva et obtient paymentData (chiffré côté client).
  2. Votre serveur crée un charge token via createChargeToken(), puis exécute la transaction via createTransaction().
// Étape 1 : créer un charge token
$token = $isv->nativeCheckout->createChargeToken(
    connectedMerchantId: 'merchant-uuid',
    amount: 1500,
    paymentData: $encryptedCardData,  // Du JS SDK Viva
    paymentMethodId: 0,               // 0 = carte par défaut
);

// Étape 2 : exécuter la transaction
$txn = $isv->nativeCheckout->createTransaction(
    connectedMerchantId: 'merchant-uuid',
    chargeToken: $token['chargeToken'],
    amount: 1500,
    isvAmount: 100,
    currencyCode: 978,                // EUR
    merchantTrns: 'INV-2026-001',
    customerTrns: 'Consultation bien-être',
);

echo $txn['transactionId'];
echo $txn['statusId'];

9 IsvWebhooks $isv->isvWebhooks

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

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

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

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

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

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

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

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

10 Webhooks $isv->webhooks

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

MéthodeSignatureRetour
verificationResponseverificationResponse(string $verificationKey)array{StatusCode, Key}
parseparse(string $rawBody)array{event_type, event_type_id, event_data}
isKnownEventWebhooks::isKnownEvent(int $eventTypeId) (statique)bool
// GET — vérification initiale
$verificationKey = config('services.viva.verification_key');
return response()->json(
    $isv->webhooks->verificationResponse($verificationKey)
);
// => {"StatusCode": 0, "Key": "votre-cle"}

// POST — parsing des événements
$event = $isv->webhooks->parse(file_get_contents('php://input'));

echo $event['event_type'];     // 'transaction.payment.created'
echo $event['event_type_id'];  // 1796
$data = $event['event_data'];

// Pattern Laravel Controller
match ($event['event_type']) {
    'transaction.payment.created' => $this->handlePayment($event['event_data']),
    'transaction.refund.created'  => $this->handleRefund($event['event_data']),
    'account.connected'           => $this->handleNewAccount($event['event_data']),
    default => null,
};

// Vérifier si un eventTypeId est connu (méthode statique)
use QrCommunication\VivaIsv\Resources\Webhooks;

if (Webhooks::isKnownEvent(1796)) {
    echo 'Événement reconnu';
}

11 IsvMessages $isv->isvMessages New v1.5.0

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

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

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

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

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

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

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

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

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

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

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

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

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

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 sourcesGET /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éthodeSignatureAuthRetour
createcreate(array $payload): arrayPOST /api/sourcesISV Basic Autharray
createForMerchantcreateForMerchant(string $connectedMerchantId, array $payload): arrayPOST /api/sourcesComposite Basic Autharray
ensureensure(array $payload): array — idempotent (via 409)ISV Basic Autharray
ensureForMerchantensureForMerchant(string $connectedMerchantId, array $payload): array — idempotent (via 409)Composite Basic Autharray

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éthodeSignatureEndpoint
validateCashPaymentvalidateCashPayment(array $payload)POST /resellers/v1/transactions/cashPayments:validate
validateBillPaymentvalidateBillPayment(array $payload)POST /resellers/v1/transactions/billPayments:validate
sendCashPaymentOtpsendCashPaymentOtp(array $payload)POST /resellers/v1/transactions/cashPayments:sendotp
sendBillPaymentOtpsendBillPaymentOtp(array $payload)POST /resellers/v1/transactions/billPayments:sendotp
cashPaymentcashPayment(array $payload)POST /resellers/v1/transactions/cashPayments
billPaymentbillPayment(array $payload)POST /resellers/v1/transactions/billPayments
createOrdercreateOrder(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.
ValeurConstanteDescription
0SUCCESSTransaction réussie
1003TERMINAL_TIMEOUTTerminal hors délai
1006DECLINEDTransaction refusée
1016ABORTEDTransaction annulée
1020INSUFFICIENT_FUNDSFonds insuffisants
1099GENERIC_ERRORErreur générique
1100IN_PROGRESSEn cours — continuer le polling
6000BAD_PARAMSParamètres invalides

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

use QrCommunication\VivaIsv\Enums\TransactionEventId;

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

Environment

use QrCommunication\VivaIsv\Enums\Environment;

// Passer en string ou en enum — les deux sont acceptés
$isv = new VivaIsvClient(..., environment: 'demo');
$isv = new VivaIsvClient(..., environment: Environment::PRODUCTION);

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

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

use QrCommunication\VivaIsv\IsvConfig;

$config = new IsvConfig(
    clientId:       'isv-client-id.apps.vivapayments.com',
    clientSecret:   'isv-client-secret',
    merchantId:     'isv-merchant-uuid',
    apiKey:         'isv-api-key',
    resellerId:     'reseller-uuid',
    resellerApiKey: 'reseller-api-key',
    environment:    'production',
);

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

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

Gestion des erreurs

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

RuntimeException
└── VivaException         (base — httpStatus, responseBody, getErrorCode(), getErrorText())
    ├── ApiException      (erreurs API 4xx/5xx)
    └── AuthenticationException  (erreurs OAuth2 — httpStatus = 401)
use QrCommunication\VivaIsv\Exceptions\ApiException;
use QrCommunication\VivaIsv\Exceptions\AuthenticationException;

try {
    $order = $isv->orders->create('merchant-uuid', 1500, isvAmount: 100);
} catch (AuthenticationException $e) {
    // Credentials ISV invalides
    echo $e->getMessage(); // 'ISV OAuth2 authentication failed: ...'
} catch (ApiException $e) {
    // Erreur API (400, 404, 500, etc.)
    echo $e->getMessage();       // Message d'erreur
    echo $e->httpStatus;         // Code HTTP
    echo $e->getErrorCode();     // Code Viva (ErrorCode)
    echo $e->getErrorText();     // Texte Viva (ErrorText)
    print_r($e->responseBody);   // Body JSON complet
}
Les méthodes capture() et recurring() lancent ApiException si ErrorCode !== 0 dans la réponse Viva Wallet.

Événements webhook (24)

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

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

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

IDTypeDescription
768command.bank.transfer.createdOrdre de virement bancaire créé
769command.bank.transfer.executedOrdre de virement bancaire exécuté
2054account.transaction.createdTransaction de compte (wallet) créée
Constante BANKING_EVENTS : Le SDK expose MerchantWebhookRegistrar::BANKING_EVENTS = [768, 769, 2054] pour éviter de coder ces IDs en dur dans votre application.

Test en sandbox

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

Carte de test

ChampValeur
Numéro4111 1111 1111 1111
ExpirationToute date future
CVV111

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

use QrCommunication\VivaIsv\Enums\TransactionEventId;

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

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

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

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

Endpoints qui ne fonctionnent PAS sur les comptes ISV pure

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

OAuth split — ISV vs Smart Checkout (obligatoire)

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

Pièges UX d'onboarding

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

Tickets support à ouvrir chez Viva

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

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