Appearance
Contrat REST ERP
Adaptateur générique rest : Epayment appelle les endpoints HTTP configurés par la régie. Montants en unités mineures (centimes). Devise ISO 4217 (XAF). Timestamps ISO 8601 UTC.
Authentification
Configurée dans les credentials chiffrés côté Epayment. Détail : Auth et secrets.
| Mode | Champs |
|---|---|
none | Aucun en-tête |
api_key_header | api_key, header_name (défaut X-Api-Key) |
bearer | bearer_token |
basic | username, password |
custom_headers | Map d'en-têtes |
En-têtes non secrets optionnels : map d'en-têtes additionnels (max 10 entrées).
Correspondance des chemins
Base URL : URL configurée dans l'intégration ERP de la régie
| Opération | Méthode | Clé mapping | Chemin par défaut | Paramètres |
|---|---|---|---|---|
| Health | GET | health_path | /health | Aucun |
| Dues | GET | dues_list_path | /dues | ?{dues_subject_query_param}={subject_ref} (défaut param : subject_ref) |
| Disbursement | GET | disbursement_batch_path | /disbursements | ?batch_ref={batch_ref} |
| Confirmation paiement | POST | payment_confirmation_path | /payment-confirmations | Corps JSON |
Clés d'identité
| Clé | Sens | Usage |
|---|---|---|
subject_ref | Contribuable / assujetti | Query param pull dues |
erp_due_ref | Ligne de créance | Corrélation paiement ; metadata checkout |
batch_ref | Lot disbursement | Query param pull lignes sortantes |
revenue_line_code | Code recette ERP | Mapping vers service catalogue |
Health
Requête : GET {base_url}{health_path}
Réponse 200 :
json
{
"ok": true,
"message": "ERP operational",
"latency_ms": 42
}| Champ | Type | Obligatoire | Description |
|---|---|---|---|
ok | boolean | Oui | true si le SI est opérationnel |
message | string | Non | Détail lisible (maintenance, version) |
latency_ms | integer ≥ 0 | Non | Latence interne ERP (informatif) |
Epayment considère la connexion valide lorsque ok est true et le code HTTP est 2xx.
Signature inbound (ordres ERP)
Lorsque la régie active inbound_signing_required, les réponses dues et disbursement ne sont plus { "items": [...] } brut. Elles doivent être une enveloppe JWS compact (RFC 7515) signée EdDSA (Ed25519) :
json
{
"signed": "<header.payload.signature>"
}| Élément | Valeur |
|---|---|
| Header JWS | { "alg": "EdDSA", "kid": "<key id>" } uniquement. Tout autre alg est refusé. |
| Payload | JSON UTF-8 avec typ, iss, aud, iat, exp, jti, ref, items |
typ | erp.dues.v1 ou erp.disbursement.v1 |
aud | epayment |
iss | Identifiant ERP configuré hors bande sur la régie (doit matcher) |
ref | subject_ref (dues) ou batch_ref (disbursement), égal au paramètre de requête |
iat / exp | Unix seconds ; exp - iat ≤ 300 ; skew ±60 s à la réception |
items | Même schéma que les réponses non signées ci-dessous |
La clé privée reste côté ERP. La régie enregistre uniquement la clé publique PEM (kid + SPKI) dans Epayment. Health et confirmations de paiement ne sont pas signés inbound.
Si inbound_signing_required est false, le format { "items": [...] } actuel reste valide (cutover).
Fixtures JWS EdDSA (clé sandbox documentaire erp-fake-1) :
Dues (pull créances)
Requête : GET {base_url}{dues_list_path}?subject_ref={subject_ref}
Réponse 200 (unsigned, cutover) :
json
{
"items": [
{
"erp_due_ref": "DUE-2026-001",
"label": "Taxe foncière 2026",
"amount_minor": 15000,
"currency": "XAF",
"due_date": "2026-06-30T23:59:59.000Z",
"revenue_line_code": "REC-TF-01",
"service_id": null,
"service_code": null,
"service_label": null,
"outstanding_minor": 15000,
"original_minor": 20000,
"allow_partial": true,
"minimum_amount_minor": 5000
}
]
}Objet due (chaque élément de items)
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
erp_due_ref | string 1-120 | Oui | Identifiant unique de la créance côté ERP |
label | string 1-500 | Oui | Libellé affiché au citoyen |
amount_minor | integer ≥ 0 | Oui | Montant proposé au paiement (centimes) |
currency | string len 3 | Oui | Code ISO 4217 (XAF) |
due_date | datetime ISO 8601 | Non | Échéance |
revenue_line_code | string max 50 | Non | Code recette pour mapping catalogue |
service_id | uuid ou null | Non | Fourni par ERP ; écrasé si mapping Epayment réussi |
service_code | string max 50 ou null | Non | Slug service ; enrichi par Epayment |
service_label | string max 200 ou null | Non | Libellé catalogue ; enrichi par Epayment |
outstanding_minor | integer ≥ 0 | Non | Solde restant dû ; défaut = amount_minor |
original_minor | integer ≥ 0 | Non | Montant d'origine avant remises ou paiements partiels |
allow_partial | boolean | Non (défaut false) | Autorise un paiement inférieur au solde |
minimum_amount_minor | integer ≥ 0 | Conditionnel | Obligatoire si allow_partial est true ; ne peut excéder outstanding_minor |
Règles de validation :
- Si
allow_partialesttrue,minimum_amount_minordoit être présent. minimum_amount_minor≤outstanding_minor(ouamount_minorsioutstanding_minorabsent).
Disbursement (pull lignes de lot)
Requête : GET {base_url}{disbursement_batch_path}?batch_ref={batch_ref}
Réponse 200 :
json
{
"items": [
{
"line_ref": "LINE-0001",
"beneficiary_ref": "BEN-8842",
"beneficiary_name": "Marie Nguema",
"amount_minor": 75000,
"currency": "XAF",
"label": "Subvention agricole T1",
"payment_method": "MTN_MOMO",
"payout": {
"phone_e164": "+242061234567",
"operator_hint": "mtn"
}
},
{
"line_ref": "LINE-0002",
"beneficiary_ref": "BEN-8843",
"beneficiary_name": "Jean Okemba",
"amount_minor": 120000,
"currency": "XAF",
"payment_method": "BANK_TRANSFER",
"payout": {
"account_number": "00123456789",
"bank_code": "BCH",
"account_holder_name": "Jean Okemba",
"iban": "CG39..."
}
}
]
}Objet ligne disbursement (chaque élément de items)
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
line_ref | string 1-120 | Oui | Identifiant ligne dans le lot ERP |
beneficiary_ref | string 1-120 | Oui | Identifiant bénéficiaire ERP |
beneficiary_name | string 1-200 | Oui | Nom affiché |
amount_minor | integer ≥ 0 | Oui | Montant à verser (centimes) |
currency | string len 3 | Oui | XAF |
label | string max 500 | Non | Motif du versement |
payment_method | enum | Non | Mode de paiement (voir ci-dessous) |
payout | objet | Non | Coordonnées selon le mode (voir variantes) |
phone_e164 | string max 20 | Non | Legacy : téléphone plat |
account | string max 120 | Non | Legacy : compte plat |
channel | string max 50 | Non | Legacy : canal libre |
Valeurs payment_method
| Valeur | Statut | Usage |
|---|---|---|
AIRTEL_MONEY | Recommandé v2 | Mobile money Airtel |
MTN_MOMO | Recommandé v2 | Mobile money MTN |
BANK_TRANSFER | Recommandé v2 | Virement bancaire |
MOBILE_MONEY | Déprécié | Résolu vers AIRTEL_MONEY ou MTN_MOMO via operator_hint |
CASH | Déprécié v2 | Non supporté en exécution disbursement v2 |
CHECK | Déprécié v2 | Non supporté en exécution disbursement v2 |
Résolution legacy : si payment_method vaut MOBILE_MONEY, Epayment déduit l'opérateur depuis payout.operator_hint (mtn, momo → MTN_MOMO ; airtel → AIRTEL_MONEY).
Variantes payout
Mobile money (AIRTEL_MONEY, MTN_MOMO, ou MOBILE_MONEY) :
json
{
"phone_e164": "+242061234567",
"operator_hint": "mtn"
}| Champ | Type | Obligatoire |
|---|---|---|
phone_e164 | string 1-20 | Oui |
operator_hint | string max 32 | Non |
Virement bancaire (BANK_TRANSFER) :
json
{
"account_number": "00123456789",
"bank_code": "BCH",
"account_holder_name": "Jean Okemba",
"iban": "CG39..."
}| Champ | Type | Obligatoire |
|---|---|---|
account_number | string 1-120 | Oui |
bank_code | string 1-50 | Oui |
account_holder_name | string 1-200 | Oui |
iban | string max 64 | Non |
Espèces (CASH, déprécié) :
json
{
"agency_code": "AG-BZV-01",
"pickup_reference": "REF-123"
}Chèque (CHECK, déprécié) :
json
{
"check_payable_to": "Marie Nguema",
"mailing_address": "BP 123, Brazzaville"
}Payment confirmation (Epayment → ERP)
Requête : POST {base_url}{payment_confirmation_path}
Corps :
json
{
"erp_due_ref": "DUE-2026-001",
"notice_ref": "ABCD1234",
"transaction_id": "550e8400-e29b-41d4-a716-446655440001",
"amount_minor": 15000,
"currency": "XAF",
"paid_at": "2026-06-15T10:30:00.000Z",
"payment": {
"channel": "MOBILE_MONEY",
"payment_ref": "ABCD1234",
"connector_code": "momo",
"payer_phone_e164": "+242069876543",
"payment_method_label": "MTN Mobile Money",
"subject_ref": "CONTRIB-8842"
}
}Corps de confirmation (champs racine)
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
erp_due_ref | string 1-120 | Oui | Créance soldée ou partiellement soldée |
notice_ref | string 1-120 | Oui | Référence avis / quittance Epayment |
transaction_id | uuid | Oui | Identifiant transaction plateforme (idempotence) |
amount_minor | integer ≥ 0 | Oui | Montant effectivement encaissé |
currency | string len 3 | Oui | XAF |
paid_at | datetime ISO 8601 | Oui | Horodatage encaissement |
payment | objet | Non | Détail canal (recommandé) |
Objet payment (détail canal)
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
channel | MOBILE_MONEY | CASH | CARD | Oui | Canal de collecte v1 |
payment_ref | string 1-120 | Oui | Référence citoyen (agence ou MM) |
connector_code | string max 64 | Non | Code connecteur (momo, airtel, paddle, …) |
payer_phone_e164 | string max 20 | Non | Téléphone payeur (mobile money) |
payment_method_label | string max 200 | Non | Libellé affiché |
subject_ref | string max 200 | Non | Contribuable si connu |
Réponse attendue : HTTP 2xx. Corps libre ou { "ok": true } pour les probes.
L'ERP doit traiter transaction_id de manière idempotente : un second POST identique ne doit pas créer un second encaissement.
Probes (régie / admin)
POST .../probe/payment-confirmation avec corps optionnel :
json
{
"erp_due_ref": "DEMO-DUE",
"subject_ref": "DEMO-SUBJECT"
}Sandbox : écriture journal uniquement. REST : appel HTTP réel avec payload de démonstration.
Codes HTTP attendus
| Situation | Code |
|---|---|
| Succès | 200-299 |
| Corps invalide côté ERP (si validation) | 400 |
| Auth invalide | 401 / 403 |
Ressource inconnue (erp_due_ref, batch_ref) | 404 |
| Indisponibilité temporaire | 503 |
Epayment journalise le code et le corps ; les erreurs 5xx déclenchent des alertes opérationnelles côté régie.