npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pi-spi/checkout

v2.0.0

Published

SDK JavaScript pour le paiement en ligne PI-SPI - expérience unifiée e-commerce

Readme

@pi-spi/checkout

SDK JavaScript pour le paiement en ligne PI-SPI. Offre une expérience de checkout unifiée pour les sites e-commerce (boutiques en ligne, marketplaces) dans l'espace UEMOA — même parcours et mêmes options que le SDK Flutter bceao_pispi_checkout.


Sommaire


Installation

npm install @pi-spi/checkout

Sans bundler (balise <script>) :

<script src="https://cdn.jsdelivr.net/npm/@pi-spi/checkout@2/dist/index.umd.js"></script>
<script>
  PispiCheckout.mountCheckout({ /* ... */ });
</script>

Fonctionnalités

| | | |---|---| | ✅ | Bouton « Payer avec PI-SPI » — 8 thèmes ou couleur personnalisée | | ✅ | Modal bottom sheet ou dialog centré (animations, Échap, piège à focus) | | ✅ | Création de demande de paiement | | ✅ | Polling automatique du statut (séquentiel, sans requêtes concurrentes) | | ✅ | Timeout de polling configurable | | ✅ | Gestion succès / rejet / timeout / échec d'envoi | | ✅ | Nom et pays du payeur affichés après création de la demande | | ✅ | Support QR Code (optionnel, via decodeQrPayload) : scan caméra (lampe torche si disponible) ou import d'une photo | | ✅ | Lecture des QR PI-SPI officiels (BarcodeDetector natif + repli jsQR) | | ✅ | Bouton de téléchargement de la facture | | ✅ | Internationalisation FR 🇫🇷 / EN 🇬🇧 / PT 🇵🇹 | | ✅ | Styles isolés (Shadow DOM) : le CSS du site ne casse pas le widget | | ✅ | ESM, CommonJS, UMD + types TypeScript |

Navigateurs supportés

Chrome / Edge ≥ 86, Firefox ≥ 78, Safari ≥ 14 (iOS et macOS). Le scan caméra nécessite HTTPS (ou localhost) et l'autorisation de l'utilisateur ; le bouton n'est pas affiché si la caméra n'est pas disponible. L'import de photo fonctionne partout.


Utilisation rapide

import { mountCheckout } from '@pi-spi/checkout';

const checkout = mountCheckout({
  container: '#checkout-pispi',
  amount: 5000, // 5 000 F CFA
  orderId: 'CMD-2025-001',
  motif: 'Achat librairie',
  locale: 'fr',

  // Configuration du bouton
  buttonOptions: { theme: 'amber' },

  // Configuration du modal
  modalOptions: { type: 'popupDialog', currencyLabel: 'fcfa' },

  // Active le scan / l'import de QR Code (optionnel)
  decodeQrPayload: (qrPayload) => {
    const result = isValidPispiQrPayload(qrPayload); // import { isValidPispiQrPayload } from '@pi-spi/qrcode'
    return result.valid ? result.data.alias : null;
  },

  createPaymentRequest: async (payload) => {
    // Appelez votre backend, qui appelle l'API Business PI-SPI
    const res = await fetch('/api/paiements-ecommerce', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!res.ok) throw new Error((await res.json()).detail ?? 'Erreur');
    return res.json(); // { txId, statut?, payeurNom?, payeurPays? }
  },

  getPaymentStatus: async (txId) => {
    const res = await fetch(`/api/demandes-paiements-sites/${txId}`);
    return res.json(); // { statut, invoiceUrl? }
  },

  onClose: (result) => {
    if (result.success) console.log('Paiement réussi', result.txId);
    else console.log('Paiement non abouti :', result.status);
  },
});

Sécurité — Ne mettez jamais de clé d'API dans le navigateur. createPaymentRequest et getPaymentStatus doivent appeler votre backend, qui détient les identifiants de l'API Business. Confirmez toujours le paiement côté serveur (avec txId) avant de livrer la commande.


Référence des options

PispiCheckoutOptions

