"> Aller au contenu principal
TypeScript TypeScript v1.0.0 MIT

Viva Local Terminal TypeScript SDK

@qrcommunication/viva-local-terminal-sdk

Node.js 18+

⚠️ Ce n'est PAS l'API cloud Viva This is NOT the Viva cloud API

Modèle peer-to-peer (P2P), sans authentification. Ce SDK ne communique pas avec api.vivapayments.com. Il parle directement au terminal de carte sur votre réseau local, en adressant l'adresse IP et le port du terminal lui-même. — Peer-to-peer (P2P), no authentication. This SDK talks directly to the card terminal on your LAN.
API cloud Viva Local Terminal API (ce SDK)
Base URL https://api.vivapayments.com https://<ip-terminal>:<port>
Réseau Internet Réseau local (LAN), P2P
Authentification OAuth2 / Basic / Bearer Aucune — pas de header Authorization
TLS CA publique Certificat auto-signé du terminal
Erreurs 400 JSON structuré Chaîne brute (texte du terminal verbatim)

Spec officielle : « In a closed network environment, authentication is not required for peer-to-peer communication. [...] eliminating the need to include an Authorization tag in the header. »

Modèle asynchrone (polling)

Les opérations de transaction (sale, refund, capturePreauth…) résolvent immédiatement un state (typiquement PROCESSING) et un sessionId. Vous générez vous-même le sessionId (UUID), puis vous interrogez terminal.sessions.get(sessionId) jusqu'à SUCCESS ou FAILURE (champ payloadData renseigné).

Prérequis réseau

  1. L'hôte exécutant votre code et le terminal doivent être sur le même LAN.
  2. Vous devez connaître l'IP et le port du terminal (app Viva.com Terminal, réseau, ou zeroconf/mDNS).
  3. Le terminal sert du HTTPS, généralement avec un certificat auto-signé — voir TLS.

Installation

Installez le SDK via npm / pnpm. Requiert Node 18+ (ou tout runtime avec fetch global : navigateurs, Deno).

Install via npm / pnpm. Requires Node 18+ (or any runtime with a global fetch).

npm install @qrcommunication/viva-local-terminal-sdk
# ou
pnpm add @qrcommunication/viva-local-terminal-sdk

Publié en ESM + CJS avec types .d.ts.

Démarrage rapide Quick Start

import { VivaLocalTerminalClient } from "@qrcommunication/viva-local-terminal-sdk";
import { randomUUID } from "node:crypto";

// 1. Pointer le client sur l'IP + port du terminal sur le LAN
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080", // IP + port du terminal
  verifyTls: false,                              // certificat auto-signé sur LAN fermé
});

// 2. Démarrer une vente (montant en centimes) — vous générez le sessionId
const sessionId = randomUUID();
const res = await terminal.transactions.sale({
  sessionId,
  amount: 1170,        // 11,70 EUR
  currencyCode: 978,   // EUR
  merchantReference: "order-123",
});
// res.state === "PROCESSING"

// 3. Interroger le résultat une fois le client ayant payé
const session = await terminal.sessions.get(sessionId);
// session.state === "SUCCESS" une fois la carte traitée

// 4. Contrôler le terminal
await terminal.device.screenControl(true);
await terminal.device.brightness(0.5);

Configuration

Le constructeur reçoit un objet d'options et n'accepte aucun identifiant (pas de clé API, client ID ou secret) — le modèle P2P est sans authentification.

The constructor takes an options object and accepts no credentials — the P2P model is unauthenticated.

Option Type Défaut Description
terminalBaseUrl string requis URL de base : scheme + IP + port (ex : https://192.168.1.50:8080). Scheme obligatoire.
verifyTls boolean true false désactive la vérification (cert auto-signé sur LAN fermé).
useIsvEndpoints boolean false Cible les variantes ISV (chemins à slash final, ex : /pos/v1/sale/).
timeout number 30000 Timeout par requête (millisecondes).
fetch typeof fetch global Implémentation fetch personnalisée (tests).
dispatcher Dispatcher Dispatcher undici. Omis + verifyTls: falseAgent undici avec rejectUnauthorized: false créé automatiquement.

Propriétés publiques

terminal.transactions  // Transactions
terminal.sessions      // Sessions
terminal.device        // Device

terminal.getConfig();  // Config courante (introspection)

TLS — certificats auto-signés TLS — self-signed certificates

Le terminal sert du HTTPS, le plus souvent avec un certificat auto-signé.

// 1. Vérification standard (CA système) — défaut. Échoue sur cert auto-signé.
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
});

