> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stablegenius.co/llms.txt
> Use this file to discover all available pages before exploring further.

# POS Terminal Integration

> Step-by-step guide to adding stablecoin payments to your POS terminal

# POS Terminal Integration

This guide walks through integrating Stable Genius payments into a physical point-of-sale terminal. By the end, your terminal will display a QR code, detect payment, and confirm the sale — all through two API calls.

## Overview

The integration pattern for POS terminals is:

1. Your terminal creates a payment intent when the cashier finalizes an order
2. The terminal displays the QR code on screen
3. The customer scans and pays from their wallet
4. Your terminal receives a webhook and shows a success screen

## Prerequisites

* Stable Genius API key (`sk_test_*` for development)
* A merchant onboarded via the [dashboard](https://app.stablegenius.co)
* A terminal with a screen capable of displaying QR codes
* Network connectivity (WiFi or cellular) for API calls and webhooks

## Step 1: Create Payment Intent at Checkout

When the cashier presses "Pay" or "Charge," your terminal calls the API:

```javascript theme={null}
// Terminal-side code (runs on your POS hardware)
async function initiatePayment(amount, orderId) {
  const response = await fetch('https://api.stablegenius.co/v1/payment-intents', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: amount,
      currency: 'usd',
      merchant_id: MERCHANT_ID,
      metadata: {
        order_id: orderId,
        terminal_id: TERMINAL_ID,
      },
    }),
  });

  return response.json();
}
```

## Step 2: Display QR Code

Render the QR code on the terminal screen. You have two options:

**Option A: Render from payload** (recommended for terminals with QR libraries)

```javascript theme={null}
import QRCode from 'qrcode'; // or your platform's QR library

const paymentIntent = await initiatePayment(4.50, 'order_456');
const qrImage = await QRCode.toDataURL(paymentIntent.qr_payload, {
  width: 300,
  margin: 2,
  color: { dark: '#000000', light: '#FFFFFF' },
});

// Display qrImage on terminal screen
displayOnScreen(qrImage, {
  amount: paymentIntent.amount,
  expiresAt: paymentIntent.expires_at,
});
```

**Option B: Display hosted image** (simpler, no QR library needed)

```javascript theme={null}
const paymentIntent = await initiatePayment(4.50, 'order_456');

// Display the hosted QR image directly
displayImageOnScreen(paymentIntent.qr_image_url, {
  amount: paymentIntent.amount,
});
```

## Step 3: Wait for Payment Confirmation

Your backend receives the webhook when the customer pays. Push the confirmation to the terminal:

```javascript theme={null}
// Your backend server
app.post('/webhooks/stablegenius', (req, res) => {
  res.status(200).json({ received: true });

  const event = req.body;

  if (event.type === 'payment_intent.confirmed') {
    const { metadata, amount, tx_hash } = event.data;

    // Notify the specific terminal
    pushToTerminal(metadata.terminal_id, {
      type: 'payment_confirmed',
      amount: amount,
      tx_hash: tx_hash,
      order_id: metadata.order_id,
    });
  }

  if (event.type === 'payment_intent.expired') {
    const { metadata } = event.data;
    pushToTerminal(metadata.terminal_id, {
      type: 'payment_expired',
      order_id: metadata.order_id,
    });
  }
});
```

## Step 4: Show Success on Terminal

When the terminal receives the confirmation push:

```javascript theme={null}
// Terminal-side code
onPaymentUpdate((update) => {
  switch (update.type) {
    case 'payment_confirmed':
      showSuccessScreen({
        amount: update.amount,
        message: 'Payment received!',
      });
      playSuccessSound();
      printReceipt(update);
      break;

    case 'payment_expired':
      showExpiredScreen({
        message: 'Payment timed out. Tap to retry.',
      });
      break;
  }
});
```

## Terminal-to-Server Communication

The webhook fires to your backend, not directly to the terminal. You need a way to push updates from your backend to the terminal. Common patterns:

| Method                   | Best For                   | Latency     |
| ------------------------ | -------------------------- | ----------- |
| WebSocket                | Always-connected terminals | Under 100ms |
| MQTT                     | IoT/embedded terminals     | Under 200ms |
| Server-Sent Events (SSE) | Web-based POS              | Under 500ms |
| Polling                  | Simple integrations        | 1-3s        |

<Tip>
  Need the lowest latency? Contact us about direct integration options for real-time payment notifications on your terminals.
</Tip>

## Handling Edge Cases

**Customer sends wrong amount:** The payment intent tracks the expected amount. If the customer sends more or less, the `transaction.created` webhook fires with the actual amount. Your system should reconcile and handle over/underpayments per your business logic.

**Network disconnection:** If the terminal loses connectivity after displaying the QR code, the payment still works — the customer's on-chain transaction is independent of your terminal's connection. When connectivity resumes, the webhook will be retried and delivered.

**Terminal reboot mid-payment:** Store the active `payment_intent_id` locally. On reboot, call `GET /v1/payment-intents/{id}` to check if payment was received while offline.

<Info>
  **Building on Android?** Our first-party POS terminal (Genie) runs on Android. Contact us about licensing the terminal software or building on our hardware platform.
</Info>