| Paramètre | Type | Obligatoire | Défaut | Description | |-----------|------|:-----------:|--------|-------------| | container | string \| HTMLElement | ✅ | — | Sélecteur CSS ou élément où monter le bouton | | amount | number | ✅ | — | Montant à payer, dans l'unité de la devise (5000 = 5 000 F CFA) | | createPaymentRequest | (payload) => Promise<CreatePaymentRequestResponse \| null> | ✅ | — | Appelée à la soumission de l'alias. Retourner null ou lever une erreur affiche « Demande de paiement non envoyée » (le message de l'erreur est affiché en détail) | | orderId | string | — | — | Identifiant de commande | | motif | string | — | — | Motif ou description du paiement | | locale | 'fr' \| 'en' \| 'pt' | — | 'fr' | Langue de l'interface (en-US, pt-BR… acceptés) | | pollIntervalMs | number | — | 3000 | Intervalle de polling en millisecondes | | pollTimeoutS | number | — | 120 | Durée max du polling en secondes avant abandon | | buttonOptions | CheckoutButtonOptions | — | voir ci-dessous | Configuration visuelle du bouton | | modalOptions | CheckoutModalOptions | — | voir ci-dessous | Configuration visuelle du modal | | getPaymentStatus | (txId) => Promise<PaymentStatusResponse> | — | — | Polling du statut. Si absent, le widget se termine après l'envoi de la demande | | decodeQrPayload | (qrPayload) => string \| { alias } \| null | — | — | Décode un payload QR PI-SPI et retourne l'alias (ex. via isValidPispiQrPayload du package @pi-spi/qrcode). Active les boutons Scanner / Choisir une photo | | onClose | (result: PispiCheckoutCloseResult) => void | — | — | Appelée à chaque fermeture du modal |

CheckoutButtonOptions

| Paramètre | Type | Défaut | Description | |-----------|------|--------|-------------| | theme | CheckoutButtonTheme | 'amber' | Thème de couleur | | backgroundColor | string (hex) | — | Couleur personnalisée, prioritaire sur theme ; logo et texte choisis automatiquement | | logoVariant | 'variant1' \| 'variant2' | 'variant1' | Variante du logo sur fond blanc personnalisé | | textFontSize | number | 18 | Taille du texte (px) | | textFontWeight | number \| string | 700 | Graisse du texte | | iconSize | number | 25 | Hauteur du logo PI-SPI (px) | | padding | string | '15px 10px' | Padding CSS | | borderRadius | number \| string | 12 | Rayon des coins | | fullWidth | boolean | true | Le bouton occupe toute la largeur du conteneur | | className | string | — | Classe ajoutée à l'élément hôte (marges, positionnement) |

CheckoutButtonTheme

| Valeur | Rendu | |--------|-------| | amber | Fond jaune amber | | marron | Fond marron foncé | | noir | Fond noir | | bleu | Fond bleu | | rouge | Fond rouge | | vert | Fond vert | | blancVariant1 | Fond blanc — logo amber | | blancVariant2 | Fond blanc — logo marron |

CheckoutModalOptions

| Paramètre | Type | Défaut | Description | |-----------|------|--------|-------------| | type | 'bottomSheet' \| 'popupDialog' | 'bottomSheet' | Modal glissant depuis le bas, ou dialog centré (scale + fade) | | currencyLabel | 'fcfa' \| 'xof' \| string | 'fcfa' | fcfa → « F CFA », xof → « XOF », autre chaîne affichée telle quelle | | logoPosition | 'center' \| 'left' | 'center' | Position du logo dans l'en-tête | | titleFontSize | number | 16 | Taille du titre (montant) | | titleFontWeight | number \| string | 800 | Graisse du titre | | titleIconSize | number | 50 | Hauteur du logo de l'en-tête | | stepTitleFontSize | number | 11 | Taille des libellés du stepper | | closeOnOverlayClick | boolean | false | Fermer au clic sur l'arrière-plan | | zIndex | number | 9999 | z-index du modal |

CreatePaymentRequestPayload

Payload transmis à createPaymentRequest.

| Champ | Type | Description | |-------|------|-------------| | payeurAlias | string | Alias de compte du payeur (UUID v4) | | montant | number | Montant de la transaction (= amount) | | orderId | string? | Identifiant de commande | | motif | string? | Motif du paiement |

CreatePaymentRequestResponse

| Champ | Type | Obligatoire | Description | |-------|------|:-----------:|-------------| | txId | string | ✅ | Identifiant de transaction | | statut | string | — | Statut initial (ex. INITIE) | | payeurNom | string | — | Nom du payeur (affiché dans l'en-tête) | | payeurPays | string | — | Pays du payeur (affiché dans l'en-tête) |

PaymentStatusResponse

| Champ | Type | Obligatoire | Description | |-------|------|:-----------:|-------------| | statut | string | ✅ | Statut courant du paiement (insensible à la casse) | | invoiceUrl | string | — | URL http(s) de la facture (affichée si succès) |

Statuts possibles

| Statut | Effet dans le widget | |--------|----------------------| | IRREVOCABLE / ACCEPTE / ACCEPTEE | ✅ Paiement réussi — bouton facture si invoiceUrl | | REJETE / REJETEE | ❌ Paiement rejeté | | Autres | ⏳ Le polling continue (les erreurs réseau ponctuelles sont ignorées) |

PispiCheckoutCloseResult

Passé à onClose à chaque fermeture (bouton ✕, Échap, bouton Fermer, facture ouverte, close(), unmount()).

| Champ | Type | Description | |-------|------|-------------| | success | boolean | true uniquement si le paiement a été accepté | | status | 'success' \| 'sent' \| 'rejected' \| 'timeout' \| 'failed' \| 'cancelled' | Issue détaillée (sent = demande envoyée sans getPaymentStatus) | | txId | string? | Identifiant de transaction, si la demande a été créée | | invoiceUrl | string? | URL de la facture si succès |


Instance

mountCheckout() retourne :

| Méthode | Description | |---------|-------------| | open() | Ouvre le modal (équivalent d'un clic sur le bouton) | | close() | Ferme le modal s'il est ouvert | | update(options) | Met à jour les options (montant, langue, thème, callbacks…) ; appliqué au bouton immédiatement et au modal à la prochaine ouverture | | unmount() | Détruit le bouton et le modal |

// Le panier change
checkout.update({ amount: 12500, motif: 'Panier mis à jour' });

Utilitaires exportés : isValidAlias, assertValidAlias, getLogoUrlForBackground, getButtonTextColor, getContrastColor, isDarkBackground, isAmberBackground, getLuminance.


Flux de paiement

[Bouton Payer avec PI-SPI]
      │
      ▼
[Étape 1 — Alias]
  Saisie / Coller / Scanner QR / Importer photo QR
  Le bouton « Payer » apparaît dès que l'alias est valide
      │
      ▼
[createPaymentRequest(payload)]  ──► Votre backend ──► API Business PI-SPI
      │
      ├── Échec ──► « Demande non envoyée » ──► retour étape 1 après 5 s (alias conservé)
      │
      ▼
[Étape 2 — Demande envoyée]  (nom et pays du payeur affichés, 2 s)
      │
      ▼
[Étape 3 — Confirmation]
  getPaymentStatus(txId) toutes les pollIntervalMs ms, timeout après pollTimeoutS s
      │
      ├── IRREVOCABLE / ACCEPTE ──► ✅ Succès + bouton facture
      ├── REJETE                ──► ❌ Rejet + bouton Fermer
      └── Timeout               ──► ❌ Délai dépassé + bouton Fermer
      │
      ▼
[onClose(PispiCheckoutCloseResult)]

Migration depuis la v1

| v1 | v2 | |----|----| | amount en centimes (15000 → 150 F CFA) | amount dans l'unité de la devise (5000 → 5 000 F CFA), comme le SDK Flutter. montant est transmis tel quel au backend | | buttonBackgroundColor | buttonOptions.backgroundColor (ou buttonOptions.theme) | | buttonLogoVariant | buttonOptions.logoVariant | | modalLogoPosition | modalOptions.logoPosition | | currencyLabel: 'F CFA' | modalOptions.currencyLabel: 'fcfa' | | onClose({ success, invoiceUrl }) | + status et txId | | QR toujours proposé, décodage intégré (@pi-spi/qrcode) | QR proposé uniquement si decodeQrPayload est fourni ; le marchand décode lui-même (ex. avec @pi-spi/qrcode) | | Envoi automatique après lecture d'un QR | L'alias est prérempli et l'utilisateur confirme avec « Payer » | | Clic sur l'arrière-plan = fermeture | Désactivé par défaut (modalOptions.closeOnOverlayClick) |

Les anciennes options « à plat » restent acceptées (dépréciées).


Démo locale

npm install
npm run example   # build + serveur sur http://localhost:5173/example/

La page example/index.html simule le backend (succès, rejet, timeout, échec, sans polling) et permet de changer thème, type de modal, langue et montant.

Structure du projet

src/
├── index.ts                 # API publique (mountCheckout + types + utilitaires)
├── widget.ts                # Orchestration bouton ↔ modal, open/close/update/unmount
├── types.ts                 # Types publics
├── theme.ts                 # Palette, thèmes du bouton, résolution des options
├── i18n.ts                  # Traductions FR / EN / PT
├── styles.ts                # CSS (injecté dans les Shadow DOM)
├── qr.ts                    # Décodage QR (image + caméra)
├── validate.ts              # Validation de l'alias
├── logo.ts / logo-data.ts   # Logos PI-SPI intégrés (généré par scripts/embed-logos.cjs)
├── icons.ts / dom.ts        # Icônes SVG, helpers DOM
└── components/
    ├── pay-button.ts        # Bouton « Payer avec »
    ├── checkout-modal.ts    # Modal + machine à états (étapes, polling)
    ├── header.ts            # Logo, payeur, montant
    ├── stepper.ts           # Indicateur d'étapes
    ├── alias-step.ts        # Étape 1 : alias, QR, scanner
    ├── states.ts            # Chargement / succès / erreur / boutons
    └── toast.ts             # Notifications

Licence

MIT · BCEAO - PI-SPI