Create a dynamic QR and show it on the speaker
This API creates a dynamic QR code and shows it directly on the TINGEEBOX device screen, without calling the show QR API separately.
note
This API only applies to devices other than TingeeBox 3LS.
POST
URLhttps://open-api.tingee.vn/v1/device/generate-and-show-dynamic-qr-code
Create a dynamic QR Code and show it on the TINGEEBOX device right away for customers to scan and pay.
Header Request
| Header | Required | Description |
|---|---|---|
Content-Type | ✓ | application/json |
x-client-id | ✓ | Partner identifier issued by TINGEE. |
x-signature | ✓ | HMAC SHA512 authentication signature. See How to generate the signature. |
x-request-timestamp | ✓ | Time the request is sent (format: yyyyMMddHHmmssSSS, UTC+7) |
Body Parameter
| Field | Type | Required | Description |
|---|---|---|---|
merchantId | number | ~ | Merchant ID (Required if you connect with a Master Merchant account). |
uuid | string | ✓ | Device code (the device must be linked to the Merchant). |
qrCodeType | string | ✓ | Dynamic QR type: - dynamic-one-time-payment: QR that can be paid only once.- dynamic-recurring-payment: QR that can be paid multiple times. |
vaAccountNumber | string | ✓ | Virtual account number (max 50 characters). |
amount | number | ✓ | Bill amount (VND), minimum 1. |
purpose | string | ✕ | Payment description. |
bankBin | string | ✓ | Bank BIN. See Supported banks. |
bankName | string | ✕ | Bank name: OCB, BIDV, MBB, ACB, VPB, PGB, VIB, STB, CTG, VCB, AGRIBANK, SHINHAN, COB, MSB, NEXTPAY, PAYOO, BAOKIM, TPB. When bankBin is passed, the system derives bankName from bankBin. QR creation is based on bankBin, so always pass bankBin. |
name | string | ✕ | QR display name. Only applies to the BAOKIM e-wallet; if omitted, the account holder name is used. |
expireInMinute | number | ~ | QR validity period (minutes), from 1 to 43200 (30 days). Default 43200. Required for OCB ( 970448). |
extraInfo | string | ✕ | Extra information when creating the QR, max 200 characters (e.g. the partner's bill code). |
deviceType | string | ✕ | Device type. No need to pass it — the system determines it from uuid. |
firstText | string | ✕ | Text shown with the QR (line 1). |
secondText | string | ✕ | Text shown with the QR (line 2). Defaults to the qrAccount of the newly created QR. |
thirdText | string | ✕ | Text shown with the QR (line 3). |
showTime | number | ✕ | How long the QR is shown on the speaker (seconds). Default 90 seconds. |
playSound | boolean | ✕ | Play a beep when showing the QR. - true: With a beep.- false/omitted: No beep. |
readAmountAfterPay | boolean | ✕ | Read the amount aloud after a successful payment. - true: The speaker reads the amount automatically.- false/omitted: The partner calls the read amount API to play it on the speaker. |
Code samples
- cURL
- NestJS
- C#
- Java
- PHP
curl --location --request POST 'https://open-api.tingee.vn/v1/device/generate-and-show-dynamic-qr-code' \
--header 'accept: application/json' \
--header 'x-client-id: YOUR_CLIENT_ID' \
--header 'x-request-timestamp: 20240509154004003' \
--header 'x-signature: YOUR_SIGNATURE' \
--header 'Content-Type: application/json' \
--data '{
"uuid": "DEVICE_UUID",
"qrCodeType": "dynamic-one-time-payment",
"vaAccountNumber": "V1T123456789",
"amount": 150000,
"purpose": "Thanh toan don hang #001",
"bankBin": "970422",
"expireInMinute": 5,
"firstText": "Quan ABC",
"showTime": 30,
"playSound": true,
"readAmountAfterPay": true
}'
import axios from "axios";
import { createHmac } from "crypto";
const CLIENT_ID = "YOUR_CLIENT_ID";
const SECRET_KEY = "YOUR_SECRET_KEY";
// Timestamp in UTC+7, format yyyyMMddHHmmssSSS
function getTimestamp() {
return new Date(Date.now() + 7 * 60 * 60 * 1000)
.toISOString()
.replace(/[-:TZ.]/g, "")
.slice(0, 17);
}
async function deviceGenerateAndShowDynamicQrCode() {
const body = {
uuid: "DEVICE_UUID",
qrCodeType: "dynamic-one-time-payment",
vaAccountNumber: "V1T123456789",
amount: 150000,
purpose: "Thanh toan don hang #001",
bankBin: "970422",
expireInMinute: 5,
firstText: "Quan ABC",
showTime: 30,
playSound: true,
readAmountAfterPay: true,
};
const bodyJson = JSON.stringify(body);
const timestamp = getTimestamp();
const signature = createHmac("sha512", SECRET_KEY)
.update(`${timestamp}:${bodyJson}`)
.digest("hex");
const res = await axios.request({
method: "POST",
url: "https://open-api.tingee.vn/v1/device/generate-and-show-dynamic-qr-code",
data: bodyJson,
headers: {
accept: "application/json",
"Content-Type": "application/json",
"x-client-id": CLIENT_ID,
"x-request-timestamp": timestamp,
"x-signature": signature,
},
});
console.log(res.data);
}
deviceGenerateAndShowDynamicQrCode();
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
public class Program
{
private const string ClientId = "YOUR_CLIENT_ID";
private const string SecretKey = "YOUR_SECRET_KEY";
private static readonly HttpClient Client = new HttpClient();
public static async Task Main()
{
var body = new
{
uuid = "DEVICE_UUID",
qrCodeType = "dynamic-one-time-payment",
vaAccountNumber = "V1T123456789",
amount = 150000,
purpose = "Thanh toan don hang #001",
bankBin = "970422",
expireInMinute = 5,
firstText = "Quan ABC",
showTime = 30,
playSound = true,
readAmountAfterPay = true
};
// Do not escape non-ASCII / special characters so the JSON matches TINGEE's JSON.stringify
var jsonOptions = new JsonSerializerOptions { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
var bodyJson = JsonSerializer.Serialize(body, jsonOptions);
// Timestamp in UTC+7, format yyyyMMddHHmmssSSS
var timestamp = DateTime.UtcNow.AddHours(7).ToString("yyyyMMddHHmmssfff");
var signature = HmacSha512(SecretKey, $"{timestamp}:{bodyJson}");
var request = new HttpRequestMessage(HttpMethod.Post, "https://open-api.tingee.vn/v1/device/generate-and-show-dynamic-qr-code");
request.Headers.Add("accept", "application/json");
request.Headers.Add("x-client-id", ClientId);
request.Headers.Add("x-request-timestamp", timestamp);
request.Headers.Add("x-signature", signature);
request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");
var response = await Client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
private static string HmacSha512(string key, string message)
{
using var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(key));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToHexString(hash).ToLower();
}
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Main {
private static final String CLIENT_ID = "YOUR_CLIENT_ID";
private static final String SECRET_KEY = "YOUR_SECRET_KEY";
public static void main(String[] args) throws Exception {
// Minified JSON, same as JSON.stringify()
String bodyJson = "{\"uuid\":\"DEVICE_UUID\",\"qrCodeType\":\"dynamic-one-time-payment\",\"vaAccountNumber\":\"V1T123456789\",\"amount\":150000,\"purpose\":\"Thanh toan don hang #001\",\"bankBin\":\"970422\",\"expireInMinute\":5,\"firstText\":\"Quan ABC\",\"showTime\":30,\"playSound\":true,\"readAmountAfterPay\":true}";
// Timestamp in UTC+7, format yyyyMMddHHmmssSSS
String timestamp = ZonedDateTime.now(ZoneId.of("Asia/Ho_Chi_Minh"))
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
String signature = hmacSha512(SECRET_KEY, timestamp + ":" + bodyJson);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://open-api.tingee.vn/v1/device/generate-and-show-dynamic-qr-code"))
.header("accept", "application/json")
.header("Content-Type", "application/json")
.header("x-client-id", CLIENT_ID)
.header("x-request-timestamp", timestamp)
.header("x-signature", signature)
.method("POST", HttpRequest.BodyPublishers.ofString(bodyJson, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
private static String hmacSha512(String key, String data) throws Exception {
Mac mac = Mac.getInstance("HmacSHA512");
mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA512"));
byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : hash) hex.append(String.format("%02x", b));
return hex.toString();
}
}
<?php
$clientId = "YOUR_CLIENT_ID";
$secretKey = "YOUR_SECRET_KEY";
$body = [
"uuid" => "DEVICE_UUID",
"qrCodeType" => "dynamic-one-time-payment",
"vaAccountNumber" => "V1T123456789",
"amount" => 150000,
"purpose" => "Thanh toan don hang #001",
"bankBin" => "970422",
"expireInMinute" => 5,
"firstText" => "Quan ABC",
"showTime" => 30,
"playSound" => true,
"readAmountAfterPay" => true,
];
// Do not escape "/" or non-ASCII characters so the JSON matches TINGEE's JSON.stringify
$bodyJson = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
// Timestamp in UTC+7, format yyyyMMddHHmmssSSS
$now = new DateTime("now", new DateTimeZone("Asia/Ho_Chi_Minh"));
$timestamp = $now->format("YmdHisv");
$signature = hash_hmac("sha512", $timestamp . ":" . $bodyJson, $secretKey);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://open-api.tingee.vn/v1/device/generate-and-show-dynamic-qr-code",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $bodyJson,
CURLOPT_HTTPHEADER => [
"Accept: application/json",
"Content-Type: application/json",
"x-client-id: " . $clientId,
"x-request-timestamp: " . $timestamp,
"x-signature: " . $signature,
],
]);
$response = curl_exec($ch);
if ($response === false) {
echo "cURL Error: " . curl_error($ch);
} else {
echo $response;
}
curl_close($ch);
Sample Response
{
"code": "00",
"message": "Success",
"data": {
"qrCode": "00020101021238570010A...",
"qrAccount": "V1T123456789",
"billId": "BILL_20251110_001"
}
}
| Field | Type | Description |
|---|---|---|
code | string | Result code (00 = success). |
message | string | Response message. |
data.qrCode | string | Dynamic QR Code string to display / scan. |
data.qrAccount | string | Receiving account number encoded in the QR. |
data.billId | string | Bill ID for the QR, used to look up/delete the dynamic QR. |
Common error codes
| Code | Description | How to handle |
|---|---|---|
1024 | Virtual account number does not exist | Check that vaAccountNumber and bankBin match an account linked to the Merchant. |
1018 | Dynamic QR is not supported for this account type | Use an account that supports dynamic QR at the corresponding bank. |
1028 | Device not found | Check that the device uuid is linked to the Merchant. Note: the dynamic QR may already have been created before this error is returned. |
1086, 1087, 1088 | BAOKIM e-wallet is not configured or not synced | Complete the BAOKIM e-wallet configuration before creating a dynamic QR. |
1095, 1096 | Master Merchant merchantId error | Pass the merchantId of a sub-Merchant that belongs to the calling Master Merchant. |
90, 91, 92, 97 | Request authentication error (timestamp, client-id, signature) | See System error codes. |
Others | Other errors | See Error codes. |