Skip to content

Orders API

Create and manage one-time payment orders.

Overview

The Orders API allows you to create payment orders with temporary addresses, query order status, and list all orders. Each order is assigned a unique payment address that expires after a configurable payment window.

Base Endpoint: /api/v1/orders

Authentication: Required (API Key)

Create Order

Create a new payment order with a temporary payment address.

Endpoint:

POST /api/v1/orders

Request Body:

json
{
  "orderReference": "ORDER-2025-001",
  "amount": "100.00",
  "currency": "USDT",
  "chainId": "ethereum-sepolia",
  "metadata": {
    "customerName": "John Doe",
    "customerEmail": "john@example.com",
    "productSku": "PROD-123"
  },
  "successUrl": "https://yoursite.com/order/success",
  "cancelUrl": "https://yoursite.com/order/cancel"
}

Parameters:

ParameterTypeRequiredDescription
orderReferencestringYesYour unique order identifier (max 255 chars)
amountstringYesPayment amount (decimal string, e.g. "100.00")
currencystringYesCurrency code (USDT, USDC, DAI)
chainIdstringYesBlockchain chain ID (see Supported Networks)
metadataobjectNoCustom metadata (max 10 key-value pairs)
successUrlstringNoRedirect URL after successful payment
cancelUrlstringNoRedirect URL after order expiration

Response (201 Created):

json
{
  "orderId": "ord_abc123def456",
  "orderReference": "ORDER-2025-001",
  "amount": "100.00",
  "currency": "USDT",
  "chainId": "ethereum-sepolia",
  "protocol": "evm",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "status": "pending",
  "metadata": {
    "customerName": "John Doe",
    "customerEmail": "john@example.com",
    "productSku": "PROD-123"
  },
  "successUrl": "https://yoursite.com/order/success",
  "cancelUrl": "https://yoursite.com/order/cancel",
  "createdAt": "2025-01-20T10:30:00.000Z",
  "expiresAt": "2025-01-20T10:40:00.000Z"
}

Example (cURL):

bash
curl -X POST https://testnet.payin.com/api/v1/orders \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your-api-key" \
  -d '{
    "orderReference": "ORDER-2025-001",
    "amount": "100.00",
    "currency": "USDT",
    "chainId": "ethereum-sepolia"
  }'

Example (TypeScript):

typescript
const response = await fetch('https://testnet.payin.com/api/v1/orders', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': process.env.PAYIN_API_KEY!
  },
  body: JSON.stringify({
    orderReference: `ORDER-${Date.now()}`,
    amount: '100.00',
    currency: 'USDT',
    chainId: 'ethereum-sepolia',
    metadata: {
      userId: 'user_123',
      productId: 'prod_456'
    },
    successUrl: 'https://yoursite.com/payment/success',
    cancelUrl: 'https://yoursite.com/payment/cancel'
  })
});

const order = await response.json();
console.log('Order created:', order.orderId);
console.log('Payment address:', order.address);

Example (Python):

python
import requests
import time

response = requests.post(
    'https://testnet.payin.com/api/v1/orders',
    headers={
        'Content-Type': 'application/json',
        'X-API-Key': os.getenv('PAYIN_API_KEY')
    },
    json={
        'orderReference': f'ORDER-{int(time.time())}',
        'amount': '100.00',
        'currency': 'USDT',
        'chainId': 'ethereum-sepolia',
        'metadata': {
            'userId': 'user_123',
            'productId': 'prod_456'
        }
    }
)

order = response.json()
print(f"Order ID: {order['orderId']}")
print(f"Payment address: {order['address']}")

Get Order

Retrieve details of a specific order.

Endpoint:

GET /api/v1/orders/:orderId

Path Parameters:

ParameterTypeDescription
orderIdstringOrder ID (e.g., ord_abc123def456)

Response (200 OK):

json
{
  "orderId": "ord_abc123def456",
  "orderReference": "ORDER-2025-001",
  "amount": "100.00",
  "currency": "USDT",
  "chainId": "ethereum-sepolia",
  "protocol": "evm",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
  "status": "completed",
  "metadata": {
    "customerName": "John Doe"
  },
  "successUrl": "https://yoursite.com/order/success",
  "createdAt": "2025-01-20T10:30:00.000Z",
  "expiresAt": "2025-01-20T10:40:00.000Z",
  "completedAt": "2025-01-20T10:35:22.000Z",
  "txHash": "0xabc123...",
  "blockNumber": 1234567,
  "confirmations": 12
}

