# Crédit wallet

import { ApiExample, ApiResponse } from "../../src/ApiExample";

Vous pouvez **déclarer** qu'un de vos clients a versé de l'argent. Vous ne créditez jamais directement.

<Callout type="danger" title="Pourquoi une demande et pas un crédit">
  Vous êtes un tiers. Seul Instafuel constate l'encaissement sur son compte bancaire.

  Si votre appel créditait immédiatement, il suffirait d'appeler l'API pour créer du crédit d'achat sans contrepartie encaissée. Toute demande passe donc par une approbation Instafuel — sans exception, et sans seuil en dessous duquel elle serait automatique.
</Callout>

## Trois verrous

Le crédit n'est possible que si les trois sont ouverts :

1. la clé porte le droit `CREDIT_REQUEST_WRITE` — sinon `403 PAPI_SCOPE_MISSING` ;
2. `walletCreditEnabled` est vrai pour votre compte — sinon `403 RESELLER_CREDIT_DISABLED` ;
3. le montant respecte vos plafonds par opération et journalier — sinon `422`.

Vos plafonds courants sont sur `GET /v1/papi/me` :

<ApiResponse
  status={200}
  title="Extrait de GET /v1/papi/me"
  body={{
    walletCreditEnabled: true,
    caps: {
      perOperationFcfa: 5000000,
      dailyFcfa: 20000000,
      dailyRemainingFcfa: 14500000,
    },
  }}
/>

## Le flux

<Stepper>

1. **Déposez la preuve de versement**

   <ApiExample
     method="POST"
     path="/v1/papi/companies/4832/wallet/credit/proof"
     file="virement-kouassi-aout.pdf"
   />

   <ApiResponse
     status={201}
     body={{
       data: [
         {
           id: "11111111-1111-4111-8111-111111111111",
           fileName: "virement-kouassi-aout.pdf",
           mimeType: "application/pdf",
           sizeBytes: 184320,
           companyId: 4832,
           uploadedAt: "2026-08-31T09:02:44.000Z",
         },
       ],
     }}
   />

   Jusqu'à **3 fichiers, 10 Mo chacun**. Notez les `id` renvoyés : ils sont à fournir à l'étape suivante.

   :::note[Preuve obligatoire, toutes sources confondues]
   Y compris pour un versement mobile money. C'est la pièce que le rapprochement bancaire d'Instafuel exigera pour approuver.
   :::

2. **Déclarez le versement**

   <ApiExample
     method="POST"
     path="/v1/papi/companies/4832/wallet/credit"
     idempotency
     body={{
       amountFcfa: 500000,
       source: "BANK_TRANSFER",
       clientReference: "VIR-2026-0814",
       proofIds: ["11111111-1111-4111-8111-111111111111"],
       note: "Versement mensuel Transports Kouassi",
     }}
   />

   La réponse est un **`202`, jamais un `200`** — et cette différence est tout le sujet :

   <ApiResponse
     status={202}
     body={{
       status: "APPROVAL_PENDING",
       approvalId: "0193a1f2-7c44-7c1e-9b0a-5f2d1c8e4b60",
       companyId: 4832,
       amountFcfa: 500000,
       source: "BANK_TRANSFER",
       clientReference: "IFP7-VIR-2026-0814",
       walletBalanceFcfa: 1450000,
       requestedAt: "2026-08-31T09:04:12.000Z",
       expiresAt: "2026-09-07T09:04:12.000Z",
     }}
   />

   `202` signifie « demande enregistrée », pas « opération effectuée ». Trois choses à lire dans cette réponse :

   - `walletBalanceFcfa` vaut **toujours 1 450 000** : le solde n'a pas bougé. Ne l'affichez pas comme crédité.
   - `clientReference` est revenue **préfixée** par votre identifiant d'apporteur (`IFP7-`). Deux partenaires utilisant « VIR-2026-0814 » sur le même wallet n'entrent donc pas en collision.
   - `expiresAt` est dans 7 jours : sans approbation d'ici là, la demande expire et il faudra la refaire.

   Un dépassement de plafond est refusé sur-le-champ, avec les chiffres qui permettent de découper le versement :

   <ApiResponse
     status={422}
     body={{
       error: {
         code: "RESELLER_CREDIT_CAP_EXCEEDED",
         message: "Montant supérieur au plafond par opération",
         details: [
           {
             field: "amountFcfa",
             messages: [
               "Plafond par opération : 5 000 000 FCFA",
               "Montant demandé : 7 500 000 FCFA",
             ],
           },
         ],
       },
     }}
   />

