Skip to main content

Fiat Orders

Fiat orders are card payments processed by us. This page covers:
  • List fiat orders (GET /api/fiat/orders) — cursor pagination
  • Create a fiat order (POST /api/fiat/orders) — returns payUrl
  • Delete a fiat order by reference (DELETE /api/fiat/orders?reference=...) — proxy-friendly safety delete
  • Fetch a fiat order by orderKey (GET /api/fiat/orders/:orderKey) — includes pricing + fee snapshot
  • Cancel a fiat order by orderKey (DELETE /api/fiat/orders/:orderKey) — cancels upstream PSP order
  • Cancel a fiat order by reference (DELETE /api/fiat/orders/cancel?reference=...)
  • List refunds (GET /api/fiat/orders/:orderKey/refunds)
  • Create a refund (POST /api/fiat/orders/:orderKey/refunds) — supports partial/multiple refunds

Base URL

All examples use:
  • https://www.niftipay.com

Authentication

These endpoints support two authentication methods: Send your API key in the x-api-key header.
curl -X GET "https://www.niftipay.com/api/fiat/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"
If you are authenticated via the dashboard.

Concepts

Amounts are stored in minor units

Fiat orders store monetary values as minor units (e.g. cents):
  • amountCents — what the customer will pay (total)
  • subtotalCents — the base subtotal (before service fee if customer pays)
  • serviceFeeCents — total service maintenance fee (provider + platform), if applicable

Currency minor unit rules

This implementation supports currencies with:
  • 0 decimals (e.g. JPY)
  • 2 decimals (most currencies)
  • 3 decimals (e.g. BHD)
When you send amount as a string/number, it is converted to minor units using the currency’s decimal rules and rejects too many decimals.

Service fee payer

The fiat checkout supports a service maintenance fee with two payer modes:
  • serviceFeePayer = "customer"
    Customer pays: total = subtotal + fee
    The fee is shown as a line item in pricing.
  • serviceFeePayer = "merchant"
    Customer pays: total = subtotal
    The fee is deducted from merchant earnings (not shown to customer as a separate line in pricing).
If serviceFeePayer is not provided on create, the system falls back to the user’s default (GET /api/fiat/settings).

References and uniqueness

On creation, the route normalizes a reference from:
  • reference (preferred)
  • else merchantReference (fallback)
Then it checks cross-type uniqueness for your account:
  • cannot conflict with a crypto order reference
  • cannot conflict with an existing fiat order reference
If there is a conflict, the API returns 409.

Pricing and fee snapshots

Responses include:

pricing

A stable, UI-friendly breakdown:
  • payer (customer or merchant)
  • subtotalCents, serviceFeeCents, totalCents
  • serviceFeePercent (may be null)
  • lines (Subtotal / Service maintenance fee / Total)

fees (order details endpoint only)

A stable snapshot structure (written by registerFiatOrderFees()), including:
  • service fee split (provider vs platform)
  • payout fee / retention hold
  • vendor net / payable now after deductions
Older orders may have some snapshot fields null; the API still returns a stable object with safe defaults.

List Fiat Orders

Endpoint

GET /api/fiat/orders Lists your fiat orders, newest first, using cursor pagination.

Query parameters

NameTypeDefaultNotes
statusstringOptional filter.
limitnumber20Min 1, max 100.
cursornumberUse nextCursor from the previous response to fetch older records.

Example: list latest orders

curl -X GET "https://www.niftipay.com/api/fiat/orders?limit=20" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example response

{
  "orders": [
    {
      "id": "6a4a2be6-6c6c-4a4a-bf9f-4f5b3e6a90aa",
      "orderKey": 2221,
      "userId": "user-id",
      "integrationId": "integration-id",
      "kind": "order",
      "amountCents": 7004,
      "subtotalCents": 6800,
      "serviceFeePayer": "customer",
      "serviceFeePercent": 3.0,
      "serviceFeeCents": 204,
      "currency": "GBP",
      "status": "new",
      "psp": "nopayn",
      "pspOrderId": "np_123",
      "pspProjectId": "proj_abc",
      "pspStatus": "new",
      "pspPaymentLinkId": null,
      "pspPaymentUrl": null,
      "orderUrl": "https://pay.nopayn.example/order/np_123",
      "returnUrl": "https://merchant.example/return",
      "failureUrl": "https://merchant.example/failure",
      "webhookUrl": "https://merchant.example/webhook",
      "merchantReference": "POS-1234",
      "createdAt": "2026-02-11T10:00:00.000Z",
      "updatedAt": "2026-02-11T10:00:05.000Z",
      "completedAt": null,
      "pricing": {
        "payer": "customer",
        "subtotalCents": 6800,
        "serviceFeeCents": 204,
        "totalCents": 7004,
        "serviceFeePercent": 3.0,
        "lines": [
          { "type": "subtotal", "amountCents": 6800, "label": "Subtotal" },
          { "type": "service_maintenance_fee", "amountCents": 204, "label": "Service maintenance fee" },
          { "type": "total", "amountCents": 7004, "label": "Total" }
        ]
      }
    }
  ],
  "nextCursor": 2221
}

