@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
- Fonctionnalités
- Navigateurs supportés
- Utilisation rapide
- Référence des options
- Instance
- Flux de paiement
- Migration depuis la v1
- Démo locale
Installation
npm install @pi-spi/checkoutSans 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.
createPaymentRequestetgetPaymentStatusdoivent appeler votre backend, qui détient les identifiants de l'API Business. Confirmez toujours le paiement côté serveur (avectxId) 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 # NotificationsLicence
MIT · BCEAO - PI-SPI
