> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mercy-pay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Encaisser un paiement

> Encaisse sans page hébergée : votre serveur collecte les informations du client et appelle cette route. En mobile money, une demande part sur le téléphone du client. Le résultat arrive par webhook.

> [!TIP]
>
> Essayez avec une clé **sandbox** (`mp_sandbox_…`) : aucun argent réel ne bouge. Voir [Test et production](https://docs.mercy-pay.com/start/environments.md).

`payment_method` choisit le canal : `mobile_money` (par défaut) ou `card`. Pour la carte, le plus simple est la page hébergée, voir [Accepter un paiement](https://docs.mercy-pay.com/payments/accept-a-payment.md). Une carte dans votre propre formulaire se met en place avec notre équipe : écrivez au support.

> [!NOTE]
>
> Un opérateur mobile money ne déplace que la devise de son pays. Si `currency` est différente, le montant est converti avant l'encaissement et la réponse donne le montant réellement prélevé, dans la devise de l'opérateur. Exemple : `1000 XAF` vers `MTN_MOMO_RWA` encaisse en RWF, et la réponse renvoie `"currency": "RWF"`. Sans taux disponible, la requête répond `422`.

## Un paiement qui échoue

La création répond toujours `201`, même si le paiement échoue ensuite chez l'opérateur. Vous l'apprenez par le webhook `payment.failed` ou en relisant le paiement : `status` passe à `failed` et `failure_code` donne la raison.

## failure\_code

Un code stable, en anglais et en minuscules, à tester dans votre code. `failure_reason` est un texte en français à afficher, pas à comparer.

| `failure_code` | Signification |
| - | - |
| `insufficient_balance` | Le payeur n'a pas assez de solde mobile money |
| `payment_not_approved` | Le payeur a refusé ou ignoré la demande sur son téléphone |
| `payer_not_found` | Le numéro n'est pas associé à l'opérateur choisi |
| `payer_limit_reached` | Le payeur a atteint sa limite chez l'opérateur |
| `transaction_expired` | Le payeur n'a pas confirmé à temps |
| `customer_not_eligible` | Ce client n'est pas éligible à ce type de paiement |
| `invalid_phone_number` | Le numéro n'est pas valide pour l'opérateur choisi |
| `amount_out_of_bounds` | Le montant est hors des limites autorisées |
| `provider_temporarily_unavailable` | L'opérateur est temporairement indisponible |
| `unspecified_failure` | Refus sans motif plus précis. C'est le code de tous les refus carte : lisez `failure_reason` |

Cette liste peut s'enrichir, mais un code ne change jamais de sens. Traitez un code inconnu comme un échec générique. Une carte refusée peut encore réussir si le client réessaie, voir [L'objet paiement](https://docs.mercy-pay.com/payments/payment-object.md#un-refus-carte-nest-pas-toujours-definitif).

## next\_action

Toujours présent. `null` quand il n'y a rien de plus à faire (le mobile money aujourd'hui). Avec `payment_method: "card"`, `type` vaut `card_payment` et `client_secret` confirme le paiement depuis votre formulaire. Décidez selon `type` : un futur moyen de paiement pourra ajouter le sien.


## OpenAPI

````yaml POST /api/v1/payments
openapi: 3.1.0
info:
  title: API Mercy Pay
  version: 1.0.0
  description: >-
    Encaissez par mobile money et par carte, facturez en récurrent et versez vos
    fonds. Les montants sont des entiers en unités mineures. Chaque réponse
    porte un en-tête `Request-Id`.
servers:
  - url: https://mercy-pay.com
    description: >-
      Production. La clé choisit l'environnement : `mp_sandbox_…` ou
      `mp_live_…`.
security:
  - bearerAuth: []
tags:
  - name: Compte
  - name: Sessions de paiement
  - name: Paiements
  - name: Remboursements
    description: Rendre un paiement au client, en entier.
  - name: Liens de paiement
  - name: Plans
  - name: Abonnements
  - name: Clients
  - name: Factures
  - name: Espace client
  - name: Soldes
  - name: Retraits
  - name: État des opérateurs
  - name: Événements
    description: Tout ce qui s'est passé sur le compte, envoyé ou non à un endpoint.
  - name: Endpoints de webhook
    description: Les URL de votre serveur qui reçoivent les événements.
paths:
  /api/v1/payments:
    post:
      tags:
        - Paiements
      summary: Encaisser un paiement
      description: >-
        Encaisse sans page hébergée : votre serveur collecte les informations du
        client et appelle cette route. En mobile money, une demande part sur le
        téléphone du client. Le résultat arrive par webhook.
      operationId: createPayment
      parameters:
        - $ref: '#/components/parameters/IdempotencyKeyRequired'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - amount
                - currency
              properties:
                amount:
                  type: integer
                  description: >-
                    Montant en unités mineures, au moins `1`. Voir [Montants et
                    devises](/payments/amounts-and-currencies).
                  minimum: 1
                currency:
                  type: string
                  description: >-
                    Code devise ISO 4217 de 3 lettres, par exemple `XAF`. En
                    mobile money, convertie vers la devise de l'opérateur si
                    besoin.
                  minLength: 3
                  maxLength: 3
                payment_method:
                  type: string
                  description: Canal, `mobile_money` par défaut
                  enum:
                    - mobile_money
                    - card
                  default: mobile_money
                country:
                  type: string
                  description: >-
                    Obligatoire en mobile money. Pays ISO 3166-1 alpha-3, par
                    exemple `CMR`.
                  minLength: 3
                  maxLength: 3
                provider:
                  type: string
                  description: >-
                    Obligatoire en mobile money. Code opérateur, par exemple
                    `MTN_MOMO_CMR`. Voir [Pays et
                    opérateurs](/payments/overview#pays-et-operateurs).
                phone:
                  type: string
                  description: >-
                    Obligatoire en mobile money. Numéro avec indicatif, par
                    exemple `237680000000`. Les espaces et le `+` sont retirés,
                    un numéro local est complété avec l'indicatif du pays.
                  maxLength: 20
                payer_name:
                  type: string
                  description: Nom du payeur
                  maxLength: 255
                payer_email:
                  type: string
                  description: E-mail du payeur
                  format: email
                  maxLength: 255
                description:
                  type: string
                  description: >-
                    500 caractères maximum. Les 22 premiers, sans accents,
                    forment le message que l'opérateur montre au client.
                  maxLength: 500
                metadata:
                  type: object
                  additionalProperties: true
                  description: >-
                    Vos données libres (jusqu'à 50 clés, valeurs de 500
                    caractères au plus), renvoyées dans les réponses et les
                    webhooks. Par exemple votre numéro de commande.
            example:
              amount: 1000
              currency: XAF
              country: CMR
              provider: MTN_MOMO_CMR
              phone: '237680000000'
              description: Commande 9123
      responses:
        '201':
          description: >-
            Paiement créé. Il reste `pending` jusqu'au webhook
            `payment.succeeded` ou `payment.failed`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Payment'
              example:
                data:
                  id: pay_5e0c8a7b3d1f4a2e9c648b7f0d2e1a93
                  object: payment
                  livemode: false
                  created: '2026-10-04T10:20:00+00:00'
                  checkout_session: null
                  amount: 1000
                  fee_amount: 50
                  net_amount: 950
                  currency: XAF
                  payment_method: mobile_money
                  mobile_money_country: CMR
                  mobile_money_provider: MTN_MOMO_CMR
                  status: pending
                  failure_code: null
                  failure_reason: null
                  metadata: null
                  next_action: null
        '400':
          $ref: '#/components/responses/IdempotencyMissing'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/RateLimited'
components:
  parameters:
    IdempotencyKeyRequired:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        maxLength: 255
      description: >-
        Obligatoire ici. Une clé unique par opération : la rejouer pendant 24
        heures renvoie la même réponse sans rien créer de plus. Voir
        [Idempotence](/developers/idempotency).
  schemas:
    Payment:
      type: object
      properties:
        id:
          type: string
          description: Identifiant du paiement (`pay_…`)
        object:
          type: string
          const: payment
        livemode:
          type: boolean
          description: '`true` pour de l''argent réel (clé live), `false` en sandbox'
        created:
          type:
            - string
            - 'null'
          format: date-time
          description: Date de création, ISO 8601
        checkout_session:
          type:
            - string
            - 'null'
          description: >-
            Session de checkout d'origine (`cs_…`), `null` pour un paiement
            direct. Avec `expand[]=checkout_session`, la session complète
        amount:
          type: integer
          description: Montant payé par le payeur, en unités mineures
        fee_amount:
          type: integer
          description: Frais retenus
        net_amount:
          type: integer
          description: Montant versé sur votre solde
        fee_paid_by:
          type: string
          enum:
            - merchant
            - customer
          description: >-
            Qui a payé les frais. `merchant` : ils sont déduits de `net_amount`.
            `customer` : ils sont ajoutés à `amount`, et `net_amount` est le
            prix demandé.
        amount_refunded:
          type: integer
          description: >-
            Ce qui a déjà été rendu au client par des remboursements, en unités
            mineures. Le paiement passe à `refunded` quand il atteint `amount`.
        currency:
          type: string
          description: >-
            Devise réellement encaissée. En mobile money, celle de l'opérateur,
            après conversion si besoin.
        payment_method:
          type: string
          description: Canal de paiement
          enum:
            - mobile_money
            - card
        mobile_money_country:
          type:
            - string
            - 'null'
          description: Pays de l'opérateur, `null` pour une carte.
        mobile_money_provider:
          type:
            - string
            - 'null'
          description: Opérateur, `null` pour une carte.
        payer:
          type: object
          description: >-
            Le payeur, tel que saisi sur la page de paiement ou transmis à la
            création. Pour le mobile money, `phone` est le numéro réellement
            débité.
          properties:
            name:
              type:
                - string
                - 'null'
            email:
              type:
                - string
                - 'null'
            phone:
              type:
                - string
                - 'null'
              description: Indicatif compris, sans `+`, par exemple `237677123456`.
        status:
          type: string
          description: Voir [Paiements](/payments/payment-object#statuts).
          enum:
            - pending
            - processing
            - succeeded
            - failed
            - canceled
            - expired
            - refunded
            - disputed
        failure_code:
          type:
            - string
            - 'null'
          description: >-
            Code stable de l'échec quand `status` vaut `failed`, sinon `null`.
            Voir [la liste](/api/payments/create#failure_code).
        failure_reason:
          type:
            - string
            - 'null'
          description: Raison de l'échec en français, à afficher telle quelle.
        metadata:
          type:
            - object
            - 'null'
          additionalProperties: true
          description: Vos données libres. Voir [Metadata](/developers/metadata).
        next_action:
          $ref: '#/components/schemas/NextAction'
    NextAction:
      type:
        - object
        - 'null'
      description: >-
        Ce que votre client doit encore faire. Décidez selon `type`, pas selon
        la seule présence du champ : un futur moyen de paiement pourra ajouter
        son propre `type`.
      properties:
        type:
          type: string
          description: >-
            `card_payment` (confirmer une carte depuis votre formulaire) ou
            `redirect` (envoyer le client sur `redirect_url`).
          enum:
            - card_payment
            - redirect
        client_secret:
          type: string
          description: >-
            `card_payment` : secret qui confirme le paiement depuis votre
            formulaire.
        redirect_url:
          type: string
          description: '`redirect` : page hébergée où le client termine.'
          format: uri
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              enum:
                - authentication_error
                - invalid_request_error
                - idempotency_error
                - payment_error
                - rate_limit_error
                - api_error
            code:
              type: string
              description: Code stable, à tester dans votre code
            message:
              type: string
              description: Explication en français, pour un humain
            param:
              type:
                - string
                - 'null'
              description: Le premier champ en cause
            doc_url:
              type: string
              description: Explication de ce code
            request_id:
              type: string
              description: >-
                Identifiant de la requête (`req_…`), aussi dans l'en-tête
                `Request-Id`
            errors:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string
              description: Détail par champ refusé, sur une erreur `422`
      required:
        - error
  responses:
    IdempotencyMissing:
      description: '`Idempotency-Key` absent'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: idempotency_error
              code: idempotency_key_missing
              message: L'en-tête Idempotency-Key est obligatoire.
              param: null
              doc_url: >-
                https://docs.mercy-pay.com/developers/errors#idempotency_key_missing
              request_id: req_01jc8x4m2v7t9qk3n5w6y8z0ab
    Unauthorized:
      description: Clé absente, invalide ou révoquée
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: authentication_error
              code: api_key_invalid
              message: Clé API invalide.
              param: null
              doc_url: https://docs.mercy-pay.com/developers/errors#api_key_invalid
              request_id: req_01jc8x4m2v7t9qk3n5w6y8z0ab
    Forbidden:
      description: Compte suspendu, ou clé live avant l'approbation du KYC
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: authentication_error
              code: live_mode_requires_kyc
              message: Le mode live demande un KYC approuvé.
              param: null
              doc_url: >-
                https://docs.mercy-pay.com/developers/errors#live_mode_requires_kyc
              request_id: req_01jc8x4m2v7t9qk3n5w6y8z0ab
    Conflict:
      description: Conflit avec l'état de la ressource, ou clé d'idempotence réutilisée
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request_error
              code: conflict
              message: 'Cette ressource est utilisée : archivez-la.'
              param: null
              doc_url: https://docs.mercy-pay.com/developers/errors#conflict
              request_id: req_01jc8x4m2v7t9qk3n5w6y8z0ab
    ValidationFailed:
      description: Données invalides, avec le détail dans `errors`
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: invalid_request_error
              code: validation_failed
              message: Le champ amount est obligatoire.
              param: amount
              doc_url: https://docs.mercy-pay.com/developers/errors#validation_failed
              request_id: req_01jc8x4m2v7t9qk3n5w6y8z0ab
              errors:
                amount:
                  - Le champ amount est obligatoire.
    RateLimited:
      description: Plus de 60 requêtes par minute avec cette clé
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              type: rate_limit_error
              code: rate_limited
              message: Trop de requêtes.
              param: null
              doc_url: https://docs.mercy-pay.com/developers/errors#rate_limited
              request_id: req_01jc8x4m2v7t9qk3n5w6y8z0ab
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        Votre clé API. Utilisez une clé sandbox (`mp_sandbox_…`) pour essayer :
        aucun argent réel ne bouge.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.