{
  "info": {
    "name": "Lango Payments API \u2014 Collection flows",
    "_postman_id": "lango-collection-flows",
    "description": "Collect money with Lango, grouped by how each provider works.\n\nEvery call authenticates with your secret API key (set `apiKey` below to an `sk_test_...` key from Dashboard -> Developers -> Applications). Then each provider follows one of three shapes:\n\n- **push** (EcoCash, OneMoney): initiate -> check status\n- **code** (InnBucks): initiate (returns a code) -> check status\n- **OTP** (O'mari): initiate -> confirm OTP -> check status\n\nRun a folder top to bottom: the Initiate request saves `paymentId` (and `checkoutToken` for OTP) so the next requests use it automatically. Base URL is the same for test and live \u2014 the key decides. Docs: https://docs.lango.co.zw/",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.lango.co.zw/v1",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "sk_test_REPLACE_ME",
      "type": "string"
    },
    {
      "key": "paymentId",
      "value": "",
      "type": "string"
    },
    {
      "key": "checkoutToken",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "1. Discover - payment methods",
      "description": "Your first call. Public (no key). Returns the methods live right now with their `flow` (push / otp / code / card) and the fields to collect. Render what comes back instead of hard-coding methods.",
      "item": [
        {
          "name": "List live payment methods",
          "request": {
            "method": "GET",
            "auth": {
              "type": "noauth"
            },
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/payment-methods",
              "host": ["{{baseUrl}}"],
              "path": ["payment-methods"]
            },
            "description": "GET /v1/payment-methods . Add ?currency=USD to filter, ?include=all to see coming-soon methods too."
          }
        }
      ]
    },
    {
      "name": "2. EcoCash / OneMoney - PUSH",
      "description": "flow=push. A prompt is pushed to the customer's phone and they approve it there (you never handle the PIN).\n\nSteps: 1) Initiate  2) Check status.",
      "item": [
        {
          "name": "1. Initiate charge (prompt pushed to phone)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Saves paymentId for the status check below",
                  "const b = pm.response.json();",
                  "if (b.id) { pm.collectionVariables.set('paymentId', b.id); console.log('paymentId =', b.id); }",
                  "if (b.checkout_token) { pm.collectionVariables.set('checkoutToken', b.checkout_token); }",
                  "if (b.code) { console.log('InnBucks code to show the customer:', b.code); }",
                  "if (b.status) { console.log('status =', b.status); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 1000,\n  \"currency\": \"USD\",\n  \"method\": \"ecocash\",\n  \"reference\": \"ORD-{{$timestamp}}\",\n  \"customer\": {\n    \"msisdn\": \"263771234567\"\n  },\n  \"metadata\": {\n    \"order_id\": \"demo-1\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/payments",
              "host": ["{{baseUrl}}"],
              "path": ["payments"]
            },
            "description": "POST /v1/payments . Returns status `awaiting_customer` - the prompt is on the customer's phone. amount is in minor units (1000 = USD 10.00)."
          }
        },
        {
          "name": "2. Check status (poll, or use a webhook)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/payments/{{paymentId}}",
              "host": ["{{baseUrl}}"],
              "path": ["payments", "{{paymentId}}"]
            },
            "description": "GET /v1/payments/{id} . Poll (back off to a few seconds) until `succeeded` / `failed` / `unknown`. In production prefer the payment.* webhook. Never show 'failed' while the state is `unknown`."
          }
        }
      ]
    },
    {
      "name": "3. InnBucks - CODE",
      "description": "flow=code. A code is issued; the customer approves it in the InnBucks app or by *569#. USD only.\n\nSteps: 1) Initiate (returns the code)  2) Check status.\n\nREQUIRED by InnBucks when you build your own UI: show the InnBucks logo, show the instructions + code, and check status at most every 30 seconds. The initiate response carries next_action { code, expires_at, instructions, logo_url, poll_interval_seconds }. Polling faster than 30s returns a 429.",
      "item": [
        {
          "name": "1. Initiate (response carries the code to show)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// The response 'code' is what the customer pays - logged to the console",
                  "const b = pm.response.json();",
                  "if (b.id) { pm.collectionVariables.set('paymentId', b.id); console.log('paymentId =', b.id); }",
                  "if (b.checkout_token) { pm.collectionVariables.set('checkoutToken', b.checkout_token); }",
                  "if (b.code) { console.log('InnBucks code to show the customer:', b.code); }",
                  "if (b.status) { console.log('status =', b.status); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 1000,\n  \"currency\": \"USD\",\n  \"method\": \"innbucks\",\n  \"reference\": \"ORD-{{$timestamp}}\",\n  \"customer\": {\n    \"msisdn\": \"263771234567\"\n  },\n  \"metadata\": {\n    \"order_id\": \"demo-1\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/payments",
              "host": ["{{baseUrl}}"],
              "path": ["payments"]
            },
            "description": "POST /v1/payments with method=innbucks . Returns status `awaiting_code_use` and a `code` (+ expiry). Show the code to the customer; they approve it in InnBucks."
          }
        },
        {
          "name": "2. Check status (poll, max once per 30s)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/payments/{{paymentId}}",
              "host": ["{{baseUrl}}"],
              "path": ["payments", "{{paymentId}}"]
            },
            "description": "GET /v1/payments/{id} . Poll until `succeeded` - at most once every 30 seconds (InnBucks rule; a faster poll returns 429 with Retry-After). This endpoint drives InnBucks resolution."
          }
        }
      ]
    },
    {
      "name": "4. O'mari - OTP (two-step)",
      "description": "flow=otp. The customer gets a one-time PIN and enters it to confirm.\n\nSteps: 1) Initiate (OTP sent, returns checkoutToken)  2) Confirm OTP  3) Check status.",
      "item": [
        {
          "name": "1. Initiate (OTP sent to phone; saves checkoutToken)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Saves paymentId AND checkoutToken (needed to confirm the OTP)",
                  "const b = pm.response.json();",
                  "if (b.id) { pm.collectionVariables.set('paymentId', b.id); console.log('paymentId =', b.id); }",
                  "if (b.checkout_token) { pm.collectionVariables.set('checkoutToken', b.checkout_token); }",
                  "if (b.code) { console.log('InnBucks code to show the customer:', b.code); }",
                  "if (b.status) { console.log('status =', b.status); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 1000,\n  \"currency\": \"USD\",\n  \"method\": \"omari\",\n  \"reference\": \"ORD-{{$timestamp}}\",\n  \"customer\": {\n    \"msisdn\": \"263771234567\"\n  },\n  \"metadata\": {\n    \"order_id\": \"demo-1\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/payments",
              "host": ["{{baseUrl}}"],
              "path": ["payments"]
            },
            "description": "POST /v1/payments with method=omari . Returns status `awaiting_otp` and a `checkout_token`. The OTP is sent to the customer's phone."
          }
        },
        {
          "name": "2. Confirm OTP",
          "request": {
            "method": "POST",
            "auth": {
              "type": "noauth"
            },
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"otp\": \"123456\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/checkout/{{checkoutToken}}/otp",
              "host": ["{{baseUrl}}"],
              "path": ["checkout", "{{checkoutToken}}", "otp"]
            },
            "description": "POST /v1/checkout/{checkout_token}/otp . The checkout token IS the credential here (no API key). A successful confirm is the real debit. Replace 123456 with the PIN the customer got."
          }
        },
        {
          "name": "3. Check status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/payments/{{paymentId}}",
              "host": ["{{baseUrl}}"],
              "path": ["payments", "{{paymentId}}"]
            },
            "description": "GET /v1/payments/{id} . Should read `succeeded` after a good OTP."
          }
        }
      ]
    },
    {
      "name": "5. Refund a payment",
      "description": "Refund a succeeded payment (full or partial).",
      "item": [
        {
          "name": "Create refund",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 1000,\n  \"reason\": \"customer_request\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/payments/{{paymentId}}/refunds",
              "host": ["{{baseUrl}}"],
              "path": ["payments", "{{paymentId}}", "refunds"]
            },
            "description": "POST /v1/payments/{id}/refunds . Omit amount for a full refund."
          }
        }
      ]
    },
    {
      "name": "6. Fee preview (optional, before charging)",
      "description": "See the exact charge and who bears the fee before you initiate - show this to the customer when they bear it.",
      "item": [
        {
          "name": "Preview fees",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 1000,\n  \"currency\": \"USD\",\n  \"method\": \"ecocash\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/preview",
              "host": ["{{baseUrl}}"],
              "path": ["preview"]
            },
            "description": "POST /v1/preview . Returns the fee breakdown and charged_amount."
          }
        }
      ]
    }
  ]
}
