A complete, open-source toolkit for integrating with GHL payment terminals (PAX A920 / L920) via RS232 serial communication. Includes working implementations in C, Python, C#, and browser-based HTML (Web Serial API), plus full protocol documentation, hardware wiring guides, and debugging tools.
Built and tested in production with GHL Malaysia (UOB acquiring). The ECR protocol used here is standard across all GHL-configured PAX terminals regardless of region or acquiring bank.
Watch the toolkit in action with a live PAX A920 terminal:
Live browser tool (no install needed): deadboy18.github.io/GHL-POS-Integration
GHL Systems (now NTT DATA Payment Services) is one of the largest payment terminal providers in Southeast Asia, founded in 1994 in Kuala Lumpur, Malaysia. They supply and manage POS terminals for banks and merchants across Malaysia, Thailand, Philippines, Indonesia, and other ASEAN markets — with over 500,000 payment terminals deployed. In May 2024, NTT DATA Japan acquired GHL Systems, and the company was rebranded to NTT DATA Payment Services Sdn. Bhd. in November 2024.
Despite the name change, the terminals, ECR protocol, and integration process remain the same. Most people in the industry still refer to them as “GHL” and the email domain is still @ghl.com. If you’re integrating a POS system with a card payment terminal in Southeast Asia, you’re very likely dealing with a GHL/NTT DATA terminal.
This toolkit handles the ECR (Electronic Cash Register) protocol – the serial communication standard that lets your POS software talk to the physical terminal to trigger card payments, voids, refunds, and settlements.
Important: While this was built and tested with GHL terminals in Malaysia, the ECR protocol and packet structure is the same across all GHL-configured PAX terminals. The card type codes may vary by region (e.g., MyDebit is Malaysia-specific), but the framing, checksums, and command structure are universal.
GHL-POS-Integration/
|
|-- c/ Pure C implementation (Windows, for embedding in agents)
|-- python/ Python GUI simulator (tkinter, for testing/prototyping)
|-- csharp/ C# / .NET implementation (Visual Studio project)
|-- web/ Browser-based tool using Web Serial API (zero install)
|-- tools/ Hex payload translator / decoder
|
|-- docs/
| |-- PROTOCOL.md Complete ECR protocol reference
| |-- HARDWARE.md Cable wiring, hardware setup, serial settings
| |-- ERROR_CODES.md Full response/error code table
| |-- CARD_CODES.md Card type code mapping
| |-- SAMPLE_TRANSACTION.md Step-by-step transaction walkthrough with hex
| |-- diagrams/ Wiring diagrams (SVG)
| |-- datasheets/ L920 datasheet, A920 guide (add your own PDFs)
|
|-- examples/
| |-- sample_communication.log Real TX/RX captures from live terminal
|
|-- README.md You are here
You need a PAX A920 terminal on an L920-BE base station, connected to your PC via a custom RS232 cable. See docs/HARDWARE.md for the complete wiring guide, or the short version:
| Implementation | Best for | Requirements |
|---|---|---|
C (c/) |
Embedding into native agents (e.g., Sentec PMS) | Windows, GCC/MinGW |
Python (python/) |
Rapid testing, GUI-based prototyping | Python 3, pyserial |
C# (csharp/) |
.NET desktop POS applications | Visual Studio, .NET |
Web (web/) |
Zero-install browser testing tool | Chrome/Edge (Web Serial API) |
Tools (tools/) |
Debugging hex payloads from logs | Python 3 |
All implementations use the same settings:
| Setting | Value |
|---|---|
| Baud Rate | 9600 |
| Data Bits | 8 |
| Parity | None |
| Stop Bits | 1 |
| Flow Control | None |
Set this in Device Manager > Ports (COM & LPT) > your COM port > Properties > Port Settings.
00 = approved.Every message between POS and terminal follows this structure:
[STX (0x02)] [Payload] [8-byte XOR Checksum] [ETX (0x03)]
The payload is always 25 bytes:
| Offset | Length | Field | Example |
|---|---|---|---|
| 0-2 | 3 | Command code | 020 = Sale |
| 3-14 | 12 | Amount in cents | 000000000100 = 1.00 |
| 15-20 | 6 | Invoice number | 000001 |
| 21-24 | 4 | Cashier ID | ` 99` (right-padded) |
The response payload is 96-125+ bytes depending on terminal firmware:
| Offset | Length | Field |
|---|---|---|
| 0-2 | 3 | Response code (e.g., 021 = Sale response) |
| 3-4 | 2 | Error/approval code (00 = success) |
| 5-26 | 22 | Masked card number |
| 27-30 | 4 | Card expiry (YYMM) |
| 31-32 | 2 | Card type code |
| 33-40 | 8 | Bank authorization code |
| 41-52 | 12 | Gross amount |
| 53-64 | 12 | Net amount |
| 65-70 | 6 | Trace number (STAN) |
| 71-76 | 6 | Invoice number |
| 77-80 | 4 | Cashier ID |
| 81-95 | 15 | Card brand name |
| 96-103 | 8 | Terminal ID (firmware v1.0.17+) |
| 104-118 | 15 | Merchant ID (firmware v1.0.17+) |
| 119-124 | 6 | Batch number (firmware v1.0.17+) |
For the complete protocol specification including checksum calculation, see docs/PROTOCOL.md.
| TX Code | RX Code | Transaction Type |
|---|---|---|
020 |
021 |
Sale |
022 |
023 |
Void |
026 |
027 |
Refund |
050 |
051 |
Settlement |
| Code | Meaning |
|---|---|
00 |
Approved / Success |
CT |
Cancelled by user or timeout on terminal |
51 |
Insufficient funds |
54 |
Expired card |
55 |
Incorrect PIN |
91 |
Issuer/bank unavailable |
Full list: docs/ERROR_CODES.md
| Code | Card Brand |
|---|---|
04 |
Visa |
05 |
Mastercard |
06 |
Diners Club |
07 |
American Express |
08 |
MyDebit (Malaysia) |
09 |
JCB |
10 |
UnionPay |
11 |
E-Wallet |
Full list with regional notes: docs/CARD_CODES.md
Here is a complete Sale transaction for RM 1.00 with invoice number 000001 and cashier ID 99:
Payload (25 bytes ASCII):
020000000000100000001 99
Broken down:
020 = Sale command000000000100 = 100 cents = RM 1.00000001 = Invoice numberChecksum calculation (8-byte XOR):
Split the payload into 8-byte blocks, pad the last block with 0xFF:
Block 1: 30 32 30 30 30 30 30 30 ("02000000")
Block 2: 30 30 30 31 30 30 30 30 ("00010000")
Block 3: 30 30 30 31 20 20 39 39 ("0001 99")
Block 4: FF FF FF FF FF FF FF FF (padding)
XOR all blocks together to get the 8-byte checksum.
Final packet:
[02] [payload 25 bytes] [checksum 8 bytes] [03]
= 35 bytes total
Raw hex (what goes on the wire):
02 30 32 30 30 30 30 30 30 30 30 30 31 30 30 30
30 30 30 30 31 20 20 39 39 [8 checksum bytes] 03
After the cardholder taps/inserts their card and the bank approves:
02 30 32 31 30 30 ... [payload] ... [checksum] 03
Key fields extracted:
30 30 = ASCII “00” = ApprovedFor a complete hex breakdown with a real captured transaction, see docs/SAMPLE_TRANSACTION.md and examples/sample_communication.log.
c/)Pure C, zero dependencies beyond Windows API. Single source file. Auto-detects Prolific USB-to-Serial adapters via Windows registry. Compiles with one command:
gcc c/ghl_simulator.c -o ghl_simulator.exe -lsetupapi
Best for: embedding into native Windows agents (e.g., Sentec PMS integration) where you cannot ship a Python runtime or .NET framework.
python/)Full GUI application built with tkinter. Features:
Requires: pip install pyserial
csharp/)Visual Studio solution with clean separation between protocol logic (GHLProtocol.cs) and UI (Program.cs). Uses System.IO.Ports.SerialPort. Publishable as a single-file .exe.
web/)Zero-install browser tool. Open index.html in Chrome or Edge, select your COM port, and run transactions. Uses the Web Serial API – no drivers, no installs, no compiling. Works on any machine with a Chromium browser and a USB serial cable.
Limitations: Web Serial API is only supported in Chromium-based browsers (Chrome, Edge, Opera). Not available in Firefox or Safari.
tools/)Payload Translator: Paste any raw hex log line (TX or RX) and get a structured JSON breakdown of every field. Essential for debugging failed transactions. Run with python tools/payload_translator.py.
Terminal not responding:
Error code CT (Cancel/Timeout):
Error code 51 (Insufficient Funds):
FTDI adapter not working:
WiFi not connecting on A920:
Payload shorter than expected (no TID/MID/Batch):
Pull requests welcome. If you’re integrating with GHL terminals in a different country or with a different acquiring bank and find regional differences in card codes or error codes, please submit a PR to update the documentation.
MIT License. See LICENSE for details.
| Developed by Deadboy | Based on GHL POS Integration Spec v1.0.17 |