0fra

Orders

Create and inspect orders directly via API — the building block under Checkout sessions.

A Checkout Session is a thin wrapper around an Order. If you want full control of the buyer-side UX (e.g. you embed wallet connection in your own front-end), you can skip Checkout sessions and create orders directly.

Create an order

curl -X POST https://api.0fra.dev/v1/orders \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant_id": "vendor_2001",
    "order_no": "ORDER-1001",
    "chain_id": 84532,
    "token": "USDC",
    "amount": "100.00",
    "metadata": { "customer_id": "cus_42" }
  }'

The response includes the pay_to (PaymentRouter address) and a fully-formed eip712 message + signature. Hand both to the buyer's wallet — wagmi's writeContract expects the message struct + signature as the two arguments of payOrder().

{
  "id": "...",
  "state": "pending_payment",
  "pay_to": "0x79abe8c3f1e0c444b10d5cbba918578d70283f74",
  "amount": "100.00",
  "computed_split": {
    "service_fee": "1000000",
    "platform_fee": "10000000",
    "merchant_gross": "89000000",
    "reserve_hold": "4450000",
    "merchant_available": "84550000"
  },
  "eip712": {
    "domain":  { "name": "0fra", "version": "1", "chainId": 84532, "verifyingContract": "0x..." },
    "primary_type": "PayOrder",
    "types":   { /* ABI types */ },
    "message": { /* full PayOrder struct */ }
  },
  "signature": "0x..."
}

computed_split values are in the token's smallest unit (USDC = 6 decimals).

On-chain submission (browser)

import { writeContract } from 'wagmi/actions';
import PaymentRouterAbi from './PaymentRouter.abi.json';

const order = await fetch('/v1/orders', { /* ... */ }).then(r => r.json());

// 1) approve USDC once per buyer
await writeContract({
  address: order.eip712.message.token,
  abi: erc20Abi,
  functionName: 'approve',
  args: [order.pay_to, maxUint256],
});

// 2) pay
await writeContract({
  address: order.pay_to,
  abi: PaymentRouterAbi,
  functionName: 'payOrder',
  args: [order.eip712.message, order.signature],
});

List & retrieve

GET  /v1/orders?state=confirmed&limit=50
GET  /v1/orders/:id
GET  /v1/orders/summary

/summary gives you total, pending, confirmed, and gross_24h for dashboards.

State machine

pending_payment ──► confirmed ──► partially_refunded ──► refunded
                ├─► failed
                └─► expired
  • confirmed means 0fra's chain indexer saw OrderPaid with the agreed amount and confirmation depth
  • failed only sets if you explicitly call our internal failure endpoint or the buyer disputes (rare)
  • expired happens automatically when deadline_at passes without confirmation

Idempotency hard rules

  • Same order_no for the same platform → 409 conflict
  • Same Idempotency-Key + same body → cached response replayed
  • Same key + different body → 409 duplicate_idempotency_key

On this page