openapi: 3.1.0
info:
  title: iPayMoney Merchant API
  version: 1.0.0
  description: >-
    Contrats marchands v1 vérifiés contre les routes, contrôleurs et services
    Rails. Les contrats partenaires et les routes v2 non opérationnelles ne
    font pas partie de cette spécification publique. Les liens de paiement
    doivent utiliser des références aléatoires privées.
servers:
  - url: https://i-pay.money/api/v1
    description: Sandbox ou Live selon les en-têtes et la clé utilisée
tags:
  - name: Payments
    description: Création et suivi des paiements marchands
  - name: Payment links
    description: Liens de paiement hébergés
paths:
  /payments:
    get:
      tags: [Payments]
      summary: Lister les paiements du compte
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/ContentType'
        - $ref: '#/components/parameters/TargetEnvironment'
        - $ref: '#/components/parameters/PaymentType'
        - name: query
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema:
            type: string
            enum: [initiated, pending, succeeded, failed, refunded]
        - name: payment_method
          in: query
          schema: { type: string }
        - name: platform
          in: query
          schema: { type: string }
        - name: created_from
          in: query
          schema: { type: string, format: date }
        - name: created_to
          in: query
          schema: { type: string, format: date }
        - name: items
          in: query
          schema: { type: integer, default: 25, minimum: 1 }
      responses:
        '200':
          description: Liste des paiements. Les statuts et moyens sont formatés pour l'affichage.
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/PaymentListItem' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '406': { $ref: '#/components/responses/NotAcceptable' }
    post:
      tags: [Payments]
      summary: Créer un paiement
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/TargetEnvironment'
        - $ref: '#/components/parameters/PaymentType'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PaymentRequest' }
            examples:
              sandboxMobile:
                summary: Paiement mobile Sandbox réussi
                value:
                  amount: 1000
                  country: BJ
                  currency: XOF
                  msisdn: '40410000000'
                  transaction_id: ORDER-SANDBOX-001
                  customer_name: Client Test
      responses:
        '200':
          description: Paiement enregistré. Le statut final doit être vérifié séparément.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PaymentCreateResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '406': { $ref: '#/components/responses/NotAcceptable' }
        '422':
          description: Référence externe déjà utilisée ou paiement invalide
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /payments/{reference}:
    get:
      tags: [Payments]
      summary: Vérifier un paiement
      description: Accepte la référence interne iPayMoney ou la référence externe du marchand.
      security:
        - bearerAuth: []
      parameters:
        - name: reference
          in: path
          required: true
          schema: { type: string }
        - $ref: '#/components/parameters/ContentType'
        - $ref: '#/components/parameters/TargetEnvironment'
        - $ref: '#/components/parameters/PaymentType'
      responses:
        '200':
          description: Paiement trouvé pour le compte authentifié
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PaymentStatus' }
        '404':
          description: Paiement introuvable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
  /external_payments:
    post:
      tags: [Payment links]
      summary: Créer un lien de paiement hébergé
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/TargetEnvironment'
        - name: Ipay-Payment-Type
          in: header
          required: true
          schema: { type: string, const: external_payment }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ExternalPaymentRequest' }
      responses:
        '200':
          description: Lien créé
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ExternalPaymentCreateResponse' }
        '404':
          description: Compte introuvable ou inactif
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '422':
          description: Paramètres invalides
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ValidationError' }
  /external_payments/{reference}:
    get:
      tags: [Payment links]
      summary: Consulter un lien de paiement
      description: >-
        Cette route est actuellement accessible sans clé et peut retourner des
        données client. Utilisez une référence privée générée aléatoirement,
        jamais un identifiant de commande séquentiel.
      security: []
      parameters:
        - name: reference
          in: path
          required: true
          description: Référence privée à forte entropie, non publiée côté client.
          schema: { type: string, minLength: 32 }
      responses:
        '200':
          description: État du lien ou du paiement associé
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ExternalPaymentStatus'
                  - $ref: '#/components/schemas/ExternalPaymentWaiting'
                  - $ref: '#/components/schemas/ExternalPaymentExpired'
        '404':
          description: Lien introuvable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Clé secrète du compte Sandbox ou Live
  parameters:
    ContentType:
      name: Content-Type
      in: header
      required: true
      description: Requis par l'authentification Rails, y compris sur les requêtes GET.
      schema: { type: string, const: application/json }
    TargetEnvironment:
      name: Ipay-Target-Environment
      in: header
      required: true
      schema:
        type: string
        enum: [sandbox, live]
    PaymentType:
      name: Ipay-Payment-Type
      in: header
      required: true
      schema: { type: string, example: mobile }
  schemas:
    PaymentRequest:
      type: object
      required: [amount, country, currency, msisdn, transaction_id, customer_name]
      properties:
        amount: { type: integer, minimum: 1, example: 1000 }
        country: { type: string, enum: [BJ, NE] }
        currency: { type: string, const: XOF }
        msisdn: { type: string, example: '40410000000' }
        transaction_id: { type: string, description: Référence unique dans le compte }
        customer_name: { type: string }
        display_currency: { type: string, description: Utilisée par certains parcours carte }
    PaymentCreateResponse:
      type: object
      required: [status, reference, public_reference]
      properties:
        status: { type: string, example: succeeded }
        reference: { type: string }
        public_reference: { type: string }
        meta_data: { type: object, additionalProperties: true }
    PaymentStatus:
      type: object
      required: [public_reference, external_reference, reference, status, msisdn, amount]
      properties:
        public_reference: { type: string }
        external_reference: { type: string }
        reference: { type: string }
        status:
          type: string
          enum: [initiated, pending, succeeded, failed, refunded]
        msisdn: { type: string }
        amount: { type: integer }
        validated_at: { type: [string, 'null'], format: date-time }
    PaymentListItem:
      type: object
      properties:
        id: { type: integer }
        reference: { type: string }
        status: { type: string, example: Succeeded }
        amount: { type: integer }
        fees: { type: integer }
        payment_method: { type: string, example: Airtel Ne }
        net_amount: { type: integer }
        platform: { type: string }
        device: { type: string }
        failure_reason: { type: [string, 'null'] }
        country: { type: string }
        currency: { type: string }
        external_reference: { type: string }
        msisdn: { type: string }
        customer_name: { type: string }
        created_at: { type: string, format: date-time }
        validated_at: { type: [string, 'null'], format: date-time }
    ExternalPaymentRequest:
      type: object
      required: [title]
      properties:
        title: { type: string }
        description: { type: string }
        amount: { type: [integer, 'null'], minimum: 50 }
        reference:
          type: string
          minLength: 32
          description: Omettre pour génération automatique, ou fournir une valeur aléatoire privée d'au moins 128 bits.
        shouldExpire:
          type: boolean
          const: true
          default: true
          description: La v1 actuelle crée toujours un lien expirant.
        on_success_redirection_url: { type: string, format: uri, pattern: '^https://' }
        on_failed_redirection_url: { type: string, format: uri, pattern: '^https://' }
    ExternalPaymentCreateResponse:
      type: object
      properties:
        title: { type: string }
        amount: { type: [integer, 'null'] }
        reference: { type: string }
        status: { type: string, example: initiated }
        should_expire: { type: boolean }
        page_url: { type: string, format: uri }
    ExternalPaymentStatus:
      type: object
      properties:
        reference: { type: string }
        amount: { type: integer }
        country: { type: string }
        currency: { type: string }
        transaction_id: { type: string }
        msisdn: { type: string }
        customer_name: { type: string }
        status: { type: string, enum: [succeeded, failed] }
        created_at: { type: string, format: date-time }
    ExternalPaymentWaiting:
      type: object
      properties:
        message: { type: string }
        status: { type: string, const: initiated }
        reference: { type: string }
        page_url: { type: string, format: uri }
    ExternalPaymentExpired:
      type: object
      properties:
        message: { type: string }
        has_expire: { type: boolean, const: true }
        status: { type: string, const: cancelled }
        reference: { type: string }
    Error:
      type: object
      properties:
        message: { type: string }
    ValidationError:
      type: object
      properties:
        errors:
          type: array
          items: { type: string }
  responses:
    BadRequest:
      description: En-tête, paramètre ou valeur invalide
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: Clé invalide
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: Compte ou environnement indisponible
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotAcceptable:
      description: Content-Type différent de application/json
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
