Aller au contenu principal

Transactions

Base URL : /api/v1/transactions

Consultation et application des mouvements financiers sur les cartes. Chaque opération mutante accepte l'en-tête Idempotency-Key et, pour les recharges Mobile Money, nécessite le contexte tenant.


Types de transaction​

Type (type)Description
credit_cashRecharge espèces (agent)
credit_mobile_moneyRecharge Mobile Money
debit_purchaseAchat de produits
debit_reservationDébit lié à une réservation
correctionCorrection manuelle (crédit)
debit_correctionCorrection manuelle (débit)
debit_lost_cardDébit pour carte perdue
refundRemboursement

GET /transactions​

Liste paginée des transactions de l'organisation.

Permissions : transaction.read ou card.manage

Paramètres de query​

ParamTypeDéfautDescription
searchstring—Recherche (code carte, porteur…)
typestring—Filtrer par type (ex: credit_cash, debit_purchase)
datedate—Jour précis (YYYY-MM-DD)
date_fromdate—Début de plage
date_todate—Fin de plage
limitinteger20Éléments par page
offsetinteger0Décalage
cursorstring—Curseur {timestamp},{id}

Réponse (200)​

Pagination par curseur : next_cursor vaut null sur la dernière page.

{
"items": [
{
"id": "uuid",
"card_id": "uuid",
"card_code": "CL-EL-0008",
"card_number": "6287940000123456",
"cardholder_name": "Fatou Diallo",
"is_voided": false,
"type": "debit_purchase",
"amount_cents": 1500,
"balance_before_cents": 5000,
"balance_after_cents": 3500,
"performed_by_user_id": "uuid",
"performed_by_name": "Jean Mbeki",
"site_id": "uuid",
"point_of_sale_id": "uuid",
"created_at": "2025-06-01T10:00:00Z",
"amount": 1500,
"amount_formatted": "1 500 FCFA",
"balance_before": 5000,
"balance_before_formatted": "5 000 FCFA",
"balance_after": 3500,
"balance_after_formatted": "3 500 FCFA",
"currency": "XOF",
"purchase_items": [
{ "product_id": "uuid", "name": "Café", "quantity": 1, "unit_price_cents": 1500 }
]
}
],
"total": 142,
"next_cursor": "2025-06-01T10:00:00+00:00,550e8400-e29b-41d4-a716-446655440000",
"currency": "XOF"
}
Champs conditionnels
  • purchase_items présent uniquement si type == "debit_purchase".
  • reservation_items présent uniquement si type == "debit_reservation".
  • is_voided est calculé en détectant les lignes de reversal associées.

POST /transactions/recharge​

Recharge une carte. Deux méthodes : cash (synchrone) et mobile_money (asynchrone via un fournisseur de paiement).

Permissions : card.recharge.cash ou card.recharge.mobile_money ou card.manage. Pour mobile_money, la permission card.recharge.mobile_money est vérifiée explicitement.

En-tête : Idempotency-Key (recommandé)

Corps de la requête​

ChampTypeRequisDescription
card_iduuidOuiCarte à recharger
amount_centsintegerOuiMontant (1 à 1 000 000 FCFA)
methodstringOui"cash" ou "mobile_money"
point_of_sale_iduuidNonPoint de vente
site_iduuidNonSite
register_iduuidNonSession de caisse
metadataobjectNonMétadonnées libres
customer_phonestringCond.Requis si method=mobile_money
payment_providerstringNon"fedapay" (défaut) ou "paygate"
POST /api/v1/transactions/recharge
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
"card_id": "uuid",
"amount_cents": 2000,
"method": "cash",
"point_of_sale_id": "uuid"
}

Réponse cash (200)​

{
"transaction": {
"id": "uuid",
"card_id": "uuid",
"type": "credit_cash",
"amount_cents": 2000,
"balance_before_cents": 5000,
"balance_after_cents": 7000,
"performed_by_user_id": "uuid",
"performed_by_name": "Jean Mbeki",
"created_at": "2025-06-01T10:00:00Z",
"amount": 2000,
"amount_formatted": "2 000 FCFA",
"balance_before": 5000,
"balance_before_formatted": "5 000 FCFA",
"balance_after": 7000,
"balance_after_formatted": "7 000 FCFA",
"currency": "XOF"
},
"new_balance_cents": 7000,
"new_balance": 7000,
"new_balance_formatted": "7 000 FCFA",
"currency": "XOF"
}

Réponse mobile_money (200)​

Crée une session de paiement asynchrone. Le crédit sera appliqué via le callback du fournisseur.

{
"payment_url": "https://pay.fedapay.com/...",
"session_id": "uuid",
"provider_tx_id": null
}
Prérequis Mobile Money

Le fournisseur de paiement doit être configuré (state.payment_client), sinon l'erreur PaymentNotConfigured est renvoyée.


POST /transactions/purchase​

Effectue un achat multi-articles. Le solde est vérifié au moment du débit.

Permissions : product.sell ou product.manage

En-tête : Idempotency-Key

Corps de la requête​

ChampTypeRequisDescription
card_iduuidOuiCarte débitée
itemsarrayOuiArticles achetés
items[].product_iduuidOuiProduit
items[].quantityintegerOuiQuantité
items[].unit_price_centsintegerOuiPrix unitaire
point_of_sale_iduuidNonPoint de vente
site_iduuidNonSite
register_iduuidNonSession de caisse
POST /api/v1/transactions/purchase
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440001
Content-Type: application/json

{
"card_id": "uuid",
"items": [
{ "product_id": "uuid", "quantity": 2, "unit_price_cents": 1000 }
]
}

Réponse (200)​