// 2. Désactiver la vérification (modèle de confiance LAN-only)
// Sur Node, un Agent undici avec rejectUnauthorized: false est attaché automatiquement.
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
  verifyTls: false,
});

// 3. Contrôle fin : fournir votre propre Dispatcher undici (ex : CA pinné)
import { Agent } from "undici";
const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
  dispatcher: new Agent({ connect: { ca: myTerminalCaPem } }),
});
Sur les runtimes sans undici (navigateur), verifyTls dépend du comportement de fetch de la plateforme.

Ressources

1 Transactions terminal.transactions

Opérations de transaction. Chaque opération résout immédiatement state + sessionId ; interroger sessions.get() pour le résultat final. Montants toujours en centimes.

Transaction operations. Each resolves immediately with state + sessionId; poll sessions.get() for the outcome. Amounts in cents.

sale(params: SaleParams): Promise<TransactionResponse>

Initier une vente — POST /pos/v1/sale. Le terminal affiche le montant et attend la carte.

SaleParams :

Champ Type Requis Description
sessionIdstringouiUUID de session généré par l'appelant
amountnumberouiMontant à autoriser, en centimes
merchantReferencestringnonRéférence libre côté marchand
customerTrnsstringnonRéférence libre côté client
preauthbooleannonLe paiement est-il une pré-autorisation
tipAmountnumbernonPourboire en centimes (incompatible avec preauth)
paymentMethodPaymentMethod | stringnonMéthode affichée en premier (ex : CardPresent)
showTransactionResultbooleannonAfficher le résultat à l'écran
showReceiptbooleannonAfficher le reçu + résultat
currencyCodenumbernonCode ISO 4217 numérique (devise marchand)
skipSurchargebooleannonDésactiver le surcharge même sur cartes éligibles
saleToAcquirerDatastringnonJSON base64 de métadonnées acquéreur
maxInstalmentsnumbernonVersements max (marchands grecs)
aadeProviderIdaadePreloadedDurationstring/boolean/numbernonParamètres AADE (Grèce)
fiscalisationDataRecord<string, unknown>nonObjet Viva Fiscalisation
isvDetailsRecord<string, unknown>nonObjet partenaire / fee / multi-marchand (ISV uniquement)

Retourne : Promise<TransactionResponse>{ state?, sessionType?, sessionId? }

const res = await terminal.transactions.sale({
  sessionId: "4bdebe62-c211-4ca0-a994-b2fbea2061c5",
  amount: 1170,          // 11,70 EUR
  currencyCode: 978,     // EUR
  merchantReference: "order-123",
  showReceipt: true,
});

console.log(res.state);       // "PROCESSING"
console.log(res.sessionType); // "SALE"

// Puis polling :
const session = await terminal.sessions.get(res.sessionId!);

capturePreauth(params: CapturePreauthParams): Promise<TransactionResponse>

Capturer (compléter) une vente pré-autorisée — POST /pos/v1/preauth-completion. Champs requis : sessionId, amount, transactionId. Optionnels : merchantReference, customerTrns, currencyCode, saleToAcquirerData, paramètres AADE.

const res = await terminal.transactions.capturePreauth({
  sessionId: crypto.randomUUID(),
  amount: 1500,          // 15,00 EUR
  transactionId: "preauth-transaction-id",
});

refund(params: RefundParams): Promise<TransactionResponse>

Rembourser / annuler une transaction par son ID d'origine — POST /pos/v1/refund. Champs requis : sessionId, amount, transactionId. Optionnels : orderCode, shortOrderCode, currencyCode, showTransactionResult, showReceipt, txnDateFrom, txnDateTo, paramètres AADE.

const res = await terminal.transactions.refund({
  sessionId: crypto.randomUUID(),
  amount: 1170,
  transactionId: "original-sale-transaction-id",
});

