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

# POS API Integration Guide

> Integrate with the UQPAY POS API for card-present transactions, including terminal registration, PIN key management, and payment processing.

This guide explains how to integrate with the UQPAY POS API for card present transactions. It covers terminal registration, PIN key management, PIN block encryption, and payment processing.

## 1. Overview

### 1.1 Prerequisites

Before starting the integration, ensure you have:

* Provide your UQPAY account ID to enable the POS API feature

### 1.2 Integration Flow

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        POS API Integration Flow                         │
└─────────────────────────────────────────────────────────────────────────┘

Step 1: Terminal Registration (One time setup)
    POS Terminal ──► Register Terminal API ──► Receive terminal_id (TID)

Step 2: PIN Key Setup
    Generate AES Key ──► Get PIN Key API ──► Decrypt to get PINKEY

Step 3: Process Payment
    Read Card ──► Encrypt PIN ──► Create Payment Intent ──► Receive Result
```

***

## 2. Terminal Registration

Each POS terminal must be registered with UQPAY before processing transactions. This is a one time setup; you only need to register each terminal once.

### 2.1 API Endpoint

```
POST /v2/terminal/register
```

API Reference: [Register Terminal](/global-acquiring/v1.6/api-reference/register-terminal)

### 2.2 Request Parameters

| Field            | Type   | Required | Description                              | Example               |
| ---------------- | ------ | -------- | ---------------------------------------- | --------------------- |
| `firm_code`      | string | Yes      | Terminal manufacturer code               | `"03"`                |
| `firm_sn`        | string | Yes      | Terminal serial number from manufacturer | `"TG676SX1764902333"` |
| `terminal_model` | string | Yes      | Terminal model name                      | `"P1000"`             |

**Supported Manufacturer Codes:**

| Code | Manufacturer |
| ---- | ------------ |
| `01` | Shengben     |
| `02` | Newland      |
| `03` | Xinguodu     |
| `04` | iMin         |
| `05` | PAX          |

### 2.3 Request Example

```json theme={null}
{
    "firm_code": "03",
    "firm_sn": "TG676SX1764902333",
    "terminal_model": "P1000"
}
```

### 2.4 Response

| Field         | Type   | Description                              |
| ------------- | ------ | ---------------------------------------- |
| `terminal_id` | string | UQPAY assigned terminal identifier (TID) |
| `firm_sn`     | string | Terminal serial number                   |
| `create_time` | string | Registration timestamp                   |

```json theme={null}
{
    "create_time": "2025-12-05 10:38:53",
    "firm_sn": "TG676SX1764902333",
    "terminal_id": "10000254"
}
```

> **Note:** Store the `terminal_id` securely. You will need it for all subsequent API calls.

***

## 3. PIN Key Management

To securely process PIN based transactions, you must obtain and manage PIN encryption keys.

### 3.1 Generate AES Private Key

Before requesting a PIN key, generate an AES private key on your terminal or server:

| Requirement | Specification                      |
| ----------- | ---------------------------------- |
| Algorithm   | AES                                |
| Key Length  | Up to 256 bits (64 hex characters) |
| Format      | Hexadecimal string                 |

**Example AES Key (256 bit):**

```
5214abf357f2cc47645c4d942c02fd0180acc6d8de8fae3708849b6a459cba00
```

> **Important:** Store this private key securely. You will use it to decrypt the `encrypt_pin_key` returned from the Get PIN Key API.

### 3.2 Request PIN Key

**API Endpoint:**

```
POST /v2/terminal/getPinKey
```

API Reference: [Get PIN Key](/global-acquiring/v1.6/api-reference/get-pin-key)

**Request Parameters:**

| Field         | Type   | Required | Description                                          | Example                                                              |
| ------------- | ------ | -------- | ---------------------------------------------------- | -------------------------------------------------------------------- |
| `terminal_id` | string | Yes      | Terminal ID received from registration               | `"10000254"`                                                         |
| `prv_key`     | string | Yes      | Your AES private key (hex format, max 64 characters) | `"5214abf357f2cc47645c4d942c02fd0180acc6d8de8fae3708849b6a459cba00"` |

**Request Example:**

```json theme={null}
{
    "terminal_id": "10000254",
    "prv_key": "5214abf357f2cc47645c4d942c02fd0180acc6d8de8fae3708849b6a459cba00"
}
```

**Response:**

| Field             | Type    | Description                                     |
| ----------------- | ------- | ----------------------------------------------- |
| `terminal_id`     | string  | Terminal identifier                             |
| `encrypt_pin_key` | string  | Encrypted PIN key (Base64 encoded)              |
| `pin_key_expire`  | integer | PIN key expiration timestamp (**milliseconds**) |

```json theme={null}
{
    "encrypt_pin_key": "LASDho1ILHoYRf/5YEyIgieoc+SXJkUsHZElXOMNGv7WnC3fZzFPYDH8mJaDbnwvom3QEtdv3NjvaEUtVeetWdQsv2RtQw4XEYh/Cg==",
    "pin_key_expire": 1764990056170,
    "terminal_id": "10000254"
}
```

### 3.3 Decrypt PIN Key

Use your AES private key to decrypt the `encrypt_pin_key` and obtain the actual PINKEY.

**Decryption Parameters:**

| Parameter | Value             |
| --------- | ----------------- |
| Algorithm | AES-256-GCM       |
| Input     | `encrypt_pin_key` |
| Key       | Your `prv_key`    |
| Output    | PINKEY            |

**Example:**

* encrypt\_pin\_key: `LASDho1ILHoYRf/5YEyIgieoc+SXJkUsHZElXOMNGv7WnC3fZzFPYDH8mJaDbnwvom3QEtdv3NjvaEUtVeetWdQsv2RtQw4XEYh/Cg==`
* actual PINKEY: `26e1734e4d52b905e74574b7bf0897daeec7e217c61b3ac0`

> See Appendix for decryption code samples.

### 3.4 Key Expiration

Monitor the `pin_key_expire` timestamp and refresh the PIN key before expiration. Request a new PIN key using the same process when needed.

***

## 4. PIN Block Encryption

When processing PIN based transactions, you must encrypt the cardholder PIN into a PIN block.

### 4.1 Overview

The PIN block is created by combining:

* Card PAN (Primary Account Number)
* Cardholder PIN
* PINKEY

### 4.2 Example

**Input:**

| Parameter   | Value                                              |
| ----------- | -------------------------------------------------- |
| Card Number | `5554748800260229`                                 |
| PIN         | `302312`                                           |
| PINKEY      | `26e1734e4d52b905e74574b7bf0897daeec7e217c61b3ac0` |

**Output:**

| Parameter | Value              |
| --------- | ------------------ |
| PINBLOCK  | `4F5D12303995DD06` |

> See Appendix for PIN block encryption code samples.

***

## 5. Create Payment Intent

After setting up your terminal and obtaining the PINKEY, you can process card present transactions.

### 5.1 API Endpoint

```
POST /v2/payment_intents/create
```

API Reference: [Create Payment Intent](/global-acquiring/v1.6/api-reference/create-payment-intent)

### 5.2 Request Example

Use the `card_present` payment method type for POS transactions. Key fields to note:

* `terminal_id`: Set to the TID received from terminal registration
* `encrypted_pin`: Set to the PINBLOCK generated from PIN encryption
* `system_trace_audit_number`: Must be unique for each new transaction (STAN)

```json theme={null}
{
    "amount": "2.22",
    "currency": "SGD",
    "payment_method": {
        "type": "card_present",
        "card_present": {
            "card_name": "mastercard 0229",
            "card_number": "5554748800260229",
            "expiry_month": "03",
            "expiry_year": "2026",
            "cardholder_verification_method": "online_pin",
            "encrypted_pin": "4F5D12303995DD06",
            "pan_entry_mode": "contactless_chip",
            "fallback": false,
            "fallback_reason": "",
            "emv_tags": "9F39010757135554748800260229D26032211837752200000F820219809F360202CE9F1E0831303030303030319F10120110A04003240000000000000000000000FF9F3303E060C89F350122950500000480019F0106A000000000019F02060000000500005F24032603315A0855547488002602295F3401009F150258129F160F3130303030303031303030303030309F1A0207029F1C08313030303030303181040000C3505F2A0207029A032512029F21031418059C01005F280204409F120A4D6173746572636172649F1101015F2D02656E9F42020978940810010101200104008701019F0A04000101049F080200028C279F02069F03069F1A0295055F2A029A039C019F37049F35019F45029F4C089F34039F21039F7C149F4C08CF57740F41E613609F4A01825F300202219F26088BD046AF7A2D8D9A9F2701809F34030203009F37041DF853909F03060000000000005F25032303019F4104000000009F0702FFC08E0E000000000000000002031E031F039F0D05B4508400009F0E0500000000009F0F05B4708480004F07A00000000410108407A00000000410109F090200029B02E0009F4E083130303030303031500A4D6173746572636172649F4005F000F0F0019F0607A00000000410109F6E0704400000323000D4030000029908EA53B2F5A7D54E309F72080000000200000000",
            "track1": "",
            "track2": "5554748800260229D26032211837752200000",
            "terminal_info": {
                "mobile_device": false,
                "system_trace_audit_number": "000003",
                "terminal_id": "10000254",
                "use_embedded_reader": true
            }
        }
    },
    "merchant_order_id": "{{$guid}}",
    "description": "pos api order from sandbox",
    "metadata": {
        "request_id": "{{$guid}}"
    },
    "return_url": "https://return-url/api/v2/callback"
}
```

### 5.3 Response Example

```json theme={null}
{
    "amount": "2.22",
    "available_payment_method_types": null,
    "cancel_time": "",
    "cancellation_reason": "",
    "captured_amount": "2.22",
    "client_secret": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtYXN0ZXJfaWQiOiIwIiwiYWNjb3VudF9pZCI6ImIxYjg5Njg0LWMyYzQtNGQ1NC1iOGE4LTM1NzI3MjdmZDEyMCIsImludGVudF9pZCI6IlBJMTk5Njc5NTM0MjE0OTkxNDYyNCIsImV4cCI6MTc2NDkwOTg4MiwiaWF0IjoxNzY0OTA4MDgyfQ.tUAF1IHWm-payNAES4qOv_Vngbm3mn5z217DayihgLI",
    "complete_time": "2025-12-05T12:14:42+08:00",
    "create_time": "2025-12-05T12:14:42+08:00",
    "currency": "SGD",
    "description": "pos api order from sandbox",
    "intent_status": "SUCCEEDED",
    "latest_payment_attempt": {
        "amount": "2.22",
        "attempt_id": "PA1996795342275743744",
        "attempt_status": "CAPTURE_REQUESTED",
        "captured_amount": "2.22",
        "complete_time": "2025-12-05T12:14:42+08:00",
        "create_time": "2025-12-05T12:14:42+08:00",
        "currency": "SGD",
        "failure_code": "",
        "refunded_amount": "0",
        "update_time": "2025-12-05T12:14:42+08:00"
    },
    "merchant_order_id": "76bcc9a2-0aad-467a-aded-06c0c03bdced",
    "metadata": {
        "request_id": "3e635c58-8774-4425-84a9-42a602299576"
    },
    "next_action": null,
    "payment_intent_id": "PI1996795342149914624",
    "return_url": "https://return-url/api/v2/callback",
    "update_time": "2025-12-05T12:14:42+08:00"
}
```

***

## 6. Error Handling

### 6.1 POS API Error Codes

| Error Code                       | Error Message                  |
| -------------------------------- | ------------------------------ |
| `account_pos_api_no_permission`  | Account pos api no permission  |
| `terminal_not_belong_to_account` | Terminal not belong to account |
| `terminal_register_failed`       | Terminal register failed       |
| `terminal_info_not_found`        | Terminal info not found        |
| `pinkey_retrieve_failed`         | Pinkey retrieve failed         |

***

## 7. Testing

### 7.1 Test Card

Use the following test card data in the sandbox environment:

| Field        | Value              |
| ------------ | ------------------ |
| Card Number  | `5554748800161559` |
| Expiry Month | `07`               |
| Expiry Year  | `2027`             |
| PIN (raw)    | \`\`               |
| Card Brand   | Mastercard         |

**Sample card\_present object for testing:**

```json theme={null}
{
        "type": "card_present",
        "card_present": {
            "card_name": "uqpay cardholder",
            "card_number": "5554748800161559",
            "expiry_month": "07",
            "expiry_year": "2027",
            "cardholder_verification_method": "online_pin",
            // "encrypted_pin": "D6CD73498446A0C9",
            "pan_entry_mode": "contactless_chip",
            "fallback": false,
            "fallback_reason": "",
            "emv_tags": "9F39010757135554748800161559D25042211103236300000F820219809F3602051B9F1E0831303030303135319F10120110A00003240000000000000000000000FF9F3303E008C89F350122950504400080019F0106A000000000019F02060000000000015F24032504305A0855547488001615595F3401009F150258129F160F3130303030313531303030303030309F1A0207029F1C0831303030303135318104000000015F2A0207029A032512059F21031652269C01005F280204409F42020978940810010101200103008701019F0A04000101049F080200028C279F02069F03069F1A0295055F2A029A039C019F37049F35019F45029F4C089F34039F21039F7C149F4A01825F300202219F260817DE07EBE32C04859F2701809F34031F03029F3704C18B521B9F03060000000000005F25032204019F4104000000009F0702FFC08E0E000000000000000002031E031F039F0D05B4508400009F0E0500000000009F0F05B4708480004F07A00000000410108407A00000000410109F090200029B0260009F4E083130303030313531500A4D6173746572636172649F4005F000F0F0019F0607A00000000410109F6E0704400000323000D4034000019F72080000000000000000",
            "track1": "",
            "track2": "5554748800161559D25042211103236300000",
            "terminal_info": {
                "mobile_device": false,
                "system_trace_audit_number": "000014",
                "terminal_id": "10000267",
                "use_embedded_reader": true
            }
        }
    }
