> ## Documentation Index
> Fetch the complete documentation index at: https://beta-developers.mizaniyapay.dz/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment Status API: GET /partner/payments/v1/{reference}/status

> Query the current lock and payment state of a product reference. Use this to reconcile missing webhooks or check status before releasing a product.

The Payment Status API lets you query, at any time, whether Mizaniya Pay holds a lock on a product reference and whether it was paid. Call it when a webhook never arrived, before selling a product on-premise, or during reconciliation. It is read-only and never changes the state of a payment.

## Endpoint

```text theme={null}
GET /partner/payments/v1/{reference}/status
```

## Authentication

This endpoint uses your Mizaniya Pay API key, passed as a header:

```text theme={null}
x-api-key: YOUR_API_KEY
```

Your API key identifies your merchant account. Results are scoped to it. A reference belonging to another merchant is returned as `not_found`.

## Path Parameters

<ParamField path="reference" type="string" required>
  The product reference you returned from your Product Information API. Non-empty, maximum 255 characters. URL-encode it if it contains reserved characters.
</ParamField>

## Rate Limit

60 requests per minute.

## How to Decide at a Glance

Branch on `locked` first. If you need to distinguish "sold" from "never paid", also read `paid`.

| `stage`     | `locked` | `paid`  | What it means                                            | What to do                                      |
| ----------- | -------- | ------- | -------------------------------------------------------- | ----------------------------------------------- |
| `locked`    | `true`   | `false` | A payment attempt is in progress.                        | Keep the product reserved.                      |
| `paid`      | `false`  | `true`  | A payment succeeded.                                     | Mark the product sold. Never release or resell. |
| `released`  | `false`  | `false` | Attempts exist but none succeeded and none holds a lock. | Safe to release the product.                    |
| `refunded`  | `false`  | `false` | A payment was refunded.                                  | Safe to release the product.                    |
| `not_found` | `false`  | `false` | No record of this reference under your account.          | Safe to release the product.                    |

<Warning>
  Only `released`, `refunded`, and `not_found` mean nothing is held. Treat any other stage as still held. On any error response, do not release the lock.
</Warning>

## Response Envelope

Every successful call returns HTTP 200 with this envelope:

```json theme={null}
{
  "message": "success",
  "statusCode": 200,
  "data": { ... }
}
```

## Response Fields

The fields below are the contents of `data`. All timestamps are UTC ISO-8601.

<ResponseField name="reference" type="string">
  The product reference you queried, echoed back.
</ResponseField>

<ResponseField name="stage" type="enum">
  One of `locked`, `paid`, `released`, `refunded`, `not_found`. Branch on this field for business logic.
</ResponseField>

<ResponseField name="locked" type="boolean">
  `true` only while Mizaniya Pay is actively holding this reference. The single field to check for a one-shot branch.
</ResponseField>

<ResponseField name="paid" type="boolean">
  `true` once a payment for this reference has succeeded.
</ResponseField>

<ResponseField name="lockReleaseTime" type="string | null">
  UTC ISO-8601 timestamp at which the lock expires. `null` when nothing is locked, and also `null` when the locking attempt carries no expiry (the lock is still considered held).
</ResponseField>

<ResponseField name="paymentId" type="string | null">
  Identifier of the attempt that decided the `stage`. Matches the `paymentId` on VTPE webhooks. `null` only when `stage` is `not_found`.
</ResponseField>

<ResponseField name="channel" type="string">
  Always `AGENCY`, matching the `channel` field on the `payment.success` webhook.
</ResponseField>

<ResponseField name="currency" type="string">
  Currency of the deciding attempt. Defaults to `DZD` when `stage` is `not_found`.
</ResponseField>

<ResponseField name="amount" type="number | null">
  Amount of the deciding attempt. `null` only when `stage` is `not_found`.
</ResponseField>

