@aveplus_dev/uemoa-qrcode-sdk
v1.0.1
Published
SDK JavaScript/TypeScript pour la generation et lecture de QR codes de paiement conformes a la norme BCEAO/UEMOA (portage du SDK Java uemoa-qrcode-sdk-core)
Readme
@aveplus_dev/uemoa-qrcode-sdk
SDK JavaScript/TypeScript pour la génération et la lecture de QR codes de paiement conformes à la norme BCEAO/UEMOA (standard EMVCo).
Portage du SDK Java de référence uemoa-qrcode-sdk-core, avec tests
d'interopérabilité croisés garantissant qu'un QR généré par un SDK est
lisible par les autres (Java, JavaScript, Python).
- Package npm :
@aveplus_dev/uemoa-qrcode-sdk - Dépôt :
gitlab.avepay.net/qrcode/qrcode-module-backend-js - Licence : Apache-2.0
Sommaire
- Installation
- Démarrage rapide
- Types de QR supportés
- Référence de l'API
- Règles de validation
- Interopérabilité entre SDK
- Développement local
- Publication du SDK sur npm
- Structure du projet
1. Installation
npm install @aveplus_dev/uemoa-qrcode-sdkLe package est publié en dual build : il fonctionne aussi bien en
CommonJS (require) qu'en ESM (import), et embarque ses propres types
TypeScript.
2. Démarrage rapide
import { UemoaQRService, MerchantChannel } from "@aveplus_dev/uemoa-qrcode-sdk";
const service = new UemoaQRService();
// --- Génération ---
const qrData = service.generateStaticQR({
type: "STATIC",
merchantChannel: MerchantChannel.STATIC_WITH_AMOUNT,
merchantInfo: {
alias: "shop-001",
name: "MA BOUTIQUE",
city: "Ouagadougou",
countryCode: "BF",
},
amount: 2500,
});
// "00020101021136280012int.bceao.pi0108shop-001..."
// --- Image PNG (base64, sans le préfixe data:) ---
const imageBase64 = await service.generateQRImageFromString(qrData);
// --- Lecture d'un QR scanné ---
const details = service.getQRCodeDetails(qrData);
// { valid: true, type: "STATIC", amount: "2500",
// channel: { code: 110, ... },
// merchant: { alias, name, city, country }, transactionId }
// --- Validation (CRC + cohérence structurelle) ---
const isValid = service.validateQRCode(qrData); // true3. Types de QR supportés
| Type | Usage | Méthode |
|---|---|---|
| Statique | QR fixe affiché en boutique, réutilisable par plusieurs clients | generateStaticQR() |
| Dynamique | QR généré pour une transaction précise (montant, e-commerce) | generateDynamicQR() |
| P2P | Transfert entre particuliers | generateP2PQR() |
generateQRData() route automatiquement selon le champ type.
Canaux marchands (MerchantChannel)
| Constante | Code | Description |
|---|---|---|
| STATIC_ONSITE | 100 | QR statique sur site |
| STATIC_WITH_AMOUNT | 110 | QR statique avec montant |
| STATIC_WITH_TXID | 120 | QR statique avec ID transaction |
| STATIC_INVOICE | 131 | QR statique sur facture |
| DYNAMIC_ONSITE | 500 | QR dynamique sur site |
| DYNAMIC_ECOMMERCE_WEB | 521 | QR dynamique e-commerce Web |
| DYNAMIC_ECOMMERCE_APP | 522 | QR dynamique e-commerce App |
| P2P_STATIC | 731 | QR statique pour particulier |
4. Référence de l'API
UemoaQRService
Façade principale, couvre la majorité des besoins d'intégration.
| Méthode | Retour | Description |
|---|---|---|
| generateQRData(data) | string | Génère selon data.type |
| generateStaticQR(data) | string | Génère un QR statique |
| generateDynamicQR(data) | string | Génère un QR dynamique |
| generateP2PQR(data) | string | Génère un QR P2P |
| generateQRImageFromString(qr, size?, margin?) | Promise<string> | Image PNG en base64 |
| parseQRCode(qr) | QRPaymentData | Parse, lève si invalide |
| getQRCodeDetails(qr) | object | Parse, ne lève jamais ({valid:false, error} si KO) |
| validateQRCode(qr) | boolean | Vérifie CRC + cohérence structurelle |
Structure des données d'entrée
interface QRPaymentData {
type: "STATIC" | "DYNAMIC" | "P2P";
merchantInfo?: {
alias: string; // identifiant du compte (obligatoire)
name: string; // max 25 caractères
city: string; // max 15 caractères
countryCode: string; // BF, CI, TG, SN, ML, BJ, GW, NE
categoryCode?: string;
};
amount?: string | number; // > 0, sans décimales (XOF)
transactionId?: string; // ^[A-Za-z0-9-]{1,25}$
billReference?: string; // ^[A-Za-z0-9-]{1,25}$
subscriptionId?: string; // ^[A-Za-z0-9-]{1,25}$
merchantChannel?: MerchantChannelInfo;
dynamicUrl?: string;
additionalData?: Record<string, string>;
}Classes bas niveau
Exportées pour les cas avancés : QRParser, CRCCalculator,
EMVFormatter, StaticQRGenerator, DynamicQRGenerator, P2PQRGenerator.
Exceptions typées
| Classe | Levée quand |
|---|---|
| QRValidationError | Donnée d'entrée invalide (montant, pays, charset…) |
| QRParsingError | QR illisible ou CRC invalide |
| QRImageGenerationError | Échec de génération de l'image PNG |
5. Règles de validation
| Champ | Règle |
|---|---|
| amount | Strictement > 0, sans décimales (le XOF n'a pas de sous-unité). "2500.00" est toléré → 2500 ; 50.5 est rejeté |
| countryCode | Doit appartenir à la zone UEMOA (8 pays) |
| name | Obligatoire, max 25 caractères |
| city | Obligatoire, max 15 caractères |
| transactionId, billReference, subscriptionId | ^[A-Za-z0-9-]{1,25}$ |
| Tous les champs texte | Doivent être représentables en ISO-8859-1 |
Contrainte de charset
Les caractères hors ISO-8859-1 (œ, €, ou les orthographes de langues
locales comme ɛ, ɔ, ŋ) sont rejetés avec une QRValidationError.
Raison : l'encodage ISO-8859-1 se comporte différemment selon le langage
face à un caractère hors plage (remplacement silencieux en Java, exception
en Python, corruption silencieuse en Node). Rejeter en amont garantit un
comportement identique dans les trois SDK. Détails dans
docs/DECISIONS.md.
6. Interopérabilité entre SDK
Un QR généré par ce SDK est lisible par les SDK Java et Python, et inversement. C'est garanti par deux mécanismes :
Vecteurs de test partagés — le fichier
test-vectors/uemoa-qr-test-vectors.json
est la source de vérité commune aux trois SDK. Il contient la spécification
écrite et trois familles de vecteurs :
interopVectors— entrées à parser vers les mêmes donnéescanonicalVectors— sorties exactes attendues (générées par la référence Java)validationVectors— entrées invalides à rejeter partout pareil
Tests croisés — la suite de tests relit des QR réellement générés par le SDK Java et vérifie que les données décodées sont identiques.
Ordre des sous-champs
import { setFieldOrderStrategy } from "@aveplus_dev/uemoa-qrcode-sdk";
setFieldOrderStrategy("java-hashmap"); // défaut : reproduit le SDK Java actuel
setFieldOrderStrategy("sorted"); // ordre canonique triéCe réglage n'affecte jamais la lecture : le parsing se fait par tag et non par position, et le CRC est recalculé sur les octets reçus. Les deux modes sont donc pleinement interopérables ; seule la comparaison octet-à-octet des chaînes diffère.
7. Développement local
git clone https://gitlab.avepay.net/qrcode/qrcode-module-backend-js.git
cd qrcode-module-backend-js
npm install # installe les dépendances
npm test # lance les 100 tests (Vitest)
npm run build # génère dist/ (CJS + ESM + types)Prérequis
- Node.js 18+
- npm 9+
Organisation des tests
| Fichier | Contenu |
|---|---|
| javaPortedTests.test.ts | 18 tests repris un-à-un des tests JUnit du SDK Java |
| sharedVectors.test.ts | Tests pilotés par les vecteurs partagés |
| crossValidation.test.ts | Tests croisés avec des QR réellement générés par Java |
| charset.test.ts | Règle ISO-8859-1 et cohérence longueur TLV / octets |
| crc.test.ts, emvFormatter.test.ts | Tests unitaires bas niveau |
| orderStrategy.test.ts, reviewFixes.test.ts | Stratégies d'ordre et régressions |
8. Publication du SDK sur npm
Prérequis
- Être membre de l'organisation npm
aveplus_dev - Être authentifié :
npm loginpuis vérifier avecnpm whoami
Procédure
# 1. Vérifier que tout passe
npm test
npm run build
# 2. Incrémenter la version (semver)
npm version patch # correction / documentation (1.0.0 -> 1.0.1)
npm version minor # nouvelle fonctionnalité (1.0.1 -> 1.1.0)
npm version major # changement incompatible (1.1.0 -> 2.0.0)
# 3. Vérifier le contenu du package avant envoi
npm publish --dry-run
# 4. Publier
npm publish --access publicContenu publié
Seuls dist/, test-vectors/ et docs/ sont inclus (champ files du
package.json). Le code source TypeScript et les tests ne sont pas
distribués.
Authentification à double facteur
L'organisation aveplus_dev impose la 2FA pour publier. Deux options :
2FA activée sur le compte : ajouter
--otp=XXXXXXà la commandeToken granulaire : créer un token avec l'option bypass 2FA, limité au scope
@aveplus_devet avec une expiration courte, puis :npm config set //registry.npmjs.org/:_authToken=<TOKEN> npm publish --access public npm config delete //registry.npmjs.org/:_authToken # nettoyer aprèsCe token contourne la 2FA sur un module de paiement : ne jamais le committer, le révoquer immédiatement après usage.
Notes
- Le package est publié en accès public. Les packages privés npm
nécessitent un plan payant (erreur
402 Payment Requiredsinon). - Une version publiée ne peut pas être remplacée : toute correction passe par une nouvelle version.
9. Structure du projet
src/
index.ts → UemoaQRService (façade publique)
models.ts → MerchantInfo, QRPaymentData, MerchantChannel
errors.ts → exceptions typées
crc.ts → CRC16-CCITT (ISO-8859-1, poly 0x1021, init 0xFFFF)
emvFormatter.ts → formatage / parsing TLV EMVCo
validator.ts → règles de validation
parser.ts → lecture d'un QR
qrImage.ts → génération d'image PNG (lib qrcode)
generators/
base.ts → logique commune + stratégies d'ordre
staticGenerator.ts
dynamicGenerator.ts
p2pGenerator.ts
tests/ → 100 tests (Vitest)
test-vectors/ → vecteurs partagés entre les 3 SDK
docs/DECISIONS.md → décisions de conception détaillées