{
  "info": {
    "_postman_id": "dcdfaf34-f355-bebe-70fb-249b2a32edd8",
    "name": "MoniWave API (v1)",
    "description": "Accept and send Mobile Money payments in Central Africa.\n\nAuthenticate with a secret key as a bearer token. Every mutating request carries a client-generated `Idempotency-Key` UUID, and duplicates replay the original response rather than charging twice.\n\nTransaction status is authoritative by polling — webhooks are a convenience, never the only way to learn an outcome.\n\n---\n\nGenerated from `docs/api/openapi.json`. Import an environment alongside it, set `apiKey` to your `sk_test_` key, and run the folders in order — folder 1 creates the collection that folder 3 refunds.\n\nSwitch outcome by editing the `msisdn` collection variable: the magic numbers and what each one does are in the testing guide.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "msisdn",
      "value": "237690000001",
      "description": "Happy path. See the testing guide for the full outcome table."
    },
    {
      "key": "collectionId",
      "value": "",
      "description": "Captured by \"Create a collection\"."
    },
    {
      "key": "checkoutToken",
      "value": "",
      "description": "Captured from the checkout session URL."
    },
    {
      "key": "checkoutUrl",
      "value": "",
      "description": "The hosted page — open it in a browser."
    },
    {
      "key": "refundId",
      "value": "",
      "description": "Captured by \"Refund a collection\"."
    },
    {
      "key": "payoutId",
      "value": "",
      "description": "Captured by \"Create a payout\"."
    }
  ],
  "item": [
    {
      "name": "0 · Health",
      "item": [
        {
          "name": "Ping",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/ping",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ping"
              ]
            },
            "description": "Liveness, and the cheapest way to prove `baseUrl` is right before touching auth.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "pm.test('baseUrl reaches the API', () => pm.expect(pm.response.responseSize).to.be.above(0));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "1 · Collections",
      "item": [
        {
          "name": "Create a collection (request money)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Client-generated UUID. A duplicate replays the original response."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/collections",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "collections"
              ]
            },
            "description": "Charges a payer. Returns `accepted` — the outcome arrives asynchronously, so poll the retrieve request below rather than treating this response as final.\n\nCreate a collection",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 5000,\n  \"currency\": \"XAF\",\n  \"msisdn\": \"{{msisdn}}\",\n  \"description\": \"API quick start\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('201', () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  pm.collectionVariables.set('collectionId', body.id);",
                  "  pm.test('starts accepted', () => pm.expect(body.status).to.eql('accepted'));",
                  "  console.log('collectionId =', body.id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve a collection (poll until settled)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/collections/{{collectionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "collections",
                "{{collectionId}}"
              ]
            },
            "description": "Status is authoritative by polling. With the happy-path MSISDN this reaches `completed` within a few seconds; re-send until it stops being `accepted` or `submitted`.\n\nRetrieve a collection"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "console.log('status =', pm.response.json().status);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2 · Hosted checkout",
      "item": [
        {
          "name": "Create a checkout session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Client-generated UUID. A duplicate replays the original response."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/checkout-sessions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "checkout-sessions"
              ]
            },
            "description": "Returns a hosted payment page URL. Open `url` in a browser to pay as a customer would, or use the two requests below to drive it over the API.\n\nCreate a checkout session",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 5000,\n  \"currency\": \"XAF\",\n  \"description\": \"API quick start\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('201', () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  pm.collectionVariables.set('checkoutUrl', body.url);",
                  "  // The session token is not a field of its own: it is the last segment of the",
                  "  // hosted URL. Same extraction the deploy runbook dry run does, for the same reason.",
                  "  pm.collectionVariables.set('checkoutToken', String(body.url).split('/c/').pop());",
                  "  pm.test('url is a hosted checkout link', () => pm.expect(body.url).to.include('/c/'));",
                  "  console.log('open this in a browser:', body.url);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Read a checkout session",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/checkout/{{checkoutToken}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "checkout",
                "{{checkoutToken}}"
              ]
            },
            "description": "Anonymous by design — the session token in the path is the whole authorisation, because this route is called by the hosted page in a customer browser that holds no key.\n\nRead a checkout session",
            "auth": {
              "type": "noauth"
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "console.log('session =', body.status, '| transaction =', body.transactionStatus);"
                ]
              }
            }
          ]
        },
        {
          "name": "Pay a checkout session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/checkout/{{checkoutToken}}/pay",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "checkout",
                "{{checkoutToken}}",
                "pay"
              ]
            },
            "description": "What the hosted page posts when the customer submits their number. Also anonymous, and also authorised by the token alone. Returns `202` — then poll the read request above.\n\nPay a checkout session",
            "auth": {
              "type": "noauth"
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"msisdn\": \"{{msisdn}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('202', () => pm.response.to.have.status(202));",
                  "console.log('accepted — re-send the read request above to watch it settle');"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3 · Refunds",
      "item": [
        {
          "name": "Refund a collection",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Client-generated UUID. A duplicate replays the original response."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/refunds",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "refunds"
              ]
            },
            "description": "Refunds the collection captured above. Omit `amount` to refund in full; send less for a partial refund, repeatedly, up to the remaining refundable amount.\n\nRefund a collection",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"collectionId\": \"{{collectionId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('201', () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  pm.collectionVariables.set('refundId', pm.response.json().id);",
                  "  console.log('refundId =', pm.response.json().id);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve a refund",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/refunds/{{refundId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "refunds",
                "{{refundId}}"
              ]
            },
            "description": "Retrieve a refund"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "4 · Payouts",
      "item": [
        {
          "name": "Create a payout (send money)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Client-generated UUID. A duplicate replays the original response."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payouts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payouts"
              ]
            },
            "description": "Pays a recipient out of the merchant balance. Refused with `payout_exceeds_balance` until collections have settled — that is the guard working, not a misconfiguration.\n\nCreate a payout",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 5000,\n  \"currency\": \"XAF\",\n  \"msisdn\": \"{{msisdn}}\",\n  \"description\": \"API quick start\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('201, or 400 payout_exceeds_balance on an empty balance', () => pm.expect([201, 400]).to.include(pm.response.code));",
                  "if (pm.response.code === 201) {",
                  "  pm.collectionVariables.set('payoutId', pm.response.json().id);",
                  "} else {",
                  "  console.log('refused:', pm.response.json().code, '— settle a collection first');",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve a payout",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payouts/{{payoutId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payouts",
                "{{payoutId}}"
              ]
            },
            "description": "Retrieve a payout"
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
