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

# Initier un paiement softpay

> Push sur le téléphone du client, ou redirection vers une page de paiement selon l'opérateur

<Warning>
  **Testez toujours `checkout_url`.** Quand elle est non vide, votre client ne recevra AUCUNE demande sur son téléphone : vous devez le rediriger vers cette URL, sinon le paiement n'aura jamais lieu.

  Ce n'est pas un cas marginal — c'est le fonctionnement normal de Wave, Orange Money, Djamo et des cartes bancaires. Un même réseau peut d'ailleurs basculer d'un mode à l'autre sans préavis, selon la route choisie par notre moteur de routage.
</Warning>

```js theme={null}
const res = await fetch('https://api.alphapay.me/api/v1/payments/softpay/', { /* … */ })
const { data } = await res.json()

if (data.checkout_url) {
  // Le paiement se règle sur une page hébergée : redirigez le client.
  window.location.href = data.checkout_url
} else {
  // Push direct : une demande arrive sur son téléphone, affichez `instructions`
  // et sondez GET /payments/{id}/ jusqu'au statut final.
}
```

<Note>
  `return_url` sert précisément à ce cas : après paiement sur la page hébergée, votre client y est ramené automatiquement. Sans lui, il reste sur la page du fournisseur.

  **Optionnel** pour la quasi-totalité des réseaux (push USSD, pas de page hébergée). **Obligatoire** pour `card`/`crypto` — sans lui, `422` immédiat (`return_url` manquant), aucun paiement initié. Ces deux réseaux n'ont *que* la page hébergée, jamais de push : sans URL de retour, votre client n'aurait aucun moyen de revenir chez vous après avoir payé.
</Note>

Voir [Pays, réseaux et format du téléphone](/api-reference/reference/formats) pour la liste des codes `network` valides par pays et le format attendu de `customer.phone`.

## Éviter un doublon en cas de coupure réseau

Le header `Idempotency-Key` est **optionnel** : sans lui, chaque appel crée un paiement — un simple retry réseau en pousse donc un second sur le téléphone du client. Avec lui, renvoyer la même clé après un timeout vous renvoie la réponse d'origine au lieu de rejouer l'opération.

Un timeout n'est jamais la preuve qu'une requête a échoué : elle a pu aboutir côté serveur. C'est précisément ce cas que la clé couvre.

```bash theme={null}
curl -X POST https://api.alphapay.me/api/v1/payments/softpay/ \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \
  -H "Content-Type: application/json" \
  -d '{ ... }'
```

Générez une clé **par intention** (un UUID convient), et réutilisez-la pour chaque retry de cette même tentative. Réutiliser une clé avec un corps différent renvoie `409`. Détails : [Idempotence](/api-reference/introduction#idempotence).


## OpenAPI

````yaml api-reference/openapi.json POST /payments/softpay/
openapi: 3.1.0
info:
  title: AlphaPay API
  description: >-
    API d'encaissement et de paiement mobile money/carte pour l'Afrique de
    l'Ouest et du Centre. Toutes les routes marchand nécessitent une clé API
    secrète (header Authorization: Bearer sk_...).
  version: 1.0.0
servers:
  - url: https://api.alphapay.me/api/v1
security:
  - bearerAuth: []
paths:
  /payments/softpay/:
    post:
      tags:
        - Paiements
      summary: Initier un paiement softpay
      description: >-
        Initie un paiement sur le réseau choisi. Selon l'opérateur, deux cas :
        soit une demande est poussée directement sur le téléphone du client
        (USSD/notification), soit `checkout_url` est renvoyée et **vous devez y
        rediriger votre client** — c'est le cas de Wave, Orange Money, Djamo et
        des cartes bancaires. Testez toujours `checkout_url` avant de conclure
        au push. Nécessite un scope de clé API `PAYIN` ou `BOTH`. Header
        `Idempotency-Key` optionnel, mais vivement recommandé : sans lui, un
        retry réseau crée un second paiement réel.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - amount
                - currency
                - country
                - customer
                - network
              properties:
                merchant:
                  type: string
                  format: uuid
                  description: >-
                    UUID du marchand visé. Ignoré pour une clé API (toujours
                    vous-même) ; pour un token dashboard gérant plusieurs
                    marchands, résout la boutique visée si le header
                    X-Merchant-Id n'est pas fourni.
                amount:
                  type: string
                  format: decimal
                  example: '2500.00'
                currency:
                  type: string
                  minLength: 3
                  maxLength: 3
                  example: XOF
                country:
                  type: string
                  minLength: 2
                  maxLength: 2
                  example: BJ
                description:
                  type: string
                  example: Abonnement mensuel
                customer:
                  type: object
                  required:
                    - email
                    - first_name
                    - last_name
                    - phone
                  properties:
                    email:
                      type: string
                      format: email
                      example: client@example.com
                    first_name:
                      type: string
                      example: Awa
                    last_name:
                      type: string
                      example: Sossou
                    phone:
                      type: string
                      example: '+22997505050'
                network:
                  type: string
                  description: Code réseau, cf. référentiel pays/réseaux
                  example: mtn_bj
                metadata:
                  type: object
                  additionalProperties: true
                fee_charge_mode:
                  type: string
                  enum:
                    - ADD_ON
                    - DEDUCTED
                  nullable: true
                  description: >-
                    ADD_ON : les frais s'ajoutent au montant débité au client.
                    DEDUCTED : les frais sont déduits du montant net reversé au
                    marchand. Défaut : configuration du marchand.
                preferred_gateway:
                  type: string
                  description: >-
                    Code gateway à privilégier si plusieurs sont éligibles pour
                    ce réseau
            example:
              amount: '2500.00'
              currency: XOF
              country: BJ
              description: Abonnement mensuel
              customer:
                email: client@example.com
                first_name: Awa
                last_name: Sossou
                phone: '+22997505050'
              network: mtn_bj
      responses:
        '201':
          description: Paiement poussé avec succès
          content:
            application/json:
              example:
                message: Payment pushed successfully
                id: 9c3f2a10-4b7e-4f1a-9d2e-9b6a7c1e4a02
                status: PENDING
                checkout_url: ''
        '403':
          description: Scope de clé API insuffisant (PAYIN requis)
          content:
            application/json:
              example:
                message: >-
                  Cette clé API n'est pas autorisée pour les opérations payin
                  (scope actuel : Payout).
                code: api_key_scope_forbidden
        '422':
          description: >-
            Requête valide mais impossible à router. Codes possibles :
            `missing_method`, `invalid_country`, `no_exchange_rate`,
            `invalid_customer`, `invalid_method`, `no_route_available`.
          content:
            application/json:
              example:
                message: 'Réseau inconnu ou inactif : ''mtn_xx''.'
                code: invalid_method
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        Identifiant unique que **vous** générez pour cette tentative (un UUID
        par exemple). Optionnel : sans lui, chaque appel est traité comme une
        nouvelle demande. Avec lui, si vous renvoyez la même clé — après un
        timeout ou une coupure réseau — AlphaPay renvoie la réponse d'origine au
        lieu de créer un second paiement. Réutilisez la même clé pour les retrys
        d'une même tentative, changez-en pour toute nouvelle intention. La même
        clé avec un corps de requête différent renvoie une erreur `409`. Voir
        [Idempotence](/api-reference/introduction#idempotence).
      schema:
        type: string
        maxLength: 255
      example: 3fa85f64-5717-4562-b3fc-2c963f66afa6
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_live_... / sk_test_...
      description: >-
        Clé API secrète du marchand — header Authorization: Bearer sk_live_xxx
        (ou sk_test_xxx en environnement de test).

````