Pagination example

If nextCursor is not null, pass it back as cursor to fetch older results:
curl -X GET "https://www.niftipay.com/api/fiat/orders?limit=20&cursor=2221" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Create Fiat Order

Endpoint

POST /api/fiat/orders Creates a fiat order and attempts to create the upstream NoPayn order. On success, returns:
  • order (DB row)
  • pricing (decorated)
  • display (decimal strings for UI)
  • payUrl (where to redirect the customer)
  • nopayn info

Payment method gating

This endpoint enforces fiat availability for your account. If fiat card payments are disabled, it returns 403.

Request body

{
  "integrationId": "YOUR_FIAT_INTEGRATION_ID",
  "amount": "19.95",
  "currency": "EUR",
  "description": "Order #1001",
  "reference": "POS-1001",
  "serviceFeePayer": "customer"
}

Fields

FieldTypeRequiredNotes
integrationIdstringMust exist and belong to you.
currencystringISO currency code (e.g. EUR, GBP).
amountstring/number⚠️Required unless amountCents is provided.
amountCentsnumber⚠️Alternative to amount. Must be a positive integer.
descriptionstringPassed to PSP for display.
referencestringPreferred merchant reference (normalized).
merchantReferencestringLegacy alias; used if reference is missing.
serviceFeePayerstring"customer" or "merchant". Defaults to your fiat settings.
Provide either amount or amountCents.

Example: create order with amount string

curl -X POST "https://www.niftipay.com/api/fiat/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "integrationId": "integration-id",
    "amount": "68.00",
    "currency": "GBP",
    "description": "POS checkout",
    "reference": "POS-1234",
    "serviceFeePayer": "customer"
  }'

Example success response (201)

{
  "order": {
    "id": "6a4a2be6-6c6c-4a4a-bf9f-4f5b3e6a90aa",
    "orderKey": 2221,
    "userId": "user-id",
    "integrationId": "integration-id",
    "kind": "order",
    "amountCents": 7004,
    "subtotalCents": 6800,
    "serviceFeePayer": "customer",
    "serviceFeePercent": 3.0,
    "serviceFeeCents": 204,
    "currency": "GBP",
    "status": "new",
    "psp": "nopayn",
    "pspProjectId": "proj_abc",
    "pspOrderId": "np_123",
    "pspStatus": "new",
    "orderUrl": "https://pay.nopayn.example/order/np_123",
    "returnUrl": "https://merchant.example/return",
    "failureUrl": "https://merchant.example/failure",
    "webhookUrl": "https://merchant.example/webhook",
    "merchantReference": "POS-1234",
    "createdAt": "2026-02-11T10:00:00.000Z",
    "updatedAt": "2026-02-11T10:00:05.000Z",
    "completedAt": null
  },
  "pricing": {
    "payer": "customer",
    "subtotalCents": 6800,
    "serviceFeeCents": 204,
    "totalCents": 7004,
    "serviceFeePercent": 3.0,
    "lines": [
      { "type": "subtotal", "amountCents": 6800, "label": "Subtotal" },
      { "type": "service_maintenance_fee", "amountCents": 204, "label": "Service maintenance fee" },
      { "type": "total", "amountCents": 7004, "label": "Total" }
    ],
    "serviceFeeBreakdown": {
      "providerPercent": 2.0,
      "platformPercent": 1.0,
      "totalPercent": 3.0,
      "providerCents": 136,
      "platformCents": 68,
      "totalCents": 204
    }
  },
  "display": {
    "subtotal": "68.00",
    "serviceFee": "2.04",
    "total": "70.04",
    "currency": "GBP"
  },
  "reference": "POS-1234",
  "nopayn": {
    "id": "np_123",
    "status": "new",
    "order_url": "https://pay.nopayn.example/order/np_123"
  },
  "payUrl": "https://pay.nopayn.example/order/np_123"
}
payUrl is what you should open/redirect the customer to.

Example: create order with amountCents

curl -X POST "https://www.niftipay.com/api/fiat/orders" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "integrationId": "integration-id",
    "amountCents": 1995,
    "currency": "EUR",
    "description": "Order #1001",
    "reference": "POS-1001"
  }'

Common create errors

Invalid JSON (400)

{ "error": "Invalid JSON" }

Payment method disabled (403)

{ "error": "Payment method disabled" }

Missing required fields (400)

{ "error": "integrationId is required" }
{ "error": "currency is required" }

Invalid amount (400)

{ "error": "amount is required (e.g. 19.95)" }
{ "error": "amountCents must be a positive integer" }
{ "error": "amount has too many decimals for JPY" }

Invalid serviceFeePayer (400)

{ "error": "serviceFeePayer must be \"customer\" or \"merchant\"" }

Reference conflict (409)

{ "error": "Reference used by crypto order" }
or
{ "error": "Reference already exists" }

Integration not found (404)

{ "error": "Integration not found" }

Upstream PSP error (502)

If NoPayn fails, the order is marked status="error" locally and you get:
{ "error": "Upstream error", "details": null }

Delete Fiat Order (by reference)

Endpoint

DELETE /api/fiat/orders?reference=<reference> This is a proxy-friendly delete that mirrors your crypto delete behavior. Matching rules:
  1. first tries strict match: fiatOrder.merchantReference === reference (newest first)
  2. fallback: if reference is numeric, match by fiatOrder.orderKey === Number(reference)

Safety rules

Deletion is conservative:
  • You cannot delete completed, paid, or refunded orders
  • If pspOrderId exists, the order must already be cancelled before deletion

Example request

curl -X DELETE "https://www.niftipay.com/api/fiat/orders?reference=POS-1234" \
  -H "x-api-key: YOUR_API_KEY"

Success response

  • 204 No Content

Error responses

Missing reference (400)
{ "error": "reference query-param required" }
Not found (404)
{ "error": "Order not found" }
Cannot delete completed/paid/refunded (409)
{ "error": "Cannot delete – order status is \"completed\"" }
Must cancel first (409)
{
  "error": "Cannot delete – order must be cancelled first (call DELETE /api/fiat/orders/cancel?reference=...)"
}

Get Fiat Order (by orderKey)

Endpoint

GET /api/fiat/orders/:orderKey Returns:
  • order (DB row)
  • pricing (decorated)
  • fees (snapshot breakdown)

Example request

curl -X GET "https://www.niftipay.com/api/fiat/orders/2221" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example response

{
  "order": {
    "id": "6a4a2be6-6c6c-4a4a-bf9f-4f5b3e6a90aa",
    "orderKey": 2221,
    "userId": "user-id",
    "integrationId": "integration-id",
    "amountCents": 7004,
    "subtotalCents": 6800,
    "serviceFeePayer": "customer",
    "serviceFeePercent": 3.0,
    "serviceFeeCents": 204,
    "currency": "GBP",
    "status": "new",
    "psp": "nopayn",
    "pspOrderId": "np_123",
    "pspProjectId": "proj_abc",
    "pspStatus": "new",
    "orderUrl": "https://pay.nopayn.example/order/np_123",
    "returnUrl": "https://merchant.example/return",
    "failureUrl": "https://merchant.example/failure",
    "webhookUrl": "https://merchant.example/webhook",
    "merchantReference": "POS-1234",
    "createdAt": "2026-02-11T10:00:00.000Z",
    "updatedAt": "2026-02-11T10:00:05.000Z",
    "completedAt": null,

    "serviceFeeProviderPercent": 2.0,
    "serviceFeePlatformPercent": 1.0,
    "serviceFeeProviderCents": 136,
    "serviceFeePlatformCents": 68,
    "payoutFeePercent": 0,
    "payoutFeeCents": 0,
    "retentionPercent": 10,
    "retentionHoldCents": 680,
    "payableNowCents": 6120,
    "feeSnapshotAt": "2026-02-11T10:00:05.000Z",
    "feeSnapshotVersion": 1
  },
  "pricing": {
    "payer": "customer",
    "subtotalCents": 6800,
    "serviceFeeCents": 204,
    "totalCents": 7004,
    "serviceFeePercent": 3.0,
    "lines": [
      { "type": "subtotal", "amountCents": 6800, "label": "Subtotal" },
      { "type": "service_maintenance_fee", "amountCents": 204, "label": "Service maintenance fee" },
      { "type": "total", "amountCents": 7004, "label": "Total" }
    ]
  },
  "fees": {
    "snapshotAt": "2026-02-11T10:00:05.000Z",
    "snapshotVersion": 1,
    "serviceFee": {
      "payer": "customer",
      "totalCents": 204,
      "providerCents": 136,
      "platformCents": 68,
      "providerPercent": 2.0,
      "platformPercent": 1.0
    },
    "payout": {
      "payoutFeePercent": 0,
      "payoutFeeCents": 0,
      "retentionPercent": 10,
      "retentionHoldCents": 680,
      "payableNowCents": 6120
    },
    "vendor": {
      "netAfterFeesCents": 6120,
      "payableNowAfterServiceFeeCents": 6120,
      "serviceFeeMerchantDeductionCents": 0
    }
  }
}