<ResponseField name="payment" type="object | null">
  Detail of the deciding attempt. `null` only when `stage` is `not_found`.

  <Expandable title="payment fields">
    <ResponseField name="payment.status" type="enum">
      Internal payment status: `pending`, `processing`, `success`, `failed`, `refunded`. Prefer `stage` for branching; this field is for diagnostics only.
    </ResponseField>

    <ResponseField name="payment.paidAt" type="string | null">
      UTC ISO-8601 timestamp of completion. `null` unless the attempt was paid.
    </ResponseField>

    <ResponseField name="payment.orderId" type="string | null">
      SATIM order identifier, present once the attempt reaches a final state.
    </ResponseField>

    <ResponseField name="payment.webhookDelivery" type="string | null">
      Delivery state of the outcome webhook sent to you: `success`, `fail`, `pending`, or `null` if none was due yet. If `fail` or `pending`, you likely never received the confirmation — this endpoint is the recovery path.
    </ResponseField>

    <ResponseField name="payment.createdAt" type="string">
      UTC ISO-8601 timestamp when the attempt was created.
    </ResponseField>

    <ResponseField name="payment.updatedAt" type="string">
      UTC ISO-8601 timestamp when the attempt was last updated.
    </ResponseField>
  </Expandable>
</ResponseField>

## Response Examples

<Tabs>
  <Tab title="locked">
    A payment attempt is in progress. Keep the product reserved. Do not sell or release it until the lock expires or you receive a final webhook.

    **Variant 1: Lock with a recorded expiry**

    ```json theme={null}
    {
      "message": "success",
      "statusCode": 200,
      "data": {
        "reference": "YAL-000123",
        "stage": "locked",
        "locked": true,
        "paid": false,
        "lockReleaseTime": "2026-07-26T18:07:41.057Z",
        "paymentId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
        "channel": "AGENCY",
        "amount": 4500,
        "currency": "DZD",
        "payment": {
          "paymentId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
          "status": "processing",
          "amount": 4500,
          "currency": "DZD",
          "paidAt": null,
          "orderId": null,
          "webhookDelivery": "pending",
          "createdAt": "2026-07-26T17:54:41.057Z",
          "updatedAt": "2026-07-26T17:54:41.057Z"
        }
      }
    }
    ```

    **Variant 2: Lock with no recorded expiry**

    A payment attempt is in progress but carries no expiry timestamp. Because the lock expiry cannot be proved, it is still reported as held. Treat this exactly like variant 1.

    ```json theme={null}
    {
      "message": "success",
      "statusCode": 200,
      "data": {
        "reference": "YAL-000123",
        "stage": "locked",
        "locked": true,
        "paid": false,
        "lockReleaseTime": null,
        "paymentId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
        "channel": "AGENCY",
        "amount": 4500,
        "currency": "DZD",
        "payment": {
          "paymentId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
          "status": "processing",
          "amount": 4500,
          "currency": "DZD",
          "paidAt": null,
          "orderId": null,
          "webhookDelivery": null,
          "createdAt": "2026-07-26T17:54:41.057Z",
          "updatedAt": "2026-07-26T17:54:41.057Z"
        }
      }
    }
    ```
  </Tab>

  <Tab title="paid">
    The customer paid. Mark the product sold. Authoritative even if `payment.webhookDelivery` is `fail`.

    ```json theme={null}
    {
      "message": "success",
      "statusCode": 200,
      "data": {
        "reference": "YAL-000123",
        "stage": "paid",
        "locked": false,
        "paid": true,
        "lockReleaseTime": null,
        "paymentId": "3d9f1e77-2b64-4c8a-9f0d-7a1e5c2b8f43",
        "channel": "AGENCY",
        "amount": 4500,
        "currency": "DZD",
        "payment": {
          "paymentId": "3d9f1e77-2b64-4c8a-9f0d-7a1e5c2b8f43",
          "status": "success",
          "amount": 4500,
          "currency": "DZD",
          "paidAt": "2026-07-26T17:57:41.057Z",
          "orderId": "SATIM-ORD-88213",
          "webhookDelivery": "fail",
          "createdAt": "2026-07-26T17:54:41.057Z",
          "updatedAt": "2026-07-26T17:57:41.057Z"
        }
      }
    }
    ```
  </Tab>

  <Tab title="released">
    Attempts exist but none succeeded and the lock is gone. Safe to release the product.

    ```json theme={null}
    {
      "message": "success",
      "statusCode": 200,
      "data": {
        "reference": "YAL-000123",
        "stage": "released",
        "locked": false,
        "paid": false,
        "lockReleaseTime": null,
        "paymentId": "6ba7b810-9dad-41d1-80b4-00c04fd430c8",
        "channel": "AGENCY",
        "amount": 4500,
        "currency": "DZD",
        "payment": {
          "paymentId": "6ba7b810-9dad-41d1-80b4-00c04fd430c8",
          "status": "failed",
          "amount": 4500,
          "currency": "DZD",
          "paidAt": null,
          "orderId": null,
          "webhookDelivery": null,
          "createdAt": "2026-07-26T17:54:41.057Z",
          "updatedAt": "2026-07-26T17:58:41.057Z"
        }
      }
    }
    ```
  </Tab>

  <Tab title="refunded">
    A payment was refunded. Safe to release the product.

    ```json theme={null}
    {
      "message": "success",
      "statusCode": 200,
      "data": {
        "reference": "YAL-000123",
        "stage": "refunded",
        "locked": false,
        "paid": false,
        "lockReleaseTime": null,
        "paymentId": "0f8fad5b-d9cb-469f-a165-70867728950e",
        "channel": "AGENCY",
        "amount": 4500,
        "currency": "DZD",
        "payment": {
          "paymentId": "0f8fad5b-d9cb-469f-a165-70867728950e",
          "status": "refunded",
          "amount": 4500,
          "currency": "DZD",
          "paidAt": "2026-07-26T17:29:41.057Z",
          "orderId": "SATIM-ORD-77102",
          "webhookDelivery": null,
          "createdAt": "2026-07-26T17:54:41.057Z",
          "updatedAt": "2026-07-26T17:54:41.057Z"
        }
      }
    }
    ```
  </Tab>

  <Tab title="not_found">
    No record of this reference under your account. Safe to release the product.

    ```json theme={null}
    {
      "message": "success",
      "statusCode": 200,
      "data": {
        "reference": "YAL-000123",
        "stage": "not_found",
        "locked": false,
        "paid": false,
        "lockReleaseTime": null,
        "paymentId": null,
        "channel": "AGENCY",
        "amount": null,
        "currency": "DZD",
        "payment": null
      }
    }
    ```
  </Tab>
