Chez oki.lu, nous générons chaque mois plusieurs milliers de factures pour des commerçants, artisans et indépendants luxembourgeois. Depuis des années, ces factures portent un QR-code de paiement Payconiq. Et depuis quelques mois, une même question revient dans toutes nos conversations avec nos clients : « et maintenant, on fait comment ? »
Digicash, Payconiq, Buckaroo : la troisième migration en quelques années
Les professionnels du pays connaissent la chanson. Il y a eu Digicash, la solution luxembourgeoise que tout le monde avait fini par adopter. Puis Digicash est devenu Payconiq : nouvelle intégration, nouvelles conditions. Aujourd’hui, Payconiq migre vers Buckaroo — et cette fois, le changement ne se limite pas au logo sur l’application.
Le modèle tarifaire bascule d’un coût fixe par transaction vers une commission proportionnelle au montant encaissé. Pour un commerce de proximité aux marges déjà comprimées, la différence n’est pas anecdotique : c’est une ponction récurrente sur chaque encaissement, qui s’ajoute aux frais des paiements par carte. Et surtout, c’est la démonstration d’une dépendance : chaque solution privée peut être rachetée, retarifée ou arrêtée du jour au lendemain, et les professionnels n’ont d’autre choix que de suivre.
Ce que nos clients nous demandent, ce n’est pas un énième prestataire. C’est une solution pérenne — une solution qui ne peut ni changer de mains, ni changer de prix, ni disparaître.
Cette solution existe. Elle est européenne, ouverte et gratuite.
Elle s’appelle le QR-code EPC (norme EPC069-12 du Conseil européen des paiements, connue en Allemagne sous le nom de Girocode). Le principe tient en une phrase : le QR-code contient les coordonnées du virement — bénéficiaire, IBAN, montant, communication — et le client le scanne directement dans son application bancaire, qui pré-remplit un virement SEPA. Il vérifie, il confirme, c’est payé.
Pas d’application supplémentaire à installer. Pas d’inscription. Pas d’intermédiaire qui touche les fonds. Et pas de commission : un virement SEPA ne coûte rien, et depuis le règlement européen sur les paiements instantanés, le virement instantané est facturé au même prix que le virement classique — c’est-à-dire, pour la quasi-totalité des comptes, zéro.
Les bénéfices sont partagés. Pour le client : un paiement en moins de trente secondes, sans saisie d’IBAN et sans risque d’erreur. Pour le professionnel : un coût d’encaissement nul et — c’est notre partie préférée, déformation d’éditeur de logiciel de facturation oblige — une réconciliation comptable automatisée : la référence de paiement contenue dans le QR revient intacte avec le virement, et chaque encaissement se rattache tout seul à sa facture.
Ça fonctionne déjà. Massivement. Juste pas ici.
Ce n’est pas une promesse de brochure. En Allemagne, la quasi-totalité des applications bancaires — Sparkasse, Volksbank, ING, Deutsche Bank, Commerzbank, DKB, N26 — scannent nativement ces QR-codes, présents sur des millions de factures. L’Autriche, où le standard est né, la Belgique, la Finlande et les Pays-Bas connaissent une adoption comparable.
Et au Luxembourg ? Le paradoxe est complet : les résidents clients de Revolut ou N26 peuvent déjà scanner un QR EPC aujourd’hui. Les clients des banques de la place, non — leurs applications n’ont tout simplement pas le lecteur. Le seul maillon manquant entre les professionnels luxembourgeois et un moyen d’encaissement gratuit et pérenne, c’est une fonctionnalité de quelques écrans dans les applications bancaires du pays.
Nous avons entrepris de le dire à qui de droit : la Chambre de Commerce, l’ABBL, le ministère de l’Économie, le ministère des Finances et les groupes parlementaires ont été saisis de la question. Nous vous tiendrons informés des réponses sur ce blog.
En attendant, nous avons construit l’outil. Il est gratuit.
Parce que la meilleure façon de faire avancer un standard, c’est de le rendre utilisable dès aujourd’hui, nous avons développé un générateur de QR EPC gratuit, sans inscription et sans aucune donnée stockée sur nos serveurs.
Il couvre trois usages :
Le QR de facture. Renseignez votre nom, votre IBAN, le montant et la communication : vous obtenez un QR-code conforme à la norme, à télécharger et à imprimer sur vos factures, vos devis ou votre comptoir. L’IBAN est vérifié automatiquement (somme de contrôle) pour éliminer toute erreur de saisie.
Le lien de paiement. L’outil génère aussi un lien à partager par email, WhatsApp ou SMS : votre client l’ouvre et trouve une page de paiement propre à votre nom, avec le QR et le mode d’emploi. Toutes les informations tiennent dans le lien lui-même — rien n’est enregistré chez nous, partagez-le comme vous partageriez un RIB.
La demande à montant libre. Laissez le montant vide et vous obtenez une page de don ou de participation libre : des boutons de montants suggérés, un champ libre, et le QR qui s’adapte instantanément. Idéal pour les associations, les clubs, les cagnottes et les pourboires.
👉 Essayez le générateur ici — et pour le tester en conditions réelles, scannez le résultat avec l’application Revolut ou N26 : le virement pré-rempli s’affiche en deux secondes.
Et si votre banque ne scanne pas encore ?
C’est précisément le sujet. Si vous êtes client d’une banque luxembourgeoise et que ce service vous semble aller de soi, dites-le-lui — les banques écoutent leurs clients bien plus que les éditeurs de logiciels. Si vous êtes commerçant, artisan ou responsable d’association et que ce standard répondrait à vos besoins, parlez-en autour de vous, et n’hésitez pas à nous écrire : chaque témoignage renforce le dossier que nous portons auprès des institutions.
Le virement instantané est devenu un droit européen. Il ne manque qu’un scanner.
oki.lu est un éditeur luxembourgeois de logiciels de facturation. Le générateur de QR EPC est et restera gratuit.
API QR EPC — Documentation développeurs
Convertissez des coordonnées bancaires en QR-code de virement SEPA, scannable par n'importe quelle application bancaire compatible. Le client scanne, son virement est pré-rempli, il confirme. Aucune donnée n'est stockée côté serveur.
§1Démarrage rapide
La réponse est directement une image PNG — pas de JSON, pas de base64.
curl -X POST https://www.oki.lu/qr-epc-api.php \
-H "Content-Type: application/json" \
-d '{
"name": "Mon Entreprise Sarl",
"iban": "LU120014123456789012",
"amount": "49.90",
"reference": "Facture 2026-001"
}' \
-o qr.png
§2Endpoint
| URL | https://www.oki.lu/qr-epc-api.php |
|---|---|
| Méthode | POST uniquement (OPTIONS supporté) |
| Requête | Content-Type: application/json |
| Réponse (succès) | image/png |
| Réponse (erreur) | application/json |
| Authentification | Aucune à ce jour |
| Taille max du corps | 20 000 octets |
§3Paramètres du corps JSON
| Champ | Type | Description | |
|---|---|---|---|
| name | string | requis | Nom du bénéficiaire. Max 70 caractères. |
| iban | string | requis | IBAN du bénéficiaire, checksum validé (mod 97). |
| bic | string | optionnel | 8 ou 11 caractères. Requis si version="001". |
| amount | string | optionnel | Montant EUR. Absent/vide = montant libre (don). 0,01 à 999 999 999,99. |
| reference | string | optionnel | Communication libre, max 140 car. Incompatible avec structured_reference. |
| structured_reference | string | optionnel | Référence ISO 11649 (RF..), checksum validé. |
| purpose | string | optionnel | Code SEPA 4 lettres (ex. GDSV). |
| version | string | optionnel | "001" ou "002" (défaut). |
| size | integer | optionnel | 3 à 20 px/module (défaut 8). |
| margin | integer | optionnel | 0 à 40 px (défaut 16). |
reference et structured_reference sont mutuellement exclusifs (erreur 422 si les deux sont envoyés).§4Réponses & erreurs
200 OK Succès
Corps : PNG brut. En-tête utile :
Content-Type: image/png X-EPC-Payload-Bytes: 64
X-EPC-Payload-Bytes — la spec recommande de rester sous 331 octets pour une lecture fiable.
Codes d'erreur
| Code | Cas |
|---|---|
| 400 | Corps vide, ou JSON malformé |
| 405 | Méthode autre que POST/OPTIONS |
| 413 | Corps > 20 000 octets |
| 422 | Champ(s) invalide(s) — détail dans le message |
| 500 | Erreur serveur |
{"error": "iban manquant ou invalide (checksum). bic invalide (8 ou 11 caractères)."}
§5Exemples
Montant fixe (facture)
curl -X POST https://www.oki.lu/qr-epc-api.php \
-H "Content-Type: application/json" \
-d '{
"name": "Mon Entreprise Sarl",
"iban": "LU120014123456789012",
"amount": "149.00",
"reference": "Facture 2026-042"
}' \
-o facture-042.png
Montant libre (don)
Omettez simplement amount.
curl -X POST https://www.oki.lu/qr-epc-api.php \
-H "Content-Type: application/json" \
-d '{
"name": "Association Les Amis du Quartier",
"iban": "LU280019400644750000",
"reference": "Don libre"
}' \
-o don.png
Référence structurée (rapprochement automatisé)
curl -X POST https://www.oki.lu/qr-epc-api.php \
-H "Content-Type: application/json" \
-d '{
"name": "Mon Entreprise Sarl",
"iban": "LU120014123456789012",
"amount": "89.50",
"structured_reference": "RF18539007547034"
}' \
-o facture-rf.png
Haute résolution (impression)
curl -X POST https://www.oki.lu/qr-epc-api.php \
-H "Content-Type: application/json" \
-d '{
"name": "Mon Entreprise Sarl",
"iban": "LU120014123456789012",
"amount": "25.00",
"size": 14,
"margin": 30
}' \
-o qr-print.png
§6Intégration PHP
<?php
$payload = json_encode([
'name' => 'Mon Entreprise Sarl',
'iban' => 'LU120014123456789012',
'amount' => '49.90',
'reference' => 'Facture 2026-001',
]);
$ch = curl_init('https://www.oki.lu/qr-epc-api.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
]);
$png = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
file_put_contents('qr.png', $png);
} else {
$err = json_decode($png, true);
error_log('Erreur QR EPC : ' . ($err['error'] ?? 'inconnue'));
}
§7Intégration JavaScript
const res = await fetch('https://www.oki.lu/qr-epc-api.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Mon Entreprise Sarl',
iban: 'LU120014123456789012',
amount: '49.90',
reference: 'Facture 2026-001',
}),
});
if (res.ok) {
const blob = await res.blob();
const url = URL.createObjectURL(blob);
document.querySelector('#qr').src = url; // <img id="qr">
} else {
const { error } = await res.json();
console.error('Erreur QR EPC :', error);
}
<img>, faites transiter l'appel par votre backend, qui appelle l'API en POST et retransmet le PNG — cela évite d'exposer un IBAN en clair dans une URL GET loguée.§8Bonnes pratiques
🔒 Jamais en GET
Ne mettez jamais l'IBAN ou le montant dans une URL loguée. L'API n'accepte que POST pour cette raison.
⚡ Sortie déterministe
Un PNG généré avec des paramètres identiques est toujours identique — cache possible par clé iban+amount+reference.
🖨️ Choisir size
8 pour écran/PDF ; 12–16 pour de l'impression grand format.
🔁 Préférer la référence structurée
Dès que possible, structured_reference est plus fiable qu'un texte libre.
§9Limites connues
§10Aide-mémoire
POST /qr-epc-api.php
Content-Type: application/json
{
"name": "string, requis, max 70",
"iban": "string, requis, IBAN valide",
"bic": "string, optionnel, requis si version=001",
"amount": "string, optionnel — vide = montant libre",
"reference": "string, optionnel, max 140 — XOR structured_reference",
"structured_reference": "string, optionnel, format RF.. — XOR reference",
"purpose": "string, optionnel, 4 lettres",
"version": "\"001\" | \"002\", défaut \"002\"",
"size": "int 3–20, défaut 8",
"margin": "int 0–40, défaut 16"
}
→ 200 image/png
→ 4xx/5xx {"error": "..."}