Cancel Fiat Order (by orderKey)

Endpoint

DELETE /api/fiat/orders/:orderKey This attempts to cancel the upstream PSP order (NoPayn) and then updates the local DB.

Behavior

  • If already cancelled, returns current state (idempotent).
  • If status is completed, cancellation is blocked (safe default).
  • Requires pspOrderId to exist.
  • Only psp="nopayn" is supported here.

Example request

curl -X DELETE "https://www.niftipay.com/api/fiat/orders/2221" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example response

{
  "order": {
    "orderKey": 2221,
    "status": "cancelled",
    "pspOrderId": "np_123",
    "pspStatus": "cancelled",
    "orderUrl": "https://pay.nopayn.example/order/np_123"
  },
  "pricing": {
    "payer": "customer",
    "subtotalCents": 6800,
    "serviceFeeCents": 204,
    "totalCents": 7004,
    "serviceFeePercent": 3.0,
    "lines": [
      { "type": "subtotal", "amountCents": 6800, "label": "Subtotal" },
      { "type": "service_maintenance_fee", "amountCents": 204, "label": "Service maintenance fee" },
      { "type": "total", "amountCents": 7004, "label": "Total" }
    ]
  },
  "fees": {
    "snapshotAt": "2026-02-11T10:00:05.000Z",
    "snapshotVersion": 1,
    "serviceFee": { "payer": "customer", "totalCents": 204, "providerCents": 136, "platformCents": 68, "providerPercent": 2.0, "platformPercent": 1.0 },
    "payout": { "payoutFeePercent": 0, "payoutFeeCents": 0, "retentionPercent": 10, "retentionHoldCents": 680, "payableNowCents": 6120 },
    "vendor": { "netAfterFeesCents": 6120, "payableNowAfterServiceFeeCents": 6120, "serviceFeeMerchantDeductionCents": 0 }
  },
  "nopayn": {
    "id": "np_123",
    "status": "cancelled",
    "order_url": "https://pay.nopayn.example/order/np_123"
  }
}

Cancel error examples

Invalid orderKey (400)
{ "error": "Invalid orderKey" }
Not found (404)
{ "error": "Not found" }
Completed cannot be cancelled (409)
{ "error": "Order is completed and cannot be cancelled" }
Missing pspOrderId (409)
{ "error": "Missing PSP order id (pspOrderId)" }
PSP not supported (501)
{ "error": "Cancel not implemented for psp=stripe" }
Upstream cancel failed (502 or provider status code)
{ "error": "Upstream cancel failed", "details": null }

Cancel Fiat Order (by reference)

Endpoint

DELETE /api/fiat/orders/cancel?reference=<reference> Cancel using the same matching rules as delete-by-reference:
  1. strict merchantReference match
  2. numeric fallback to orderKey

Example request

curl -X DELETE "https://www.niftipay.com/api/fiat/orders/cancel?reference=POS-1234" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Responses

  • Returns order + pricing + nopayn on success
  • Same error patterns as cancel by orderKey

Refunds (Fiat)

Refunds are executed at the PSP level (NoPayn) and support partial / multiple refunds. Refund creation is conservative:
  • Only for kind="order" (not payment links)
  • Only allowed when order is paid or completed
  • Prevents over-refund by reading current refunds from NoPayn first
Refund ceiling depends on service fee payer:
  • If serviceFeePayer="customer": refundable ceiling = total paid (amountCents)
  • If serviceFeePayer="merchant": refundable ceiling = subtotal only (subtotalCents)
    (Because the customer never paid the service fee)

List refunds

Endpoint

GET /api/fiat/orders/:orderKey/refunds Returns:
  • refunds (from NoPayn)
  • computed meta: maxRefundableCents, refundedCents, remainingRefundableCents

Example request