</Tabs>

## Error Responses

Errors carry a stable `errorCode` to branch on. The human-readable `message` may change. On any error, do not release the lock. Retry or fall back to your existing reconciliation process.

<AccordionGroup>
  <Accordion title="401 — Missing or unrecognized API key">
    The `x-api-key` header was absent, empty, sent more than once, or does not match any active key. Also returned when the merchant behind the key no longer exists.

    ```json theme={null}
    {
      "statusCode": 401,
      "message": "Invalid or missing api-key",
      "errorCode": "PARTNER_API401_UNAUTHORIZED",
      "timestamp": "2026-07-26T17:59:41.058Z"
    }
    ```
  </Accordion>

  <Accordion title="403 — Merchant account is not active">
    The key is valid but the merchant account is not in an active state. Contact Mizaniya Pay support.

    ```json theme={null}
    {
      "statusCode": 403,
      "message": "Merchant account is not active",
      "errorCode": "PARTNER_API403_MERCHANT_INACTIVE",
      "timestamp": "2026-07-26T17:59:41.058Z",
      "details": {
        "merchantId": "9c1f8a52-1d3e-4b7a-9f10-2c4e6a8b0d11"
      }
    }
    ```
  </Accordion>

  <Accordion title="400 — Invalid reference">
    The reference failed validation. It must be a non-empty string of at most 255 characters.

    ```json theme={null}
    {
      "statusCode": 400,
      "message": "Dto validation error",
      "errorCode": "VALIDATION400",
      "timestamp": "2026-07-26T17:59:41.058Z",
      "details": {
        "errors": [
          {
            "field": "referenceId",
            "errors": ["referenceId must be shorter than or equal to 255 characters"]
          }
        ]
      }
    }
    ```
  </Accordion>

  <Accordion title="429 — Rate limit exceeded">
    You exceeded 60 requests per minute. Back off and retry. This endpoint is safe to call again at any time.

    ```json theme={null}
    {
      "statusCode": 429,
      "message": "ThrottlerException: Too Many Requests"
    }
    ```
  </Accordion>
