> ## 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 集成指南

> 通过 UQPAY POS API 集成刷卡交易，涵盖终端注册、PIN 密钥管理与支付处理。

本指南介绍如何集成 UQPAY POS API 以处理刷卡交易，涵盖终端注册、PIN 密钥管理、PIN 块加密和支付处理。

<h2 id="1-overview">1. 概览</h2>

<h3 id="1-1-prerequisites">1.1 前置条件</h3>

开始集成前，请确保：

* 已向 UQPAY 提供你的账户 ID，以启用 POS API 功能

<h3 id="1-2-integration-flow">1.2 集成流程</h3>

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        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
```

***

<h2 id="2-terminal-registration">2. 终端注册</h2>

每台 POS 终端在处理交易前都必须先在 UQPAY 完成注册。这是一次性操作，每台终端只需注册一次。

<h3 id="2-1-api-endpoint">2.1 API Endpoint</h3>

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

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

<h3 id="2-2-request-parameters">2.2 请求参数</h3>

| 字段               | 类型     | 必填 | 说明         | 示例                    |
| ---------------- | ------ | -- | ---------- | --------------------- |
| `firm_code`      | string | 是  | 终端厂商代码     | `"03"`                |
| `firm_sn`        | string | 是  | 厂商提供的终端序列号 | `"TG676SX1764902333"` |
| `terminal_model` | string | 是  | 终端型号       | `"P1000"`             |

**支持的厂商代码：**

| 代码   | 厂商       |
| ---- | -------- |
| `01` | Shengben |
| `02` | Newland  |
| `03` | Xinguodu |
| `04` | iMin     |
| `05` | PAX      |

<h3 id="2-3-request-example">2.3 请求示例</h3>

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

<h3 id="2-4-response">2.4 响应</h3>

| 字段            | 类型     | 说明                 |
| ------------- | ------ | ------------------ |
| `terminal_id` | string | UQPAY 分配的终端标识（TID） |
| `firm_sn`     | string | 终端序列号              |
| `create_time` | string | 注册时间               |

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

> **注意：** 请妥善保存 `terminal_id`，后续所有 API 调用都需要用到。

***

<h2 id="3-pin-key-management">3. PIN 密钥管理</h2>

要安全处理基于 PIN 的交易，你需要获取并管理 PIN 加密密钥。

<h3 id="3-1-generate-aes-private-key">3.1 生成 AES 私钥</h3>

请求 PIN 密钥前，先在终端或服务器上生成一个 AES 私钥：

| 要求   | 规格                   |
| ---- | -------------------- |
| 算法   | AES                  |
| 密钥长度 | 最长 256 位（64 个十六进制字符） |
| 格式   | 十六进制字符串              |

**AES 密钥示例（256 位）：**

```
5214abf357f2cc47645c4d942c02fd0180acc6d8de8fae3708849b6a459cba00
```

> **重要：** 请妥善保存该私钥。后续需要用它解密 Get PIN Key API 返回的 `encrypt_pin_key`。

<h3 id="3-2-request-pin-key">3.2 请求 PIN 密钥</h3>

**API Endpoint：**

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

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

**请求参数：**

| 字段            | 类型     | 必填 | 说明                         | 示例                                                                   |
| ------------- | ------ | -- | -------------------------- | -------------------------------------------------------------------- |
| `terminal_id` | string | 是  | 注册时返回的终端 ID                | `"10000254"`                                                         |
| `prv_key`     | string | 是  | 你的 AES 私钥（十六进制格式，最长 64 字符） | `"5214abf357f2cc47645c4d942c02fd0180acc6d8de8fae3708849b6a459cba00"` |

**请求示例：**

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

**响应：**

| 字段                | 类型      | 说明                     |
| ----------------- | ------- | ---------------------- |
| `terminal_id`     | string  | 终端标识                   |
| `encrypt_pin_key` | string  | 加密后的 PIN 密钥（Base64 编码） |
| `pin_key_expire`  | integer | PIN 密钥过期时间戳（**毫秒**）    |

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

<h3 id="3-3-decrypt-pin-key">3.3 解密 PIN 密钥</h3>

使用你的 AES 私钥解密 `encrypt_pin_key`，得到实际的 PINKEY。

**解密参数：**

| 参数 | 值                 |
| -- | ----------------- |
| 算法 | AES-256-GCM       |
| 输入 | `encrypt_pin_key` |
| 密钥 | 你的 `prv_key`      |
| 输出 | PINKEY            |

**示例：**

* encrypt\_pin\_key：`LASDho1ILHoYRf/5YEyIgieoc+SXJkUsHZElXOMNGv7WnC3fZzFPYDH8mJaDbnwvom3QEtdv3NjvaEUtVeetWdQsv2RtQw4XEYh/Cg==`
* 实际 PINKEY：`26e1734e4d52b905e74574b7bf0897daeec7e217c61b3ac0`

> 解密代码示例见附录。

<h3 id="3-4-key-expiration">3.4 密钥过期</h3>

留意 `pin_key_expire` 时间戳，在过期前刷新 PIN 密钥。按相同流程重新请求新的 PIN 密钥即可。

***

<h2 id="4-pin-block-encryption">4. PIN 块加密</h2>

处理基于 PIN 的交易时，你需要将持卡人 PIN 加密为 PIN 块。

<h3 id="4-1-overview">4.1 概览</h3>

PIN 块由以下内容组合生成：

* 卡 PAN（Primary Account Number）
* 持卡人 PIN
* PINKEY

<h3 id="4-2-example">4.2 示例</h3>

**输入：**

| 参数     | 值                                                  |
| ------ | -------------------------------------------------- |
| 卡号     | `5554748800260229`                                 |
| PIN    | `302312`                                           |
| PINKEY | `26e1734e4d52b905e74574b7bf0897daeec7e217c61b3ac0` |

**输出：**

| 参数       | 值                  |
| -------- | ------------------ |
| PINBLOCK | `4F5D12303995DD06` |

> PIN 块加密代码示例见附录。

***

<h2 id="5-create-payment-intent">5. 创建 Payment Intent</h2>

完成终端设置并获取 PINKEY 后，即可处理刷卡交易。

<h3 id="5-1-api-endpoint">5.1 API Endpoint</h3>

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

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

<h3 id="5-2-request-example">5.2 请求示例</h3>

POS 交易使用 `card_present` 支付方式类型。需要注意的关键字段：

* `terminal_id`：设置为终端注册时返回的 TID
* `encrypted_pin`：设置为 PIN 加密生成的 PINBLOCK
* `system_trace_audit_number`：每笔新交易必须唯一（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"
}
```