curl -X GET "https://www.niftipay.com/api/fiat/orders/2221/refunds" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json"

Example response

{
  "order": {
    "orderKey": 2221,
    "amountCents": 7004,
    "subtotalCents": 6800,
    "serviceFeePayer": "customer",
    "currency": "GBP",
    "status": "completed",
    "pspOrderId": "np_123"
  },
  "nopaynOrderId": "np_123",
  "refunds": [
    { "id": "rf_1", "amount": 1000, "status": "completed", "created": "2026-02-11T12:00:00.000Z" }
  ],
  "meta": {
    "serviceFeePayer": "customer",
    "maxRefundableCents": 7004,
    "refundedCents": 1000,
    "remainingRefundableCents": 6004
  }
}

Common list-refunds errors

Invalid orderKey (400)
{ "error": "Invalid orderKey" }
Not found (404)
{ "error": "Not found" }
Missing pspOrderId (400)
{
  "error": "Order is missing pspOrderId (NoPayn order id). It may not have been created in NoPayn yet."
}
NoPayn upstream error (502)
{ "error": "Failed to fetch refunds from NoPayn", "details": null }

Create refund

Endpoint

POST /api/fiat/orders/:orderKey/refunds

Request body

{
  "amountCents": 1000,
  "description": "Partial refund"
}
Optional: specify order lines (for item-based refunds):
{
  "amountCents": 1000,
  "description": "Refund 1 item",
  "orderLines": [
    { "merchantOrderLineId": "line-1", "quantity": 1 }
  ]
}

Fields

FieldTypeRequiredNotes
amountCentsnumberPositive integer. Must not exceed remaining refundable.
descriptionstringOptional PSP description.
orderLinesarray/nullIf provided: must be a non-empty array with { merchantOrderLineId, quantity }, or null.
orderLines validation:
  • orderLines must be non-empty if provided as an array
  • each line requires a non-empty merchantOrderLineId
  • quantity must be a positive integer

Example request

curl -X POST "https://www.niftipay.com/api/fiat/orders/2221/refunds" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "amountCents": 1000,
    "description": "Partial refund"
  }'

Example response (201)

{
  "order": {
    "id": "6a4a2be6-6c6c-4a4a-bf9f-4f5b3e6a90aa",
    "orderKey": 2221,
    "status": "completed",
    "pspOrderId": "np_123",
    "currency": "GBP",
    "amountCents": 7004,
    "subtotalCents": 6800,
    "serviceFeePayer": "customer"
  },
  "nopaynOrderId": "np_123",
  "refundOrder": {
    "id": "rf_2",
    "amount": 1000,
    "status": "completed"
  },
  "refunds": [
    { "id": "rf_1", "amount": 1000, "status": "completed" },
    { "id": "rf_2", "amount": 1000, "status": "completed" }
  ],
  "meta": {
    "serviceFeePayer": "customer",
    "maxRefundableCents": 7004,
    "refundedCents": 2000,
    "remainingRefundableCents": 5004
  }
}
If the order becomes fully refunded (remaining refundable reaches 0), the local fiatOrder.status is updated to "refunded".

Common refund errors

Invalid JSON (400)
{ "error": "Invalid JSON" }
Refunds only for paid/completed (409)
{ "error": "Refunds are only allowed for paid/completed orders (current status: \"new\")" }
Refunds only for kind=order (400)
{ "error": "Refunds can only be created for kind=order (not payment_link)." }
Missing pspOrderId (400)
{
  "error": "Order is missing pspOrderId (NoPayn order id). It may not have been created in NoPayn yet."
}
Nothing left to refund (409)
{ "error": "Nothing left to refund for this order." }
Amount exceeds remaining refundable (400)
{
  "error": "amountCents cannot exceed remaining refundable (6004)",
  "meta": { "maxRefundableCents": 7004, "refundedCents": 1000, "remainingRefundableCents": 6004 }
}
Invalid orderLines (400)
{ "error": "orderLines cannot be an empty array" }
{ "error": "orderLines[].merchantOrderLineId is required" }
{ "error": "orderLines[].quantity must be a positive integer" }
NoPayn refund create failed (502)
{ "error": "Failed to create refund in NoPayn", "details": null }

Status notes

Fiat order status values come from the PSP status field in practice (e.g. NoPayn’s status), plus local states:
  • new — created locally (and usually also created upstream)
  • cancelled — cancelled locally (and upstream when possible)
  • completed / paid — successful payment
  • refunded — fully refunded (local state set when refundable remaining reaches 0)
  • error — upstream create failed