Skip to content

Instantly share code, notes, and snippets.

@standa
Created November 27, 2019 10:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save standa/1dea88b6048a5b7dd56bf5d10d3eec1a to your computer and use it in GitHub Desktop.
Save standa/1dea88b6048a5b7dd56bf5d10d3eec1a to your computer and use it in GitHub Desktop.
Ingenico POS Payment Terminal Test Script
<?php declare(strict_types=1);
namespace Maternia\Utils\Terminal;
error_reporting(E_ALL);
ini_set('display_errors', '1');
ob_implicit_flush();
const HOST = '192.168.0.176';
const PORT = 1818;
const STX = "\x02";
const ETX = "\x03";
const EOT = "\x04";
const ENQ = "\x05";
const ACK = "\x06";
const NAK = "\x15";
const DLE = "\x10";
const WACK = "\x13";
const FS = "\x1C"; // Field separator
const GS = "\x1D"; //
const MESSAGE_SPEC = [
'STX' => 'C1', // start of transmission
'num' => 'C1', // CAC / SAC
'messagetype' => 'C2', // TYPE
'flag' => 'C4', // reserved for future use
'mc' => 'C4', // Message counter is a unique message identifier.
// The response must have the same message number as the corresponding request.
// message data header
'datatype' => 'C1', // 0 - request from source cash desk, 5 - response from POS terminal
'src' => 'C2', // ID of source device
'dest' => 'C2', // ID of the destination device
'ver' => 'C1', // protocol version 1 - basic
'tid' => 'C2', // transaction ID
'data' => 'C*', // message data
// 'ETX' => 'C1', // End of transmission
// 'CRC' => 'C2', // CRC-16 of the transmission
];
function pack_spec(): string
{
return implode('', array_keys(MESSAGE_SPEC));
}
function unpack_spec(): string
{
return implode('/', array_map(
function ($k, $v) { return "$v$k"; },
array_keys(MESSAGE_SPEC),
array_values(MESSAGE_SPEC)
));
}
$payload = [
STX, "\x00", "\x30", "\x31", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x30", "\x39", "\x30", "\x30",
"\x31", "\x31", "\x32", "\x30",
ETX, // ETX
"\xcb", "\xfd", // CRC-16
];
function read_message(string $m): array
{
// return unpack(unpack_spec(), $m);
// $data = unpack('C1STX/C1num/n1messagetype/N1flag/N1mc/C1datatype/n1src/n1dest/C1ver/n1tid/C*data', substr($m, 0, -3));
// return array_merge($data, unpack('C1ETX/C1CRC1/C1CRC2', substr($m, -3)));
return unpack(unpack_spec(), $m);
}
// @see https://www.php.net/manual/en/function.crc32.php#28012
function crc16(string $string): int {
$crc = 0xFFFF;
for ($x = 0, $xMax = strlen($string); $x < $xMax; $x++) {
$crc ^= ord($string[$x]);
for ($y = 0; $y < 8; $y++) {
if (($crc & 0x0001) == 0x0001) {
$crc = (($crc >> 1) ^ 0xA001);
} else {
$crc >>= 1;
}
}
}
return $crc;
}
// debug bytes as hex
function _dhex(string $bytes): void
{
echo '0x[ ' . implode(' ', array_map(function (int $v): string { return sprintf('%02s', dechex($v)); }, unpack("C*", $bytes))) . ' ]' . PHP_EOL;
}
/**
* Basic terminal test script. Successful output should look like this:
*
* Status Information Request for status IDLE ('0')
*
starting
socket_create(): OK.
Connecting to '192.168.0.176' via port '1818'...socket_connect(): OK.
socket_read(ENQ): 0x[ 05 ]
socket_read(ENQ): ENQ OK
socket_write(status_info_request): sending request: 23 bytes
0x[ 00 00 00 01 00 00 00 00 00 00 00 00 00 09 00 00 01 01 02 00 00 00 00 ]
socket_write(status_info_request): 23 bytes written
socket_read(status_info_request): reading response:0x[ 06 00 ]
socket_read(status_info_request): ACK OK
socket_read(status_info_request): Read 24 bytes:
0x[ 02 00 30 32 30 30 30 30 30 30 30 30 35 30 31 39 30 31 32 30 30 03 8f 7f ]
socket_read(status_info_request): Response validation OK
socket_read(status_info_request): Full response match OK
socket_write(status_info_request): Send ACK
socket_read(status_info_request): Read 1 bytes: 0x[ 04 ]
socket_read(status_info_request): EOT OK
*/
echo 'starting'.PHP_EOL;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === false) {
echo "socket_create() error : " . socket_strerror(socket_last_error()) . "\n";
} else {
echo "socket_create(): OK.\n";
}
printf("Connecting to '%s' via port '%s'...", HOST, PORT);
$result = socket_connect($socket, HOST, PORT);
if ($socket === false) {
echo "socket_connect() error: ($result) " . socket_strerror(socket_last_error($socket)) . "\n";
} else {
echo "socket_connect(): OK.\n";
}
echo 'socket_read(ENQ): ';
$out = socket_read($socket, 1);
_dhex($out);
if ($out !== ENQ) {
echo 'socket_read(ENQ): ENQ ERROR; expected ENQ 0x05' . PHP_EOL;
exit;
} else {
echo 'socket_read(ENQ): ENQ OK' . PHP_EOL;
}
echo "socket_write(status_info_request): sending request: " . count($payload) . ' bytes' . PHP_EOL;
_dhex(pack('C*', ...$payload));
$cnt = socket_write($socket, implode('', $payload));
echo "socket_write(status_info_request): $cnt bytes written" . PHP_EOL;
if ($cnt !== count($payload)) {
echo "socket_write(status_info_request): ERROR: $cnt bytes written, expected: " . count($payload) . PHP_EOL;
}
$out = socket_read($socket, 2);
echo 'socket_read(status_info_request): reading response:';
_dhex($out);
if ($out === ACK . $payload[1]) {
echo 'socket_read(status_info_request): ACK OK'. PHP_EOL;
} else {
echo 'socket_read(status_info_request): ACK ERROR. Received: ' . PHP_EOL;
_dhex($out);
exit;
}
$out = socket_read($socket, 2048);
echo 'socket_read(status_info_request): Read ' . strlen($out) . ' bytes:' . PHP_EOL;
_dhex($out);
if (substr($out, 0, 2) === STX . $payload[1] && substr($out, -3) === ETX . "\x8f\x7f") {
echo 'socket_read(status_info_request): Response validation OK' . PHP_EOL;
} else {
echo 'socket_read(status_info_request): Response validation ERROR: start and end + crc were: ' . PHP_EOL;
_dhex(substr($out, 0, 2));
_dhex(substr($out, -3));
exit;
}
if ($out !== "\x02\x00\x30\x32\x30\x30\x30\x30\x30\x30\x30\x30\x35\x30\x31\x39\x30\x31\x32\x30\x30\x03\x8f\x7f") {
echo 'socket_read(status_info_request): Response validation ERROR: Invalid response contents' . PHP_EOL;
exit;
} else {
echo 'socket_read(status_info_request): Full response match OK' . PHP_EOL;
}
echo "socket_write(status_info_request): Send ACK" . PHP_EOL;
socket_write($socket, ACK . $payload[1]);
$out = socket_read($socket, 1);
echo 'socket_read(status_info_request): Read ' . strlen($out) . ' bytes: ';
_dhex($out);
if ($out === "\x04") {
echo 'socket_read(status_info_request): EOT OK' . PHP_EOL;
} else {
echo 'socket_read(status_info_request): Terminal did not send EOT but:' . PHP_EOL;
_dhex($out);
}
socket_close($socket);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment