{
  "info": {
    "name": "FahiPay Merchants API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Explore the FahiPay Merchant API for hosted payments, payment requests and customer-approved charges. Set client_id, client_secret and scope in collection variables, then send OAuth / Get access token. The response script saves access_token for subsequent authenticated requests. The default scopes allow payment creation and lookup; request the additional scopes listed in the Merchant guide before using other operations. Your merchant account determines live or sandbox mode. Confirm sandbox mode before testing payment operations. Amounts are integer laari: 100 laari = MVR 1.00. Keep credentials and tokens private and remove them before sharing or exporting the collection. Guide: https://fahipay.mv/developers/docs/merchants/MERCHANT_API.md"
  },
  "auth": {
    "type": "oauth2",
    "oauth2": [
      {
        "key": "accessToken",
        "value": "{{access_token}}",
        "type": "string"
      },
      {
        "key": "addTokenTo",
        "value": "header",
        "type": "string"
      },
      {
        "key": "tokenType",
        "value": "Bearer",
        "type": "string"
      },
      {
        "key": "grant_type",
        "value": "client_credentials",
        "type": "string"
      },
      {
        "key": "accessTokenUrl",
        "value": "{{base_url}}/oauth/token",
        "type": "string"
      },
      {
        "key": "clientId",
        "value": "{{client_id}}",
        "type": "string"
      },
      {
        "key": "clientSecret",
        "value": "{{client_secret}}",
        "type": "string"
      },
      {
        "key": "scope",
        "value": "{{scope}}",
        "type": "string"
      },
      {
        "key": "client_authentication",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.fahipay.mv/merchants",
      "type": "string"
    },
    {
      "key": "client_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "client_secret",
      "value": "",
      "type": "string"
    },
    {
      "key": "scope",
      "value": "payments:read payments:create",
      "type": "string"
    },
    {
      "key": "access_token",
      "value": "",
      "type": "string"
    },
    {
      "key": "payment_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "authorization_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "payment_request_id",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "OAuth",
      "item": [
        {
          "name": "Get access token",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{base_url}}/oauth/token",
            "description": "Obtain a Bearer access token using your OAuth client credentials. Request only the scopes your integration needs.\n\nUse the returned token in `Authorization: Bearer YOUR_ACCESS_TOKEN`. Tokens expire after 24 hours.",
            "auth": {
              "type": "noauth"
            },
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "{{client_id}}",
                  "type": "text"
                },
                {
                  "key": "client_secret",
                  "value": "{{client_secret}}",
                  "type": "text"
                },
                {
                  "key": "scope",
                  "value": "{{scope}}",
                  "type": "text"
                }
              ]
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 200) {",
                  "  const token = pm.response.json();",
                  "  if (token.access_token) pm.collectionVariables.set(\"access_token\", token.access_token);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Payments",
      "item": [
        {
          "name": "List Payments",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/payments",
            "description": "List your payments in the current live or sandbox mode, newest first.\n\n- Combine the filters below to narrow the results. Keep them unchanged when loading the next page.\n- Returned pending payments past their deadline are saved as `expired`, unless a payment attempt is still processing. Each expiry emits `payment.expired` once when webhooks are enabled."
          },
          "response": []
        },
        {
          "name": "Create Payment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{base_url}}/payments",
            "description": "Create a hosted checkout link and a PayMV QR link for a customer payment.\n\n- Send the customer to `payment_url`, or display the image at `qr_url`.\n- Payments expire after 24 hours unless you set `expires_at`.\n- Confirm the payment is `paid` using the API or a verified webhook before fulfilling the order.\n\nUse a reference of 1–100 characters after trimming and an integer amount from 1 to 9,999,999,999 laari.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reference\": \"ORDER-2026-001\",\n  \"amount\": 2000\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Get Payment",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/payments/{{payment_id}}",
            "description": "Retrieve a payment and its current status. Hosted payments include checkout URLs; authorization payments omit them.\n\nA pending payment past its deadline is saved as `expired`, unless a payment attempt is still processing. Each expiry emits `payment.expired` once when webhooks are enabled."
          },
          "response": []
        },
        {
          "name": "Cancel Payment",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{base_url}}/payments/{{payment_id}}/cancel",
            "description": "Cancel a pending payment before the customer pays. This does not refund a completed payment.\n\n- Success returns `cancelled` and emits `payment.cancelled` when webhooks are enabled. Its QR image is no longer available.\n- Repeating a successful cancellation returns HTTP 200 without another event.\n- To collect payment again, create a new payment with a new reference."
          },
          "response": []
        },
        {
          "name": "Get PayMV QR",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/public/qr/{{payment_id}}",
            "description": "Display the PayMV QR image for a pending, unexpired hosted payment. This public endpoint returns PNG image bytes and does not require merchant credentials.\n\nUse the exact `qr_url` returned by payment creation. Unavailable payments and authorization payments return HTTP 404.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "Authorizations",
      "item": [
        {
          "name": "List Authorizations",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/authorizations",
            "description": "List your authorizations in the current live or sandbox mode, newest first. Filter by status and use `pagination.next_cursor` as `after` for the next page."
          },
          "response": []
        },
        {
          "name": "Create Authorization",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{base_url}}/authorizations",
            "description": "Ask a customer to approve future payments, such as gym membership fees, subscription renewals, or usage-based charges.\n\n- Send the customer to `approval_url`. The default approval window is 24 hours.\n- Once the authorization is `active`, your application submits each charge when it is due. FahiPay does not schedule charges automatically.\n- A gym charging MVR 500 monthly could set a per-payment limit of `50000` laari and a monthly period limit of `50000`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reference\": \"GYM-MEMBER-1042-2026\",\n  \"title\": \"Monthly gym membership\",\n  \"description\": \"Up to 12 monthly gym membership payments of MVR 500.00.\",\n  \"type\": \"recurring\",\n  \"limits\": {\n    \"per_payment\": 50000,\n    \"period\": {\n      \"interval\": \"month\",\n      \"amount\": 50000\n    },\n    \"total\": 600000,\n    \"max_payments\": 12\n  },\n  \"expires_at\": \"2026-12-31T23:59:59+05:00\",\n  \"return_url\": \"https://merchant.example/authorizations/complete\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Get Authorization",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/authorizations/{{authorization_id}}",
            "description": "Retrieve an authorization, including its status, approved limits, total amount paid, and number of successful payments."
          },
          "response": []
        },
        {
          "name": "Charge Authorization",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{base_url}}/authorizations/{{authorization_id}}/charge",
            "description": "Collect a payment using an active authorization approved by the customer. Your application submits this request whenever a payment is due.\n\n- Each charge must fit within the authorization limits. Period limits reset at midnight in +05:00; weeks begin on Sunday.\n- Insufficient balance returns HTTP 402 (`insufficient_balance`); a wallet limit failure returns HTTP 422 (`wallet_limit_exceeded`). Neither creates a payment.\n- Reuse the original reference and amount when retrying. A matching successful payment is returned without charging again. See the Idempotency-Key section of the Merchant guide for retry rules: https://fahipay.mv/developers/docs/merchants/MERCHANT_API.md#optional-idempotency-key\n\nUse a reference of 1–100 characters after trimming and an integer amount from 1 to 9,999,999,999 laari.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reference\": \"GYM-MEMBER-1042-2026-01\",\n  \"amount\": 50000,\n  \"title\": \"Monthly gym membership\",\n  \"description\": \"Gym membership for member 1042, January 2026\",\n  \"bill_number\": \"GYM/2026/01-1042\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Cancel Authorization",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{base_url}}/authorizations/{{authorization_id}}/cancel",
            "description": "Cancel a pending authorization or revoke an active one to stop future charges.\n\n- A charge in progress returns HTTP 409; retry after it finishes.\n- Already cancelled or revoked authorizations return HTTP 200.\n- Previous payments are not reversed."
          },
          "response": []
        },
        {
          "name": "List Authorization Payments",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/authorizations/{{authorization_id}}/payments",
            "description": "List payments made through this authorization, newest first.\n\n- Filter by `paid` or `refunded`, or omit the status to include both.\n- History remains available after the authorization is cancelled, revoked, or expired.\n- Keep filters unchanged when loading the next page. An authorization with no payments returns an empty `data` array."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Payment Requests",
      "item": [
        {
          "name": "List Payment Requests",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/payment-requests",
            "description": "List payment requests created by your merchant in the current live or sandbox mode, newest first. Combine the filters below and use `pagination.next_cursor` as `after` for the next page."
          },
          "response": []
        },
        {
          "name": "Create Payment Request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": "{{base_url}}/payment-requests",
            "description": "Request a payment from a customer using their mobile number, email address, or FahiPay VPA. Customer approval creates a paid payment.\n\n- Requests expire after 24 hours unless you set `expires_at`.\n- In sandbox mode, use VPA `test@fp` (or `test@fahipay`) or email `test@fahipay.mv`. Other recipients, including mobile numbers, return `sandbox_recipient_not_allowed`.\n\nUse a reference of 1–100 characters after trimming and an integer amount from 1 to 9,999,999,999 laari.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reference\": \"SCHOOL-ACTIVITY-2026-001\",\n  \"amount\": 75000,\n  \"customer\": {\n    \"type\": \"vpa\",\n    \"value\": \"test@fp\"\n  },\n  \"title\": \"School activity fee\",\n  \"description\": \"Activity fee for January 2026\",\n  \"terminal_label\": \"COUNTER01\",\n  \"bill_number\": \"ACT-2026-001\",\n  \"return_url\": \"https://merchant.example/payment-requests/complete\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Get Payment Request",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{base_url}}/payment-requests/{{payment_request_id}}",
            "description": "Retrieve a payment request, including its customer identifier, current status, and payment reference after payment."
          },
          "response": []
        },
        {
          "name": "Cancel Payment Request",
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{base_url}}/payment-requests/{{payment_request_id}}/cancel",
            "description": "Cancel a pending payment request before it is paid. Already-cancelled requests return the cancelled request.\n\nPaid, rejected, expired, refunded, or currently processing requests cannot be cancelled."
          },
          "response": []
        }
      ]
    }
  ]
}