unreferencedRefund(params: UnreferencedRefundParams): Promise<TransactionResponse>

Remboursement non lié à une transaction d'origine — POST /pos/v1/unreferenced-refund. Champs requis : sessionId, amount. Optionnels : merchantReference, customerTrns, showTransactionResult, showReceipt, paramètres AADE, fiscalisationData.

const res = await terminal.transactions.unreferencedRefund({
  sessionId: crypto.randomUUID(),
  amount: 500, // 5,00 EUR
});

abort(sessionId: string): Promise<TransactionResponse>

Interrompre une session SALE en cours — POST /pos/v1/abort.

await terminal.transactions.abort("4bdebe62-c211-4ca0-a994-b2fbea2061c5");

aadeFimControl(token: string): Promise<AadeFimControlResponse>

Action de contrôle AADE FIM (fiscalisation grecque) — POST /pos/v1/aade-fim-control. Crée l'Echo Message + le Control Message et valide la Master Key, le KCV et la Session Key chiffrée.

Retourne : Promise<{ status?: string; message?: string }>

const res = await terminal.transactions.aadeFimControl(
  "ECR0210U/RCFB77000041/CMAC_K:1ED9F7AE0B2509281BBC2DE38EF2A12B:CC5FFF",
);
console.log(res.status);  // "success"
console.log(res.message); // "Fim control success"

retrieve(params?: RetrieveTransactionsParams): Promise<TransactionsListResponse>

Lister les transactions historiques avec filtres optionnels — GET /pos/v1/transactions. transactionTypes accepte un id unique ou un tableau (envoyé séparé par virgules).

Retourne : Promise<{ isSuccess?: boolean; transactions?: Array<Record<string, unknown>> }>

import { TransactionTypeId } from "@qrcommunication/viva-local-terminal-sdk";

const result = await terminal.transactions.retrieve({
  transactionTypes: [TransactionTypeId.SALE, TransactionTypeId.REFUND],
  startDate: "2026-03-01T00:00:00Z",
  endDate: "2026-03-31T23:59:59Z",
});

for (const txn of result.transactions ?? []) {
  console.log(txn.orderCode);
}

2 Sessions terminal.sessions

Récupération de session (polling). Après qu'une opération a retourné PROCESSING, interrogez la session pour le résultat final. payloadData n'est renseigné qu'une fois la transaction terminée (SUCCESS/FAILURE).

get(sessionId: string): Promise<SessionResponse>

Récupérer le détail d'une session — GET /pos/v1/sessions/{sessionId}.

Retourne : Promise<{ state?, sessionType?, sessionId?, payloadData? }>

import { SessionState } from "@qrcommunication/viva-local-terminal-sdk";

async function pollSession(terminal, sessionId) {
  for (;;) {
    const session = await terminal.sessions.get(sessionId);

    if (session.state === SessionState.SUCCESS) {
      return session; // session.payloadData contient le détail
    }
    if (session.state === SessionState.FAILURE) {
      throw new Error("Paiement échoué");
    }

    await new Promise((r) => setTimeout(r, 2000)); // attendre 2 s
  }
}

3 Device terminal.device

Contrôle du terminal physique : réveil/veille de l'écran et luminosité.

screenControl(wakeUpLock: boolean): Promise<DeviceResponse>

Contrôler l'écran / mode veille — POST /pos/v1/awake-lock. true : réveil + écran actif jusqu'au déverrouillage. false : veille normale.

Retourne : Promise<{ isSuccess?: boolean }>

const res = await terminal.device.screenControl(true);
console.log(res.isSuccess); // true

brightness(brightness: number): Promise<DeviceResponse>

Régler la luminosité — POST /pos/v1/brightness. Valeur dans (0, 1] (0 interdit). Lève un RangeError hors plage.

await terminal.device.brightness(0.5); // 50 %
await terminal.device.brightness(1.0); // maximum
// await terminal.device.brightness(0); // ❌ RangeError

Enums

Exportés depuis le point d'entrée du package.

SessionState

