{
  "openapi": "3.1.0",
  "info": {
    "title": "FluxxPay Collect API",
    "version": "1.0.0",
    "description": "Encaissement Mobile Money (Bénin, Togo, Côte d'Ivoire) via une façade API unique.",
    "contact": {
      "email": "contact@fluxpay.com"
    }
  },
  "servers": [
    {
      "url": "https://api.fluxxpay.me/api/v1",
      "description": "Production"
    },
    {
      "url": "https://api.fluxxpay.me/api/v1",
      "description": "Production (alias)"
    }
  ],
  "tags": [
    { "name": "Collect", "description": "Mobile Money collect" }
  ],
  "security": [{ "ApiKeyAuth": [] }],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Clé API marchand (UUID). Bearer avec la même valeur est aussi accepté."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": { "type": "string", "maxLength": 128 },
        "description": "Clé d'idempotence pour les POST (recommandée en production)."
      },
      "ReferencePath": {
        "name": "reference",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "maxLength": 100 },
        "description": "Référence marchand de la collecte."
      },
      "SyncQuery": {
        "name": "sync",
        "in": "query",
        "required": false,
        "schema": { "type": "boolean", "default": false },
        "description": "Si true, interroge le PSP lorsque le statut est encore PENDING."
      }
    },
    "schemas": {
      "CollectOperator": {
        "type": "object",
        "required": ["slug", "label", "gateway_code", "requires_otp"],
        "properties": {
          "slug": { "type": "string", "example": "mtn" },
          "label": { "type": "string", "example": "MTN Mobile Money" },
          "gateway_code": { "type": "string", "example": "MTN_BJ" },
          "aliases": {
            "type": "array",
            "items": { "type": "string" }
          },
          "requires_otp": { "type": "boolean", "example": false }
        }
      },
      "CollectCountry": {
        "type": "object",
        "required": ["code", "currency_default", "operators"],
        "properties": {
          "code": { "type": "string", "enum": ["BJ", "CI", "TG"] },
          "currency_default": { "type": "string", "example": "XOF" },
          "operators": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/CollectOperator" }
          },
          "unsupported": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      },
      "OperatorsCatalog": {
        "type": "object",
        "required": ["api_version", "countries"],
        "properties": {
          "api_version": { "type": "string", "example": "2025-01.1" },
          "countries": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/CollectCountry" }
          }
        }
      },
      "CollectMobileMoneyRequest": {
        "type": "object",
        "required": ["amount", "phone", "country", "operator", "reference"],
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 1,
            "description": "Montant en centimes (ex. 10000 = 100 XOF).",
            "example": 10000
          },
          "phone": { "type": "string", "maxLength": 20, "example": "2290166000000" },
          "country": { "type": "string", "enum": ["BJ", "CI", "TG"] },
          "operator": { "type": "string", "maxLength": 32, "example": "mtn" },
          "reference": { "type": "string", "maxLength": 100, "example": "ORDER-2025-001" },
          "currency": { "type": "string", "maxLength": 3, "default": "XOF" },
          "description": { "type": "string", "maxLength": 255 },
          "customer_first_name": { "type": "string", "maxLength": 64 },
          "customer_last_name": { "type": "string", "maxLength": 64 }
        }
      },
      "CollectConfirmRequest": {
        "type": "object",
        "required": ["reference", "otp"],
        "properties": {
          "reference": { "type": "string", "maxLength": 100 },
          "otp": {
            "type": "string",
            "minLength": 4,
            "maxLength": 8,
            "pattern": "^[0-9]+$",
            "example": "123456"
          }
        }
      },
      "CollectStatus": {
        "type": "string",
        "enum": [
          "PENDING",
          "PROCESSING",
          "AWAITING_OTP",
          "COMPLETED",
          "FAILED",
          "CANCELLED"
        ]
      },
      "CollectResponse": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "reference": { "type": "string" },
          "status": { "$ref": "#/components/schemas/CollectStatus" },
          "amount": { "type": "integer" },
          "currency": { "type": "string" },
          "country": { "type": "string" },
          "operator": { "type": "string" },
          "phone": { "type": "string" },
          "psp_reference": { "type": "string" },
          "message": { "type": "string" },
          "environment": { "type": "string", "enum": ["test", "live"] },
          "idempotent_replay": { "type": "boolean" },
          "pending_since_seconds": { "type": "integer" },
          "sandbox": { "type": "boolean" }
        }
      },
      "CollectError": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": { "type": "string" },
          "detail": { "type": "string" }
        }
      },
      "ValidationErrors": {
        "type": "object",
        "additionalProperties": {
          "type": "array",
          "items": { "type": "string" }
        }
      }
    }
  },
  "paths": {
    "/collect/operators/": {
      "get": {
        "operationId": "listCollectOperators",
        "tags": ["Collect"],
        "summary": "Catalogue opérateurs",
        "description": "Liste les pays et opérateurs Mobile Money pris en charge par l'API Collect v1.",
        "responses": {
          "200": {
            "description": "Catalogue",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OperatorsCatalog" }
              }
            }
          },
          "401": {
            "description": "Clé API invalide ou manquante",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectError" }
              }
            }
          }
        }
      }
    },
    "/collect/mobile-money/": {
      "post": {
        "operationId": "createCollectMobileMoney",
        "tags": ["Collect"],
        "summary": "Initier une collecte",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CollectMobileMoneyRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Collecte créée",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectResponse" }
              }
            }
          },
          "200": {
            "description": "Réponse idempotente (replay)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectResponse" }
              }
            }
          },
          "400": {
            "description": "Requête invalide",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    { "$ref": "#/components/schemas/CollectError" },
                    { "$ref": "#/components/schemas/ValidationErrors" }
                  ]
                }
              }
            }
          },
          "401": { "description": "Non autorisé" },
          "403": { "description": "Marchand introuvable pour cette clé" },
          "500": { "description": "Erreur serveur" }
        }
      }
    },
    "/collect/mobile-money/confirm/": {
      "post": {
        "operationId": "confirmCollectMobileMoney",
        "tags": ["Collect"],
        "summary": "Confirmer OTP (Coris)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CollectConfirmRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Collecte confirmée",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectResponse" }
              }
            }
          },
          "400": {
            "description": "OTP invalide ou collecte non éligible",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectError" }
              }
            }
          },
          "404": {
            "description": "Collecte introuvable",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectError" }
              }
            }
          }
        }
      }
    },
    "/collect/mobile-money/{reference}/": {
      "get": {
        "operationId": "getCollectMobileMoneyStatus",
        "tags": ["Collect"],
        "summary": "Statut d'une collecte",
        "parameters": [
          { "$ref": "#/components/parameters/ReferencePath" },
          { "$ref": "#/components/parameters/SyncQuery" }
        ],
        "responses": {
          "200": {
            "description": "Statut actuel",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectResponse" }
              }
            }
          },
          "404": {
            "description": "Collecte introuvable",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CollectError" }
              }
            }
          }
        }
      }
    }
  }
}
