ECR Interface Protocol
1. Encrypt the data of the protocol
1.1 Get 6 letters
When the Wonder terminal enters the pairing page, a 6-digit random uppercase string or the QR code generated by it will be displayed. The client device can obtain this string by scanning a code or manually entering it
Example:
const letters = '260880';
1.2 Generate the AES key
- JavaScript
- Java
- Golang
- C
const letters = "260880";
// AES-256 key
const key = crypto.createHash("sha256").update(letters).digest();
console.log("AES Key:", key.toString("hex"));
// AES Key: d866085b751bf6a9844da9a540d5ac8fba82b5aff408b8aefc6a76e6f61d5bdf
String letters = "260880";
MessageDigest sha = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = sha.digest(letters.getBytes("UTF-8"));
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
letters := "260880"
key := sha256.Sum256([]byte(letters))
const char *letters = "260880";
unsigned char key[32];
SHA256((unsigned char*)letters, strlen(letters), key);
1.3 Encrypt the data of the protocol with AES256
- JavaScript
- Java
- Golang
- C
const crypto = require("crypto");
/**
* 安全的密钥派生函数
*/
const deriveKeySecure = (pinCode, salt) => {
return crypto.pbkdf2Sync(pinCode, salt, 100000, 32, 'sha256');
};
const pkcs7Pad = (data, blockSize) => {
const padding = blockSize - (data.length % blockSize);
const padded = Buffer.alloc(data.length + padding);
data.copy(padded);
padded.fill(padding, data.length);
return padded;
};
const pkcs7Unpad = (padded) => {
const padding = padded[padded.length - 1];
if (padding < 1 || padding > padded.length) {
throw new Error('Invalid padding');
}
// 验证所有填充字节都正确
for (let i = padded.length - padding; i < padded.length; i++) {
if (padded[i] !== padding) {
throw new Error('Invalid padding');
}
}
return padded.slice(0, padded.length - padding);
};
const encrypt = (pinCode, data) => {
// 生成随机盐和IV
const salt = crypto.randomBytes(16);
const iv = crypto.randomBytes(16);
// 使用PBKDF2派生密钥
const key = deriveKeySecure(pinCode, salt);
// PKCS7填充
const paddedData = pkcs7Pad(Buffer.from(data, 'utf8'), 16);
// AES-256-CBC加密
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
cipher.setAutoPadding(false); // 禁用自动填充
const ciphertext = Buffer.concat([cipher.update(paddedData), cipher.final()]);
// 拼接盐、IV和密文
const result = Buffer.concat([salt, iv, ciphertext]);
return result.toString('base64');
};
const decrypt = (pinCode, encryptedBase64) => {
// Base64解码
const encryptedData = Buffer.from(encryptedBase64, 'base64');
// 验证数据长度(至少包含盐+IV)
// 16盐 + 16IV
if (encryptedData.length < 32) {
throw new Error('Encrypted data too short');
}
// 提取盐、IV和密文
const salt = encryptedData.slice(0, 16);
const iv = encryptedData.slice(16, 32);
const ciphertext = encryptedData.slice(32);
// 使用PBKDF2派生密钥
const key = deriveKeySecure(pinCode, salt);
// 验证密文长度
if (ciphertext.length % 16 !== 0) {
throw new Error('Invalid ciphertext length');
}
// AES-256-CBC解密
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
decipher.setAutoPadding(false); // 禁用自动填充
const decrypted = Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]);
// 去除填充
const unpadded = pkcs7Unpad(decrypted);
return unpadded.toString('utf8');
};
// 测试Key1
const key1 = '260880';
// JSON 数据
const data1 = JSON.stringify({
header: {
requestID: 'ff467f02-5b69-45f3-81aa-bffcca55fe80',
clientDeviceSN: '126498561093',
timestamp: '2025-11-12T10:11:04+00:00',
},
body: {
pairUuid: '8daf4dc0-6ad6-44b1-8556-a0f1549c0fc9',
},
});
console.log('key1:', key1);
const encryptedData1 = encrypt(key1, data1);
console.log('Encrypted Data 1:', encryptedData1);
const decryptedData1 = decrypt(key1, encryptedData1);
console.log('Decrypted Data 1:', decryptedData1);
function generateRandomStringSimple(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
// 生成安全的随机字节
const randomBytes = crypto.randomBytes(length);
for (let i = 0; i < length; i++) {
// 使用模62确保均匀分布到62个字符
const index = randomBytes[i] % 62;
result += chars[index];
}
return result;
}
// 测试Key2
// const key2 = generateRandomStringSimple(32);
const key2 = 'OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN';
console.log('key2:', key2);
const encryptedData2 = encrypt(key2, data1);
console.log('Encrypted Data 2:', encryptedData2);
const decryptedData2 = decrypt(key2, encryptedData2);
console.log('Decrypted Data 2:', decryptedData2);
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;
public class test {
private static final int SALT_LENGTH = 16;
private static final int IV_LENGTH = 16;
private static final int KEY_LENGTH = 32; // AES-256
private static final int ITERATIONS = 100000;
private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; // PKCS5Padding在Java中实际是PKCS7
/**
* 安全的密钥派生函数
*/
private static SecretKey deriveKeySecure(String pinCode, byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(pinCode.toCharArray(), salt, ITERATIONS, KEY_LENGTH * 8);
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), "AES");
}
/**
* 加密函数
*/
public static String encrypt(String pinCode, String data) throws Exception {
// 生成随机盐
byte[] salt = new byte[SALT_LENGTH];
SecureRandom.getInstanceStrong().nextBytes(salt);
// 生成随机IV
byte[] iv = new byte[IV_LENGTH];
SecureRandom.getInstanceStrong().nextBytes(iv);
// 使用PBKDF2派生密钥
SecretKey key = deriveKeySecure(pinCode, salt);
// 创建AES-256-CBC加密器
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
// 加密(Java的PKCS5Padding会自动处理填充)
byte[] ciphertext = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 拼接盐、IV和密文
byte[] result = new byte[salt.length + iv.length + ciphertext.length];
System.arraycopy(salt, 0, result, 0, salt.length);
System.arraycopy(iv, 0, result, salt.length, iv.length);
System.arraycopy(ciphertext, 0, result, salt.length + iv.length, ciphertext.length);
// Base64编码
return Base64.getEncoder().encodeToString(result);
}
/**
* 解密函数
*/
public static String decrypt(String pinCode, String encryptedBase64) throws Exception {
// Base64解码
byte[] encryptedData = Base64.getDecoder().decode(encryptedBase64);
// 验证数据长度(至少包含盐+IV)
if (encryptedData.length < SALT_LENGTH + IV_LENGTH) {
throw new IllegalArgumentException("Encrypted data too short");
}
// 提取盐、IV和密文
byte[] salt = new byte[SALT_LENGTH];
byte[] iv = new byte[IV_LENGTH];
byte[] ciphertext = new byte[encryptedData.length - SALT_LENGTH - IV_LENGTH];
System.arraycopy(encryptedData, 0, salt, 0, SALT_LENGTH);
System.arraycopy(encryptedData, SALT_LENGTH, iv, 0, IV_LENGTH);
System.arraycopy(encryptedData, SALT_LENGTH + IV_LENGTH, ciphertext, 0, ciphertext.length);
// 使用PBKDF2派生密钥
SecretKey key = deriveKeySecure(pinCode, salt);
// 创建AES-256-CBC解密器
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
// 解密(Java的PKCS5Padding会自动去除填充)
byte[] decrypted = cipher.doFinal(ciphertext);
return new String(decrypted, StandardCharsets.UTF_8);
}
/**
* 生成随机字符串
*/
public static String generateRandomStringSimple(int length) {
final String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
SecureRandom random = new SecureRandom();
StringBuilder result = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int index = random.nextInt(62); // 0-61
result.append(chars.charAt(index));
}
return result.toString();
}
public static void main(String[] args) {
try {
// 测试Key1
String key1 = "260880";
// 创建JSON数据
Map<String, Object> header = new HashMap<>();
header.put("requestID", "ff467f02-5b69-45f3-81aa-bffcca55fe80");
header.put("clientDeviceSN", "126498561093");
header.put("timestamp", "2025-11-12T10:11:04+00:00");
Map<String, Object> body = new HashMap<>();
body.put("pairUuid", "8daf4dc0-6ad6-44b1-8556-a0f1549c0fc9");
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("header", header);
dataMap.put("body", body);
JSONObject jsonData = new JSONObject(dataMap);
String data1 = jsonData.toString();
System.out.println("Key1: " + key1);
System.out.println("Original Data: " + data1);
// 加密
String encryptedData1 = encrypt(key1, data1);
System.out.println("Encrypted Data 1: " + encryptedData1);
// 解密
String decryptedData1 = decrypt(key1, encryptedData1);
System.out.println("Decrypted Data 1: " + decryptedData1);
// 验证
if (decryptedData1.equals(data1)) {
System.out.println("✓ Test 1: Encryption/Decryption successful");
} else {
System.out.println("✗ Test 1: Encryption/Decryption failed");
}
// 测试Key2
String key2 = "OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN";
// String key2 = generateRandomStringSimple(32);
System.out.println("\nKey2: " + key2);
// 使用相同的测试数据
String encryptedData2 = encrypt(key2, data1);
System.out.println("Encrypted Data 2: " + encryptedData2);
// 解密
String decryptedData2 = decrypt(key2, encryptedData2);
System.out.println("Decrypted Data 2: " + decryptedData2);
// 验证
if (decryptedData2.equals(data1)) {
System.out.println("✓ Test 2: Encryption/Decryption successful");
} else {
System.out.println("✗ Test 2: Encryption/Decryption failed");
}
// 错误密钥测试
System.out.println("\n--- Additional Tests ---");
String wrongKey = "wrong_password";
try {
decrypt(wrongKey, encryptedData1);
System.out.println("✗ Wrong key test: Decryption should have failed but didn't");
} catch (Exception e) {
System.out.println("✓ Wrong key test: Decryption correctly failed");
}
// 测试生成随机字符串
String randomStr = generateRandomStringSimple(32);
System.out.println("\nGenerated random string (32 chars): " + randomStr);
// 测试使用随机字符串作为密钥
System.out.println("\nRandom key: " + randomStr);
String testMessage = "This is a test message";
String encryptedData3 = encrypt(randomStr, testMessage);
String decryptedData3 = decrypt(randomStr, encryptedData3);
if (decryptedData3.equals(testMessage)) {
System.out.println("✓ Random key test: Encryption/Decryption successful");
} else {
System.out.println("✗ Random key test: Encryption/Decryption failed");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"golang.org/x/crypto/pbkdf2"
)
// 安全密钥派生函数
func deriveKeySecure(pinCode string, salt []byte) []byte {
// pbkdf2.Key(password, salt, iterations, keyLength, hashFunction)
return pbkdf2.Key([]byte(pinCode), salt, 100000, 32, sha256.New)
}
// PKCS7填充
func pkcs7Pad(data []byte, blockSize int) []byte {
padding := blockSize - (len(data) % blockSize)
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
// PKCS7去除填充
func pkcs7Unpad(data []byte) ([]byte, error) {
if len(data) == 0 {
return nil, fmt.Errorf("empty data")
}
padding := int(data[len(data)-1])
if padding < 1 || padding > len(data) {
return nil, fmt.Errorf("invalid padding")
}
// 验证所有填充字节都正确
for i := len(data) - padding; i < len(data); i++ {
if data[i] != byte(padding) {
return nil, fmt.Errorf("invalid padding")
}
}
return data[:len(data)-padding], nil
}
// 加密函数
func Encrypt(pinCode, data string) (string, error) {
// 生成随机盐(16字节)
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", fmt.Errorf("failed to generate salt: %w", err)
}
// 生成随机IV(16字节)
iv := make([]byte, 16)
if _, err := rand.Read(iv); err != nil {
return "", fmt.Errorf("failed to generate IV: %w", err)
}
// 使用PBKDF2派生密钥
key := deriveKeySecure(pinCode, salt)
// 创建AES-256-CBC加密器
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("failed to create cipher: %w", err)
}
// PKCS7填充
paddedData := pkcs7Pad([]byte(data), block.BlockSize())
// 加密
ciphertext := make([]byte, len(paddedData))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, paddedData)
// 拼接盐、IV和密文
result := make([]byte, 0, len(salt)+len(iv)+len(ciphertext))
result = append(result, salt...)
result = append(result, iv...)
result = append(result, ciphertext...)
// Base64编码
encB64 := base64.StdEncoding.EncodeToString(result)
return encB64, nil
}
// 解密函数
func Decrypt(pinCode, encryptedBase64 string) (string, error) {
// Base64解码
encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", fmt.Errorf("failed to decode base64: %w", err)
}
// 验证数据长度(至少包含盐+IV:16+16=32字节)
if len(encryptedData) < 32 {
return "", fmt.Errorf("encrypted data too short")
}
// 提取盐、IV和密文
salt := encryptedData[:16]
iv := encryptedData[16:32]
ciphertext := encryptedData[32:]
// 验证密文长度是否为块大小的倍数
if len(ciphertext)%aes.BlockSize != 0 {
return "", fmt.Errorf("invalid ciphertext length")
}
// 使用PBKDF2派生密钥
key := deriveKeySecure(pinCode, salt)
// 创建AES-256-CBC解密器
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("failed to create cipher: %w", err)
}
// 解密
decrypted := make([]byte, len(ciphertext))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(decrypted, ciphertext)
// 去除填充
unpadded, err := pkcs7Unpad(decrypted)
if err != nil {
return "", fmt.Errorf("failed to unpad: %w", err)
}
return string(unpadded), nil
}
// 生成随机字符串
func GenerateRandomStringSimple(length int) (string, error) {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
result := make([]byte, length)
randomBytes := make([]byte, length)
// 生成安全的随机字节
if _, err := rand.Read(randomBytes); err != nil {
return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
for i := 0; i < length; i++ {
// 使用模62确保均匀分布到62个字符
result[i] = chars[randomBytes[i]%62]
}
return string(result), nil
}
// 测试数据结构
type Header struct {
RequestID string `json:"requestID"`
ClientDeviceSN string `json:"clientDeviceSN"`
Timestamp string `json:"timestamp"`
}
type Body struct {
PairUuid string `json:"pairUuid"`
}
type TestData struct {
Header Header `json:"header"`
Body Body `json:"body"`
}
func main() {
// 测试Key1
key1 := "260880"
// 创建测试数据
testData := TestData{
Header: Header{
RequestID: "ff467f02-5b69-45f3-81aa-bffcca55fe80",
ClientDeviceSN: "126498561093",
Timestamp: "2025-11-12T10:11:04+00:00",
},
Body: Body{
PairUuid: "8daf4dc0-6ad6-44b1-8556-a0f1549c0fc9",
},
}
// 将数据转换为JSON字符串
data1, err := json.Marshal(testData)
if err != nil {
fmt.Printf("Failed to marshal JSON: %v\n", err)
return
}
fmt.Printf("Key1: %s\n", key1)
// 加密
encryptedData1, err := Encrypt(key1, string(data1))
if err != nil {
fmt.Printf("Encryption failed: %v\n", err)
return
}
fmt.Printf("Encrypted Data 1: %s\n", encryptedData1)
// 解密
decryptedData1, err := Decrypt(key1, encryptedData1)
if err != nil {
fmt.Printf("Decryption failed: %v\n", err)
return
}
fmt.Printf("Decrypted Data 1: %s\n", decryptedData1)
// 验证解密后的数据是否与原始数据相同
if decryptedData1 == string(data1) {
fmt.Println("✓ Test 1: Encryption/Decryption successful")
} else {
fmt.Println("✗ Test 1: Encryption/Decryption failed")
}
// 测试Key2
// key2, err := GenerateRandomStringSimple(32)
// if err != nil {
// fmt.Printf("Failed to generate random string: %v\n", err)
// return
// }
key2 := "OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN"
fmt.Printf("\nKey2: %s\n", key2)
// 使用相同的测试数据
encryptedData2, err := Encrypt(key2, string(data1))
if err != nil {
fmt.Printf("Encryption failed: %v\n", err)
return
}
fmt.Printf("Encrypted Data 2: %s\n", encryptedData2)
// 解密
decryptedData2, err := Decrypt(key2, encryptedData2)
if err != nil {
fmt.Printf("Decryption failed: %v\n", err)
return
}
fmt.Printf("Decrypted Data 2: %s\n", decryptedData2)
// 验证解密后的数据是否与原始数据相同
if decryptedData2 == string(data1) {
fmt.Println("✓ Test 2: Encryption/Decryption successful")
} else {
fmt.Println("✗ Test 2: Encryption/Decryption failed")
}
// 额外测试:错误密钥测试
fmt.Println("\n--- Additional Tests ---")
// 测试错误密钥
wrongKey := "wrong_password"
_, err = Decrypt(wrongKey, encryptedData1)
if err != nil {
fmt.Printf("✓ Wrong key test: Decryption correctly failed with error: %v\n", err)
} else {
fmt.Println("✗ Wrong key test: Decryption should have failed but didn't")
}
// 测试损坏的数据
corruptedData := encryptedData1[:len(encryptedData1)-10] + "abc"
_, err = Decrypt(key1, corruptedData)
if err != nil {
fmt.Printf("✓ Corrupted data test: Decryption correctly failed with error: %v\n", err)
} else {
fmt.Println("✗ Corrupted data test: Decryption should have failed but didn't")
}
// 测试生成随机字符串
randomStr, err := GenerateRandomStringSimple(32)
if err != nil {
fmt.Printf("Failed to generate random string: %v\n", err)
return
}
fmt.Printf("\nGenerated random string (32 chars): %s\n", randomStr)
// 测试使用随机字符串作为密钥
fmt.Printf("\nRandom key: %s\n", randomStr)
encryptedData3, err := Encrypt(randomStr, "This is a test message")
if err != nil {
fmt.Printf("Encryption with random key failed: %v\n", err)
return
}
decryptedData3, err := Decrypt(randomStr, encryptedData3)
if err != nil {
fmt.Printf("Decryption with random key failed: %v\n", err)
return
}
if decryptedData3 == "This is a test message" {
fmt.Println("✓ Random key test: Encryption/Decryption successful")
} else {
fmt.Println("✗ Random key test: Encryption/Decryption failed")
}
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#define SALT_LENGTH 16
#define IV_LENGTH 16
#define KEY_LENGTH 32 // AES-256
#define ITERATIONS 100000
// 错误处理宏
#define CHECK_OPENSSL(expr) \
do { \
if (!(expr)) { \
fprintf(stderr, "OpenSSL error at %s:%d\n", __FILE__, __LINE__); \
ERR_print_errors_fp(stderr); \
exit(EXIT_FAILURE); \
} \
} while(0)
// Base64编码
char* base64_encode(const unsigned char* input, int length) {
BIO *bio, *b64;
BUF_MEM *bufferPtr;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
BIO_write(bio, input, length);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bufferPtr);
char* encoded = (char*)malloc(bufferPtr->length + 1);
memcpy(encoded, bufferPtr->data, bufferPtr->length);
encoded[bufferPtr->length] = '\0';
BIO_free_all(bio);
return encoded;
}
// Base64解码
unsigned char* base64_decode(const char* input, int* out_length) {
BIO *bio, *b64;
int decode_len = strlen(input);
unsigned char* buffer = (unsigned char*)malloc(decode_len);
bio = BIO_new_mem_buf(input, -1);
b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
*out_length = BIO_read(bio, buffer, decode_len);
BIO_free_all(bio);
return buffer;
}
// PBKDF2密钥派生
void derive_key_secure(const char* pin_code, const unsigned char* salt,
unsigned char* key) {
CHECK_OPENSSL(PKCS5_PBKDF2_HMAC(
pin_code, strlen(pin_code),
salt, SALT_LENGTH,
ITERATIONS,
EVP_sha256(),
KEY_LENGTH,
key
));
}
// PKCS7填充
unsigned char* pkcs7_pad(const unsigned char* data, int data_len,
int block_size, int* out_len) {
int padding = block_size - (data_len % block_size);
*out_len = data_len + padding;
unsigned char* padded = (unsigned char*)malloc(*out_len);
memcpy(padded, data, data_len);
memset(padded + data_len, padding, padding);
return padded;
}
// PKCS7去除填充
unsigned char* pkcs7_unpad(const unsigned char* padded_data, int padded_len,
int* out_len) {
if (padded_len == 0) {
*out_len = 0;
return NULL;
}
unsigned char padding = padded_data[padded_len - 1];
if (padding < 1 || padding > padded_len) {
fprintf(stderr, "Invalid padding\n");
return NULL;
}
// 验证所有填充字节
for (int i = padded_len - padding; i < padded_len; i++) {
if (padded_data[i] != padding) {
fprintf(stderr, "Invalid padding byte\n");
return NULL;
}
}
*out_len = padded_len - padding;
unsigned char* data = (unsigned char*)malloc(*out_len);
memcpy(data, padded_data, *out_len);
return data;
}
// 加密函数
char* encrypt(const char* pin_code, const char* data) {
// 生成随机盐
unsigned char salt[SALT_LENGTH];
unsigned char iv[IV_LENGTH];
CHECK_OPENSSL(RAND_bytes(salt, SALT_LENGTH));
CHECK_OPENSSL(RAND_bytes(iv, IV_LENGTH));
// 派生密钥
unsigned char key[KEY_LENGTH];
derive_key_secure(pin_code, salt, key);
// 创建加密上下文
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
CHECK_OPENSSL(ctx != NULL);
// 初始化加密
CHECK_OPENSSL(EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv));
// PKCS7填充(OpenSSL会自动处理,但为了与其他语言一致,我们手动处理)
int block_size = EVP_CIPHER_block_size(EVP_aes_256_cbc());
int padded_len;
unsigned char* padded_data = pkcs7_pad((unsigned char*)data, strlen(data),
block_size, &padded_len);
// 加密
unsigned char* ciphertext = (unsigned char*)malloc(padded_len);
int ciphertext_len;
int final_len;
CHECK_OPENSSL(EVP_EncryptUpdate(ctx, ciphertext, &ciphertext_len,
padded_data, padded_len));
CHECK_OPENSSL(EVP_EncryptFinal_ex(ctx, ciphertext + ciphertext_len, &final_len));
int total_ciphertext_len = ciphertext_len + final_len;
// 拼接盐、IV和密文
int total_len = SALT_LENGTH + IV_LENGTH + total_ciphertext_len;
unsigned char* result = (unsigned char*)malloc(total_len);
memcpy(result, salt, SALT_LENGTH);
memcpy(result + SALT_LENGTH, iv, IV_LENGTH);
memcpy(result + SALT_LENGTH + IV_LENGTH, ciphertext, total_ciphertext_len);
// Base64编码
char* encoded = base64_encode(result, total_len);
// 清理
EVP_CIPHER_CTX_free(ctx);
free(padded_data);
free(ciphertext);
free(result);
return encoded;
}
// 解密函数
char* decrypt(const char* pin_code, const char* encrypted_base64) {
// Base64解码
int decoded_len;
unsigned char* decoded = base64_decode(encrypted_base64, &decoded_len);
if (decoded_len < SALT_LENGTH + IV_LENGTH) {
fprintf(stderr, "Encrypted data too short\n");
free(decoded);
return NULL;
}
// 提取盐、IV和密文
unsigned char* salt = decoded;
unsigned char* iv = decoded + SALT_LENGTH;
unsigned char* ciphertext = decoded + SALT_LENGTH + IV_LENGTH;
int ciphertext_len = decoded_len - SALT_LENGTH - IV_LENGTH;
// 派生密钥
unsigned char key[KEY_LENGTH];
derive_key_secure(pin_code, salt, key);
// 创建解密上下文
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
CHECK_OPENSSL(ctx != NULL);
// 初始化解密
CHECK_OPENSSL(EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv));
// 解密
unsigned char* decrypted = (unsigned char*)malloc(ciphertext_len);
int decrypted_len;
int final_len;
CHECK_OPENSSL(EVP_DecryptUpdate(ctx, decrypted, &decrypted_len,
ciphertext, ciphertext_len));
CHECK_OPENSSL(EVP_DecryptFinal_ex(ctx, decrypted + decrypted_len, &final_len));
int total_decrypted_len = decrypted_len + final_len;
// 去除填充
int unpadded_len;
unsigned char* unpadded = pkcs7_unpad(decrypted, total_decrypted_len, &unpadded_len);
if (unpadded == NULL) {
free(decrypted);
free(decoded);
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
// 转换为字符串
char* result = (char*)malloc(unpadded_len + 1);
memcpy(result, unpadded, unpadded_len);
result[unpadded_len] = '\0';
// 清理
EVP_CIPHER_CTX_free(ctx);
free(decrypted);
free(decoded);
free(unpadded);
return result;
}
// 生成随机字符串
char* generate_random_string_simple(int length) {
const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
char* result = (char*)malloc(length + 1);
// 使用系统随机数
for (int i = 0; i < length; i++) {
unsigned char random_byte;
CHECK_OPENSSL(RAND_bytes(&random_byte, 1));
result[i] = chars[random_byte % 62];
}
result[length] = '\0';
return result;
}
// 清理内存的版本
void free_string(char* str) {
if (str != NULL) {
free(str);
}
}
int main() {
// 初始化OpenSSL
OPENSSL_init_crypto(0, NULL);
// 测试Key1
char* key1 = "260880";
// 测试数据
char* data1 = "{\"header\":{\"requestID\":\"ff467f02-5b69-45f3-81aa-bffcca55fe80\","
"\"clientDeviceSN\":\"126498561093\",\"timestamp\":\"2025-11-12T10:11:04+00:00\"},"
"\"body\":{\"pairUuid\":\"8daf4dc0-6ad6-44b1-8556-a0f1549c0fc9\"}}";
printf("Key1: %s\n", key1);
printf("Original Data: %s\n", data1);
// 加密
char* encrypted_data1 = encrypt(key1, data1);
if (encrypted_data1 == NULL) {
fprintf(stderr, "Encryption failed\n");
return 1;
}
printf("Encrypted Data 1: %s\n", encrypted_data1);
// 解密
char* decrypted_data1 = decrypt(key1, encrypted_data1);
if (decrypted_data1 == NULL) {
fprintf(stderr, "Decryption failed\n");
free_string(encrypted_data1);
return 1;
}
printf("Decrypted Data 1: %s\n", decrypted_data1);
// 验证
if (strcmp(decrypted_data1, data1) == 0) {
printf("✓ Test 1: Encryption/Decryption successful\n");
} else {
printf("✗ Test 1: Encryption/Decryption failed\n");
}
// 清理
free_string(encrypted_data1);
free_string(decrypted_data1);
// 测试Key2
char* key2 = "OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN";
// char* key2 = generate_random_string_simple(32);
printf("\nKey2: %s\n", key2);
// 使用相同的测试数据
char* encrypted_data2 = encrypt(key2, data1);
if (encrypted_data2 == NULL) {
fprintf(stderr, "Encryption failed\n");
free_string(key2);
return 1;
}
printf("Encrypted Data 2: %s\n", encrypted_data2);
char* decrypted_data2 = decrypt(key2, encrypted_data2);
if (decrypted_data2 == NULL) {
fprintf(stderr, "Decryption failed\n");
free_string(encrypted_data2);
free_string(key2);
return 1;
}
printf("Decrypted Data 2: %s\n", decrypted_data2);
// 验证
if (strcmp(decrypted_data2, data1) == 0) {
printf("✓ Test 2: Encryption/Decryption successful\n");
} else {
printf("✗ Test 2: Encryption/Decryption failed\n");
}
// 清理
free_string(encrypted_data2);
free_string(decrypted_data2);
// 错误密钥测试
printf("\n--- Additional Tests ---\n");
char* wrong_key = "wrong_password";
char* wrong_decrypted = decrypt(wrong_key, encrypted_data2 ? encrypted_data2 : "");
if (wrong_decrypted == NULL) {
printf("✓ Wrong key test: Decryption correctly failed\n");
} else {
printf("✗ Wrong key test: Decryption should have failed but didn't\n");
free_string(wrong_decrypted);
}
// 测试生成随机字符串
char* random_str = generate_random_string_simple(32);
printf("\nGenerated random string (32 chars): %s\n", random_str);
// 测试使用随机字符串作为密钥
printf("\nRandom key: %s\n", random_str);
char* test_message = "This is a test message";
char* encrypted_data3 = encrypt(random_str, test_message);
if (encrypted_data3 == NULL) {
fprintf(stderr, "Encryption with random key failed\n");
free_string(random_str);
return 1;
}
char* decrypted_data3 = decrypt(random_str, encrypted_data3);
if (decrypted_data3 == NULL) {
fprintf(stderr, "Decryption with random key failed\n");
free_string(random_str);
free_string(encrypted_data3);
return 1;
}
if (strcmp(decrypted_data3, test_message) == 0) {
printf("✓ Random key test: Encryption/Decryption successful\n");
} else {
printf("✗ Random key test: Encryption/Decryption failed\n");
}
// 清理
free_string(random_str);
free_string(encrypted_data3);
free_string(decrypted_data3);
// 清理OpenSSL
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
return 0;
}
2. Request and Response Protocol Data Format
2.1 Request data structure
Request JSON
| Variable | Type | Required | Description |
|---|---|---|---|
| version | String | Y | Current communication protocol version number |
| action | enum | Y | Actions: Pair, DeviceInfo, Sale, ... |
| data | String | Y | Encrypted data |
Request "data" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| header | Object | Y | Protocol header |
| body | Object | N | Protocol body |
Request "data.header" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| requestID | UUID | Y | Request ID: Every time communication takes place, it is crucial to ensure that the Request ID is a unique UUID. In the event of a response, the corresponding identifier will be returned in the response header as the "responseID". |
| clientDeviceSN | String | Y | Client device serial number |
| timestamp | datetime | Y | The timestamp of the current request |
Example:
/*
Encrypt the original data:
{
"header": {
"requestID": "9c07d8d7-2a43-4a29-9c6d-6b8d8f7d44e5",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
},
"body": {
"pairUuid": "3f37e6c0-bf6e-4c00-b1fa-b2bd5e1d6a3b"
}
}
*/
{
"version": "2.0",
"action": "Pair",
"data": "WJq3v/ZX/aMMR45WFqfs9BZ2J93EtSTYQh4JOD17ldQkzXi6ohsjxCdl4mpvAOqhFyWPVuEadUubmRzfACUAQpqledyJhOdtQTMlr4KnZXBJw6g3TOFTeKe1I/DqMiEEG0X9BEelUNS+0+FpaoWN1mVkuKEpt8XMGrHyA+M4XbaMwjQ3fMeoR77FULPvM9MCkw6B3QTGONMml51X8pqXsBYTrcpS5SLFVvdL5e+1CcIuVvliyrV3MDd+cwlSjHNIoaLwDEgu9wDNjGTYe1L8Ww=="
}
2.2 Response data structure
Request JSON
| Variable | Type | Required | Description |
|---|---|---|---|
| version | String | Y | Current communication protocol version number |
| action | enum | Y | Actions: DeviceInfo, Sale, PreAuth, Abort, Void, Refund |
| data | String | Y | Encrypted data |
Response "data" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| header | Object | Y | Protocol header |
| body | Object | N | Protocol body |
Response "data.header" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| responseID | String | Y | The response ID in the response Header is derived from the request ID in the request header. |
| serverDeviceSN | String | Y | The serial number of the terminal payment device |
| timestamp | datetime | N | The timestamp of the current request |
Example:
/*
Encrypt the original data:
{
"header": {
"responseID": "9c07d8d7-2a43-4a29-9c6d-6b8d8f7d44e5",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"pairUuid": "3f37e6c0-bf6e-4c00-b1fa-b2bd5e1d6a3b",
"ackUuid": "4d366618-49cf-4f93-bd23-25388a573b0c"
}
}
*/
{
"version": 2.0,
"action": "Pair",
"data": "G6RaFsGPTiVzAYsUTu/gY1Igjv+4THb1tZ7vwXWP9f1CYRRrNKKuz4Op7ai9xCqKCdBymnO/YAQTB9egq10DIp/mxQ4pG1dGyL+Erkd91G4xYJd9S7iXNqnCwEn2wOhMIn3sP0H6vgTzoGX9ZOi59pLhuGqYTbHBPvDIkavEIweWWVQjEUdQn7HG0ymqxq4O36giOlh5NldSPwH5LjbnOamEm+Y41jIW/dRexnd7hhKkF/TrZg2lDYKOry9NflNzoMPHiB2/7YgVVmgVfBysW9dqqNRiJXxYLOD6UGoJlQqsICeiidKm6SYL4dkKHYniFEnZ0a5KGdxZs7viSahmiA=="
}
3. Interface Protocol
3.1 Pair
After the customer device acquires the 6-digit pairing code, it uses the "Pair" protocol for pairing. After the Wonder terminal receives a pairing request, it will return a UUID identifier, which will be used by the "Ack" protocol.
Action: Pair
Request "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| pairUuid | UUID | Y | Pair the uuid identifier |
Example:
/*
Key: AES Key 1
Encrypt the original data:
{
"header": {
"requestID": "9c07d8d7-2a43-4a29-9c6d-6b8d8f7d44e5",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
},
"body": {
"pairUuid": "3f37e6c0-bf6e-4c00-b1fa-b2bd5e1d6a3b"
}
}
*/
{
"version": "2.0",
"action": "Pair",
"data": "KhGmDIJZQXsNHrLM1hDB/kjPjlSpmUQVNIrRLEAptCHb+GHiqdZTnfDRWJVBJDzTyLtqDXLJYRR3433sIEm7hBvpI8vZ4WeTIxcp1BUd4GwRjutbUMCQ8vc5GTlu8aWvSoChYWeHXIu5MmWXbfkWe8AMAHLtnjuvrZfV17mTlvX+09hskf5zCcFKKXVq6qA24o4d5crbIoKEmRI0K94YSPo99N9mEdnAVdh5xNxQmzsp1JlKMnxQGIy2K1HuUmabW1JLWID/In4sADJDdRk2069oYrP1PWYUqKKoahtH7GNQqJ0Syux/IykdoXVZ2dmn"
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| pairUuid | UUID | Y | Pair the uuid identifier |
| ackUuid | UUID | Y | Ack the uuid identifier |
| aesKey | String | Y | The AES key 2 used for encryption |
Example:
/*
Key: AES Key 1
Encrypt the original data:
{
"header": {
"responseID": "9c07d8d7-2a43-4a29-9c6d-6b8d8f7d44e5",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"pairUuid": "3f37e6c0-bf6e-4c00-b1fa-b2bd5e1d6a3b",
"ackUuid": "4d366618-49cf-4f93-bd23-25388a573b0c",
"aesKey": "OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN"
}
}
*/
{
"version": 2.0,
"action": "Pair",
"data": "eYNmWJXELRz0FETVoGgk0y/a8d56DdScry4J748Xm2oA0SF7hzsRmUNtX+xp1cMc41/lIggAI5VbA6UBwBF89y1WawoKCHyoJ6q9caOH2EwIub2xtjRXnhwxm2LJtjIFKL/BqVIPh+ZaY7CbBSSFewPnj7LXjpKL2MbHrTPNgrLucdH+hPG6XwkvWfZBYTYu0KZMMg2am+3RKQra6o0V0AXeyNSfOsgVUx8s8IZ5VJOxTOhwyd1wNp3DYW8MkkqPqrTwzO/mR2D2FBTJB9XVsTpVR7vlJwPW1IflLHDthz0xUuxLhngDeN/ziNVBeHVWhswZqasmh7QUJL4SrlVQHk783a9+vDrTp2JrU1kEvKYQufjeI7CGnf752LEaAw3YEKyP3Q7YHbUDplfoCzZ+mITyPJkPltt60SxXdfIu5gV9mfHmf+bZTKU1OOzpJjRm"
}
3.2 Ack
After the client terminal initiates the pairing, it will receive an "ackUuid" identifier. This protocol needs to carry the "ackUuid" identifier to confirm that the pairing has been successful.
Action: Ack
Request "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| ackUuid | UUID | Y | The Wonder device will use this UUID to confirm whether the pairing was successful |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"requestID": "73a95cfe-b0f1-42b3-82d1-5a640f61e359",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
},
"body": {
"ackUuid": "4d366618-49cf-4f93-bd23-25388a573b0c"
}
}
*/
{
"version": "2.0",
"action": "Ack",
"data": "1Rlzdz9YVYhK9cI0tqodWqay2D8t0vs3slnvVn0lW5G2EqFcfQtC/4j7oQ+X/MOvADkbVGVDREB54LxJWK5UtzNVx1+z5R/ydgYryjcUuI4p724nsggY0lIbIBE+6yCDZQE6iXYZYAVAUkdyukNB18WbmqEK0+V+uPt/NWc4H0t5drhK+nz8uu62h4PT/Fxma5/ZcnBbRqjSMe1nUJmlqY0vKmJhhZ17N9M85rgh/JUsMebNMmERR6XDbnuls4bKY4hffSVzNbWHjBWjdfX60KU2r7SBE8HiEKS41FcVDhl2dmVcNybz/+RTSxMsnEvA"
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| deviceStatus | String | Y | Payment device transaction status: Free / Busy Free: The current device is idle and available for new transactions; Busy: The current device is occupied with an ongoing transaction and is not available for other transactions at the moment. |
| networkStatus | String | Y | The current network connection status of the payment device: Connected / Disconnected |
| softwareVersion | String | Y | The software version of the payment device |
| businessID | String | Y | The response ID in the response Header is derived from the request ID in the request header. |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "73a95cfe-b0f1-42b3-82d1-5a640f61e359",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"deviceStatus": "Free",
"networkStatus": "Connected",
"softwareVersion": "1.0.0(188)",
"businessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f"
}
}
*/
{
"version": 2.0,
"action": "Ack",
"data": "thygIk5FDezl1xaeyOFkwduPBRl8PS65YQ5+aa1bLCT6gjIVGbjGsQlvkOl+24To6ztvk3CtJ5Jgeimy93RuW3cGYflth+Eb8A2031pbUa8918aJ08lHhVmbeajnWoaN9iWmExBuqkN4EA76SB6ELELwJQR3z0xlfREuhDyhlhIeBTs5dy+UptkKzIpHzcMB6aPL37qF+TIAW6M4B7hLniTM5RfCmFwQjDRNrj78pwA8teLrvPrGSXceQnbMxlk2BXl1BhVnhTSxVh8iP/KtLi9MxO4YvSfBsrfOkwrFLNIh3SardG0V2QVgGwdfKxmCSIf4GxuDF3bJz8XM8CowAogC0dY5zea4Ms2ifblCI13dD/uMJF3qrS36Xuj+LN7lO3UYWYtNzBy0vYo45PrZ/5Zh4WOeZ5sZk43Ycotj+fk="
}
3.3 Device Info
Action: DeviceInfo
Request "data.body" structure
This request has no body
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"requestID": "2c7f32e1-b9e4-4b34-96cc-15c51289f69b",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
}
}
*/
{
"version": "2.0",
"action": "DeviceInfo",
"data": "fZL3Z4ZlYRz5dt5ls0orrG5Pw1vdHxK+35tOc1aDIyInrHErmJRfeohzuviFFb7FuaxMZYPKU2GEMVIq8BvS3ua2xAj1CWHnduaO0YGTHfcuYTOTZxy3CZHvMtphPux+LZ2n3QlkhRPXeMwDlcOeaq1lKLerkcQf1SDSK14MvrTVg/NuB3BArPVmZsX9zBZ1pDrc7ixr4O/yNLD6RR/9JOKt3XWYqAJyztN06hYfSSI="
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| deviceStatus | String | Y | Payment device transaction status: Free / Busy Free: The current device is idle and available for new transactions; Busy: The current device is occupied with an ongoing transaction and is not available for other transactions at the moment. |
| networkStatus | String | Y | The current network connection status of the payment device: Connected / Disconnected |
| softwareVersion | String | Y | The software version of the payment device |
| businessID | String | Y | The response ID in the response Header is derived from the request ID in the request header. |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "2c7f32e1-b9e4-4b34-96cc-15c51289f69b",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"deviceStatus": "Free",
"networkStatus": "Connected",
"softwareVersion": "1.0.0(188)",
"businessID": "ff467f02-5b69-45f3-81aa-bffcca55fe8f"
}
}
*/
{
"version": "2.0",
"action": "DeviceInfo",
"data": "+AGm65vmYsIBpb0oOlvyCkcJ/BurpmzLL7LusZW2mlfmEE3bYAD8hbwbrOshjozgFtkL+oMSrSfvs8rxHxdr5RU9WVCBgfognPaJj+9XnNJ43vnY7cjr5LvN5OZg8HNEiRYRQipGkjlPqiqWpyYCHs1nSIFoR/KSUvIokFKDY5USySXSBs5cp+vdbO6THLjWPYrgywQ8aBfNdaUUEMZEVURRhoQYY0frhko6I2i6iatD7b4BiO2W+J8ycDClo+3QBRmRUPjZA6w5CobbWXckYZO2GxWDg1X3i8IJqx2qioVew9mzYeJ74a4/6EIiUy/ebqGOqHeVTGEuCa0jogZkVixbYBoBbb3yhdLyPa0s8aPGatYmO5XzUTpsXXoDwmXf00yJdgE5Q7vNmxwv94jSp4SAVxB8gR9yvO3iLXqG+3I="
}
3.4 Sale
Action: Sale
Request "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| referenceID | String | Y | Reference ID: Every time a transaction request is initiated, it is crucial to ensure the uniqueness of this ID. Failure to do so will render any subsequent operations on the transaction invalid. |
| customerOrderID | String | N | The order ID of the customer's transaction |
| currency | String | Y | Example: HKD / USD / RMB |
| amount | String | Y | Sale amount |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"requestID": "5debf769-49d7-4c9b-b6f4-8a9d90e1a874",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
},
"body": {
"referenceID": "f8b13b22-16ca-4a87-95a4-df4bebf09ee1",
"customerOrderID": "e246c1cc-02f6-4ac5-b7fb-066cb2b2f5b1",
"currency": "HKD",
"amount": "10.20"
}
}
*/
{
"version": "2.0",
"action": "Sale",
"data": "OE9dcJmLIlqcsAeVyLx1JW7T60f+o32mH+miGIyZRQkLdDuutmQGw/pnYDYPyBOgvCiES4ySbj1UPrUTJWRJaxGTqtP4mI/faqS6iKjPbsfSa2gg8DGc9CnBBHZ6tM902yMJ8n1Jwn0wtfqs3v0NPL3iPb+C5tSqJHpBHwuInndHy/u/Rg/SK+AiY8946eqhYfvyd1mPzl1fMDYErs+6L1ObQkPGiFmWQIuXk0xgdsl4oe7TS4Xci7+33gHpMPLgYMMEztZj6y7A8cUPXXVNHSeqG2+A1EcutsRHJTD5J43W3rsIZH3TMaeiLkLOWY4poHU7RrFoGBUOIh+bP4LtOgpd9+c9wb//cEKAZbLY4AqxyL3NEyySYaZYBUaDhdsd9A9TO2DBrCUm2Wl03LmpuDdiLfzwwrJj9S4Ms6JsCmKrFE7w94n/8/DWD9zTDOKD"
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| status | String | Y | Response status: Success / Failed / Pending |
| errorCode | String | Y | |
| errorMessage | String | Y | |
| customerOrderID | String | N | The order ID of the customer's transaction |
| currency | String | N | Example: HKD / USD / RMB |
| amount | String | N | Sale amount |
| paymentMethod | String | N | Payment method |
| paymentEntryType | String | N | Payment entry type, Example: contactless |
| rrn | String | N | Receiver Reference Number |
| brn | String | N | Bindo Reference Number |
| transactionStatus | String | N | Transaction Status: Success / Failed / Pending / Voided |
| transactionTime | Datetime | N | Transaction Time |
| creditCard | Object | N | |
| creditCard.panPrefix6Digits | String | Y | First 6 digits of the credit card number |
| creditCard.panLast4Digits | String | Y | Last 4 digits of the credit card number |
| creditCard.panHash | String | Y | SHA512 |
| creditCard.panToken | String | Y |
Example 1:
When the status is Pending
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "5debf769-49d7-4c9b-b6f4-8a9d90e1a874",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"status": "Pending",
"errorCode": "",
"errorMessage": ""
}
}
*/
{
"version": "2.0",
"action": "Sale",
"data": "CcDYsa/fPAjhaaTi5e0SQHJ5AGeWrfhu+D70RJwU9vizGFB38D9j65iEUbBQqs7qbjOQ9AGaBkhNhBWNYUXtdDT5FT9R/2aSLodTgpTPbsWNGhkihyaaVQ+9kN/RjL/frPrvKV1VQsRDEG783RKRLY5Uz0RsSGLXbqkW0drzKxnAvAs13KlcqrfTe0rHNnZ+fKzuW7imxytn1nXKUtGciIXCE+NzXvRSmYbk0WPM+hTCM2WbC+Vul8vD3++vp4v7u4z3d+A7LRcE30W3q6TZKciZ9pnau1A4eVjxAY5EJ93uMWNnny5/yDOE2s1gKHS1"
}
Example 2:
When the status is Failed
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "5debf769-49d7-4c9b-b6f4-8a9d90e1a874",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"status": "Failed",
"errorCode": "5xxxx",
"errorMessage": "The equipment is being traded"
}
}
*/
{
"version": "2.0",
"action": "Sale",
"data": "aDmtvHICThbeeWzsusOZbKWWwJOI1PZC5mUHufTvsVr8vO8UyqBw99XfujjCJNq6ibse10OsBLbGc488Ty7qZW/aO8cJi/KQgqVhPU84pQ1wmfbbbZODvlV0PmD9JU6dqjU2BvChhNfFVXKYF6AGXjMh6/NFQlY4G59w0kI1vgNYyRHohoyqHBX9qm4+MUlb0f010DtTPZKMzfRL//KBilJjctok9CWz9CjRim39D969UoS4WLm7x1eXAC/DMPaw2KM/MlUp05BFzcArZlgf1bCtxhvL1TVcwhVQSLHtxYk5Mrc8rIeLMZ0I8Yw5JsbXATMgSmcz+KzaHSwlg7TtB+8rpA1VLelBf0PZmPXwOKU="
}
Example 3:
When the status is Success
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "5debf769-49d7-4c9b-b6f4-8a9d90e1a874",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"status": "Success",
"errorCode": "",
"errorMessage": "",
"customerOrderID": "e246c1cc-02f6-4ac5-b7fb-066cb2b2f5b1",
"currency": "HKD",
"amount": "10.20",
"paymentMethod": "visa",
"paymentEntryType": "contactless",
"rrn": "3263492852830699521",
"brn": "3263492852495159296",
"transactionStatus": "Success",
"transactionTime": "2023-06-30T09:08:52+00:00",
"creditCard": {
"panPrefix6Digits": "555555",
"panLast4Digits": "1234",
"panHash": "xxxxxxx",
"panToken": ""
}
}
}
*/
{
"version": "2.0",
"action": "Sale",
"data": "7Z12NF2oZU9zZvQkxAUsWe6fgHZux1IlYJ56DRoK6IpSRdizzq4+Fb5dosmaZZo1rere/21Dxq88IXKKS0yRc3qbaLKwMmRInInvoeSNgOLeSBhAjk8x8P5KJ6m46qzxXOB79SNSxrW+phn5nDiuJ8K/RHYN6yTi0ODRul9fxdZfj0HV/WCDq7IoSYnoDNBuAdm6o3DNcYqB2goGWIJseCpsEojEig58ZgEb5oC9WEUIajhj5FEstEJ2xeUh5DFzUyRryQ1XGO2VGh0eziofOCdYryyjLFD1VkOOP2dtqYJm5Pr1q3GMO5NG67T/E6FLLyP0SWioS+6kLG8qnUiMp2hW4FWK3+BA4xJxHRxXeoh0a/kJKZoe+r+WJow9cafcsLdi0Klv64puK35m6Lgn1Xck28vCaZ+2r/ECPcCnfCjZpTvGB60RhzCBJXY04Sspw40rsmKeS4RJVV4r7SdJBVFX5qtvgdisUTBvgJgIQOHzuTCInTh6CY2HtnzTpzg06KNoYMYN7iwnXE6rIi9c5jCj0T/AWWlX3FpH1x9L4Ie+v2fzNK9D2brlrieGF7v1PbWw1aAvw7MEtdA4k13srOx9ZwQvVc0RQU+Jrom34Gj+QaOMk2XbIZUm6618K8P2GUMrlGOouPEm+WAVFokZCQMNLzi+NXp4u1tsZOwbq3ns0XiVvSAi4hj+c0hmwxbtcJXk+p/ElfnhTW2uMGvgQ7pqyuEO+3dxAWqZ/h2dMiYRx/fYXXVrCRCiBwrdo/kKH2KWouEmIVGrPhoAhhgJMBtIAWav2p0aNnf5RSdkaNhoCMMKHVVdEXBJ5qGQb4df"
}
3.5 Abort
Action: Abort
Request "data.body" structure
This request has no body
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"requestID": "c5bf3cbe-a146-4f8c-bb8e-209c1e7b8437",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
}
}
*/
{
"version": 2.0,
"action": "Abort",
"data": "uKd8e4L5AULV+jo/XPg4bzMzppIkvDN8NLoB3NAhUwgB+WZ5yMJBifa9gH/nNh/PJe9Kw96L5BFg0tTjK6WqiL4U35DxDbE+99G+PLRK4AJhhfsEDHwMYZGxILtWItM+9+SGD+zoqLDx/72Jz8IcJhVb8fkPHiECllL/Lyp7Y66TRe59OjvCOov4hHxgh5Cwe8m566LK+2gxCgiUj6sdDZaWngBBx4V/4EG2IkZe2lc="
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| status | String | Y | Response status: Success / Failed / Pending |
| errorCode | String | Y | |
| errorMessage | String | Y |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "c5bf3cbe-a146-4f8c-bb8e-209c1e7b8437",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"status": "Success",
"errorCode": "",
"errorMessage": ""
}
}
*/
{
"version": 2.0,
"action": "Abort",
"data": "Agv/204TIuODU9mRa0g31lG9QKpqY/qD+PSCsN9NjYaOZOW46B9IGL/4qMRIjEo7sMtyMMqwcaMyhoXo6VlvrWwF+nmrdO2HP0QwMYjroYUaTIc8yfX8Edq3UhFG69eZTM1oaX4i8+f/x2PxdsEv+6gIKEqjwHITNG9nFn29ZtNcOcMbJUud7/8SynQXgY9Sz5Q2XgAYrVgxK4WHKWltdktflKGe+Be0I6x6f5s2Gl4YWZFm+BtT3tzIIVUotmUkyxggw7a6PT/BnLWRKzv1Igth1HxMkyLhC6DoiO7+1Tq6/Lq9p2oXJUeCueOV4Z/i"
}
3.6 Void
Action: Void
Request "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| orgReferenceID | String | Y | The reference ID that was carried during the transaction made at that time. |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"requestID": "8e2c77fb-ff49-4c49-82f5-ae741cb7d3d9",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
},
"body": {
"orgReferenceID": "f8b13b22-16ca-4a87-95a4-df4bebf09ee1"
}
}
*/
{
"version": 2.0,
"action": "Void",
"data": "DOdyYB+YN2pT6M7kTImwlruw0Gdn5NHD0Cz3qAgcQaDSr66vQ1X8HtK2eDmeReQIcwjivvwR/+eHFriBT6MssLQLrVD6ZX9Kk427HGFHGuezs9AFd16RLTrl/rODHj3v0efolW8WDXDcz3uzZ3SyRkKCAWvpshL/y5ANLeSk/jAkFRQat/035jn5s80axaJlwEKPh3PNF8QHZY3OFAJqG1UaLMz/DGuteHl3kAKPWNHvUfdVClt+FgfFHvwk+e7njMez638BCMQD3MAxadv7RicdGjT3M+qVeTI+SnDJ4gJJmsPKm2kLz/ATU7xKDN+u"
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| status | String | Y | Response status: Success / Failed / Pending |
| errorCode | String | Y | |
| errorMessage | String | Y |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "8e2c77fb-ff49-4c49-82f5-ae741cb7d3d9",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"status": "Success",
"errorCode": "",
"errorMessage": ""
}
}
*/
{
"version": 2.0,
"action": "Void",
"data": "wmNcblqksB4rvya2WETsBOJf9sg1/rD1W4xx64cfqHi44SVYqXvmBUNRtaGI4+UirFD67WMmpd3z6vgXZsPgfYnYbJm4calNpBAWqgUxgntjJzDm5YRTTZqCzsW16WDu81XozqSRopHUO6tXSPR+X9pxCAA6TKEM3gf1WibryIgcN+LTUjPnz3NvI8weIQCfZIJWDZiiXbIMPCQTXMTUAklxfbQtXgVmi3Eutt7pmmH+GmUOiJOq1ldSuCNUc2PnRIkVc30DAW9g8nIBmHJo887dzuHP/CeBlUgC4FETdUKM+UDvuNJAJl05lVZSeSTC"
}
3.7 Refund
Action: Refund
Request "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| referenceID | String | Y | Reference ID: Every time a transaction request is initiated, it is crucial to ensure the uniqueness of this ID. Failure to do so will render any subsequent operations on the transaction invalid. |
| orgReferenceID | String | Y | The reference ID that was carried during the transaction made at that time. |
| currency | String | Y | Example: HKD / USD / RMB |
| amount | String | Y | preAuth amount |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"requestID": "0f7d24de-76fc-46f8-861b-2d0d31a2dd9e",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
},
"body": {
"referenceID": "97cc8c76-65e0-4bd2-9e16-9b9e09f65c42",
"orgReferenceID": "4b02c099-4dc9-4b30-bd2c-27bf3223df6b",
"currency": "HKD",
"amount": "10.20"
}
}
*/
{
"version": 2.0,
"action": "Refund",
"data": "9s+NYd52kApZBoNgI2PogmN02RP/CFmOM4lrFnAL0tTDiz/g8dlKhvNUby2uo8Yuui6OqRBHpPTkIR97ov9q5E93EWYsDSBqpEZuIx/FF5q05GR2BkEnWiYre1+ZhROnihycHQv8HXIHuihB+d+n4bGMtjxFyV4t9hbH43HBqtS+d6yXMBNObWA+WwIZKPwAUq+rnlvSfuIoeIUwoTy6GqhEK8R9I1El05Hc1Kg0KJBdGHlXLBYU+xkQvGPn3C1KRUG8lmRIzTXaIDbNw+4GAhq14IeyhXfzhS6JWysUza8XTOaXUiKYc3Ka4gb3jTt/ZynCTmjE0A/Za037nW4AFAZy478Bq/cqkgZP8qvVVd7qYxkqpe+dBM9eU5BhYSqkIGvpOiDiWTBy8TXqRfexRbkDAkRFm7QD/ng/P1dJO2Y="
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| status | String | Y | Response status: Success / Failed / Pending |
| errorCode | String | Y | |
| errorMessage | String | Y |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "0f7d24de-76fc-46f8-861b-2d0d31a2dd9e",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"status": "Success",
"errorCode": "",
"errorMessage": ""
}
}
*/
{
"version": 2.0,
"action": "Refund",
"data": "NfjFtfEdlWXJ/sXqaApef8FQcjoOr8mZynJlBfOw5OdOgtvUUdB7GTbGJvNt6Ye94DsavJQhz5+drVUaSwZVTB8BVmBN/CsE2tfcPz3Bu9F59jzIPU9+AKMjp1/1aQ6/btm6bEcauWSZshQE+G3oVOgM0EgI/ZFGGN5n7i5OzW6tCWvLl9SvoaIXbsolp4AT9tzO51nKl8Plr0y7jTOatls6D4sSxPnLguuQJrqA9Q/wUMMYakwX+7MaedMIP0iitImsvoLOJRULTVLRhCbv0FebFZ/X1sDeSHe23yuLlnbdlhpSXjToRsWgCUtOLALc"
}
3.8 Query Transaction Status
Action: TransactionStatus
Request "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| targetReferenceID | String | Y | The reference ID that was carried during the transaction made at that time. |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"action": "TransactionStatus",
"requestID": "18fd2b62-6f65-40f2-8b94-88ef32f07a3f",
"clientDeviceSN": "126498561093",
"timestamp": "2025-11-12T10:11:04+00:00"
},
"body": {
"targetReferenceID": "f8b13b22-16ca-4a87-95a4-df4bebf09ee1"
}
}
*/
{
"version": 2.0,
"action": "TransactionStatus",
"data": "3U0hWqglZgydHjzniajpGTJcl2rjH8b1jINmfnDKq+c6ogfrhxVpjtY9AybhgmP9s4mMo2BvPLKMumyzFfZ8n+nInKQTMPUYvIB+TJqV9wdxhqORsfIH8o9hLJd9ot31opwxVdIqlGdU3S3qNzEeZQ4KmqcbyneolQv6niBkiHNf3dnyZHXv7gr6yY56P5g3gORRK82a2JZBkZGwPJj9Me1aazM7QImj1AiSzaBu9Q+fqSTcYvtF7JGe0rvWp6BGXoBdlN2MEJPC+UtK0ZLdolwtaX5HV6wLeXm+dRh/6BCC9cXugE0XY1WCFSqcqxs2C9yagn5fbMwd7ZhVJ2qewI9Mc2TCkBAYQcZX+RRDllE="
}
Response "data.body" structure
| Variable | Type | Required | Description |
|---|---|---|---|
| status | String | Y | Response status: Success / Failed / Pending |
| errorCode | String | Y | |
| errorMessage | String | Y | |
| ... | Any | Y | It is consistent with the returned information of the current query transaction type |
Example:
/*
Key: AES Key 2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN)
Encrypt the original data:
{
"header": {
"responseID": "18fd2b62-6f65-40f2-8b94-88ef32f07a3f",
"serverDeviceSN": "NEXGO-N96-1170270945",
"timestamp": "2025-11-12T10:12:04+00:00"
},
"body": {
"status": "Success",
"errorCode": "",
"errorMessage": "",
"customerOrderID": "e246c1cc-02f6-4ac5-b7fb-066cb2b2f5b1",
"currency": "HKD",
"amount": "10.20",
"paymentMethod": "visa",
"paymentEntryType": "contactless",
"rrn": "3263492852830699521",
"brn": "3263492852495159296",
"transactionStatus": "Success",
"transactionTime": "2023-06-30T09:08:52+00:00",
"creditCard": {
"panPrefix6Digits": "555555",
"panLast4Digits": "1234",
"panHash": "xxxxxxx",
"panToken": ""
}
}
}
*/
{
"version": 2.0,
"action": "gBzyZALiwFJKCafXs52mAn0yishykDmpTHSSEy7i4HYVulo3pT816TrdZjw9P6ha7eMIeKM3iror6BXNtKFI38vzSLykjLOHCKuwUnAxdsph8exTzklpsgLcplJ2Pt4dEJbnEPRvsX+e1e20tX+pJcoNkR3psyvDPAfHoMsZgwERdmgdl7HbcwQd1UPItf7EICVGUAzksBCkwvDYq8E7FRPNV7EbJc4uIGiWSCY2Q0bcs23k16/K5O9+uEEgo3j6J/eooEzuhhkZMx+rETRAud9KLFr0OXpfwACwR4T18jBWF9LU6YxGM2bqiBKLsE8Gctj/XgXbZuQK7zgunHXE+xgqZgiHQlKlRsecJlK3Uf59O094RsEyI8yyJQd5kT/X8fIoMpLCzQqs08IH91lVvQws1ZzipDy/DyxYIBpQzeVQYQ/OCkIDxy4s7ew4hWlKCTSDW32sDUTwA6aej0U+jZk71eUSWhQlVX63br9YRhZ9n+mSI8Fkix3FHerzVBdsbDSlE88fP7vj9N96YIyOILRVjCcJRIZ1ixlxtRwpTTw/In42j6YLffvkV0/aGQjQYeVEnymdGhC/HhlHhSlWmGjbfiJ3UXxsxSyeANeccAlV76+7t6kME55uPC2AU16VYIwfWsj5T7cBSsxaEqmVlQiOgiLNRbR3wp1Q4azzCe1owFD5xt6SWds5zM17cnLlxgqonOHboKUIFDq8lUx/OXFDk8aBcqYJ4wYUau/3iH1ItZi2hDzOsMf5AliRMytaW+TUS/szyGcBGIIEWVEUPeuDoTGAVDtg4QV5TYpKcTmZneiYWXA0OziglknyLXZ2"
}