ValeurDescription
PROCESSINGL'opération est en cours sur le terminal
BUSY_ERRORUne autre transaction est déjà en cours
SERVER_ERRORErreur côté serveur du terminal
SESSION_NOT_FOUND_ERRORLe sessionId fourni est introuvable (abort)
SUCCESSLa transaction s'est terminée avec succès
FAILURELa transaction s'est terminée en erreur

SessionType

ValeurDescription
SALETransaction de vente
CAPTURE_PREAUTHCapture d'une vente pré-autorisée
CANCELRemboursement / annulation référencé
UNREFERENCED_REFUNDRemboursement non référencé
ABORTInterruption d'une session SALE

TransactionTypeId

ConstanteValeurDescription
SALE5Vente
REFUND4Remboursement
REVERSAL7Annulation (reversal)
IRIS_SALE60Vente IRIS
IRIS_REFUND61Remboursement IRIS
IRIS_VOID85Annulation IRIS

PaymentMethod

ConstanteValeur
CARD_PRESENTCardPresent
IRISIris

Gestion des erreurs Error Handling

Erreurs 400 = chaînes brutes. La Local Terminal API ne retourne pas de codes d'erreur structurés. Sur une 400, elle renvoie un corps en chaîne de caractères brute (ex : "ZeroconfException error message Amount cannot be null"). Le SDK préserve ce texte verbatim dans ApiError.message. — 400 errors are raw strings, not JSON. Preserved verbatim in ApiError.message.
Error
└── VivaError     (base de toute erreur SDK)
    └── ApiError  (levée sur 4xx/5xx ET sur terminal injoignable : httpStatus === 0)
MembreTypeDescription
messagestringTexte d'erreur — chaîne brute du terminal verbatim sur 400
httpStatusnumberCode HTTP (0 si le terminal est injoignable)
responseBodyVivaErrorBody | nullCorps d'erreur ; chaîne brute préservée sous { raw: "..." }
getErrorText()string | nullmessage, error, puis fallback sur raw
import { ApiError } from "@qrcommunication/viva-local-terminal-sdk";

try {
  const res = await terminal.transactions.sale({ sessionId, amount: 1170 });
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.httpStatus);        // 400, ou 0 si injoignable sur le LAN
    console.log(err.getErrorText());    // ex: "ZeroconfException error message Amount cannot be null"
    console.log(err.responseBody?.raw); // chaîne brute du terminal (si 400 non-JSON)

    if (err.httpStatus === 0) {
      // Terminal injoignable — vérifier IP/port/réseau
    }
  } else {
    throw err;
  }
}

Variantes Merchant vs ISV Merchant vs ISV variants

Chaque endpoint existe en deux variantes : merchant (sans slash final) et ISV (avec slash final, ex : /pos/v1/sale/). Un seul jeu de méthodes + le toggle global useIsvEndpoints gère les deux.

const terminal = new VivaLocalTerminalClient({
  terminalBaseUrl: "https://192.168.1.50:8080",
  useIsvEndpoints: true,
});

await terminal.transactions.sale({
  sessionId,
  amount: 1170,
  isvDetails: { /* objet partenaire / fee / multi-marchand ISV */ },
});

isvDetails est omis du corps quand il est undefined — inoffensif pour les appels merchant.

Changelog v1.0.0

✨ Version initiale. Jumeau TypeScript du SDK PHP, modèle P2P sans authentification pour la Local Terminal API de Viva.com. GitHub Release v1.0.0 →
  • Modèle P2P sans authentification : communication directe avec le terminal sur le LAN, aucun header Authorization.
  • TLS configurable (verifyTls: false ou dispatcher undici personnalisé) pour certificats auto-signés.
  • Ressource Transactions : sale(), capturePreauth(), refund(), unreferencedRefund(), abort(), aadeFimControl(), retrieve().
  • Ressource Sessions : get() (polling du résultat).
  • Ressource Device : screenControl(), brightness().
  • Enums : SessionState, SessionType, TransactionTypeId, PaymentMethod.
  • Variantes Merchant et ISV via useIsvEndpoints.
  • Erreurs 400 préservées en chaîne brute ; ApiError avec httpStatus === 0 sur terminal injoignable. TypeScript strict, ESM + CJS + .d.ts.