Print a receipt from the device
This API instructs a Tingee Box device (TINGEE MAX models with printing support) to print a payment invoice or a receipt using a customizable template.
POST
URLhttps://open-api.tingee.vn/v1/device/print-receipt
Send a print receipt command to a Tingee Box MAX device.
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
Receipt templates
The device supports 2 print templates, selected with templateType:
- Template 1 – Simple retail (default): default title
HÓA ĐƠN(orPHIẾU TẠM TÍNHwheninvoiceStage = PRE_PAYMENT). - Template 2 – Detailed sales: default title
HÓA ĐƠN BÁN HÀNG(orPHIẾU YÊU CẦU THANH TOÁNwheninvoiceStage = PRE_PAYMENT).
Some fields are only printed on one template — see the "Applies to" note in the Description column. The total printed on the receipt is calculated by the system from items (unit price × quantity), minus lineDiscount, discount, vatReduction, plus tax per taxPercent/taxValue.
| Field | Type | Required | Description |
|---|---|---|---|
uuid | string | ✓ | Device identifier (the device must be linked to the Merchant). |
merchantId | number | ~ | Merchant ID (Required if you connect with a Master Merchant account). |
templateType | number | ✕ | Receipt template: - 1: Simple retail - 2: Detailed sales Default 1. |
lineWidth | number | ✕ | Print line width (number of characters), from 20 to 80. Default 30. |
qrAlignment | number | ✕ | QR code alignment: 0 (Left), 1 (Center), 2 (Right). Default 1. |
logo | string | ✕ | Name of a logo preloaded on the device. Valid values: printer_receipt_logo, printer_receipt_logo2, printer_receipt_logo3, printer_receipt_logo4. If omitted, no logo is printed. |
copies | number | ✕ | Number of copies to print, from 1 to 9. Default 1. Applies to: template 1. |
voiceFile | number | ✕ | Notification sound file, from 1 to 6 (1–5: noti_voice1-5.mp3, 6: print only, no sound). Default 1. |
title | string | ✕ | Receipt title (max 100 characters), printed in uppercase. If omitted, the template's default title is used. |
storeName | string | ✓ | Store name (max 100 characters). |
branchName | string | ✕ | Branch name (max 100 characters). Applies to: template 1. |
address | string | ✕ | Store address (max 200 characters). Applies to: template 1. |
hotline | string | ✕ | Phone number / hotline (max 20 characters). Applies to: template 1. |
sellerAddressLines | string[] | ✕ | Seller address lines; each element is printed on its own line. Applies to: template 2. |
taxCode | string | ✕ | Store tax code (max 50 characters). Applies to: template 2. |
orderCode | string | ✕ | Order code / waybill code (max 50 characters). Printed as the receipt number when invoiceNo is not passed. |
invoiceNo | string | ✕ | Receipt number (max 50 characters). |
createdAt | string | ✕ | Receipt creation time (ISO 8601, e.g. 2026-10-14T15:30:00+07:00), printed as dd/MM/yyyy HH:mm. Defaults to the time of the API call. |
channelName | string | ✕ | Sales channel (max 50 characters, e.g. Bán tại quầy, App). Applies to: template 2. |
employeeCode | string | ✕ | Sales employee code (max 20 characters). Applies to: template 2. |
customerName | string | ✕ | Customer name (max 100 characters). |
customerId | string | ✕ | Customer code (max 50 characters). Applies to: template 2. |
items | object[] | ✓ | List of products. See the items element structure. |
totalAmount | number | ✓ | Total amount (VND), minimum 0. Note: this value is not printed; the total on the receipt is calculated by the system from items. |
discount | number | ✕ | Discount on the whole receipt (VND), minimum 0. Default 0. |
vatReduction | number | ✕ | VAT reduction (VND), minimum 0. |
invoiceStage | string | ✕ | Receipt stage: - PRE_PAYMENT: Provisional bill (before payment) - POST_PAYMENT: Receipt (after payment) Default POST_PAYMENT. |
paidAmount | number | ✕ | Amount tendered by the customer (VND), minimum 0. Applies to: template 1, when invoiceStage = POST_PAYMENT. |
customerPaid | number | ✕ | Amount tendered by the customer (VND), minimum 0. If omitted, it equals the amount due. Applies to: template 2, when invoiceStage = POST_PAYMENT. |
paymentMethodLabel | string | ✕ | Payment method label (max 30 characters, e.g. Tiền mặt, Chuyển khoản). Template 2 defaults to Tiền mặt. Applies to: when invoiceStage = POST_PAYMENT. |
paymentAmount | number | ✕ | Actual payment amount (VND), minimum 0, printed next to paymentMethodLabel. If omitted, customerPaid or the amount due is used. Applies to: template 2, when invoiceStage = POST_PAYMENT. |
changeAmount | number | ✕ | Change returned to the customer (VND), minimum 0. Applies to: when invoiceStage = POST_PAYMENT. |
note | string | ✕ | Receipt note (max 200 characters). Applies to: template 1. |
qrCode | string | ✕ | Content of the QR code printed on the receipt (max 500 characters). |
qrHint | string | ✕ | Message shown with the QR code, e.g. scanning instructions (max 500 characters). Applies to: when invoiceStage = PRE_PAYMENT (template 1 also requires qrCode). |
bankAccountName | string | ✕ | Receiving account holder name (max 50 characters). Applies to: template 1, when invoiceStage = PRE_PAYMENT. |
bankAccountNo | string | ✕ | Receiving account number (max 100 characters). Applies to: template 1, when invoiceStage = PRE_PAYMENT. |
bankName | string | ✕ | Receiving bank name (max 50 characters, e.g. MBB, VCB). Applies to: template 1, when invoiceStage = PRE_PAYMENT. |
barcode | string | ✕ | Code128 barcode (max 20 characters). Note: not printed on the receipt yet. |
footerNote | string | ✕ | Note / thank-you message at the bottom of the receipt (max 200 characters). |
items element structure
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | Product name. |
quantity | number | ✓ | Quantity, minimum 0. |
unitPrice | number | ✓ | Unit price (VND), minimum 0. |
code | string | ✕ | Product code (max 50 characters). Currently not printed on any template. |
unit | string | ✕ | Unit of measure (max 20 characters, e.g. Cái, Hộp), printed after the product name. Applies to: template 1. |
lineDiscount | number | ✕ | Discount on the product line (VND), minimum 0. Template 1 sums these into a "Giảm KM" (promotion discount) line; template 2 prints it right below the product. |
promotionLabel | string | ✕ | Promotion label printed next to lineDiscount (max 20 characters). Default KM. Applies to: template 2. |
taxPercent | number | ✕ | Tax rate applied to the product, from 0 to 100 (e.g. 8, 10). Tax is grouped by rate at the bottom of the receipt. |
taxValue | number | ✕ | Precomputed tax amount for the product (VND), minimum 0. Only takes effect when taxPercent is set. If omitted, it is calculated as ( unitPrice × quantity − lineDiscount) × taxPercent / 100. |
Code samples
- cURL
- NestJS
- C#
- Java
- PHP
curl --location --request POST 'https://open-api.tingee.vn/v1/device/print-receipt' \
--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-001",
"copies": 1,
"storeName": "Cửa hàng tiện lợi",
"orderCode": "DH123456",
"totalAmount": 150000,
"items": [
{
"name": "Sản phẩm A",
"quantity": 1,
"unitPrice": 100000
},
{
"name": "Sản phẩm B",
"quantity": 1,
"unitPrice": 50000
}
],
"voiceFile": 6
}'
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 devicePrintReceipt() {
const body = {
uuid: "DEVICE-001",
copies: 1,
storeName: "Cửa hàng tiện lợi",
orderCode: "DH123456",
totalAmount: 150000,
items: [
{
name: "Sản phẩm A",
quantity: 1,
unitPrice: 100000,
},
{
name: "Sản phẩm B",
quantity: 1,
unitPrice: 50000,
},
],
voiceFile: 6,
};
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/print-receipt",
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);
}
devicePrintReceipt();
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-001",
copies = 1,
storeName = "Cửa hàng tiện lợi",
orderCode = "DH123456",
totalAmount = 150000,
items = new object[]
{
new
{
name = "Sản phẩm A",
quantity = 1,
unitPrice = 100000
},
new
{
name = "Sản phẩm B",
quantity = 1,
unitPrice = 50000
}
},
voiceFile = 6
};
// 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/print-receipt");
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-001\",\"copies\":1,\"storeName\":\"Cửa hàng tiện lợi\",\"orderCode\":\"DH123456\",\"totalAmount\":150000,\"items\":[{\"name\":\"Sản phẩm A\",\"quantity\":1,\"unitPrice\":100000},{\"name\":\"Sản phẩm B\",\"quantity\":1,\"unitPrice\":50000}],\"voiceFile\":6}";
// 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/print-receipt"))
.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-001",
"copies" => 1,
"storeName" => "Cửa hàng tiện lợi",
"orderCode" => "DH123456",
"totalAmount" => 150000,
"items" => [
[
"name" => "Sản phẩm A",
"quantity" => 1,
"unitPrice" => 100000,
],
[
"name" => "Sản phẩm B",
"quantity" => 1,
"unitPrice" => 50000,
],
],
"voiceFile" => 6,
];
// 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/print-receipt",
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": {
"uuid": "DEVICE-001"
}
}
| Field | Type | Description |
|---|---|---|
code | string | Result code (00 = success). |
message | string | Response message. |
data.uuid | string | Identifier of the device that received the print command. |
note
code = 00 means the print command was sent to the device; it does not confirm that the device has finished printing.
Common error codes
| Code | Description | How to handle |
|---|---|---|
1028 | Device not found | Check that the device uuid is linked to the Merchant (or the merchantId passed). |
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. |