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

# POS Integration

> Complete guide for integrating POS terminals with OLIVE

# POS Integration Guide

Complete guide for integrating Point-of-Sale terminals with OLIVE for NFC card payments.

## Overview

POS (Point-of-Sale) terminals allow merchants to accept OLIVE card payments. Each terminal authenticates using HMAC signatures with a per-client API key ID and secret.

<CardGroup cols={3}>
  <Card title="NFC Cards" icon="credit-card">
    Read contactless OLIVE cards
  </Card>

  <Card title="HMAC Auth" icon="fingerprint">
    Secure signature-based authentication
  </Card>

  <Card title="Real-time" icon="bolt">
    Instant payment processing
  </Card>
</CardGroup>

***

## Prerequisites

<Steps>
  <Step title="Merchant Registration">
    Register as a processor via `/processors` endpoint (admin only)
  </Step>

  <Step title="Receive Credentials">
    Obtain API key and HMAC secret (shown once at creation)
  </Step>

  <Step title="Terminal Setup">
    Configure terminal with credentials
  </Step>

  <Step title="Test Transaction">
    Perform test payment in sandbox
  </Step>
</Steps>

<Warning>
  API key and HMAC secret are only shown once at processor creation. Store them securely!
</Warning>

***

## Authentication

POS terminals use HMAC-SHA256 signatures for authentication.

### Required Headers

| Header         | Description           | Example                |
| -------------- | --------------------- | ---------------------- |
| `X-API-Key-ID` | POS API key ID        | `pos_key_abc123`       |
| `X-Timestamp`  | RFC3339 timestamp     | `2026-03-10T12:00:00Z` |
| `X-Signature`  | HMAC-SHA256 signature | `a1b2c3d4e5...`        |

### Signature Generation

```javascript theme={null}
const crypto = require('crypto');

function signRequest(apiKeyId, hmacSecret, method, path, rawBody) {
  const timestamp = new Date().toISOString();
  const canonicalString = `${method}\n${path}\n${timestamp}\n${rawBody}`;
  
  const signature = crypto
    .createHmac('sha256', hmacSecret)
    .update(canonicalString)
    .digest('hex');
  
  return {
    'X-API-Key-ID': apiKeyId,
    'X-Timestamp': timestamp,
    'X-Signature': signature,
    'Content-Type': 'application/json'
  };
}

// Example usage
const headers = signRequest(
  'pos_key_abc123',
  'secret_xyz789',
  'POST',
  '/api/v1/pos/payment',
  JSON.stringify({
    card_serial: 'OLIV0001',
    merchant_id: 'MERCHANT001',
    terminal_id: 'TERM001',
    amount: '150.00',
    currency: 'SLE',
    transaction_ref: 'POS-TXN-123',
    pin: '1234',
    processor_id: 'proc-uuid-123'
  })
);
```

<Info>
  Signatures are valid for 5 minutes. Ensure your terminal clock is synchronized with NTP.
</Info>

<Info>
  The `/payment/*` routes (for in-app integrations) use the same HMAC authentication but do **not** require a `pin` field. The partner's HMAC signature provides the security instead of the cardholder PIN.
</Info>

***

## Payment Flow

<Steps>
  <Step title="Card Tap">
    Customer taps NFC card on terminal
  </Step>

  <Step title="Read Card">
    Terminal reads card serial number via NFC
  </Step>

  <Step title="Verify Card">
    Call `POST /api/v1/pos/verify-card` to validate card
  </Step>

  <Step title="Collect PIN">
    If valid, prompt customer for 4-digit PIN
  </Step>

  <Step title="Process Payment">
    Call `POST /api/v1/pos/payment` with amount and PIN
  </Step>

  <Step title="Display Result">
    Show success/failure message to customer
  </Step>
</Steps>

### Sequence Diagram

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant T as Terminal
    participant G as Gateway
    participant W as Wallet-Core

    C->>T: Tap NFC Card
    T->>G: POST /pos/verify-card
    G->>W: Validate Card
    W-->>G: Card Valid + Balance
    G-->>T: Cardholder Name + Balance
    T->>C: Enter PIN
    C->>T: PIN Input
    T->>G: POST /pos/payment
    G->>W: Process Payment
    W-->>G: Payment Result
    G-->>T: Transaction Receipt
    T->>C: Success Message
```

***

## API Endpoints

### Verify Card

Check if card is valid and get cardholder info:

```bash theme={null}
curl -X POST "https://demo.api.vultlocal.com/api/v1/pos/verify-card" \
  -H "X-API-Key-ID: pos_key_abc123" \
  -H "X-Timestamp: 2026-03-10T12:00:00Z" \
  -H "X-Signature: a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "card_serial": "OLIV0001",
    "pin": "1234"
  }'
```

**Response:**

```json theme={null}
{
  "success": true,
  "message": "Card verified successfully",
  "is_active": true,
  "balance": "1500.00",
  "holder_name": "John Doe"
}
```

### Process Payment

Execute payment after PIN verification:

```bash theme={null}
curl -X POST "https://demo.api.vultlocal.com/api/v1/pos/payment" \
  -H "X-API-Key-ID: pos_key_abc123" \
  -H "X-Timestamp: 2026-03-10T12:00:00Z" \
  -H "X-Signature: a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "card_serial": "OLIV0001",
    "merchant_id": "MERCHANT001",
    "terminal_id": "TERM001",
    "amount": "150.00",
    "currency": "SLE",
    "pin": "1234",
    "transaction_ref": "INV-12345",
    "processor_id": "proc-uuid-123"
  }'