3. **Suivez la demande**

   <ApiExample
     path="/v1/papi/credit-requests"
     query={{ status: "PENDING_APPROVAL", perPage: 20 }}
   />

   <ApiResponse
     status={200}
     body={{
       data: [
         {
           approvalId: "0193a1f2-7c44-7c1e-9b0a-5f2d1c8e4b60",
           companyId: 4832,
           amountFcfa: 500000,
           source: "BANK_TRANSFER",
           clientReference: "IFP7-VIR-2026-0814",
           status: "PENDING_APPROVAL",
           proofIds: ["11111111-1111-4111-8111-111111111111"],
           requestedAt: "2026-08-31T09:04:12.000Z",
           expiresAt: "2026-09-07T09:04:12.000Z",
         },
       ],
       pagination: {
         page: 1,
         perPage: 20,
         total: 1,
         totalPages: 1,
       },
     }}
   />

   | `status` | Signification |
   |---|---|
   | `PENDING_APPROVAL` | en attente de la validation Instafuel |
   | `APPROVED` | wallet crédité, écriture passée au grand livre |
   | `REJECTED` | refusée — le motif figure dans `rejectionReason` |
   | `EXPIRED` | 7 jours sans approbation |

4. **Instafuel approuve**

   Le wallet est crédité, l'écriture est passée au grand livre et les preuves y sont rattachées. La demande reflète alors l'écriture produite :

   <ApiResponse
     status={200}
     body={{
       approvalId: "0193a1f2-7c44-7c1e-9b0a-5f2d1c8e4b60",
       status: "APPROVED",
       amountFcfa: 500000,
       approvedAt: "2026-09-01T08:12:55.000Z",
       ledgerEntry: {
         ref: "LED-8C40D21A",
         type: "CREDIT",
         amountFcfa: 500000,
         balanceAfterFcfa: 1950000,
         createdAt: "2026-09-01T08:12:55.000Z",
         confirmedAt: "2026-09-01T08:12:55.000Z",
       },
     }}
   />

   `balanceAfterFcfa` est le solde après écriture : c'est à ce moment-là, et pas avant, que l'argent est disponible pour les pleins.

</Stepper>

## Champs

| Champ | Obligatoire | Détail |
|---|---|---|
| `amountFcfa` | oui | entier FCFA strictement positif |
| `source` | oui | `BANK_TRANSFER`, `MOBILE_MONEY`, `CASH_DEPOSIT`, `CHEQUE` — la **nature réelle** du versement |
| `clientReference` | oui | votre référence : numéro de virement, identifiant de transaction mobile money… |
| `proofIds` | oui | 1 à 3 identifiants de preuves déposées à l'étape 1 |
| `note` | non | commentaire libre, 500 caractères |

## Plafonds

Le plafond journalier compte les crédits **déjà approuvés** *et* les demandes **encore en attente**. Empiler des demandes sous le plafond ne le contourne pas.

| `error.code` | HTTP | Cause |
|---|---|---|
| `RESELLER_CREDIT_DISABLED` | 403 | crédit non activé pour votre compte |
| `RESELLER_CREDIT_CAP_EXCEEDED` | 422 | dépassement du plafond par opération |
| `RESELLER_DAILY_CAP_EXCEEDED` | 422 | dépassement du plafond journalier |
| `PROOF_REQUIRED` | 400 | aucune preuve fournie |
| `PROOF_INVALID` | 422 | preuve inexistante, supprimée, déjà rattachée, ou appartenant à une autre entreprise |
| `PROOF_TOO_MANY` | 400 | plus de 3 preuves |
| `WALLET_BLOCKED` | 400 | wallet de l'entreprise bloqué |

Un `422` de plafond ne se retente pas à l'identique : découpez le versement ou demandez un relèvement.

## Revérification à l'approbation

Entre votre demande et l'approbation, il peut s'écouler plusieurs jours. Tout est **revérifié** au moment de l'exécution : votre compte est-il toujours actif, le crédit toujours autorisé, l'entreprise toujours dans votre portefeuille, les preuves toujours valides, le wallet non bloqué.

Si l'une de ces conditions a changé, l'exécution échoue et la demande **reste en attente** plutôt que d'être marquée approuvée à tort.

## Ce que vous ne pouvez pas faire

Aucun remboursement, aucun débit, aucun blocage de wallet, aucune annulation d'une écriture passée. Le grand livre est immuable : une correction est une nouvelle écriture, passée par Instafuel.
