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 merchant-specific credentials.
NFC Cards Read contactless OLIVE cards
HMAC Auth Secure signature-based authentication
Real-time Instant payment processing
Prerequisites
Merchant Registration
Register as a processor via /processors endpoint (admin only)
Receive Credentials
Obtain API key and HMAC secret (shown once at creation)
Terminal Setup
Configure terminal with credentials
Test Transaction
Perform test payment in sandbox
API key and HMAC secret are only shown once at processor creation. Store them securely!
Authentication
POS terminals use HMAC-SHA256 signatures for authentication.
Header Description Example X-Client-IDProcessor/merchant ID proc_abc123X-TimestampUnix timestamp (seconds) 1704067200X-SignatureHMAC-SHA256 signature a1b2c3d4e5...
Signature Generation
const crypto = require ( 'crypto' );
function signRequest ( clientId , hmacSecret , method , path , body ) {
const timestamp = Math . floor ( Date . now () / 1000 ). toString ();
// Canonical string: METHOD|PATH|BODY|TIMESTAMP
const canonicalString = body
? ` ${ method } | ${ path } | ${ JSON . stringify ( body ) } | ${ timestamp } `
: ` ${ method } | ${ path } || ${ timestamp } ` ;
const signature = crypto
. createHmac ( 'sha256' , hmacSecret )
. update ( canonicalString )
. digest ( 'hex' );
return {
'X-Client-ID' : clientId ,
'X-Timestamp' : timestamp ,
'X-Signature' : signature ,
'Content-Type' : 'application/json'
};
}
// Example usage
const headers = signRequest (
'proc_abc123' ,
'secret_xyz789' ,
'POST' ,
'/api/v1/pos/payment' ,
{ card_serial: 'OLIV0001' , amount: 5000 , pin: '1234' }
);
Signatures are valid for 5 minutes. Ensure your terminal clock is synchronized with NTP.
Payment Flow
Card Tap
Customer taps NFC card on terminal
Read Card
Terminal reads card serial number via NFC
Verify Card
Call POST /pos/verify-card to validate card
Collect PIN
If valid, prompt customer for 4-digit PIN
Process Payment
Call POST /pos/payment with amount and PIN
Display Result
Show success/failure message to customer
Sequence Diagram
API Endpoints
Verify Card
Check if card is valid and get cardholder info:
curl -X POST "https://olive-gateway-a6ba.onrender.com/api/v1/pos/verify-card" \
-H "X-Client-ID: proc_abc123" \
-H "X-Timestamp: 1704067200" \
-H "X-Signature: a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{
"card_serial": "OLIV0001"
}'
Response:
{
"success" : true ,
"card" : {
"serial" : "OLIV0001" ,
"status" : "active" ,
"cardholder" : "John Doe" ,
"balance" : 150000 ,
"currency" : "SLE"
}
}
Process Payment
Execute payment after PIN verification:
curl -X POST "https://olive-gateway-a6ba.onrender.com/api/v1/pos/payment" \
-H "X-Client-ID: proc_abc123" \
-H "X-Timestamp: 1704067200" \
-H "X-Signature: a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{
"card_serial": "OLIV0001",
"amount": 5000,
"pin": "1234",
"reference": "INV-12345"
}'
Response:
{
"success" : true ,
"transaction_id" : "txn_abc123" ,
"message" : "Payment successful" ,
"data" : {
"amount" : 5000 ,
"fee" : 50 ,
"currency" : "SLE" ,
"cardholder" : "John Doe" ,
"new_balance" : 145000 ,
"timestamp" : "2025-01-15T10:30:00Z"
}
}
Refund Payment
Refund a previous transaction:
curl -X POST "https://olive-gateway-a6ba.onrender.com/api/v1/pos/refund" \
-H "X-Client-ID: proc_abc123" \
-H "X-Timestamp: 1704067200" \
-H "X-Signature: a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{
"original_transaction_id": "txn_abc123",
"amount": 5000,
"reason": "Customer request"
}'
Error Handling
Error Code Meaning Terminal Action CARD_NOT_FOUNDCard serial not in system Ask for different card CARD_BLOCKEDCard is blocked/disabled Card cannot be used INSUFFICIENT_BALANCENot enough funds Show balance, reduce amount INVALID_PINWrong PIN entered Retry (max 3 attempts) SIGNATURE_EXPIREDTimestamp too old Resync terminal clock INVALID_SIGNATUREHMAC verification failed Check credentials
Error Response Example
{
"success" : false ,
"error" : {
"code" : "INSUFFICIENT_BALANCE" ,
"message" : "Cardholder has insufficient balance" ,
"details" : {
"requested" : 50000 ,
"available" : 15000
}
}
}
Terminal Implementation
Sample Terminal Code
const axios = require ( 'axios' );
const crypto = require ( 'crypto' );
class OlivePOS {
constructor ( clientId , hmacSecret , baseUrl ) {
this . clientId = clientId ;
this . hmacSecret = hmacSecret ;
this . baseUrl = baseUrl ;
}
signRequest ( method , path , body ) {
const timestamp = Math . floor ( Date . now () / 1000 ). toString ();
const canonical = body
? ` ${ method } | ${ path } | ${ JSON . stringify ( body ) } | ${ timestamp } `
: ` ${ method } | ${ path } || ${ timestamp } ` ;
const signature = crypto
. createHmac ( 'sha256' , this . hmacSecret )
. update ( canonical )
. digest ( 'hex' );
return {
'X-Client-ID' : this . clientId ,
'X-Timestamp' : timestamp ,
'X-Signature' : signature ,
'Content-Type' : 'application/json'
};
}
async verifyCard ( cardSerial ) {
const path = '/api/v1/pos/verify-card' ;
const body = { card_serial: cardSerial };
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 , reference ) {
const path = '/api/v1/pos/payment' ;
const body = { card_serial: cardSerial , amount , pin , reference };
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 (
'proc_abc123' ,
'secret_xyz789' ,
'https://olive-gateway-a6ba.onrender.com'
);
// Verify card
const cardInfo = await pos . verifyCard ( 'OLIV0001' );
console . log ( `Cardholder: ${ cardInfo . card . cardholder } ` );
// Process payment
const result = await pos . processPayment ( 'OLIV0001' , 5000 , '1234' , 'INV-001' );
console . log ( `Transaction: ${ result . transaction_id } ` );
Best Practices
Security
Store HMAC secret securely (encrypted)
Use HTTPS only
Never log credentials
Implement PIN attempt limits
User Experience
Show cardholder name for verification
Display balance before payment
Print/show receipts
Clear error messages
Reliability
Handle network errors gracefully
Implement retry with same reference
Sync terminal clock with NTP
Queue failed transactions
Compliance
Log all transactions locally
Daily reconciliation
Keep receipts/records
Report issues promptly
Testing
Sandbox Environment
Use sandbox credentials for testing:
Environment Base URL Sandbox https://sandbox-api.olive.example.comProduction https://olive-gateway-a6ba.onrender.com
Test Cards
Card Serial PIN Behavior TEST00011234Success (balance: 100,000) TEST00021234Insufficient balance TEST0003any Card blocked