<h3 id="5-3-response-example">5.3 响应示例</h3>

```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"
}
```

***

<h2 id="6-error-handling">6. 错误处理</h2>

<h3 id="6-1-pos-api-error-codes">6.1 POS API 错误码</h3>

| 错误码                              | 错误信息                           |
| -------------------------------- | ------------------------------ |
| `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         |

***

<h2 id="7-testing">7. 测试</h2>

<h3 id="7-1-test-card">7.1 测试卡</h3>

在沙盒环境使用以下测试卡数据：

| 字段      | 值                  |
| ------- | ------------------ |
| 卡号      | `5554748800161559` |
| 有效期月    | `07`               |
| 有效期年    | `2027`             |
| PIN（明文） | \`\`               |
| 卡组织     | Mastercard         |

**用于测试的 card\_present 对象示例：**

```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
            }
        }
    }
```

> **注意：** 每笔测试交易必须使用唯一的 `system_trace_audit_number`。

***

<h2 id="8-appendix">8. 附录</h2>

<h3 id="8-1-code-samples">8.1 代码示例</h3>

<h4 id="aes-256-gcm-decryption-python">AES-256-GCM 解密（Python）</h4>

```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
```

<h4 id="pin-block-encryption-python">PIN 块加密（Python）</h4>

```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
```

<h3 id="8-2-glossary">8.2 术语表</h3>

| 术语        | 定义                                      |
| --------- | --------------------------------------- |
| TID       | 终端标识，由 UQPAY 分配的唯一 ID                   |
| PINKEY    | 用于加密持卡人 PIN 的密钥                         |
| PIN Block | 持卡人 PIN 的加密表示                           |
| STAN      | 系统跟踪审计号，交易的唯一跟踪编号                       |
| EMV       | Europay、Mastercard、Visa；芯片卡标准           |
| PAN       | Primary Account Number（卡号）              |
| CVM       | 持卡人验证方式（Cardholder Verification Method） |
