ECR Interface Protocol

1. Encrypt the data of the protocol

1. Encrypt the data of the protocol

1.1 Get a 6-digit pair Pin Code

When the Wonder terminal enters the pair page, a 6-digit pair Pin Code(pinCode1) 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 pinCode1 = '260880';

1.2 Encrypt the data of the protocol with AES256

  • JavaScript
  • Node.js
  • Java
  • Go
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>浏览器端AES加解密演示</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f7fa; color: #333; line-height: 1.6; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background-color: #fff; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); overflow: hidden; } .header { background-color: #4a90e2; color: white; padding: 20px; text-align: center; } .header h1 { font-size: 24px; font-weight: 500; } .content { padding: 30px; } .section { margin-bottom: 30px; padding: 20px; border-radius: 8px; border: 1px solid #e1e8ed; background-color: #fafbfc; } .section-title { font-size: 18px; font-weight: 500; margin-bottom: 15px; color: #4a90e2; border-bottom: 1px solid #e1e8ed; padding-bottom: 8px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; font-weight: 500; color: #555; } .form-group input, .form-group textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; font-family: inherit; } .form-group textarea { resize: vertical; min-height: 100px; } .btn { background-color: #4a90e2; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; margin-right: 10px; margin-bottom: 10px; transition: background-color 0.3s; } .btn:hover { background-color: #357abd; } .btn-secondary { background-color: #6c757d; } .btn-secondary:hover { background-color: #5a6268; } .result { margin-top: 20px; padding: 15px; border-radius: 4px; background-color: #e9ecef; font-family: 'Courier New', Courier, monospace; font-size: 14px; word-break: break-all; } .result.success { background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; } .result.error { background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; } .test-results { margin-top: 20px; } .test-item { margin-bottom: 20px; padding: 15px; border-radius: 4px; border: 1px solid #e1e8ed; background-color: #fff; } .test-title { font-weight: 500; margin-bottom: 10px; color: #4a90e2; } .test-details { font-family: 'Courier New', Courier, monospace; font-size: 13px; margin-bottom: 8px; } .test-status { font-weight: 500; padding: 4px 8px; border-radius: 4px; display: inline-block; } .test-status.passed { background-color: #d4edda; color: #155724; } .test-status.failed { background-color: #f8d7da; color: #721c24; } .copy-btn { background-color: #28a745; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-left: 10px; transition: background-color 0.3s; } .copy-btn:hover { background-color: #218838; } @media (max-width: 768px) { body { padding: 10px; } .content { padding: 20px; } .btn { width: 100%; margin-right: 0; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>浏览器端AES-256-CBC加解密演示</h1> </div> <div class="content"> <!-- 手动加密解密区域 --> <div class="section"> <div class="section-title">手动加密解密</div> <div class="form-group"> <label for="key">密钥 (Password/Key):</label> <input type="text" id="key" placeholder="请输入密钥" value="260880"> </div> <div class="form-group"> <label for="plaintext">明文数据:</label> <textarea id="plaintext" placeholder="请输入要加密的明文数据">123</textarea> </div> <div class="form-group"> <label for="ciphertext">密文数据:</label> <textarea id="ciphertext" placeholder="请输入要解密的密文数据"></textarea> </div> <button class="btn" onclick="encryptData()">加密</button> <button class="btn btn-secondary" onclick="decryptData()">解密</button> <div id="manual-result" class="result"></div> </div> <!-- 自动测试区域 --> <div class="section"> <div class="section-title">自动测试</div> <button class="btn" onclick="runTests()">运行所有测试</button> <div id="test-results" class="test-results"></div> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script> <script> /** * AES加密解密工具类 * 使用CryptoJS实现AES-256-CBC加密解密 * 包含PKCS7填充和PBKDF2密钥派生功能 */ const AESCrypto = { // 常量定义 BLOCK_SIZE: 16, // AES块大小(字节) KEY_SIZE: 256 / 32, // 密钥大小(WordArray单位) ITERATIONS: 100, // PBKDF2迭代次数 SALT_SIZE: 16, // 盐大小(字节) IV_SIZE: 16, // IV大小(字节) /** * PKCS7填充函数 * @param {CryptoJS.lib.WordArray} wordArray - 要填充的数据 * @param {number} [blockSize=16] - 块大小 * @returns {CryptoJS.lib.WordArray} 填充后的数据 */ pkcs7Pad: function(wordArray, blockSize = this.BLOCK_SIZE) { if (!wordArray || typeof wordArray.sigBytes !== 'number') { throw new Error('Invalid input: wordArray must be a valid CryptoJS WordArray'); } const padding = blockSize - (wordArray.sigBytes % blockSize); const paddingWords = []; const paddingWord = ((padding & 0xff) << 24) | ((padding & 0xff) << 16) | ((padding & 0xff) << 8) | (padding & 0xff); for (let i = 0; i < padding / 4; i++) { paddingWords.push(paddingWord); } return wordArray.clone().concat(CryptoJS.lib.WordArray.create(paddingWords, padding)); }, /** * PKCS7去填充函数 * @param {CryptoJS.lib.WordArray} wordArray - 要去填充的数据 * @returns {CryptoJS.lib.WordArray} 去填充后的数据 */ pkcs7Unpad: function(wordArray) { if (!wordArray || typeof wordArray.sigBytes !== 'number') { throw new Error('Invalid input: wordArray must be a valid CryptoJS WordArray'); } const sigBytes = wordArray.sigBytes; if (sigBytes === 0) { throw new Error('Invalid input: wordArray is empty'); } // 获取最后一个字节作为填充长度 const lastWord = wordArray.words[Math.floor((sigBytes - 1) / 4)]; const padding = lastWord & 0xff; // 验证填充长度 if (padding < 1 || padding > this.BLOCK_SIZE) { throw new Error('Invalid PKCS7 padding: padding length out of range'); } // 验证所有填充字节 for (let i = sigBytes - padding; i < sigBytes; i++) { const currentWord = wordArray.words[Math.floor(i / 4)]; const currentByte = (currentWord >> (24 - (i % 4) * 8)) & 0xff; if (currentByte !== padding) { throw new Error('Invalid PKCS7 padding: inconsistent padding bytes'); } } // 创建去填充后的数据 return CryptoJS.lib.WordArray.create(wordArray.words, sigBytes - padding); }, /** * 使用PBKDF2派生密钥 * @param {string} pinCode - 密码/密钥 * @param {CryptoJS.lib.WordArray} salt - 盐值 * @returns {CryptoJS.lib.WordArray} 派生的密钥 */ deriveKeySecure: function(pinCode, salt) { if (!pinCode || !salt) { throw new Error('Invalid input: pinCode and salt are required'); } return CryptoJS.PBKDF2(pinCode, salt, { keySize: this.KEY_SIZE, iterations: this.ITERATIONS, hasher: CryptoJS.algo.SHA256 }); }, /** * 加密数据 * @param {string} pinCode - 密码/密钥 * @param {string} data - 要加密的明文数据 * @returns {string} Base64编码的加密结果 */ encrypt: function(pinCode, data) { try { if (!pinCode) { throw new Error('Invalid input: pinCode and data are required'); } // 生成随机盐和IV const salt = CryptoJS.lib.WordArray.random(this.SALT_SIZE); const iv = CryptoJS.lib.WordArray.random(this.IV_SIZE); // 派生密钥 const key = this.deriveKeySecure(pinCode, salt); // 转换数据并填充 const dataWA = CryptoJS.enc.Utf8.parse(data); const paddedData = this.pkcs7Pad(dataWA); // 执行加密 const encrypted = CryptoJS.AES.encrypt(paddedData, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding }); // 拼接盐、IV和密文并Base64编码 return CryptoJS.enc.Base64.stringify( salt.clone().concat(iv).concat(encrypted.ciphertext) ); } catch (error) { console.error('Encryption failed:', error); throw error; } }, /** * 解密数据 * @param {string} pinCode - 密码/密钥 * @param {string} encryptedBase64 - Base64编码的加密数据 * @returns {string} 解密后的明文数据 */ decrypt: function(pinCode, encryptedBase64) { try { if (!pinCode || !encryptedBase64) { throw new Error('Invalid input: pinCode and encryptedBase64 are required'); } // 解析Base64数据 const encryptedData = CryptoJS.enc.Base64.parse(encryptedBase64); // 验证数据长度 if (encryptedData.sigBytes < this.SALT_SIZE + this.IV_SIZE) { throw new Error('Invalid encrypted data: data too short'); } // 提取盐、IV和密文 const salt = CryptoJS.lib.WordArray.create( encryptedData.words.slice(0, this.SALT_SIZE / 4), this.SALT_SIZE ); const iv = CryptoJS.lib.WordArray.create( encryptedData.words.slice(this.SALT_SIZE / 4, (this.SALT_SIZE + this.IV_SIZE) / 4), this.IV_SIZE ); const ciphertext = CryptoJS.lib.WordArray.create( encryptedData.words.slice((this.SALT_SIZE + this.IV_SIZE) / 4), encryptedData.sigBytes - this.SALT_SIZE - this.IV_SIZE ); // 派生密钥 const key = this.deriveKeySecure(pinCode, salt); // 执行解密 const decrypted = CryptoJS.AES.decrypt({ ciphertext: ciphertext }, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.NoPadding }); // 去填充并转换为字符串 const unpaddedData = this.pkcs7Unpad(decrypted); return CryptoJS.enc.Utf8.stringify(unpaddedData); } catch (error) { console.error('Decryption failed:', error); throw error; } } }; /** * 加密数据并显示结果 */ function encryptData() { const key = document.getElementById('key').value; const plaintext = document.getElementById('plaintext').value; const resultDiv = document.getElementById('manual-result'); try { const encrypted = AESCrypto.encrypt(key, plaintext); document.getElementById('ciphertext').value = encrypted; resultDiv.innerHTML = `加密成功: <br><strong>密文:</strong> ${encrypted}`; resultDiv.className = 'result success'; } catch (error) { resultDiv.innerHTML = `加密失败: ${error.message}`; resultDiv.className = 'result error'; } } /** * 解密数据并显示结果 */ function decryptData() { const key = document.getElementById('key').value; const ciphertext = document.getElementById('ciphertext').value; const resultDiv = document.getElementById('manual-result'); try { const decrypted = AESCrypto.decrypt(key, ciphertext); resultDiv.innerHTML = `解密成功: <br><strong>明文:</strong> ${decrypted}`; resultDiv.className = 'result success'; } catch (error) { resultDiv.innerHTML = `解密失败: ${error.message}`; resultDiv.className = 'result error'; } } /** * 添加测试结果到UI * @param {string} title - 测试标题 * @param {string} details - 测试详细信息 * @param {boolean} passed - 是否通过 */ function addTestResult(title, details, passed) { const testResultsDiv = document.getElementById('test-results'); const testItem = document.createElement('div'); testItem.className = 'test-item'; const testTitle = document.createElement('div'); testTitle.className = 'test-title'; testTitle.innerHTML = title; const testDetails = document.createElement('div'); testDetails.className = 'test-details'; testDetails.innerHTML = details.replace(/\n/g, '<br>'); const testStatus = document.createElement('div'); testStatus.className = `test-status ${passed ? 'passed' : 'failed'}`; testStatus.textContent = passed ? '通过' : '失败'; testItem.appendChild(testTitle); testItem.appendChild(testDetails); testItem.appendChild(testStatus); testResultsDiv.appendChild(testItem); } /** * 运行所有测试 */ function runTests() { // 清空之前的测试结果 document.getElementById('test-results').innerHTML = ''; console.log('=== AESCrypto 测试开始 ==='); let testCount = 0; let passedCount = 0; // 测试1: 基本功能测试 testCount++; try { const pinCode1 = '260880'; const data1 = '123'; const encrypted1 = AESCrypto.encrypt(pinCode1, data1); const decrypted1 = AESCrypto.decrypt(pinCode1, encrypted1); const passed = decrypted1 === data1; if (passed) passedCount++; const details = `密钥: ${pinCode1}\n明文: ${data1}\n密文: ${encrypted1}\n解密后: ${decrypted1}`; addTestResult(`测试${testCount} - 基本功能测试`, details, passed); } catch (error) { addTestResult(`测试${testCount} - 基本功能测试`, error.message, false); } // 测试2: 复杂密钥和中文数据测试 testCount++; try { const pinCode2 = 'OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN'; const data2 = '这是一段中文测试数据'; const encrypted2 = AESCrypto.encrypt(pinCode2, data2); const decrypted2 = AESCrypto.decrypt(pinCode2, encrypted2); const passed = decrypted2 === data2; if (passed) passedCount++; const details = `密钥: ${pinCode2}\n明文: ${data2}\n密文: ${encrypted2}\n解密后: ${decrypted2}`; addTestResult(`测试${testCount} - 复杂密钥和中文数据测试`, details, passed); } catch (error) { addTestResult(`测试${testCount} - 复杂密钥和中文数据测试`, error.message, false); } // 测试3: 空数据测试 testCount++; try { const pinCode3 = '260880'; const data3 = ''; const encrypted3 = AESCrypto.encrypt(pinCode3, data3); const decrypted3 = AESCrypto.decrypt(pinCode3, encrypted3); const passed = decrypted3 === data3; if (passed) passedCount++; const details = `密钥: ${pinCode3}\n明文: (空字符串)\n密文: ${encrypted3}\n解密后: (空字符串)`; addTestResult(`测试${testCount} - 空数据测试`, details, passed); } catch (error) { addTestResult(`测试${testCount} - 空数据测试`, error.message, false); } // 测试4: 特殊字符测试 testCount++; try { const pinCode4 = '260880'; const data4 = '!@#$%^&*()_+-=[]{}|;:,.<>?'; const encrypted4 = AESCrypto.encrypt(pinCode4, data4); const decrypted4 = AESCrypto.decrypt(pinCode4, encrypted4); const passed = decrypted4 === data4; if (passed) passedCount++; const details = `密钥: ${pinCode4}\n明文: ${data4}\n密文: ${encrypted4}\n解密后: ${decrypted4}`; addTestResult(`测试${testCount} - 特殊字符测试`, details, passed); } catch (error) { addTestResult(`测试${testCount} - 特殊字符测试`, error.message, false); } // 测试5: 长文本测试 testCount++; try { const pinCode5 = '260880'; const data5 = '这是一段较长的测试文本,用于测试AES加密解密的性能和正确性。这段文本包含了多种字符,包括中文、英文、数字和特殊符号。'; const encrypted5 = AESCrypto.encrypt(pinCode5, data5); const decrypted5 = AESCrypto.decrypt(pinCode5, encrypted5); const passed = decrypted5 === data5; if (passed) passedCount++; const details = `密钥: ${pinCode5}\n明文长度: ${data5.length} 字符\n密文长度: ${encrypted5.length} 字符\n解密后长度: ${decrypted5.length} 字符\n解密后与明文一致: ${passed}`; addTestResult(`测试${testCount} - 长文本测试`, details, passed); } catch (error) { addTestResult(`测试${testCount} - 长文本测试`, error.message, false); } // 测试6: 错误密钥测试 testCount++; try { const pinCode6 = '260880'; const wrongKey = 'wrongkey'; const data6 = '123'; const encrypted6 = AESCrypto.encrypt(pinCode6, data6); // 使用错误密钥解密,应该失败 const decrypted6 = AESCrypto.decrypt(wrongKey, encrypted6); // 这里应该抛出错误,如果没有抛出则测试失败 addTestResult(`测试${testCount} - 错误密钥测试`, '使用错误密钥解密时没有抛出预期的错误', false); } catch (error) { // 预期应该抛出错误,所以测试通过 passedCount++; addTestResult(`测试${testCount} - 错误密钥测试`, `使用错误密钥解密时正确抛出错误: ${error.message}`, true); } // 显示测试总结 const summary = document.createElement('div'); summary.className = 'test-item'; summary.innerHTML = ` <div class="test-title">测试总结</div> <div class="test-details"> 总测试数: ${testCount}<br> 通过测试: ${passedCount}<br> 失败测试: ${testCount - passedCount}<br> 通过率: ${((passedCount / testCount) * 100).toFixed(2)}% </div> <div class="test-status ${passedCount === testCount ? 'passed' : 'failed'}"> ${passedCount === testCount ? '全部通过' : '部分失败'} </div> `; document.getElementById('test-results').appendChild(summary); console.log(`=== AESCrypto 测试结束 ===`); console.log(`总测试数: ${testCount}`); console.log(`通过测试: ${passedCount}`); console.log(`失败测试: ${testCount - passedCount}`); console.log(`通过率: ${((passedCount / testCount) * 100).toFixed(2)}%`); } // 页面加载完成后自动运行测试 window.addEventListener('load', function() { runTests(); }); </script> </body> </html>
const crypto = require("crypto"); /** * 加密解密工具类 */ class CryptoUtil { // 常量定义 static get ALGORITHM() { return 'aes-256-cbc'; } static get SALT_LENGTH() { return 16; } static get IV_LENGTH() { return 16; } static get KEY_LENGTH() { return 32; } static get ITERATIONS() { return 100; } static get HASH_ALGORITHM() { return 'sha256'; } /** * 安全的密钥派生函数 * @param {string|Buffer} pinCode - 密码或密钥 * @param {Buffer} salt - 盐值 * @returns {Promise<Buffer>} 派生的密钥 */ static async deriveKeySecure(pinCode, salt) { return new Promise((resolve, reject) => { crypto.pbkdf2( pinCode, salt, CryptoUtil.ITERATIONS, CryptoUtil.KEY_LENGTH, CryptoUtil.HASH_ALGORITHM, (err, derivedKey) => { if (err) return reject(err); resolve(derivedKey); } ); }); } /** * PKCS7填充 * @param {Buffer} data - 要填充的数据 * @param {number} blockSize - 块大小 * @returns {Buffer} 填充后的数据 */ static pkcs7Pad(data, blockSize = 16) { const padding = blockSize - (data.length % blockSize); const padded = Buffer.alloc(data.length + padding); data.copy(padded); padded.fill(padding, data.length); return padded; } /** * PKCS7取消填充 * @param {Buffer} padded - 填充后的数据 * @returns {Buffer} 取消填充后的数据 * @throws {Error} 无效的填充 */ static pkcs7Unpad(padded) { if (!Buffer.isBuffer(padded)) { throw new TypeError('Input must be a Buffer'); } const length = padded.length; if (length === 0) { throw new Error('Invalid padding: empty input'); } const padding = padded[length - 1]; if (padding < 1 || padding > length) { throw new Error(`Invalid padding: padding value ${padding} out of range`); } // 验证所有填充字节都正确 for (let i = length - padding; i < length; i++) { if (padded[i] !== padding) { throw new Error('Invalid padding: inconsistent padding bytes'); } } return padded.slice(0, length - padding); } /** * 加密数据 * @param {string|Buffer} pinCode - 密码或密钥 * @param {string|Buffer} data - 要加密的数据 * @returns {Promise<string>} 加密后的Base64字符串 * @throws {TypeError} 无效的输入类型 * @throws {Error} 加密失败 */ static async encrypt(pinCode, data) { // 参数验证 if (typeof pinCode !== 'string' && !Buffer.isBuffer(pinCode)) { throw new TypeError('pinCode must be a string or Buffer'); } if (typeof data !== 'string' && !Buffer.isBuffer(data)) { throw new TypeError('data must be a string or Buffer'); } try { // 生成随机盐和IV const salt = await CryptoUtil.generateRandomBytes(CryptoUtil.SALT_LENGTH); const iv = await CryptoUtil.generateRandomBytes(CryptoUtil.IV_LENGTH); // 使用PBKDF2派生密钥 const key = await CryptoUtil.deriveKeySecure(pinCode, salt); // 转换数据为Buffer const dataBuffer = typeof data === 'string' ? Buffer.from(data, 'utf8') : data; // PKCS7填充 const paddedData = CryptoUtil.pkcs7Pad(dataBuffer, 16); // AES-256-CBC加密 const cipher = crypto.createCipheriv(CryptoUtil.ALGORITHM, 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'); } catch (error) { throw new Error(`Encryption failed: ${error.message}`); } } /** * 解密数据 * @param {string|Buffer} pinCode - 密码或密钥 * @param {string} encryptedBase64 - 加密后的Base64字符串 * @returns {Promise<string>} 解密后的字符串 * @throws {TypeError} 无效的输入类型 * @throws {Error} 解密失败 */ static async decrypt(pinCode, encryptedBase64) { // 参数验证 if (typeof pinCode !== 'string' && !Buffer.isBuffer(pinCode)) { throw new TypeError('pinCode must be a string or Buffer'); } if (typeof encryptedBase64 !== 'string') { throw new TypeError('encryptedBase64 must be a string'); } try { // Base64解码 const encryptedData = Buffer.from(encryptedBase64, 'base64'); // 验证数据长度(至少包含盐+IV) const minLength = CryptoUtil.SALT_LENGTH + CryptoUtil.IV_LENGTH; if (encryptedData.length < minLength) { throw new Error(`Encrypted data too short: expected at least ${minLength} bytes, got ${encryptedData.length} bytes`); } // 提取盐、IV和密文 const salt = encryptedData.slice(0, CryptoUtil.SALT_LENGTH); const iv = encryptedData.slice(CryptoUtil.SALT_LENGTH, CryptoUtil.SALT_LENGTH + CryptoUtil.IV_LENGTH); const ciphertext = encryptedData.slice(CryptoUtil.SALT_LENGTH + CryptoUtil.IV_LENGTH); // 验证密文长度 if (ciphertext.length % 16 !== 0) { throw new Error('Invalid ciphertext length: must be a multiple of 16 bytes'); } // 使用PBKDF2派生密钥 const key = await CryptoUtil.deriveKeySecure(pinCode, salt); // AES-256-CBC解密 const decipher = crypto.createDecipheriv(CryptoUtil.ALGORITHM, key, iv); decipher.setAutoPadding(false); // 禁用自动填充 const decrypted = Buffer.concat([\ decipher.update(ciphertext),\ decipher.final(),\ ]); // 去除填充 const unpadded = CryptoUtil.pkcs7Unpad(decrypted); return unpadded.toString('utf8'); } catch (error) { throw new Error(`Decryption failed: ${error.message}`); } } /** * 生成随机字节 * @param {number} length - 字节长度 * @returns {Promise<Buffer>} 随机字节 */ static async generateRandomBytes(length) { return new Promise((resolve, reject) => { crypto.randomBytes(length, (err, bytes) => { if (err) return reject(err); resolve(bytes); }); }); } /** * 生成随机字符串 * @param {number} length - 字符串长度 * @returns {Promise<string>} 随机字符串 */ static async generateRandomString(length) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; // 生成安全的随机字节 const randomBytes = await CryptoUtil.generateRandomBytes(length); for (let i = 0; i < length; i++) { // 使用模62确保均匀分布到62个字符 const index = randomBytes[i] % chars.length; result += chars[index]; } return result; } } // 测试函数 async function runTests() { try { // 测试pinCode1 const pinCode1 = '260880'; // JSON 数据 const data1 = JSON.stringify({ "header": { "requestID": "18fd2b62-6f65-40f2-8b94-88ef32f07a3f", "clientDeviceSN": "126498561093", "timestamp": "2025-11-12T10:11:04+00:00" }, "body": { "targetReferenceID": "f8b13b22-16ca-4a87-95a4-df4bebf09ee1" } }); console.log('pinCode1:', pinCode1); const encryptedData1 = await CryptoUtil.encrypt(pinCode1, data1); console.log('Encrypted Data 1:', encryptedData1); const decryptedData1 = await CryptoUtil.decrypt(pinCode1, encryptedData1); console.log('Decrypted Data 1:', decryptedData1); console.log('Decrypted Data 1 is valid JSON:', JSON.parse(decryptedData1)); // 测试pinCode2 const pinCode2 = 'OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN'; console.log('pinCode2:', pinCode2); const encryptedData2 = await CryptoUtil.encrypt(pinCode2, data1); console.log('Encrypted Data 2:', encryptedData2); const decryptedData2 = await CryptoUtil.decrypt(pinCode2, encryptedData2); console.log('Decrypted Data 2:', decryptedData2); console.log('Decrypted Data 2 is valid JSON:', JSON.parse(decryptedData2)); // 测试随机字符串生成 const randomString = await CryptoUtil.generateRandomString(32); console.log('Generated Random String:', randomString); // 测试使用随机字符串作为密钥 const encryptedData3 = await CryptoUtil.encrypt(randomString, data1); const decryptedData3 = await CryptoUtil.decrypt(randomString, encryptedData3); console.log('Random key encryption/decryption works:', JSON.parse(decryptedData3).header.action === 'TransactionStatus'); } catch (error) { console.error('Test failed:', error.message); console.error(error.stack); } } // 运行测试 runTests();
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; /** * AES加密解密工具类,使用AES-256-CBC模式和PBKDF2密钥派生 */ public class AESCryptoUtil { // 加密相关常量 private static final int SALT_LENGTH = 16; // 盐的长度(字节) private static final int IV_LENGTH = 16; // IV的长度(字节) private static final int KEY_LENGTH = 32; // AES-256密钥长度(字节) private static final int ITERATIONS = 100; // PBKDF2迭代次数 private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; // 加密算法(PKCS5Padding在Java中实际是PKCS7) private static final String KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA256"; // 密钥派生算法 private static final String AES_ALGORITHM = "AES"; // AES算法名称 // 线程安全的SecureRandom实例 private static final SecureRandom SECURE_RANDOM; static { try { SECURE_RANDOM = SecureRandom.getInstanceStrong(); } catch (NoSuchAlgorithmException e) { // 如果强随机数生成器不可用,回退到默认实现 throw new RuntimeException("Failed to initialize SecureRandom", e); } } /** * 使用PBKDF2算法从密码和盐派生AES密钥 * * @param pinCode 密码或PIN码 * @param salt 盐值 * @return 派生的AES密钥 * @throws NoSuchAlgorithmException 如果算法不可用 * @throws InvalidKeySpecException 如果密钥规范无效 * @throws IllegalArgumentException 如果输入参数无效 */ private static SecretKey deriveKeySecure(String pinCode, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException { if (pinCode == null || pinCode.isEmpty()) { throw new IllegalArgumentException("Pin code cannot be null or empty"); } if (salt == null || salt.length != SALT_LENGTH) { throw new IllegalArgumentException("Invalid salt"); } SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_ALGORITHM); KeySpec spec = new PBEKeySpec(pinCode.toCharArray(), salt, ITERATIONS, KEY_LENGTH * 8); SecretKey tmp = factory.generateSecret(spec); return new SecretKeySpec(tmp.getEncoded(), AES_ALGORITHM); } /** * 加密数据 * * @param pinCode 加密密钥(用于派生AES密钥) * @param data 要加密的数据 * @return Base64编码的加密数据(包含盐、IV和密文) * @throws Exception 如果加密过程中发生错误 * @throws IllegalArgumentException 如果输入参数无效 */ public static String encrypt(String pinCode, String data) throws Exception { if (pinCode == null || pinCode.isEmpty()) { throw new IllegalArgumentException("Pin code cannot be null or empty"); } if (data == null) { throw new IllegalArgumentException("Data cannot be null"); } // 生成随机盐 byte[] salt = new byte[SALT_LENGTH]; SECURE_RANDOM.nextBytes(salt); // 生成随机IV byte[] iv = new byte[IV_LENGTH]; SECURE_RANDOM.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)); // 加密数据 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); } /** * 解密数据 * * @param pinCode 解密密钥(用于派生AES密钥) * @param encryptedBase64 Base64编码的加密数据 * @return 解密后的数据 * @throws Exception 如果解密过程中发生错误 * @throws IllegalArgumentException 如果输入参数无效 */ public static String decrypt(String pinCode, String encryptedBase64) throws Exception { if (pinCode == null || pinCode.isEmpty()) { throw new IllegalArgumentException("Pin code cannot be null or empty"); } if (encryptedBase64 == null || encryptedBase64.isEmpty()) { throw new IllegalArgumentException("Encrypted data cannot be null or empty"); } // 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)); // 解密数据 byte[] decrypted = cipher.doFinal(ciphertext); return new String(decrypted, StandardCharsets.UTF_8); } /** * 生成指定长度的随机字符串 * * @param length 字符串长度 * @return 随机字符串 * @throws IllegalArgumentException 如果长度小于等于0 */ public static String generateRandomString(int length) { if (length <= 0) { throw new IllegalArgumentException("Length must be positive"); } final String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; StringBuilder result = new StringBuilder(length); for (int i = 0; i < length; i++) { int index = SECURE_RANDOM.nextInt(chars.length()); result.append(chars.charAt(index)); } return result.toString(); } /** * 测试加密解密功能 */ public static void main(String[] args) { try { // 测试pinCode1 String pinCode1 = "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("pinCode1: " + pinCode1); System.out.println("Original Data: " + data1); // 加密 String encryptedData1 = encrypt(pinCode1, data1); System.out.println("Encrypted Data 1: " + encryptedData1); // 解密 String decryptedData1 = decrypt(pinCode1, 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"); } // 测试pinCode2 String pinCode2 = "OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN"; System.out.println("\npinCode2: " + pinCode2); // 使用相同的测试数据 String encryptedData2 = encrypt(pinCode2, data1); System.out.println("Encrypted Data 2: " + encryptedData2); // 解密 String decryptedData2 = decrypt(pinCode2, 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 = generateRandomString(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) { System.err.println("Test failed with exception: " + e.getMessage()); e.printStackTrace(); } } }
package main import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "testing" "golang.org/x/crypto/pbkdf2" ) // AESCrypto 提供AES-256-CBC加密解密功能 type AESCrypto struct { iterations int } // NewAESCrypto 创建一个新的AESCrypto实例 func NewAESCrypto(iterations int) *AESCrypto { if iterations <= 0 { iterations = 100 // 默认迭代次数 } return &AESCrypto{ iterations: iterations, } } // 常量定义 const ( SaltSize = 16 // 盐的大小(字节) IVSize = 16 // IV的大小(字节) KeySize = 32 // 密钥的大小(字节,AES-256需要32字节) BlockSize = aes.BlockSize CharsSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" CharsSetSize = len(CharsSet) ) // deriveKey 使用PBKDF2算法从pinCode和salt派生密钥 func (a *AESCrypto) deriveKey(pinCode string, salt []byte) []byte { return pbkdf2.Key([]byte(pinCode), salt, a.iterations, KeySize, sha256.New) } // pkcs7Pad 对数据进行PKCS7填充 func pkcs7Pad(data []byte, blockSize int) []byte { padding := blockSize - (len(data) % blockSize) padText := bytes.Repeat([]byte{byte(padding)}, padding) return append(data, padText...) } // pkcs7Unpad 去除PKCS7填充 func pkcs7Unpad(data []byte) ([]byte, error) { if len(data) == 0 { return nil, errors.New("empty data") } padding := int(data[len(data)-1]) if padding < 1 || padding > len(data) { return nil, errors.New("invalid padding") } // 验证所有填充字节都正确 for i := len(data) - padding; i < len(data); i++ { if data[i] != byte(padding) { return nil, errors.New("invalid padding") } } return data[:len(data)-padding], nil } // Encrypt 使用AES-256-CBC算法加密数据 func (a *AESCrypto) Encrypt(pinCode, data string) (string, error) { if pinCode == "" { return "", errors.New("pinCode is empty") } // 生成随机盐 salt := make([]byte, SaltSize) if _, err := rand.Read(salt); err != nil { return "", fmt.Errorf("failed to generate salt: %w", err) } // 生成随机IV iv := make([]byte, IVSize) if _, err := rand.Read(iv); err != nil { return "", fmt.Errorf("failed to generate IV: %w", err) } // 使用PBKDF2派生密钥 key := a.deriveKey(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, SaltSize+IVSize+len(ciphertext)) result = append(result, salt...) result = append(result, iv...) result = append(result, ciphertext...) // Base64编码 return base64.StdEncoding.EncodeToString(result), nil } // Decrypt 使用AES-256-CBC算法解密数据 func (a *AESCrypto) Decrypt(pinCode, encryptedBase64 string) (string, error) { if pinCode == "" { return "", errors.New("pinCode is empty") } if encryptedBase64 == "" { return "", errors.New("encrypted data is empty") } // Base64解码 encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64) if err != nil { return "", fmt.Errorf("failed to decode base64: %w", err) } // 验证数据长度(至少包含盐+IV) if len(encryptedData) < SaltSize+IVSize { return "", errors.New("encrypted data too short") } // 提取盐、IV和密文 salt := encryptedData[:SaltSize] iv := encryptedData[SaltSize : SaltSize+IVSize] ciphertext := encryptedData[SaltSize+IVSize:] // 验证密文长度是否为块大小的倍数 if len(ciphertext)%BlockSize != 0 { return "", errors.New("invalid ciphertext length") } // 使用PBKDF2派生密钥 key := a.deriveKey(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 } // GenerateRandomStringSimple 生成指定长度的随机字符串 func GenerateRandomStringSimple(length int) (string, error) { if length <= 0 { return "", errors.New("length must be positive") } result := make([]byte, length) // 为了获得均匀分布,需要生成足够的随机字节 // 每个随机字节有256种可能,62个字符,需要拒绝采样 // 但为了简单高效,我们可以一次生成足够的字节 // 使用约1.2倍长度来减少重试次数 bufSize := int(float64(length) * 1.2) buf := make([]byte, bufSize) for i := 0; i < length; { // 生成随机字节 if _, err := rand.Read(buf); err != nil { return "", fmt.Errorf("failed to generate random bytes: %w", err) } // 处理每个随机字节 for _, b := range buf { // 使用拒绝采样确保均匀分布 // 62 * 4 = 248,我们只接受0-247的值 if b < 248 { // 将0-247映射到0-61,确保均匀分布 idx := int(b) % CharsSetSize result[i] = CharsSet[idx] i++ if i == length { break } } } } 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(t *testing.T) { // 创建加密解密实例 crypto := NewAESCrypto(100) // 测试pinCode1 pinCode1 := "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("pinCode1: %s\n", pinCode1) // 加密 encryptedData1, err := crypto.Encrypt(pinCode1, string(data1)) if err != nil { fmt.Printf("Encryption failed: %v\n", err) return } fmt.Printf("Encrypted Data 1: %s\n", encryptedData1) // 解密 decryptedData1, err := crypto.Decrypt(pinCode1, 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") } // 测试pinCode2 // pinCode2, err := GenerateRandomStringSimple(32) // if err != nil { // fmt.Printf("Failed to generate random string: %v\n", err) // return // } pinCode2 := "OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN" fmt.Printf("\npinCode2: %s\n", pinCode2) // 使用相同的测试数据 encryptedData2, err := crypto.Encrypt(pinCode2, string(data1)) if err != nil { fmt.Printf("Encryption failed: %v\n", err) return } fmt.Printf("Encrypted Data 2: %s\n", encryptedData2) // 解密 decryptedData2, err := crypto.Decrypt(pinCode2, 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 = crypto.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 = crypto.Decrypt(pinCode1, 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 := crypto.Encrypt(randomStr, "This is a test message") if err != nil { fmt.Printf("Encryption with random key failed: %v\n", err) return } decryptedData3, err := crypto.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") } }

2. Request and Response Protocol Data Format

2.1 Request header

VariableTypeRequiredDescription
x-p-business-idStringYThe current business ID, Specify the ECR device to receive orders for a certain Business
x-device-snStringYThe serial number of the ecr terminal payment device
x-request-idStringYEvery time communication takes place, it is crucial to ensure that the Request ID is a unique UUID.

2.2 Request data structure

Request JSON
VariableTypeRequiredDescription
versionStringYCurrent communication protocol version number
actionenumYActions: Pair, DeviceInfo, Sale, ...
dataStringYEncrypted data
Request "data" structure
VariableTypeRequiredDescription
headerObjectYProtocol header
bodyObjectNProtocol body
Request "data.header" structure
VariableTypeRequiredDescription
requestIDUUIDYRequest 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".
clientDeviceSNStringYClient device serial number
timestampdatetimeYThe timestamp of the current request
businessIDStringNThe unique ID of the current merchant's business
Example:
/* Pin Code: pinCode1 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": "pePKeo2urjKmSoah7AWb8d9JDqjqtJG781Yh8NxICIbJ72y4kgxuau+yX6bXF/M6gz9oyuIJC1Q/shNrwKP8Ev8qzoZ587LtPOaQrENU2W/p6ue7PRoY9C3pEo4M2bZeQlELOQj8ohKqmEdrlcntpJem3Q+yFeXTPxAwvpKfKTgYbpdHQSC//5WnoxNGnAHLaqRTaEDRr0wWYAcdb7mY0/h7yNkkSNffPjmvlMmDBTmWP/Dq+c7k8otszx5m0YBTyOFxOJipLQytMwoZIFzG/3m3H7jMH68VvWemhp+DPh5TFa+cgoZpASaoz/oLylv8" }

2.3 Response data structure

Request JSON
VariableTypeRequiredDescription
versionStringYCurrent communication protocol version number
actionenumYActions: DeviceInfo, Sale, PreAuth, Abort, Void, Refund
dataStringYEncrypted data
Response "data" structure
VariableTypeRequiredDescription
headerObjectYProtocol header
bodyObjectNProtocol body
Response "data.header" structure
VariableTypeRequiredDescription
responseIDStringYThe response ID in the response Header is derived from the request ID in the request header.
serverDeviceSNStringYThe serial number of the terminal payment device
timestampdatetimeYThe timestamp of the current request
businessIDStringNThe unique ID of the current merchant's business
Example:
/* Pin Code: pinCode1 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": "id4/0hIX8IG0GJuuH4OeLqHy50LdXMoG96A4jPNhnpl37h+Y6z/tvkisdkJh2kfGBjLQtOJ/TptfeHkNT31mL+kEvzArOg0VxTTd2AksF1qShOQDvK6Xvqhk0MsT6Vx+McSA5HJGp+cvNODc64TmZ/Z9qH7Sg/rcrj2uPq28a1YfB19FNL5w0CmvIj8VfXvaNhaBSguFodQWqP5XazvsHtI/E6Nyk4qsNIHNFlb10IKWws2Kgzg6voFYuZMBqKcu18iHTZcMc+YV/4bShFA3QuTR9562U91bCgebTq9cdgV/Or0VDZbfcnCY4EGM1z7G5oE6IBsdPmDBAS45X/279gDd1lgoqSX8FOsRSfwCldjMUUfKFvsH0m7OKGopfjRW" }

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
  • Pin Code: pinCode1

Request "data.body" structure

VariableTypeRequiredDescription
pairUuidUUIDYPair the uuid identifier
Example:
/* Pin Code: pinCode1 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": "Hlf2xR22p3Mkc2Hvj0XrvlyZmb5vNk6MxzMDpfE86Y1w5jxK1HYqeWgMGzjlEFr3QZw3N7Czpm05NlBLfAyhrdCW/V5Wcf2QsfAvOQV4cNP6VyKR0XwQHXqydqJwzrDftJ+6uamLYQlJn1D4UbQtFz1oR9QCJlM/KJHLcup1IrqZ6U7WN+N/Qi0bxWYcVcEn4KVegSTDFUIUbabXlAJoYNQwP9AocaBVegm2orKbFgxUGlFJRxFrjm5I2JiAKSfi3cFOezyQ/2BB2jvfuGTLoEsujuEwLkfRzmn7cxRXfDhNncoVeOVqu+HcGYcmomrh" }

Response "data.body" structure

VariableTypeRequiredDescription
pairUuidUUIDYPair the uuid identifier
ackUuidUUIDYAck the uuid identifier
pinCodeStringYIn the test code, it is pinCode2, which is used in the Ack interface and the business interface
Example:
/* Pin Code: pinCode1 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", "pinCode": "OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN" } } */ { "version": 2.0, "action": "Pair", "data": "hvii5rlCFHUh1o91yfaNKDbnXIjpLOzF+rQ+D0/GVx7HjBU4oM/YRk41tMnkpFeagXXFNKgBrMNQzQC7ohU9R6OSbGKQMzQvgarrEY3cSHmGv1FuVL290jcEzD5bW99CJSi0bKeo/oTh/aPLadXp2gZNRWEkuP8sEZCM7w7pv0pQvwcnfJKBnD6tgkDgMIUPs1QaNIGPom0s12m9k4R/iQmB+8A6UWkscSd48g85ZJ77WWAeuQtmgRoW8/VNss5aRjIf6CBH0OYE3rYCACYl8fIrBMLsRBl24NmIhiTSt97bKOXFXMugoc+/jTWAz+YgVXadkUTUhG1QBxhJanS0EEMu2x5Q/Q2IMaX8kWmU4mZOFA1yrfXSNSzYdtS63P0SPaSw0uB0uObF5K7qEeYkhI8HDIu0dLJklXafbpWHBhAUFvUUNnbtW/WXvQd8xwzs" }

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
  • Pin Code: pinCode2

Request "data.body" structure

VariableTypeRequiredDescription
ackUuidUUIDYThe Wonder device will use this UUID to confirm whether the pairing was successful
Example:
/* Pin Code: pinCode2 (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": "HyVyYTEFadRFQyO3ErhsscZbuwH2nmzt7WcqaX7zwOU2dYZ2kK1nkwKTxE7FDVR1Lcra0yfcOIZdxO/qdAv2NRDwUHkMiBvkIxZMVlaM+3CUdhpqG0Xtub28gt9Z5KI6HpeGdbSGHm1qDImHh+DEVEe55mUew4/czadIQ3kvpVv2vk1U4h2NVWjkw6C4EZMtGKnWyMZmraUxj/kE8LRvckRiYy9MF+v1Dq2goIwSHwa/skZjHJ8d5ZbGrssuWMJgXJCflvTh3TFqntcIrASYobNVWcPsUByQS9vYuZdpDU/PuQ7NyIvXSGp2vmJyUpfq" }

Response "data.body" structure

VariableTypeRequiredDescription
deviceStatusStringYPayment device transaction status: Free / Busy <br> Free: The current device is idle and available for new transactions; <br>Busy: The current device is occupied with an ongoing transaction and is not available for other transactions at the moment.
networkStatusStringYThe current network connection status of the payment device: Connected / Disconnected
softwareVersionStringYThe software version of the payment device
businessIDStringYThe response ID in the response Header is derived from the request ID in the request header.
Example:
/* Pin Code: pinCode2 (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": "o8DusOr0hm/aEr3flPjUYZ2dIBztnOz/KbO+VL/9deQvsZvREYwn3Rks8N3B4+XuoKraTp6mlh2DsMTU7z/scvgy6jwnWFnVD8O1aOPE6yZn+nvlqohCwkxgqBRS0/fSqPE0oWu8d8BNBQAGgHzxmpPwogcNEoIUE9DmKnJ9ZXXskoG3SbPdUoBgtKsJiX3cDvS3jOK1FPPuOoSOt9pSf1Kt71/wQE5bLozJ7W3UhZuzQ9MqGa+PGKBPjGqUn9GXx4Pvp2sJBy/MasMIdIcPf/XTJNCXeXEAaa0A50cEbY+/1fYeS9GMD9RATln8mnjy/k+UBqCE9T6n41xCXcCyw6hKtcvfgmH28JXY4tSxpIKMGFityhMZvdFyccdR+RW3YqDYHm2g7vfyThIoAl/3pV2KfhzXc/Po+pg0Y3I6yoc=" }

3.3 Device Info

  • Action: DeviceInfo
  • Pin Code: pinCode2

Request "data.body" structure

This request has no body

Example:
/* Pin Code: pinCode2 (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": "LzskNnsYgifR+ZjuXVjPDfT2Nw3YhbA4OAmT2yBBFgR39Q5TjSrnaW44czmmBcVlr2GL327sHk62o5FMXsqyxUNJC0uq1hEgp1gTzxzLYNnkwqbb7NeSFfdscqReOPiOMbY7rWN6sjfoVpVbTKSEVYmhZAVx10TrzSPE7w63pHNZl6utlzkcZNVwZSQO6qqkXtAGbKtryxZ1J0VA8mIt80aDKqEpECYICV1twM6gHgw=" }

Response "data.body" structure

VariableTypeRequiredDescription
deviceStatusStringYPayment device transaction status: Free / Busy <br> Free: The current device is idle and available for new transactions; <br>Busy: The current device is occupied with an ongoing transaction and is not available for other transactions at the moment.
networkStatusStringYThe current network connection status of the payment device: Connected / Disconnected
softwareVersionStringYThe software version of the payment device
businessIDStringYThe response ID in the response Header is derived from the request ID in the request header.
Example:
/* Pin Code: pinCode2 (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": "ZhnFiGfiT5Oir46CtHaQbVpegL5pRkNELxhOdN+XbqzIiaUmoimyxD3VYspmdNhyy2ZoOsrDZGvNhOxp/5/Cf21XBb2j8P8Gl/eMV7yHafjvqhoYGQY+m2PoYYAwBsWH0XDmbti+rfg7VQFFvHBoOqnfYmktcSJzHjm5hX4qyUMQdNWeTF4e+pkZ3jvTTLvdzMZlrMjwXOMxE1ikrbR6sgCV2cFx2Lrdvlh8gJdE6SM35CZna88X/s/tMPe2w32Lh0ou2eHKlARBuCG69KxEp7RpBQtw9+PDrzX7u4bL13V6b6LZJnejuwIsbxIz0yAPr6eApB41Vr6pELCXOyh1zRoxlF8RN5c8AhYE5NrSLCcvMOwZxvGjihrePAjO7vJFivowjoIwYMad1H31uLCIGrjOoD2JEycnpDWuGyRZcgU=" }

3.4 Sale

  • Action: Sale
  • Pin Code: pinCode2

Request "data.body" structure

VariableTypeRequiredDescription
referenceIDStringYReference 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.
paymentMethodenumNPayment methods: <br>-credit_card, <br>-fps, <br>-octopus. <br>-consumer_presented_qr_code, <br> -all, <br> If the payment method is transmitted, it will directly redirect to the target page
restrictedPaymentMethodsArrayNPayment Methods
currencyStringYExample: HKD / USD / RMB
amountStringYSale amount
remarkStringNRemark
Example:
/* Pin Code: pinCode2 (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", "currency": "HKD", "amount": "10.20" } } */ { "version": "2.0", "action": "Sale", "data": "x0YiJ9xhtmSxU02TlNC1TVfFY1/QKuQYniemnpEakcEiVmEDuMWQgFmnjAQrnl16omCQkOWW0sQuC9RjZH67gCfWBpQAQlO10RWw/R5XBmGs1jeJlND0IEKz6BLhbCflbME+GkZLyQ45D2vZdtI9KTSfy/CRmHdzngI31wU2002WUeAwaZt/R9teYmkXqrqtQBVn8j0gGpm4lS20hDqqIOB4olb4QgjFINopRh2eKGSTKsCnFBBWcvYiXYYHw/tDeQTwMRrIKfYYKU9ASmYcy+vG84G4renwsieFLWbp9hSfBpozjUOxNl7C2ziOdOYnOlzABW9a1MCLFx6ZJFcuGWb1rsrvOB1i4PAEtIH/ar0=" }

Response "data.body" structure

VariableTypeRequiredDescription
statusStringYResponse status: Success / Failed / Pending
errorCodeStringY
errorMessageStringY
currencyStringNExample: HKD / USD / RMB
amountStringNSale amount
paymentMethodStringNPayment method
paymentEntryTypeStringNPayment entries, Example: contactless
transactionUuidStringNTransaction UUID
allowVoidBooleanNAllow Void: true / false <br>true: Allows void <br>false: Does not allow void
allowRefundBooleanNAllow Refund: true / false <br>true: Allows refund <br>false: Does not allow refund
consumerCountryCodeStringNConsumer country code
rrnStringNReceiver Reference Number
brnStringNBindo Reference Number
transactionTypeStringNTransaction type: Sale / PreAuth / Void / Refund
transactionTimeDatetimeNTransaction Time
creditCardObjectN
creditCard.panPrefix6DigitsStringYFirst 6 digits of the credit card number
creditCard.panLast4DigitsStringYLast 4 digits of the credit card number
creditCard.consumerIdentifyStringYConsumer identify, e.g. Card Number, Open ID, etc.
Example 1:

When the status is Pending

/* Pin Code: pinCode2 (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": "TOGegN7Gul4p+OeymxhD12sB2u6G50/SbhM+pEbY45IpW9gdVPWx7fxQSxvRK44lClO5+9y7vkaCfDksl4Ngq0fm1Uq/1v4rUhwKAt1VJS8f3lHgSWT0UaKu0qNY/7ynJmBRpjfSPaKgRH8lWTHMhbf364GE9N0Rd8CDsbPUEjSR1LTtXuXoKSOqQeywrADg+D+AAQpzz+iwq005HIzUic/L9PD/JiXRcER0Qv3ngvvmpUEB/V3ycvx1sFN/1Ja/3ne60LqYOBVRa1aEPyU0WbNGO4+Yu7GYc+XiOXgEyiBDtcPQcFOeDi+PanWLtp1k" }
Example 2:

When the status is Failed

/* Pin Code: pinCode2 (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": "s9j7u8roWhx/55iOwMIY6ePbKradiQGo/3gdzDcCJMEEIiGZVQ0g9C7fGR56tSqw2p4jdU0iIoZ9zOuZG0oUmsyqqUXvB40bMYLSpfrlBhqvGYAoSHuXBtqDKr/ithRvIj90y0apTv6ud0W7eAfb8YECGuniBaOFdooXI5CbdPzaDhJ84iaXF07KmBmjBuWPlM4wMMUWVghtFiIOEC08HFkEuPdRQamABD+yYUao/Nv1NuHG0+geN+CVHPZkEJQgQZDlJA0NdVkHI9qlwtr8V4E1Onw6/dJeXCXqSj351c0HzxMTWWSuGNSbxF48YrDIejeiV4VxTs5GokpKgSEHOaPvPhsAB+lRenv1JAtqZcM=" }
Example 3:

When the status is Success

/* Pin Code: pinCode2 (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": "", "currency": "HKD", "amount": "10.20", "paymentMethod": "visa", "paymentEntryType": "contactless", "rrn": "3263492852830699521", "brn": "3263492852495159296", "transactionType": "Success", "transactionTime": "2023-06-30T09:08:52+00:00", "creditCard": { "panPrefix6Digits": "555555", "panLast4Digits": "1234", "consumerIdentify": "xxxxxxx" } } } */ { "version": "2.0", "action": "Sale", "data": "zqZex4ZAGwgB5NmBD3FH4FGZBqld/s1dqLltYfIf4TPljkvuy9BmK5Vq8Cs4dY/gFZO9dp8L3xEAT6TcnT1o8aGx5ffy8PWkrB4uXo1cQpoc8h534Ph8QTmqQ2LN9QUf0nOdAK2u164KUETHPFgMXY8J4Y2Z8S9fHJqp40XIHbUCo98bXJg/f+f4pslampeLCeAl56iKBTiKNy+Z/KHGj6SHzFfysJk9iCewWn9JZBh+Pts5e3o0fhw3VPVDVSKEgqceAyrIzUD834KpuZocO9Xk1XsxscC7FBB06GBJg8wUCqb0NqLGfMmmUdC5XAT+DOQ6q80U+jKjkY+m5rxTJEeQshzbP3Ulo/k1rCyk80czbH45GlhYPLNxiWPaJgVXm05GL6ClvjACx8ASBKbG2cyn3nmoI53s2/EsT3nLZ1rOf4SC3Di1pfWOZB2hEqCkaDmZTVFMEyYdGsMlO0nl2w3KEq6zf9LGLXrqDhORPq73u1ESckj4zNjHsLoTP8/ZyWc/1J2OLJRcxk4UfZ3C7vzkD0zIc8phOCZFbu4vRQZ+W4U0ZK9FZeDOby24z4NSHRut5zMLhERIWwW+A5ozXHkNp0JU+yjln0JkAemzy8BmPxWl5t8Y0Zih5rbcZbJj7j+xri9dz10KfdAvU7mAZVllrheCpt0eflbIHCSyMUUl4Km1nnNaeC2/z43tbvGhthEhqr64Yj5U4GSJWD84WVrwS/dzhC29d3TOsxlfC3M=" }

3.5 Abort

  • Action: Abort
  • Pin Code: pinCode2

Request "data.body" structure

This request has no body

Example:
/* Pin Code: pinCode2 (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": "AG1mb0IbA6EzpyvE6g6ySqemX3XiXLVm8wlGbPkOfsRNuxSCy1fz3/VlDqRd3PxDvy8FHCv6pvB0nhxysM+V85NNbQTC15+GgrnLeupS9IMSYYPYAmhyXtqae1DbXcqCj9F614QJIB0bWzRpqWmOgap/uzTIwFHf/qGMKCCCADouMiDxnirp1EunsRBeECyBLdlBoqPaWoNhhMISyZu5+9eul5E+eZMmKwNUDL4KJw0=" }

Response "data.body" structure

VariableTypeRequiredDescription
statusStringYResponse status: Success / Failed / Pending
errorCodeStringY
errorMessageStringY
Example:
/* Pin Code: pinCode2 (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": "MPf0p+jMU+smn2s/S1so9c4x3YLmJeJ3TzXbtiniGAVFhjYJrCCOdC0wc32c738qd1D5F8WlaWXqz+uNtQzgkLgmx3wz/rDGBNRh11tNzY4AfZEra3JfWEWEsNDFpDco9FMjF4mO/AhD/KFYe5+1suMCtJdLYE5ka9XnJr3xoYd8YrHdkY3Bpq8/tveW8FHIwMOj67hyOvtjdtnemv/uD52QTcSZMVCF6vQFbK9VJb74tE2kbGWlWmYxtWwPRwrv5gTh6YSdMESoFxITjE29SkVXv+oNa85B/NqOMSFEc6AsgIKV0xCpebIPOM6Owz6G" }

3.6 Void

  • Action: Void
  • Pin Code: pinCode2

Request "data.body" structure

VariableTypeRequiredDescription
orgReferenceIDStringYThe reference ID that was carried during the transaction made at that time.
Example:
/* Pin Code: pinCode2 (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": "p3Lowjj5f+OD2W2K2XeSrbpk31s+5jBe+5fG4XdqMU2JE+8jDN51akS1+sq5DvDL9vMKUSlHEO5868yNb7hbA/CHCB5xein9imTEaelM0BDpoTnew1mRkELIcWZYECyghXlev44oIhBEwMKHqQdUwfpKpPL3dr7CmLmBP7+Uiau1M3gai4V9Y3ClQqgCROqOegLZ9TKHHBtA9k5r7ArDlKShqjYmtXA0ASvkxc01tHFQbcrLAeW8yOA8NlQzO+oQrpafWE+5CSEqWP7OZlc5EJi69/X2Zw9x4H3p/fl8SFjEf3Og7RO15zIZTd708cju" }

Response "data.body" structure

VariableTypeRequiredDescription
statusStringYResponse status: Success / Failed / Pending
errorCodeStringY
errorMessageStringY
Example:
/* Pin Code: pinCode2 (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": "hknIQAGtuz/m0wpmRK7ya22HBiS9jI84cu2KHlCHPbVgQj0urBKWKEXopqnLESWTvyezR5XOwMTFSzTc0vb9/qySDYVXJqyHsynPrI3Ad6NswcBbkwKWVjT+vmQWX6IWqGqQyMDGjxKX8AgZf+3f0xyJTgOEvUPRC2+6sNaMfNR7szcfMxhZ5ji6GrL8xT6j/b/8CxYSsA/9f5rC/bQehxSHf/iUS3cLXABMfZn4wUqmfuCCfSXtgdlNSa3IT6K860sOJfnn6LLKqdT937vNqjeF/mOgrZnjdwxFGokgZNKdPdHbmc1TDwAw/QHxk1+E" }

3.7 Refund

  • Action: Refund
  • Pin Code: pinCode2

Request "data.body" structure

VariableTypeRequiredDescription
referenceIDStringYReference 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.
orgReferenceIDStringYThe reference ID that was carried during the transaction made at that time.
currencyStringYExample: HKD / USD / RMB
amountStringYRefund amount
Example:
/* Pin Code: pinCode2 (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": "y7D+Argj9zj7/+Z2shQY35DvhruwzC3IMLyM01HL6dlE6CFTPOgJuxv9aHb0VaoMu4b9jRaTjaCHu5BPjUYS+XbzYlDngkuiAloWpAgB2Z6Z+VzDqPK/MV0rKTwdCrcYcIpgnDCzI7G8w5Q/0ey8C7scx/fvsHlYmCG1DOjYA88jmJam10vm2Y4fVTsz4jR4Nh9a1qjsf/JgsaPyqf/F3nnmH2Fxov2fc8xTf7Zvexk0ShotgG7hdQ4Ifj31Tyqr36pM5bIQa70eKuq/SuFp4O2IGpfugb/9ztMRp7jjZ3qm2cVpxJG5IcpMfdDZGWFJFb9jz0Pu5iN0BKDA+CibBnb4wUCZK1A7nNA94fbpPJX8ehuTOoTiKbR92Gihq/KXtpRDwGzKvatOy2xdND1KE9fH83d6pmaAoiYrUaQiH9Y=" }

Response "data.body" structure

VariableTypeRequiredDescription
statusStringYResponse status: Success / Failed / Pending
errorCodeStringY
errorMessageStringY
Example:
/* Pin Code: pinCode2 (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": "L13PVs3Bj9CCqFHboha1OLUdQZ8/BNP9ZX0pCtp7bl5Uvy+8xhUoohBxyZR1pwMMf61Uwpdm+ivQ11dcVK8X3NjB+AWkF6R4dIW+n+jooZ5RFf1dpDB3EJS4BRhACf9lq9htJPnIqjvWchBLSq/AGEClCF6C1rTNn5hF/p2pxFPqIAJHpxEMV/VjIsCoZGZ3vh02MD+EvIl7DFYmvcHprsnyw4kJ6oy4QfUqmbttBkeDocYl9w2ofuXh119aUYoqflazUUWPvclyoh/tanwvMk2zoD6zBFhQBdXIrG9ONqEpIWTMF87Uw/SMnQ0PCmwx" }

3.8 Query Transaction Status

  • Action: TransactionStatus
  • Pin Code: pinCode2

Request "data.body" structure

VariableTypeRequiredDescription
targetReferenceIDStringYThe reference ID that was carried during the transaction made at that time.
Example:
/* Pin Code: pinCode2 (OvSdpyD2FoUNR5rNyte41QqZzR1Y4DVN) Encrypt the original data: { "header": { "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": "PFR7QydsxUlVYXFh9g7n1DKLUx5DtPLNOnSCCYIvZPAg9aZHx1rt6rNmW1BheI2MQO+Sq6G3LC4DlLxSQxTwOmN7mJREzckYVcnze9GhbENDP4tMmbLErjlU5lgctk9rkRYs/hNTZThfEd340c5JAM5JSPGSGyNZVs9Yf4FQa6hGxUcfW7RXu9dVntrHfPwuwg1A2vTSeh6QdQEgNJO+9jHygoD45RxiiMbPVFYkCMq5gve8Pcwwu950ADhTwK4kzKm2yyuZGjl7okuhAtnY3ADJ6nZzED94Q8fKRHs1VN7vnHyxIDVd9B0hNqLOacn3" }

Response "data.body" structure

VariableTypeRequiredDescription
statusStringYResponse status: Success / Failed / Pending
errorCodeStringY
errorMessageStringY
...AnyYIt is consistent with the returned information of the current query transaction type
Example:
/* Pin Code: pinCode2 (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": "", "currency": "HKD", "amount": "10.20", "paymentMethod": "visa", "paymentEntryType": "contactless", "rrn": "3263492852830699521", "brn": "3263492852495159296", "transactionType": "Success", "transactionTime": "2023-06-30T09:08:52+00:00", "creditCard": { "panPrefix6Digits": "555555", "panLast4Digits": "1234", "consumerIdentify": "xxxxxxx" } } } */ { "version": 2.0, "action": "VOSss8buM+a+5PbdwyLeAsAckCpeJX494TT5i3KZpApAft7cfLFPkaQJesUK+vnRQQKfmRCKfg2Bm3iKRe1Tj/E1OvkuS7qpP18ZVXu1Z4kouiyvJLayyUqw5Qodg8IB2xK9giFmrvm3Ss9EUbBUnyjSDcVw1jUxHqy4q59fxJ/LRKKNya+19YuqcXlKlpiHrAdsdjODiHTXVv0cDR3QSvGGmm3ZQPpK2DBnPuuUym39Gb5B9sgEn8Sv49GyYdx9l/RBBL6XSNDMKBMMQZ7xcj4LTDP2ksZxF1alhuugSCXWnxBiNpQb3HIa+lyQtT/TXt5miFPDG+EvYHKVrAqeEbLbKY3QUqPFr3AlX7QW06xsPdOrsFrafwMgasZaTLd4TFtAYDpuTxFrfpJbN/LLGMSjFq/uyn/NLDXGwPerKGSdjurdmQv6eJ5lBKcpLGePXmIKBkynSeC63iGUK3MI1gv0wqwNezcN/BtQM5t2yyv0FbxL3I+133x+VKV0uP1xYf5rZ8gpeKaRXUYEoBtb+LM7VhlDp5ZWumxzfHr8mVt4Z/ikvTmaEEAdpTdvllNDIw3YaBbRIjK1/y2qhZhx8ohULIg5aom0NjgVS9YWvp0G9sayeqYd+coQa9knBT7KHgOv/hJNQWZs6CZ1YIhjbpC9qhkbaCr8PNbRJHnJbrFMVO9QrSGXnD7Tjr5pXMVYeg+R3C/zicbKKcs7ZyWzp00x2/FdtvtzbPmYcXIRyG0=" }

3.9 Sale Directly (to do)

  • Action: SaleDirectly
  • Pin Code: pinCode2
  • CMD: 0101

Request "data.body" structure

VariableTypeRequiredDescription
referenceIDStringYReference 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.
qrCodeStringYThe QR code string
currencyStringYExample: HKD / USD / RMB
amountStringYSale amount
remarkStringNRemark
timeoutNumberNThe timeout in seconds. The default is 0, indicating that it will never time out
Example:
/* Pin Code: pinCode2 (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", "qrCode": "22349671249672146346", "currency": "HKD", "amount": "10.20", "timeout": 120 } } */ { "version": "2.0", "action": "SaleDirectly", "data": "z/IjDI8eUlFKCTrCbtt4U1EbW77HfYZYglG6HApsR7xMJnhLVJF8BlyxQMjhR8ALMMLCu+HAGAL/WMrHo0BZPdwdCHyLQnqeEJX4F3wshkiBkw44XOrjk9H2UxYGwammnJF1WX5qFpHBqEsvc8TsDQ1tcpL/Xsb7Z8NyUYG4M/kIQDRXLwXHs1tohf6Xop4jhXOP+3VWJIkovUJo7pZi+voj0AwjpRofjUk9I/J5pgazU7UCX2Q8ag7j+rhj7XoNDj8GvhkL0OPFcWeEbjeunZBt0xAeOThYDV4B3v/+ycUIQ9u3vFbwnIcbzfX89hRkEmgtY1clCUv2xBFlbh1v25tx80/2tsycHYziWjct3TzW84SM80POcCso68rcQo8xqJokgt/kn/bZb2t8pxs4gviN0LK+324VDT5AzHJxhF4=" }

Response "data.body" structure

VariableTypeRequiredDescription
statusStringYResponse status: Success / Failed
errorCodeStringN
errorMessageStringN
currencyStringYExample: HKD / USD / RMB
amountStringYSale amount
consumer_identify_hashStringY
acquirer_typeStringYAcquirer type