```

**Response:**

`fee_amount` is dynamic and comes from the active POS fee configuration. If the merchant setup charges a fee for the transaction, this field returns that amount instead of `0.00`.

```json theme={null}
{
  "success": true,
  "message": "Payment successful",
  "transaction_id": "txn_abc123",
  "approval_code": "882211",
  "amount": "150.00",
  "remaining_balance": "1450.00",
  "fee_amount": "1.50"
}
```

### Refund Payment

Refund a previous transaction:

```bash theme={null}
curl -X POST "https://demo.api.vultlocal.com/api/v1/pos/refund" \
  -H "X-API-Key-ID: pos_key_abc123" \
  -H "X-Timestamp: 2026-03-10T12:00:00Z" \
  -H "X-Signature: a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "original_transaction_id": "txn_abc123",
    "amount": "150.00",
    "reason": "Customer request",
    "initiated_by": "admin_user_01"
  }'
```

***

## Error Handling

| Error Code         | Meaning                             | Terminal Action                            |
| ------------------ | ----------------------------------- | ------------------------------------------ |
| HMAC auth error    | Missing/invalid HMAC headers        | Check API key ID, timestamp, and signature |
| Validation error   | Missing or malformed request fields | Fix the request payload                    |
| Invalid PIN        | Wrong PIN entered                   | Retry with the correct PIN                 |
| Insufficient funds | Not enough balance                  | Reduce amount or use another card          |

### Error Response Example

```json theme={null}
{
  "success": false,
  "success": false,
  "error": "Insufficient funds"
}
```

***

## Terminal Implementation

### Sample Terminal Code

```javascript theme={null}
const axios = require('axios');
const crypto = require('crypto');

class OlivePOS {
  constructor(apiKeyId, hmacSecret, baseUrl) {
    this.apiKeyId = apiKeyId;
    this.hmacSecret = hmacSecret;
    this.baseUrl = baseUrl;
  }

  signRequest(method, path, body) {
    const timestamp = new Date().toISOString();
    const rawBody = JSON.stringify(body);
    const canonical = `${method}\n${path}\n${timestamp}\n${rawBody}`;
    
    const signature = crypto
      .createHmac('sha256', this.hmacSecret)
      .update(canonical)
      .digest('hex');
    
    return {
      'X-API-Key-ID': this.apiKeyId,
      'X-Timestamp': timestamp,
      'X-Signature': signature,
      'Content-Type': 'application/json'
    };
  }

  async verifyCard(cardSerial, pin) {
    const path = '/api/v1/pos/verify-card';
    const body = { card_serial: cardSerial, pin };
    const headers = this.signRequest('POST', path, body);
    
    const response = await axios.post(
      `${this.baseUrl}${path}`,
      body,
      { headers }
    );
    
    return response.data;
  }

  async processPayment(cardSerial, amount, pin, transactionRef) {
    const path = '/api/v1/pos/payment';
    const body = {
      card_serial: cardSerial,
      merchant_id: 'MERCHANT001',
      terminal_id: 'TERM001',
      amount,
      currency: 'SLE',
      pin,
      transaction_ref: transactionRef,
      processor_id: 'proc-uuid-123'
    };
    const headers = this.signRequest('POST', path, body);
    
    const response = await axios.post(
      `${this.baseUrl}${path}`,
      body,
      { headers }
    );
    
    return response.data;
  }
}

// Usage
const pos = new OlivePOS(
  'pos_key_abc123',
  'secret_xyz789',
  'https://demo.api.vultlocal.com'
);

// Verify card
const cardInfo = await pos.verifyCard('OLIV0001', '1234');
console.log(`Cardholder: ${cardInfo.holder_name}`);

// Process payment
const result = await pos.processPayment('OLIV0001', '150.00', '1234', 'INV-001');
console.log(`Transaction: ${result.transaction_id}`);
```

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Security" icon="shield">
    * Store HMAC secret securely (encrypted)
    * Use HTTPS only
    * Never log credentials
    * Implement PIN attempt limits
  </Card>

  <Card title="User Experience" icon="user">
    * Show cardholder name for verification
    * Display balance before payment
    * Print/show receipts
    * Clear error messages
  </Card>

  <Card title="Reliability" icon="refresh">
    * Handle network errors gracefully
    * Implement retry with same reference
    * Sync terminal clock with NTP
    * Queue failed transactions
  </Card>

  <Card title="Compliance" icon="clipboard-check">
    * Log all transactions locally
    * Daily reconciliation
    * Keep receipts/records
    * Report issues promptly
  </Card>
</CardGroup>

***

## Testing

### Sandbox Environment

Use sandbox credentials for testing:

| Environment | Base URL                                |
| ----------- | --------------------------------------- |
| Sandbox     | `https://sandbox-api.olive.example.com` |
| Demo        | `https://demo.api.vultlocal.com`        |

### Test Cards

| Card Serial | PIN    | Behavior                   |
| ----------- | ------ | -------------------------- |
| `TEST0001`  | `1234` | Success (balance: 100,000) |
| `TEST0002`  | `1234` | Insufficient balance       |
| `TEST0003`  | any    | Card blocked               |

***

## Related

<CardGroup cols={2}>
  <Card title="POS Payment API" icon="credit-card" href="/api-reference/pos/payment">
    Payment endpoint reference
  </Card>

  <Card title="Verify Card API" icon="check" href="/api-reference/pos/verify-card">
    Card verification endpoint
  </Card>

  <Card title="Processor Setup" icon="building" href="/api-reference/processors/create">
    Register as processor
  </Card>

  <Card title="Security Guide" icon="shield" href="/reference/security">
    HMAC authentication details
  </Card>
</CardGroup>