</AccordionGroup>

## Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  import fetch from "node-fetch";

  const API_KEY = process.env.VTPE_API_KEY;
  const BASE_URL = "https://api.mizaniyapay.dz";

  async function getPaymentStatus(reference) {
    const encoded = encodeURIComponent(reference);
    const res = await fetch(
      `${BASE_URL}/partner/payments/v1/${encoded}/status`,
      {
        method: "GET",
        headers: {
          "x-api-key": API_KEY,
          "Content-Type": "application/json"
        }
      }
    );

    if (!res.ok) {
      const error = await res.json();
      throw new Error(`${error.statusCode}: ${error.message}`);
    }

    const { data } = await res.json();
    return data;
  }

  // Usage
  const status = await getPaymentStatus("ORDER-1001");

  switch (status.stage) {
    case "paid":
      // Fulfill the order
      console.log("Payment confirmed at", status.payment.paidAt);
      break;
    case "locked":
      // Keep reserved; check back later
      console.log("Lock expires at", status.lockReleaseTime);
      break;
    case "released":
    case "refunded":
    case "not_found":
      // Safe to release
      console.log("No active lock — safe to release");
      break;
    default:
      // Treat unknown stages as held
      console.warn("Unknown stage:", status.stage);
  }
  ```

  ```python Python theme={null}
  import os
  import httpx

  API_KEY = os.environ["VTPE_API_KEY"]
  BASE_URL = "https://api.mizaniyapay.dz"

  def get_payment_status(reference: str) -> dict:
      from urllib.parse import quote
      encoded = quote(reference, safe="")
      response = httpx.get(
          f"{BASE_URL}/partner/payments/v1/{encoded}/status",
          headers={"x-api-key": API_KEY}
      )

      if response.status_code != 200:
          error = response.json()
          raise Exception(f"{error['statusCode']}: {error['message']}")

      return response.json()["data"]

  # Usage
  status = get_payment_status("ORDER-1001")

  if status["stage"] == "paid":
      # Fulfill the order
      print("Payment confirmed at", status["payment"]["paidAt"])
  elif status["stage"] == "locked":
      # Keep reserved; check back later
      print("Lock expires at", status["lockReleaseTime"])
  elif status["stage"] in ("released", "refunded", "not_found"):
      # Safe to release
      print("No active lock — safe to release")
  else:
      # Treat unknown stages as held
      print("Unknown stage:", status["stage"])
  ```
</CodeGroup>

## Integration Notes

<Note>
  This endpoint is a safety net, not a replacement for webhooks. Keep handling `payment.success` and `payment.fail` as your primary signal.
</Note>

* **Branch on `stage` or `locked`, never on `payment.status`.** The internal status set may grow; `stage` is the contract.
* **A single reference can have multiple payment attempts.** Mizaniya Pay always resolves them and returns the one that decides the outcome.
* **A successful payment always wins over an in-flight attempt.** If `stage` is `paid`, the product is sold even if another attempt is still processing.
* **`not_found` returns HTTP 200**, not 404. It is a business answer, not an error.
* **Do not poll in a tight loop.** Call when you need a decision, or at most once every few seconds for a given reference.
