{
  "openapi": "3.1.0",
  "info": {
    "title": "Faymaco API",
    "version": "1.0.0",
    "description": "Collect payments from your customers in West Africa via WhatsApp (Wave / Orange Money): recurring subscriptions and one-off payment requests. Faymaco sends the payment request, reminds on unpaid, collects, and notifies you with signed webhooks. Human docs: https://docs.fayma.co — LLM guide: https://docs.fayma.co/llms.txt",
    "contact": {
      "name": "Faymaco",
      "url": "https://fayma.co"
    }
  },
  "servers": [
    {
      "url": "https://apifayko.peelo.chat/api/v1",
      "description": "Production (use fk_live_ keys)"
    },
    {
      "url": "https://playground.fayma.co/api/v1",
      "description": "Test (use fk_test_ keys)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Subscriptions",
      "description": "Recurring subscription lifecycle"
    },
    {
      "name": "Payment Requests",
      "description": "One-off payment requests (invoice, order, deposit)"
    }
  ],
  "paths": {
    "/subscriptions": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Create a subscription",
        "description": "Creates a recurring subscription for a customer. Idempotent via the optional Idempotency-Key header.",
        "operationId": "createSubscription",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "example": "order-12345"
            },
            "description": "STRONGLY RECOMMENDED — always send this. Client-generated unique value per logical operation (e.g. your order id or a UUID), reused across retries. Retrying with the same key replays the original response instead of creating a duplicate. TTL 24h. This is currently the only safeguard against duplicate subscriptions: without it, a double-submit creates two active subscriptions and double-bills the customer."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSubscriptionRequest"
              },
              "example": {
                "customer": {
                  "name": "Awa Diop",
                  "phone": "+221770000000"
                },
                "amount": 5000,
                "currency": "XOF",
                "frequency": "monthly",
                "webhooks": {
                  "onSuccess": "https://your-app.com/webhooks/faymaco",
                  "onExpired": "https://your-app.com/webhooks/faymaco"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscription created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "List subscriptions",
        "operationId": "listSubscriptions",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paused",
                "cancelled"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Pagination cursor from data.pagination.nextCursor."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "subscriptions": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Subscription"
                          }
                        },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "nextCursor": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/subscriptions/{id}": {
      "get": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Retrieve a subscription",
        "operationId": "getSubscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The subscription",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/subscriptions/{id}/pause": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Pause a subscription",
        "operationId": "pauseSubscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/subscriptions/{id}/resume": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Resume a subscription",
        "operationId": "resumeSubscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resumed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/subscriptions/{id}/cancel": {
      "post": {
        "tags": [
          "Subscriptions"
        ],
        "summary": "Cancel a subscription",
        "description": "Definitive. Stops ongoing reminders.",
        "operationId": "cancelSubscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/payment-requests": {
      "post": {
        "tags": [
          "Payment Requests"
        ],
        "summary": "Create a one-off payment request",
        "description": "Creates a one-off payment request (invoice, order, deposit). Faymaco sends the WhatsApp payment request at dueDate (default: now, within a minute), reminds on the exact dates provided, collects via Wave / Orange Money, and fires the payment_request.succeeded webhook. Idempotent via the Idempotency-Key header. One active request per (phone, calendar month) — duplicates return 409 DUPLICATE_REQUEST. One-off checkout: omit reminders (nothing is ever resent), pass expiresIn so an abandoned request auto-expires instead of lingering, and replaceExisting: true so a retry supersedes the previous link.",
        "operationId": "createPaymentRequest",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "example": "order-4821"
            },
            "description": "STRONGLY RECOMMENDED — always send this. Client-generated unique value per logical operation, reused across retries. Retrying with the same key replays the original response instead of creating a duplicate. TTL 24h."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequestRequest"
              },
              "example": {
                "customer": {
                  "name": "Awa Diop",
                  "phone": "+221770000000"
                },
                "amount": 25000,
                "dueDate": "2026-08-20T09:00:00Z",
                "reminders": [
                  "2026-08-22T09:00:00Z",
                  "2026-08-25T09:00:00Z"
                ],
                "webhooks": {
                  "onSuccess": "https://your-app.com/webhooks/faymaco"
                },
                "externalRef": "order-4821"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment request created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequestCreateResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "get": {
        "tags": [
          "Payment Requests"
        ],
        "summary": "List payment requests",
        "description": "Lists the account's one-off payment requests (dashboard-created ones included; filter with source=api). Subscriptions never appear here.",
        "operationId": "listPaymentRequests",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paid",
                "overdue",
                "cancelled",
                "expired"
              ]
            },
            "description": "status=expired lists the abandoned checkouts."
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "api",
                "dashboard"
              ]
            },
            "description": "api → only requests created through the API."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Pagination cursor from data.pagination.nextCursor."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payment requests",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "paymentRequests": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PaymentRequest"
                          }
                        },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "nextCursor": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/payment-requests/{id}": {
      "get": {
        "tags": [
          "Payment Requests"
        ],
        "summary": "Retrieve a payment request",
        "operationId": "getPaymentRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payment request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequestResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/payment-requests/{id}/cancel": {
      "post": {
        "tags": [
          "Payment Requests"
        ],
        "summary": "Cancel a payment request",
        "description": "Sets the request to cancelled and stops the pending send/reminders. A request already paid, cancelled or expired cannot be cancelled (400 INVALID_STATE). This is the immediate counterpart to expiresIn: cancel on demand, expire on a timer.",
        "operationId": "cancelPaymentRequest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentRequestResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "webhooks": {
    "subscription.payment.succeeded": {
      "post": {
        "summary": "A subscription cycle was paid",
        "description": "POSTed to webhooks.onSuccess. Headers: X-Faymaco-Event, X-Faymaco-Timestamp (unix s), X-Faymaco-Signature (t=<ts>,v1=<hmac>). Verify v1 = HMAC_SHA256(accountSecret, \"<ts>.<rawBody>\").",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx, quickly."
          }
        }
      }
    },
    "subscription.payment.failed": {
      "post": {
        "summary": "A due date passed unpaid (~10 days)",
        "description": "POSTed to webhooks.onExpired. Same headers/signature as succeeded.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx, quickly."
          }
        }
      }
    },
    "payment_request.succeeded": {
      "post": {
        "summary": "A one-off payment request was paid",
        "description": "POSTed to the request's webhooks.onSuccess. Same headers/signature as subscription events. data.source is 'platform' (paid via WhatsApp / Wave / Orange Money) or 'manual' (merchant marked it paid from the dashboard).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentRequestWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledge with any 2xx, quickly."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key as Bearer token: `Authorization: Bearer fk_live_...` (prod) or `fk_test_...` (test). Requires a Pro+ plan."
      }
    },
    "responses": {
      "Error": {
        "description": "Error response",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Customer": {
        "type": "object",
        "required": [
          "name",
          "phone"
        ],
        "properties": {
          "name": {
            "type": "string",
            "example": "Awa Diop"
          },
          "phone": {
            "type": "string",
            "description": "International format",
            "example": "+221770000000"
          }
        }
      },
      "Pricing": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "number",
            "example": 5000
          },
          "currency": {
            "type": "string",
            "default": "XOF",
            "example": "XOF"
          },
          "frequency": {
            "type": "string",
            "enum": [
              "monthly",
              "quarterly",
              "semi_annual",
              "annual"
            ]
          }
        }
      },
      "CreateSubscriptionRequest": {
        "type": "object",
        "required": [
          "customer",
          "amount",
          "frequency"
        ],
        "properties": {
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "amount": {
            "type": "number",
            "description": "Amount charged per cycle.",
            "example": 5000
          },
          "frequency": {
            "type": "string",
            "enum": [
              "monthly",
              "quarterly",
              "semi_annual",
              "annual"
            ]
          },
          "currency": {
            "type": "string",
            "default": "XOF"
          },
          "startDate": {
            "type": "string",
            "format": "date-time",
            "description": "Date of the 1st cycle. Default: now."
          },
          "startNextMonth": {
            "type": "boolean",
            "description": "true → 1st cycle on the 1st of next month."
          },
          "webhooks": {
            "type": "object",
            "properties": {
              "onSuccess": {
                "type": "string",
                "format": "uri",
                "description": "Called on every successful payment."
              },
              "onExpired": {
                "type": "string",
                "format": "uri",
                "description": "Called when a due date passes unpaid."
              }
            }
          }
        }
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "6a33..."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "cancelled"
            ],
            "example": "active"
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "pricing": {
            "$ref": "#/components/schemas/Pricing"
          },
          "nextDueDate": {
            "type": "string",
            "format": "date-time",
            "example": "2026-07-01T00:00:00Z"
          },
          "cycleCount": {
            "type": "integer",
            "example": 0
          }
        }
      },
      "SubscriptionResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "properties": {
              "subscription": {
                "$ref": "#/components/schemas/Subscription"
              }
            }
          }
        }
      },
      "CreatePaymentRequestRequest": {
        "type": "object",
        "required": [
          "customer",
          "amount"
        ],
        "properties": {
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "amount": {
            "type": "number",
            "description": "Amount requested (> 0).",
            "example": 25000
          },
          "currency": {
            "type": "string",
            "default": "XOF",
            "description": "Only XOF is supported."
          },
          "dueDate": {
            "type": "string",
            "format": "date-time",
            "description": "When the WhatsApp request is sent. Default: now (sent within a minute)."
          },
          "reminders": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date-time"
            },
            "description": "EXACT reminder dates, each strictly after dueDate and before expiresAt. OMIT THIS FIELD for a one-off checkout: no reminder is ever sent (default behaviour). Plan cap: Pro 3 · Max 5 · Enterprise 10."
          },
          "webhooks": {
            "type": "object",
            "properties": {
              "onSuccess": {
                "type": "string",
                "format": "uri",
                "description": "Called when the request is paid (payment_request.succeeded)."
              },
              "onExpired": {
                "type": "string",
                "format": "uri",
                "description": "Called when the request expires unpaid (payment_request.expired). Requires expiresIn or expiresAt. Defaults to the onSuccess URL when omitted — the event name tells them apart."
              }
            }
          },
          "externalRef": {
            "type": "string",
            "description": "Your internal id, echoed back in the webhook.",
            "example": "order-4821"
          },
          "expiresIn": {
            "type": "integer",
            "minimum": 60,
            "maximum": 7776000,
            "description": "Checkout validity, in seconds counted from dueDate (60s min, 90 days max). Unpaid at that point, the request auto-closes: status becomes 'expired', pending sends are cancelled, it leaves the customer's WhatsApp list, and payment_request.expired fires. Mutually exclusive with expiresAt. Omit both to keep the request open indefinitely.",
            "example": 1800
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Absolute expiry date, alternative to expiresIn (same 60s / 90 days window after dueDate)."
          },
          "replaceExisting": {
            "type": "boolean",
            "default": false,
            "description": "true → instead of returning 409 DUPLICATE_REQUEST, cancel the open request already held by this phone number this month and create the new one. Ids of the cancelled requests come back in data.replaced. Requests created with expiresIn/expiresAt never trigger the duplicate check in the first place — a customer may check out several times a month."
          }
        }
      },
      "PaymentRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "6a7b..."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paid",
              "overdue",
              "cancelled",
              "expired"
            ],
            "example": "active",
            "description": "expired = checkout validity elapsed without payment (abandoned cart). cancelled = closed on purpose, by you or by replaceExisting."
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "amount": {
            "type": "number",
            "example": 25000
          },
          "currency": {
            "type": "string",
            "example": "XOF"
          },
          "amountToPay": {
            "type": "number",
            "description": "What the customer actually pays (= amount + fees when fees.mode is customer_pays).",
            "example": 25000
          },
          "fees": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "merchant_absorbs",
                  "customer_pays"
                ]
              },
              "provider": {
                "type": "string"
              },
              "originalAmount": {
                "type": "number"
              },
              "totalAmount": {
                "type": "number"
              },
              "feeAmount": {
                "type": "number"
              }
            }
          },
          "dueDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "paymentMethod": {
            "type": [
              "string",
              "null"
            ]
          },
          "reminders": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date-time"
            }
          },
          "remindersSent": {
            "type": "integer",
            "example": 0
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "dashboard"
            ]
          },
          "externalRef": {
            "type": [
              "string",
              "null"
            ],
            "example": "order-4821"
          },
          "webhooks": {
            "type": "object",
            "properties": {
              "onSuccess": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "onExpired": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Checkout validity deadline, null for an open-ended request."
          },
          "expiredAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the request actually expired, null while it has not."
          }
        }
      },
      "PaymentRequestResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "properties": {
              "paymentRequest": {
                "$ref": "#/components/schemas/PaymentRequest"
              }
            }
          }
        }
      },
      "PaymentRequestCreateResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": true
          },
          "data": {
            "type": "object",
            "properties": {
              "paymentRequest": {
                "$ref": "#/components/schemas/PaymentRequest"
              },
              "scheduled": {
                "type": "array",
                "description": "The WhatsApp sends scheduled by this creation.",
                "items": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "enum": [
                        "send_payment_request",
                        "send_reminder",
                        "expire_payment_request"
                      ]
                    },
                    "scheduledAt": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              },
              "replaced": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Ids of the requests cancelled by replaceExisting. Empty array when nothing was replaced."
              }
            }
          }
        }
      },
      "PaymentRequestWebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_xxx"
          },
          "event": {
            "type": "string",
            "const": "payment_request.succeeded"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "properties": {
              "paymentRequestId": {
                "type": "string"
              },
              "externalRef": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "order-4821"
              },
              "paidAt": {
                "type": "string",
                "format": "date-time"
              },
              "source": {
                "type": "string",
                "enum": [
                  "platform",
                  "manual"
                ],
                "description": "platform = paid via WhatsApp (Wave / Orange Money); manual = merchant marked it paid from the dashboard."
              },
              "customer": {
                "$ref": "#/components/schemas/Customer"
              },
              "amount": {
                "type": "number"
              },
              "amountPaid": {
                "type": "number",
                "description": "What the customer actually paid (includes grossed-up fees when applicable)."
              },
              "currency": {
                "type": "string",
                "example": "XOF"
              }
            }
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_xxx"
          },
          "event": {
            "type": "string",
            "enum": [
              "subscription.payment.succeeded",
              "subscription.payment.failed"
            ]
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "properties": {
              "subscriptionId": {
                "type": "string"
              },
              "cycleNumber": {
                "type": "integer"
              },
              "paidAt": {
                "type": "string",
                "format": "date-time"
              },
              "customer": {
                "$ref": "#/components/schemas/Customer"
              },
              "pricing": {
                "$ref": "#/components/schemas/Pricing"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "NO_API_KEY",
                  "INVALID_API_KEY",
                  "FEATURE_NOT_AVAILABLE",
                  "QUOTA_EXCEEDED",
                  "ACCOUNT_SUSPENDED",
                  "VALIDATION_ERROR",
                  "INVALID_STATE",
                  "INVALID_DATE",
                  "UNSUPPORTED_CURRENCY",
                  "REMINDER_LIMIT_EXCEEDED",
                  "SUBSCRIPTION_NOT_FOUND",
                  "PAYMENT_REQUEST_NOT_FOUND",
                  "DUPLICATE_REQUEST",
                  "IDEMPOTENCY_IN_PROGRESS",
                  "RATE_LIMITED"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "description": "Optional. e.g. QUOTA_EXCEEDED → { used, limit, plan } · DUPLICATE_REQUEST → { existingPaymentRequestId }."
              }
            }
          }
        }
      },
      "PaymentRequestExpiredWebhookEvent": {
        "type": "object",
        "description": "Sent when a checkout expires unpaid. Use it to release the order/seat you reserved.",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_xxx"
          },
          "event": {
            "type": "string",
            "const": "payment_request.expired"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "properties": {
              "paymentRequestId": {
                "type": "string"
              },
              "externalRef": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "order-4821"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time"
              },
              "expiredAt": {
                "type": "string",
                "format": "date-time"
              },
              "customer": {
                "$ref": "#/components/schemas/Customer"
              },
              "amount": {
                "type": "number"
              },
              "currency": {
                "type": "string",
                "example": "XOF"
              }
            }
          }
        }
      }
    }
  }
}