{
"transaction": {
"id": "uuid",
"card_id": "uuid",
"type": "debit_purchase",
"amount_cents": 2000,
"balance_before_cents": 7000,
"balance_after_cents": 5000,
"created_at": "2025-06-01T10:05:00Z",
"amount": 2000,
"amount_formatted": "2 000 FCFA",
"balance_before": 7000,
"balance_before_formatted": "7 000 FCFA",
"balance_after": 5000,
"balance_after_formatted": "5 000 FCFA",
"currency": "XOF"
},
"new_balance_cents": 5000,
"new_balance": 5000,
"new_balance_formatted": "5 000 FCFA",
"currency": "XOF"
}

POST /transactions/correction​

Applique une correction manuelle (crédit ou débit).

Permissions : card.correction ou card.manage. Si la correction rend le solde négatif, card.balance.manual_adjust est requis et le porteur doit être de niveau préscolaire (metadata.education_level ∈ preschool/maternelle).

En-tête : Idempotency-Key

Corps de la requête​

ChampTypeRequisDescription
card_iduuidOuiCarte concernée
amount_centsintegerOuiMontant absolu (> 0)
directionstringNon"credit" ou "debit"
reasonstringOuiMotif de la correction
original_transaction_iduuidNonTransaction d'origine
site_iduuidNonSite
point_of_sale_iduuidNonPoint de vente
register_iduuidNonSession de caisse
POST /api/v1/transactions/correction
Content-Type: application/json

{
"card_id": "uuid",
"amount_cents": 500,
"direction": "credit",
"reason": "Erreur de caisse"
}
Champ direction obsolète

Si direction est omis, le signe de amount_cents est utilisé : négatif = débit, positif = crédit. Ce mode est déprécié — utilisez direction.

Réponse (200)​

Même enveloppe que purchase, avec "type": "correction".


POST /transactions/refund​

Rembourse tout ou partie d'une transaction.

Permissions : reservation.refund, reservation.manage, card.correction ou card.manage

En-tête : Idempotency-Key

Corps de la requête​

ChampTypeRequisDescription
card_iduuidOuiCarte recréditée
amount_centsintegerOuiMontant (> 0)
reasonstringOuiMotif
reservation_iduuidNonRéservation concernée
original_transaction_iduuidNonTransaction d'origine
site_iduuidNonSite
point_of_sale_iduuidNonPoint de vente
register_iduuidNonSession de caisse
POST /api/v1/transactions/refund
Content-Type: application/json

{
"card_id": "uuid",
"amount_cents": 1000,
"reason": "Réservation annulée",
"reservation_id": "uuid"
}

Réponse (200)​

Même enveloppe que purchase, avec "type": "refund".


POST /transactions/{id}/void​

Annule une transaction : inverse son effet financier et restaure le stock le cas échéant.

Permissions : card.void ou card.correction

En-tête : Idempotency-Key (défaut void:{id})

Corps de la requête​

ChampTypeRequisDescription
reasonstringOuiMotif de l'annulation
site_iduuidNonSite
point_of_sale_iduuidNonPoint de vente
register_iduuidNonSession de caisse

Réponse (200)​

Format spécifique

Contrairement aux autres réponses de transaction, void n'applique pas de monetize : type est le nom de la variante Rust ("Correction", "DebitCorrection"), les montants sont des entiers bruts, et performed_by est un objet { id, kind }.

{
"message": "Transaction annulée",
"transaction": {
"id": "uuid",
"card_id": "uuid",
"type": "Correction",
"amount_cents": 2000,
"balance_before_cents": 5000,
"balance_after_cents": 7000,
"performed_by": { "id": "uuid", "kind": "TenantUser" },
"point_of_sale_id": "uuid",
"site_id": "uuid",
"register_id": null,
"reference_order_id": "uuid",
"idempotency_key": "void:uuid",
"metadata": {},
"created_at": "2025-06-01T10:10:00Z"
}
}

Éligibilité au void​

La type de reversal dépend du type d'origine :

Type d'origineType de reversal
debit_purchase, debit_reservationCorrection
credit_cash, credit_mobile_money, correctionDebitCorrection
debit_correctionCorrection

Une transaction déjà annulée est rejetée (validation).


POST /transactions/{id}/modify​

Modifie une vente : annule l'ancienne et crée une nouvelle de façon atomique.

Permissions : card.correction

En-tête : Idempotency-Key

Corps de la requête​

ChampTypeRequisDescription
card_iduuidOuiCarte
itemsarrayOuiNouveaux articles
reasonstringOuiMotif de la modification
point_of_sale_iduuidNonPoint de vente
site_iduuidNonSite
register_iduuidNonSession de caisse

Réponse (200)​

{
"message": "Vente modifiée",
"transaction": {
"id": "uuid",
"card_id": "uuid",
"type": "debit_purchase",
"amount_cents": 1500,
"balance_before_cents": 5000,
"balance_after_cents": 3500,
"created_at": "2025-06-01T10:15:00Z",
"amount": 1500,
"amount_formatted": "1 500 FCFA",
"balance_before": 5000,
"balance_before_formatted": "5 000 FCFA",
"balance_after": 3500,
"balance_after_formatted": "3 500 FCFA",
"currency": "XOF"
},
"new_balance_cents": 3500,
"new_balance": 3500,
"new_balance_formatted": "3 500 FCFA",
"currency": "XOF"
}
Comportement

modify est un « void + re-create » : les anciens articles sont remis en stock, l'ancien total recrédité, une nouvelle vente créée, et la transaction d'origine marquée comme remplacée.


Erreurs courantes​

CodeDescription
400Montant invalide (≤ 0 ou > plafond)
403Permission ou plafond insuffisant
404Carte ou transaction introuvable
409Transaction déjà annulée
422customer_phone manquant pour Mobile Money