Example (cURL):

bash
curl https://testnet.payin.com/api/v1/orders/ord_abc123def456 \
  -H "X-API-Key: your-api-key"

Example (TypeScript):

typescript
const response = await fetch(
  `https://testnet.payin.com/api/v1/orders/${orderId}`,
  {
    headers: {
      'X-API-Key': process.env.PAYIN_API_KEY!
    }
  }
);

const order = await response.json();
console.log('Order status:', order.status);

if (order.status === 'completed') {
  console.log('Payment confirmed!');
  console.log('Transaction:', order.txHash);
}

Get Order by Reference

Retrieve order using your own order reference.

Endpoint:

GET /api/v1/orders/by-reference/:orderReference

Path Parameters:

ParameterTypeDescription
orderReferencestringYour order reference

Response: Same as "Get Order"

Example:

bash
curl https://testnet.payin.com/api/v1/orders/by-reference/ORDER-2025-001 \
  -H "X-API-Key: your-api-key"

List Orders

Get a paginated list of all orders.

Endpoint:

GET /api/v1/orders

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger20Items per page (max: 100)
statusstring-Filter by status: pending, completed, expired
currencystring-Filter by currency: USDT, USDC, DAI
chainIdstring-Filter by chain
orderReferencestring-Search by order reference
sortBystringcreatedAtSort field
sortOrderstringdescSort direction: asc, desc

Response (200 OK):

json
{
  "data": [
    {
      "orderId": "ord_abc123def456",
      "orderReference": "ORDER-2025-001",
      "amount": "100.00",
      "currency": "USDT",
      "chainId": "ethereum-sepolia",
      "address": "0x742d35...",
      "status": "completed",
      "createdAt": "2025-01-20T10:30:00.000Z",
      "completedAt": "2025-01-20T10:35:22.000Z"
    },
    // ... more orders
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 157,
    "totalPages": 8,
    "hasMore": true
  }
}

Example (cURL):

bash
# Get pending orders
curl "https://testnet.payin.com/api/v1/orders?status=pending&limit=10" \
  -H "X-API-Key: your-api-key"

# Get USDT orders sorted by amount
curl "https://testnet.payin.com/api/v1/orders?currency=USDT&sortBy=amount&sortOrder=desc" \
  -H "X-API-Key: your-api-key"

Example (TypeScript):

typescript
// Get all pending orders
const response = await fetch(
  'https://testnet.payin.com/api/v1/orders?status=pending',
  {
    headers: {
      'X-API-Key': process.env.PAYIN_API_KEY!
    }
  }
);

const { data: orders, pagination } = await response.json();

console.log(`Found ${pagination.total} pending orders`);
orders.forEach(order => {
  console.log(`${order.orderReference}: ${order.amount} ${order.currency}`);
});

Order Status Flow

Orders transition through the following statuses:

pending → completed (payment received and confirmed)
pending → expired (payment window expired without payment)

Status Definitions:

StatusDescriptionTerminal
pendingOrder created, awaiting paymentNo
completedPayment received and confirmedYes
expiredPayment window expired without valid paymentYes

Webhook Events

  • order.completed - Fired when order receives confirmed payment
  • order.expired - Fired when order expires without payment

See Webhooks Guide for details.

Payment Window

Orders have a configurable payment window (default: 10 minutes):

Timeline:

Order Created → Payment Window (10 min) → Grace Period (5 min) → Expiration
     ↓                                                                  ↓
  pending                                                           expired
  • Payment Window: User should send payment within this time
  • Grace Period: Extra time for transaction confirmation
  • After Expiration: Order cannot be paid, create a new order

Configuration:

bash
# Set payment window to 15 minutes
PUT /api/v1/config/payment_window_minutes
{
  "value": 15
}

Metadata

Store custom data with orders (max 10 key-value pairs):

