{
  "openapi": "3.1.0",
  "info": {
    "title": "Lango API",
    "version": "2026-09-12",
    "description": "The Lango payment gateway API. All amounts are integers in minor units (USD 100.00 = 10000). Authenticate with a Bearer API key: `sk_test_...` keys hit the sandbox and can never move real money; `sk_live_...` keys are issued after verification. Errors always arrive in one envelope with a human-readable message and a request_id you can quote to support.",
    "contact": {
      "email": "support@lango.co.zw",
      "url": "https://docs.lango.co.zw"
    }
  },
  "servers": [
    {
      "url": "https://api.lango.co.zw",
      "description": "Production and sandbox (environment is selected by the key)"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Payment methods",
      "description": "Discover the payment methods that are live right now. The first call an integration makes."
    },
    {
      "name": "Payments",
      "description": "Create and read payments"
    },
    {
      "name": "Refunds",
      "description": "Return money to customers"
    },
    {
      "name": "Preview",
      "description": "Quote fees with the same engine that charges"
    },
    {
      "name": "Balance",
      "description": "What Lango owes you, per currency"
    },
    {
      "name": "Payment links",
      "description": "Checkouts you can send or print"
    },
    {
      "name": "Events",
      "description": "The event log behind your webhooks"
    },
    {
      "name": "Testing",
      "description": "Sandbox-only helpers"
    },
    {
      "name": "Reports",
      "description": "Pull period analytics into your own systems."
    }
  ],
  "paths": {
    "/v1/payments": {
      "post": {
        "tags": ["Payments"],
        "operationId": "createPayment",
        "summary": "Create a payment",
        "description": "Requires an `Idempotency-Key` header. Include `customer.msisdn` to send the PIN prompt immediately (direct charge); omit it for hosted checkout and redirect the customer to `checkout_url`. Retrying with the same key replays the original response â€” it never charges twice.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentRequest"
              },
              "example": {
                "amount": 10000,
                "currency": "USD",
                "method": "ecocash",
                "reference": "ORD-4417",
                "customer": {
                  "msisdn": "263771234567"
                },
                "return_url": "https://yourstore.co.zw/thanks",
                "metadata": {
                  "order_id": "4417"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payment, with every fee itemised.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "409": {
            "description": "Idempotency-Key conflict (`idempotency_key_reused`, `request_in_progress`) or `reference_in_use`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": ["Payments"],
        "operationId": "listPayments",
        "summary": "List payments",
        "description": "Newest first. Page with `starting_after` set to the last id of the previous page.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "A payment id; results strictly after it"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": ["data", "has_more"]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      }
    },
    "/v1/payments/{id}": {
      "get": {
        "tags": ["Payments"],
        "operationId": "getPayment",
        "summary": "Retrieve a payment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "pay_01J8XQ7M4K"
          }
        ],
        "responses": {
          "200": {
            "description": "The payment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/payments/{id}/refunds": {
      "post": {
        "tags": ["Refunds"],
        "operationId": "createRefund",
        "summary": "Refund a payment",
        "description": "Omit `amount` for a full refund of what remains refundable. Partial refunds are allowed up to the remaining amount.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "Minor units. Omit for a full refund."
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The refund.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refund"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/refunds/{id}": {
      "get": {
        "tags": ["Refunds"],
        "operationId": "getRefund",
        "summary": "Retrieve a refund",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "ref_01J8XQ7M4K"
          }
        ],
        "responses": {
          "200": {
            "description": "The refund.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Refund"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/preview": {
      "post": {
        "tags": ["Preview"],
        "operationId": "preview",
        "summary": "Preview fees (no money moves)",
        "description": "Computed by the same engine that charges, with your merchant's actual fee configuration. Pass `amount` for the collection side, `payout_amount` for the payout side, or both.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreviewRequest"
              },
              "example": {
                "amount": 10000,
                "currency": "USD",
                "method": "ecocash",
                "payout_amount": 500000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreviewResponse"
                },
                "example": {
                  "customer_pays": 10300,
                  "merchant_receives": 10000,
                  "commission": 300,
                  "payout": {
                    "gross": 500000,
                    "imtt": 9804,
                    "transfer_fee": 200,
                    "net": 489996,
                    "effective_rate": "2.00%"
                  },
                  "warnings": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          }
        }
      }
    },
    "/v1/pricing/preview": {
      "post": {
        "tags": ["Preview"],
        "operationId": "publicPricingPreview",
        "summary": "Public pricing preview (no authentication)",
        "description": "The same engine, quoting the platform's standard published rates. Powers the pricing calculator on lango.co.zw.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreviewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The quote at standard rates.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreviewResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          }
        }
      }
    },
    "/v1/balance": {
      "get": {
        "tags": ["Balance"],
        "operationId": "getBalance",
        "summary": "Retrieve your balance",
        "description": "Per currency, never summed. Requires the `settlements:read` scope.",
        "responses": {
          "200": {
            "description": "Available balance per currency.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "available": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "currency": {
                            "$ref": "#/components/schemas/Currency"
                          },
                          "amount": {
                            "type": "integer",
                            "description": "Minor units"
                          }
                        },
                        "required": ["currency", "amount"]
                      }
                    }
                  },
                  "required": ["available"]
                },
                "example": {
                  "available": [
                    {
                      "currency": "USD",
                      "amount": 125000
                    },
                    {
                      "currency": "ZWG",
                      "amount": 3120400
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/settlements": {
      "get": {
        "tags": ["Settlements"],
        "operationId": "listSettlements",
        "summary": "List payout history",
        "description": "Every executed payout, newest first. Requires `settlements:read`.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Settlement id to page after."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of settlements.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Settlement"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": ["data", "has_more"]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/settlements/next": {
      "get": {
        "tags": ["Settlements"],
        "operationId": "getNextSettlement",
        "summary": "Forecast the next payout",
        "description": "Accrued balance and projected deductions per currency, computed live by the same engine that will take them. Requires `settlements:read`.",
        "responses": {
          "200": {
            "description": "The forecast, per currency, never summed.",
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "currency": "USD",
                      "accrued": 700000,
                      "threshold": 50000,
                      "cycle_days": 7,
                      "below_minimum": false,
                      "deductions": [
                        {
                          "type": "imtt",
                          "amount": 13725
                        },
                        {
                          "type": "transfer_fee",
                          "amount": 200
                        }
                      ],
                      "projected_net": 686075
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/settlements/{id}": {
      "get": {
        "tags": ["Settlements"],
        "operationId": "getSettlement",
        "summary": "Retrieve a settlement advice",
        "description": "The full fee breakdown â€” every deduction named with its rate. Requires `settlements:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The settlement advice."
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/settlements/{id}/export.csv": {
      "get": {
        "tags": ["Settlements"],
        "operationId": "exportSettlementCsv",
        "summary": "Reconciliation export",
        "description": "CSV matching the advice, followed by one row per payment in the payout. Hand it to your accountant's software. Requires `settlements:read`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CSV.",
            "content": {
              "text/csv": {}
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/reports/summary": {
      "get": {
        "tags": ["Reports"],
        "operationId": "getReportSummary",
        "summary": "Period reporting summary",
        "description": "The same analytics the dashboard draws, key-authenticated for your own systems: a time-series plus status and method breakdowns and per-currency totals over a date range. Aggregates only — never individual customer data. Requires the `payments:read` scope.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start date (inclusive), YYYY-MM-DD. Defaults to 30 days ago.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End date (inclusive), YYYY-MM-DD. Defaults to today.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "bucket",
            "in": "query",
            "required": false,
            "description": "Time-series granularity.",
            "schema": {
              "type": "string",
              "enum": ["day", "week", "month"],
              "default": "day"
            }
          },
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "description": "Restrict to a single currency. Omit to return every currency, side by side.",
            "schema": {
              "$ref": "#/components/schemas/Currency"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The period summary. Series and totals are keyed by currency and never summed across currencies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "from": {
                      "type": "string",
                      "format": "date"
                    },
                    "to": {
                      "type": "string",
                      "format": "date"
                    },
                    "bucket": {
                      "type": "string",
                      "enum": ["day", "week", "month"]
                    },
                    "buckets": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "The ordered bucket labels (YYYY-MM-DD) the series aligns to."
                    },
                    "currencies": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Currency"
                      }
                    },
                    "series": {
                      "type": "object",
                      "description": "Per currency: parallel arrays aligned to `buckets` (collected_minor, count, succeeded, failed, net_minor, commission_minor, vat_minor, provider_fee_minor)."
                    },
                    "totals": {
                      "type": "object",
                      "description": "Per currency: summed totals for the whole window, plus success_rate."
                    },
                    "by_status": {
                      "type": "object",
                      "description": "Per currency: count by payment status."
                    },
                    "by_method": {
                      "type": "object",
                      "description": "Per currency: count by payment method."
                    }
                  },
                  "required": [
                    "from",
                    "to",
                    "bucket",
                    "buckets",
                    "currencies",
                    "series",
                    "totals",
                    "by_status",
                    "by_method"
                  ]
                },
                "example": {
                  "from": "2026-08-16",
                  "to": "2026-09-14",
                  "bucket": "day",
                  "buckets": ["2026-09-13", "2026-09-14"],
                  "currencies": ["USD"],
                  "series": {
                    "USD": {
                      "collected_minor": [120000, 84000],
                      "count": [3, 2],
                      "succeeded": [3, 2],
                      "failed": [0, 0],
                      "net_minor": [116400, 81480],
                      "commission_minor": [3000, 2100],
                      "vat_minor": [450, 315],
                      "provider_fee_minor": [150, 105]
                    }
                  },
                  "totals": {
                    "USD": {
                      "collected_minor": 204000,
                      "count": 5,
                      "succeeded": 5,
                      "failed": 0,
                      "success_rate": 1,
                      "net_minor": 197880,
                      "commission_minor": 5100,
                      "vat_minor": 765,
                      "provider_fee_minor": 255
                    }
                  },
                  "by_status": {
                    "USD": {
                      "completed": 5
                    }
                  },
                  "by_method": {
                    "USD": {
                      "ecocash": 5
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/v1/customers/{msisdn}/payments": {
      "get": {
        "tags": ["Customers"],
        "operationId": "getCustomerPayments",
        "summary": "Has this person paid?",
        "description": "The whole relationship with one customer number: per-currency totals and payment history. Lookups run on a keyed hash â€” the clear number never touches a query. Requires `payments:read`.",
        "parameters": [
          {
            "name": "msisdn",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Any human format; normalised to 2637XXXXXXXX."
          }
        ],
        "responses": {
          "200": {
            "description": "Masked number, totals and payments.",
            "content": {
              "application/json": {
                "example": {
                  "customer_msisdn_masked": "077â€¢â€¢â€¢4567",
                  "totals": [
                    {
                      "currency": "USD",
                      "paid_minor": 30000,
                      "payments": 3
                    }
                  ],
                  "payments": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          }
        }
      }
    },
    "/v1/payment_links": {
      "post": {
        "tags": ["Payment links"],
        "operationId": "createPaymentLink",
        "summary": "Create a payment link",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentLinkRequest"
              },
              "example": {
                "amount": 10000,
                "currency": "USD",
                "reference": "INV-091",
                "description": "September invoice",
                "single_use": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link. Send `url` to the customer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentLink"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          }
        }
      },
      "get": {
        "tags": ["Payment links"],
        "operationId": "listPaymentLinks",
        "summary": "List payment links",
        "responses": {
          "200": {
            "description": "Your most recent links.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentLink"
                      }
                    }
                  },
                  "required": ["data"]
                }
              }
            }
          }
        }
      }
    },
    "/v1/payment_links/bulk": {
      "post": {
        "tags": ["Payment links"],
        "operationId": "createPaymentLinksBulk",
        "summary": "Create up to 100 links in one call",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "links": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "$ref": "#/components/schemas/CreatePaymentLinkRequest"
                    }
                  }
                },
                "required": ["links"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created links, in request order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentLink"
                      }
                    }
                  },
                  "required": ["data"]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          }
        }
      }
    },
    "/v1/events": {
      "get": {
        "tags": ["Events"],
        "operationId": "listEvents",
        "summary": "List events",
        "description": "Every event Lango generated for you â€” the log behind your webhooks.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  },
                  "required": ["data", "has_more"]
                }
              }
            }
          }
        }
      }
    },
    "/v1/events/{id}/replay": {
      "post": {
        "tags": ["Events"],
        "operationId": "replayEvent",
        "summary": "Replay an event to your webhooks",
        "description": "Re-queues delivery of this event to every subscribed endpoint. Use after an outage on your side.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "evt_01J8XQ7M4K"
          }
        ],
        "responses": {
          "200": {
            "description": "How many deliveries were queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deliveries_queued": {
                      "type": "integer"
                    }
                  },
                  "required": ["id", "deliveries_queued"]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/test/simulate": {
      "post": {
        "tags": ["Testing"],
        "operationId": "simulate",
        "summary": "Force a payment outcome (sandbox only)",
        "description": "Test keys only. Advances a payment to an outcome immediately instead of waiting for the resolver's schedule.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "payment_id": {
                    "type": "string"
                  },
                  "outcome": {
                    "type": "string",
                    "enum": ["completed", "failed"]
                  }
                },
                "required": ["payment_id", "outcome"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payment id and its status after the transition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "$ref": "#/components/schemas/PaymentStatus"
                    }
                  },
                  "required": ["id", "status"]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/payment-methods": {
      "get": {
        "tags": ["Payment methods"],
        "summary": "List available payment methods",
        "description": "The first call an integration makes. Returns the payment methods that are live right now (active-only by default), each with its flow, the fields to collect, the currencies it supports, and a logo URL. Load these from the API rather than hard-coding them, so methods can be added or retired without any change to your integration. Public — no API key.",
        "operationId": "listPaymentMethods",
        "security": [],
        "parameters": [
          {
            "name": "currency",
            "in": "query",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/Currency"
            },
            "description": "Only methods supporting this currency."
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": ["all"]
            },
            "description": "`all` returns the full catalogue including coming-soon methods, each flagged by `status` and `available`."
          }
        ],
        "responses": {
          "200": {
            "description": "The available payment methods.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "environment": {
                      "type": "string",
                      "example": "production"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentMethod"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/payment-methods/{method}/logo.svg": {
      "get": {
        "tags": ["Payment methods"],
        "summary": "Payment method logo",
        "description": "The method's logo as an SVG image, for rendering its icon.",
        "operationId": "paymentMethodLogo",
        "security": [],
        "parameters": [
          {
            "name": "method",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "ecocash"
          }
        ],
        "responses": {
          "200": {
            "description": "SVG image.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Unknown method.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "payment.succeeded": {
      "post": {
        "summary": "The provider confirmed the money moved",
        "description": "Every delivery carries `Lango-Signature: t=<unix>,v1=<hex>` where v1 = HMAC-SHA256(secret, \"{t}.{raw_body}\"). Verify with a 300-second tolerance and constant-time comparison. Respond 2xx within 10 seconds; retries at 1m, 5m, 30m, 2h, 6h, 12h for 24 hours.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "payment.failed": {
      "post": {
        "summary": "The provider definitively declined",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "payment.expired": {
      "post": {
        "summary": "The customer never acted and the window closed",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "payment.unresolved": {
      "post": {
        "summary": "24 hours of unknown; a human is investigating",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "refund.succeeded": {
      "post": {
        "summary": "A refund completed",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "refund.failed": {
      "post": {
        "summary": "A refund could not be completed",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "settlement.paid": {
      "post": {
        "summary": "A settlement batch paid out to your bank",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acknowledged."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key: `Authorization: Bearer sk_test_...`. Keys are scoped (payments:read, payments:write, refunds:write, settlements:read) and environment-bound."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "description": "Uniquely identifies this attempt (your order id is usually perfect). Same key + same body replays the original response; same key + different body returns 409."
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "The request is invalid; the message says what to fix and `param` names the field.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthenticated": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Valid key, missing scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such object for this merchant.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "Currency": {
        "type": "string",
        "enum": ["USD", "ZWG"],
        "description": "Amounts never mix currencies."
      },
      "PaymentStatus": {
        "type": "string",
        "enum": [
          "created",
          "initiated",
          "awaiting_customer",
          "awaiting_otp",
          "awaiting_code_use",
          "completed",
          "failed",
          "expired",
          "unknown",
          "unresolved",
          "settled",
          "refunded"
        ],
        "description": "Fulfil on completed/settled only. Treat `unknown` as \"still confirming\", never as failed."
      },
      "Fee": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "example": "commission"
          },
          "amount": {
            "type": "integer",
            "description": "Minor units"
          },
          "bearer": {
            "type": "string",
            "enum": ["customer", "merchant", "split", "none"]
          }
        },
        "required": ["type", "amount", "bearer"]
      },
      "Payment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "pay_01J8XQ7M4K"
          },
          "status": {
            "$ref": "#/components/schemas/PaymentStatus"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "amount": {
            "type": "integer",
            "description": "The order amount, minor units"
          },
          "charged_amount": {
            "type": "integer",
            "description": "What the customer pays (order + customer-borne fees)"
          },
          "merchant_net": {
            "type": "integer",
            "description": "What lands in your balance. charged_amount âˆ’ merchant_net = commission, exactly."
          },
          "fees": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Fee"
            }
          },
          "reference": {
            "type": "string",
            "description": "Your reference; unique per merchant"
          },
          "checkout_token": {
            "type": ["string", "null"]
          },
          "checkout_url": {
            "type": ["string", "null"],
            "description": "Send the customer here for hosted checkout"
          },
          "customer_msisdn_masked": {
            "type": ["string", "null"],
            "example": "077â€¢â€¢â€¢4567"
          },
          "expires_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "method": {
            "type": "string",
            "description": "Payment method used, e.g. ecocash, onemoney.",
            "example": "ecocash"
          },
          "description": {
            "type": ["string", "null"],
            "description": "Merchant free-text narration shown to the customer and in the dashboard.",
            "example": "Kmafinsa sale receipt KMF738393"
          }
        },
        "required": [
          "id",
          "status",
          "currency",
          "amount",
          "charged_amount",
          "merchant_net",
          "fees",
          "reference",
          "created_at"
        ]
      },
      "CreatePaymentRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 1,
            "description": "Minor units"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "method": {
            "type": "string",
            "example": "ecocash"
          },
          "reference": {
            "type": "string",
            "maxLength": 64,
            "description": "Unique per merchant"
          },
          "customer": {
            "type": "object",
            "properties": {
              "msisdn": {
                "type": "string",
                "example": "263771234567",
                "description": "Omit the whole customer object for hosted checkout"
              }
            }
          },
          "return_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2000
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Your own JSON; returned on every read and webhook"
          },
          "description": {
            "type": "string",
            "maxLength": 200,
            "description": "Optional free-text narration shown to the customer at checkout and in the dashboard.",
            "example": "Kmafinsa sale receipt KMF738393"
          }
        },
        "required": ["amount", "currency", "method", "reference"]
      },
      "Refund": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "ref_01J8XQ7M4K"
          },
          "payment_id": {
            "type": "string"
          },
          "amount": {
            "type": "integer",
            "description": "Minor units"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "status": {
            "type": "string",
            "enum": ["requested", "approved", "submitted", "completed", "failed"]
          },
          "reason": {
            "type": ["string", "null"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "payment_id", "amount", "currency", "status", "created_at"]
      },
      "PreviewRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 1,
            "description": "Collection side: the order amount, minor units"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "method": {
            "type": "string",
            "default": "ecocash"
          },
          "payout_amount": {
            "type": "integer",
            "minimum": 1,
            "description": "Payout side: gross payout to quote, minor units"
          }
        },
        "required": ["currency"]
      },
      "PreviewResponse": {
        "type": "object",
        "properties": {
          "customer_pays": {
            "type": "integer"
          },
          "merchant_receives": {
            "type": "integer"
          },
          "commission": {
            "type": "integer"
          },
          "payout": {
            "type": "object",
            "properties": {
              "gross": {
                "type": "integer"
              },
              "imtt": {
                "type": "integer",
                "description": "Grossed up correctly: net Ã— r/(1+r), half-up"
              },
              "transfer_fee": {
                "type": "integer"
              },
              "net": {
                "type": "integer"
              },
              "effective_rate": {
                "type": "string",
                "example": "2.00%"
              }
            },
            "required": ["gross", "imtt", "transfer_fee", "net", "effective_rate"]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Honest caveats, e.g. rates not yet contractually confirmed"
          }
        },
        "required": ["warnings"]
      },
      "PaymentLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "lnk_01J8XQ7M4K"
          },
          "token": {
            "type": "string",
            "example": "plk_01J8XQ7M4K"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Send this to the customer"
          },
          "amount": {
            "type": "integer"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "reference": {
            "type": ["string", "null"],
            "description": "Becomes the payment's reference"
          },
          "description": {
            "type": ["string", "null"]
          },
          "single_use": {
            "type": "boolean"
          },
          "used_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "expires_at": {
            "type": ["string", "null"],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "token", "url", "amount", "currency", "single_use", "created_at"]
      },
      "CreatePaymentLinkRequest": {
        "type": "object",
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 1
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "reference": {
            "type": "string",
            "maxLength": 64
          },
          "description": {
            "type": "string",
            "maxLength": 200
          },
          "single_use": {
            "type": "boolean",
            "default": true
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["amount", "currency"]
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_01J8XQ7M4K"
          },
          "type": {
            "type": "string",
            "enum": [
              "payment.succeeded",
              "payment.failed",
              "payment.expired",
              "payment.unresolved",
              "refund.succeeded",
              "refund.failed",
              "settlement.paid"
            ]
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": ["id", "type", "payload", "created_at"]
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "authentication_error",
                  "permission_error",
                  "not_found",
                  "idempotency_conflict",
                  "rate_limited",
                  "api_error"
                ]
              },
              "code": {
                "type": "string",
                "example": "parameter_invalid"
              },
              "message": {
                "type": "string",
                "description": "Written for humans; safe to show your user for validation errors"
              },
              "param": {
                "type": "string"
              },
              "request_id": {
                "type": "string",
                "example": "req_01J8XQ7M4K",
                "description": "Quote this to support"
              }
            },
            "required": ["type", "code", "message", "request_id"]
          }
        },
        "required": ["error"]
      },
      "Settlement": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "gross": {
            "type": "integer",
            "description": "Minor units"
          },
          "refunds": {
            "type": "integer"
          },
          "imtt": {
            "type": "integer"
          },
          "transfer_fee": {
            "type": "integer"
          },
          "net": {
            "type": "integer"
          },
          "transfer_reference": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": ["paid", "reconciled"]
          },
          "cut_off_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "executed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "required": ["id", "currency", "gross", "net", "status"]
      },
      "PaymentMethodField": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "msisdn"
          },
          "label": {
            "type": "string",
            "example": "Mobile number"
          },
          "type": {
            "type": "string",
            "enum": ["msisdn", "otp", "pin", "card_number", "card_expiry", "card_cvv", "text"]
          },
          "required": {
            "type": "boolean"
          },
          "step": {
            "type": "string",
            "enum": ["charge", "confirm"],
            "description": "Which call the value goes in: the initial charge, or the OTP confirm."
          },
          "example": {
            "type": "string"
          },
          "help": {
            "type": "string"
          }
        }
      },
      "PaymentMethod": {
        "type": "object",
        "properties": {
          "method": {
            "type": "string",
            "example": "ecocash"
          },
          "display_name": {
            "type": "string",
            "example": "EcoCash"
          },
          "flow": {
            "type": "string",
            "enum": ["push", "otp", "code", "card"],
            "description": "How the customer authorises: a prompt pushed to their phone (push), a one-time PIN they enter (otp), a code they approve in an app (code), or card entry (card)."
          },
          "requires_pin": {
            "type": "boolean",
            "description": "card flows only: whether a card PIN is required (e.g. Zimswitch)."
          },
          "description": {
            "type": "string"
          },
          "currencies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Currency"
            }
          },
          "collects": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentMethodField"
            },
            "description": "The fields to collect from the customer for this method."
          },
          "logo_url": {
            "type": "string",
            "format": "uri",
            "description": "SVG logo URL for rendering the method icon."
          },
          "available": {
            "type": "boolean",
            "description": "Whether a customer can pay with this method right now in this environment."
          },
          "status": {
            "type": "string",
            "enum": ["active", "coming_soon"]
          }
        },
        "example": {
          "method": "ecocash",
          "display_name": "EcoCash",
          "flow": "push",
          "requires_pin": false,
          "description": "A payment request is pushed to the customer EcoCash phone and they approve it there. Collect only the mobile number, start the charge, then check the payment status; the merchant never handles the PIN.",
          "currencies": ["USD", "ZWG"],
          "collects": [
            {
              "name": "msisdn",
              "label": "Mobile number",
              "type": "msisdn",
              "required": true,
              "step": "charge",
              "example": "263771234567"
            }
          ],
          "logo_url": "https://api.lango.co.zw/v1/payment-methods/ecocash/logo.svg",
          "available": true,
          "status": "active"
        }
      }
    }
  }
}
