Skip to main content

Fiat Orders

Fiat orders are card payments processed by us. You create an order from your backend, we return a hosted payment page (payUrl), the customer pays by card, and we notify your server with a webhook. This page covers:
  • List fiat orders (GET /api/fiat/orders) — cursor pagination
  • Create a fiat order (POST /api/fiat/orders) — returns payUrl + qrUrl
  • 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

Before you start

You need three things before your first POST /api/fiat/orders:
If fiat card payments are disabled for your account, POST /api/fiat/orders returns 403 Payment method disabled. See payment method gating.

How to get your integrationId

A fiat integration is the per-site configuration an order is attached to: which URLs the customer comes back to, and where status updates are sent. Every fiat order must reference exactly one integration.

From the dashboard (easiest)

  1. Open Dashboard → Settings → Fiat.
  2. Fill the Create integration form:
    • Name — anything you recognise, e.g. My Shop.
    • Return URL (required) — where the customer lands after paying.
    • Failure URL — where the customer lands after a failed/cancelled payment.
    • Merchant webhook URL (required) — where we POST status updates for this integration’s orders.
    • Contact URL — your contact/support page, shown to blocked customers.
  3. The new integration appears under Your integrations with Integration ID: <id>. Press Copy ID.
That value is the integrationId you send when creating an order.

From the API

integrations[].id → use as integrationId. Full CRUD (create, update, delete) is documented in Fiat integrations.
One integration per site/storefront. Running several shops on one account? Create one integration each — that keeps return URLs and webhooks from crossing over, and lets you filter orders per site.

What each integration URL does

Webhooks are how you get paid-status. The card result arrives on your server, not in the browser redirect. Register the URL either on the integration (merchantWebhookUrl) or in Settings → Webhooks bound to this integration — see scoping webhooks to integrations. Verify the signature before trusting a payload.

IDs you will see, and which to store

Store orderKey and your own reference against your order record — everything else can be re-fetched from them.

End-to-end flow

  1. Create the order — POST /api/fiat/orders with integrationId, amount (or amountCents), currency, and your reference.
  2. Redirect the customer to payUrl (or show qrUrl for in-person/mobile).
  3. Customer pays; we bounce them to returnUrl or failureUrl.
  4. Webhook fires to your endpoint with the new status — this is the event you fulfil the order on.
  5. Optionally fetch GET /api/fiat/orders/:orderKey for the pricing and fee snapshot, or issue 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.
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.

Payment URL + QR Code

All fiat order responses include:
  • payUrl — the masked payment link to redirect the customer to
  • qrUrl — a QR code image URL encoding the payUrl, ready to embed in checkout UIs
The qrUrl uses the same external QR API as crypto orders:
You can display the QR image directly in an <img> tag or embed it in mobile checkout flows.

Supported fiat currencies

The following fiat currencies are supported for fiat card orders:
Currencies with 0 decimal places (e.g. JPY, KRW, VND, XAF, XOF) must be sent as whole numbers. Most currencies use 2 decimal places.

List Fiat Orders

Endpoint

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

Query parameters

Example: list latest orders

Example response

Pagination example

If nextCursor is not null, pass it back as cursor to fetch older results:

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)
  • qrUrl (QR code image encoding the payUrl)
  • nopayn info

Payment method gating

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

Request body

Fields

Provide either amount or amountCents.
The order’s webhookUrl is always copied from the integration’s merchantWebhookUrl — it cannot be overridden per order. Change it in Settings → Fiat, or via PATCH /api/fiat/integrations/:id.

Example: create order with amount string

Example success response (201)

payUrl is what you should open/redirect the customer to. qrUrl is a ready-to-use QR code image that encodes the same payment link.

Example: create order with amountCents


Common create errors

Invalid JSON (400)

Payment method disabled (403)

The FIAT card method is switched off automatically when an account reaches the chargeback threshold (3 chargebacks in the last 30 days by default). Contact support to have it re-enabled.

Missing required fields (400)

Invalid amount (400)

Invalid serviceFeePayer (400)

Reference conflict (409)

or

Integration not found (404)

Upstream PSP error (502)

If NoPayn fails, the order is marked status="error" locally and you get:

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

Success response

  • 204 No Content

Error responses

Missing reference (400)
Not found (404)
Cannot delete completed/paid/refunded (409)
Must cancel first (409)

Get Fiat Order (by orderKey)

Endpoint

GET /api/fiat/orders/:orderKey Returns:
  • order (DB row)
  • pricing (decorated)
  • fees (snapshot breakdown)
  • payUrl (masked payment link)
  • qrUrl (QR code image for the payment link)

Example request

Example response


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

Example response

Cancel error examples

Invalid orderKey (400)
Not found (404)
Completed cannot be cancelled (409)
Missing pspOrderId (409)
PSP not supported (501)
Upstream cancel failed (502 or provider status code)

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

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

Example response

Common list-refunds errors

Invalid orderKey (400)
Not found (404)
Missing pspOrderId (400)
NoPayn upstream error (502)

Create refund

Endpoint

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

Request body

Optional: specify order lines (for item-based refunds):

Fields

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

Example response (201)

If the order becomes fully refunded (remaining refundable reaches 0), the local fiatOrder.status is updated to "refunded".

Common refund errors

Invalid JSON (400)
Refunds only for paid/completed (409)
Refunds only for kind=order (400)
Missing pspOrderId (400)
Nothing left to refund (409)
Amount exceeds remaining refundable (400)
Invalid orderLines (400)
NoPayn refund create failed (502)

Node.js Example

A complete Node.js example showing how to create a fiat payment order with pricing breakdown and response parsing.
See the full working script with setup and helper function: API Examples (Node.js)

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