json
{
  "metadata": {
    "userId": "user_123",
    "productId": "prod_456",
    "productName": "Premium Subscription",
    "customerEmail": "user@example.com",
    "internalNotes": "VIP customer"
  }
}

Use Cases:

  • Link order to your internal systems
  • Store customer information
  • Track product details
  • Add internal notes

Metadata Privacy

Metadata is returned in API responses and webhooks. Don't store sensitive information like passwords or full credit card numbers.

Redirect URLs

Configure where to send users after payment:

json
{
  "successUrl": "https://yoursite.com/order/success?orderId={{orderId}}",
  "cancelUrl": "https://yoursite.com/order/cancel?orderId={{orderId}}"
}

URL Parameters Added:

  • orderReference - Your order reference
  • status - Order status
  • amount - Payment amount
  • currency - Payment currency
  • chainId - Blockchain chain
  • txHash - Transaction hash (on success)

Example Redirect:

https://yoursite.com/order/success
  ?orderReference=ORDER-2025-001
  &status=completed
  &amount=100.00
  &currency=USDT
  &chainId=ethereum-sepolia
  &txHash=0xabc123...

Fallback to Global Config: If not specified, uses global defaults:

  • default_order_success_url
  • default_order_cancel_url

Error Responses

Duplicate Order Reference (409 Conflict)

json
{
  "error": "Duplicate order reference",
  "message": "Order with reference 'ORDER-2025-001' already exists",
  "code": "DUPLICATE_ORDER_REFERENCE",
  "statusCode": 409,
  "existingOrderId": "ord_abc123"
}

Solution: Use a unique order reference or fetch the existing order.

No Available Addresses (503 Service Unavailable)

json
{
  "error": "No available addresses",
  "message": "Address pool exhausted for protocol 'evm'",
  "code": "NO_AVAILABLE_ADDRESSES",
  "statusCode": 503
}

Solution: Import more addresses to the pool. See Address Management.

Invalid Amount (400 Bad Request)

json
{
  "error": "Invalid amount",
  "message": "Amount must be a positive number",
  "code": "INVALID_AMOUNT",
  "statusCode": 400
}

Solution: Provide a valid positive decimal string (e.g., "100.00").

Invalid Chain (400 Bad Request)

json
{
  "error": "Invalid chain",
  "message": "Chain 'invalid-chain' is not supported",
  "code": "INVALID_CHAIN",
  "statusCode": 400
}

Solution: Use a valid chain ID from Supported Networks.

Best Practices

1. Use Unique Order References

typescript
// ✅ GOOD: Timestamp-based reference
const orderReference = `ORDER-${Date.now()}-${userId}`;

// ✅ GOOD: UUID-based reference
import { v4 as uuidv4 } from 'uuid';
const orderReference = `ORDER-${uuidv4()}`;

// ❌ BAD: Sequential numbers (can collide)
const orderReference = `ORDER-${orderCount}`;

2. Handle Errors Gracefully

typescript
try {
  const response = await createOrder(orderData);
  return response;
} catch (error) {
  if (error.code === 'DUPLICATE_ORDER_REFERENCE') {
    // Fetch existing order instead
    return await getOrderByReference(orderData.orderReference);
  }
  throw error;
}

3. Store Order ID

typescript
// Save PayIn order ID to your database
await db.orders.update({
  where: { id: yourOrderId },
  data: {
    payinOrderId: order.orderId,
    paymentAddress: order.address,
    paymentStatus: 'pending'
  }
});

4. Use Webhooks

Don't poll for order status. Use webhooks instead:

typescript
// ❌ BAD: Polling
setInterval(async () => {
  const order = await getOrder(orderId);
  if (order.status === 'completed') {
    // Handle completion
  }
}, 5000);

// ✅ GOOD: Webhook
app.post('/webhooks/payin', async (req, res) => {
  const event = req.body;

  if (event.type === 'order.completed') {
    await handleOrderCompletion(event.data);
  }

  res.json({ received: true });
});

5. Set Appropriate Payment Windows

typescript
// Short-lived items (checkout)
const order = await createOrder({
  ...orderData,
  // Use global config (10 min default)
});

// High-value items (consider longer window)
// Configure globally via config API

Next Steps

Released under the MIT License.