```

> **Note:** Remember to use a unique `system_trace_audit_number` for each test transaction.

***

## 8. Appendix

### 8.1 Code Samples

#### AES-256-GCM Decryption (Python)

```python theme={null}
# aes_decrypt.py
import sys
import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

def aes_decrypt(prv_key: str, encrypt_pin_key: str) -> str:
    key = bytes.fromhex(prv_key)
    ciphertext = base64.b64decode(encrypt_pin_key)
    nonce = ciphertext[:12]
    tag = ciphertext[-16:]
    ct = ciphertext[12:-16]
    cipher = Cipher(algorithms.AES(key), modes.GCM(nonce, tag), backend=default_backend())
    decryptor = cipher.decryptor()
    plaintext = decryptor.update(ct) + decryptor.finalize()
    return plaintext.decode("utf-8")

if __name__ == "__main__":
    print(aes_decrypt(sys.argv[1], sys.argv[2]))
```

```bash theme={null}
# Usage
python3 aes_decrypt.py <prv_key> <encrypt_pin_key>

# Example
python3 aes_decrypt.py 5214abf357f2cc47645c4d942c02fd0180acc6d8de8fae3708849b6a459cba00 "LASDho1ILHoYRf/5YEyIgieoc+SXJkUsHZElXOMNGv7WnC3fZzFPYDH8mJaDbnwvom3QEtdv3NjvaEUtVeetWdQsv2RtQw4XEYh/Cg=="
# Output: 26e1734e4d52b905e74574b7bf0897daeec7e217c61b3ac0
```

#### PIN Block Encryption (Python)

```python theme={null}
# pinblock.py
import sys
import binascii
from Crypto.Cipher import DES3
from Crypto.Util.strxor import strxor

def create_pin_block(card_number: str, pin: str, pinkey: str) -> str:
    pin_field_hex = f"0{len(pin)}{pin}".ljust(16, 'F')
    pin_field_bytes = binascii.unhexlify(pin_field_hex)
    pan_part = card_number[-13:-1]
    pan_field_hex = "0000" + pan_part
    pan_field_bytes = binascii.unhexlify(pan_field_hex)
    pin_block = strxor(pin_field_bytes, pan_field_bytes)
    pek_bytes = binascii.unhexlify(pinkey)
    cipher = DES3.new(pek_bytes, DES3.MODE_ECB)
    encrypted = cipher.encrypt(pin_block)
    return binascii.hexlify(encrypted).decode('utf-8').upper()

if __name__ == "__main__":
    print(create_pin_block(sys.argv[1], sys.argv[2], sys.argv[3]))
```

```bash theme={null}
# Usage
python3 pinblock.py <card_number> <pin> <pinkey>

# Example
python3 pinblock.py 5554748800260229 302312 26e1734e4d52b905e74574b7bf0897daeec7e217c61b3ac0
# Output: 4F5D12303995DD06
```

### 8.2 Glossary

| Term      | Definition                                                 |
| --------- | ---------------------------------------------------------- |
| TID       | Terminal Identifier, unique ID assigned by UQPAY           |
| PINKEY    | PIN encryption key used to encrypt cardholder PINs         |
| PIN Block | Encrypted representation of cardholder PIN                 |
| STAN      | System Trace Audit Number, unique transaction trace number |
| EMV       | Europay, Mastercard, Visa; chip card standard              |
| PAN       | Primary Account Number (card number)                       |
| CVM       | Cardholder Verification Method                             |
