Invoice XML Format
Overview
Vietnamese e-invoices must follow the XML format defined by the General Department of Taxation (GDT). This document describes the XML structure, required fields, and validation rules.
XML Structure
Complete XML Example
xml
<?xml version="1.0" encoding="UTF-8"?>
<HDon>
<DLHDon>
<!-- General Information -->
<TTChung>
<PBan>2.0.0</PBan> <!-- XML Version -->
<THDon>Hóa đơn GTGT</THDon> <!-- Invoice Type Name -->
<KHMSHDon>1</KHMSHDon> <!-- Template Code -->
<KHHDon>C25TAA</KHHDon> <!-- Template Symbol -->
<SHDon>00000001</SHDon> <!-- Invoice Number -->
<MHSo>01GTKT0/001</MHSo> <!-- Model Number -->
<NLap>2025-01-20</NLap> <!-- Issue Date -->
<SBKe></SBKe> <!-- Serial Declaration -->
<NBKe></NBKe> <!-- Declaration Date -->
<DVTTe>VND</DVTTe> <!-- Currency -->
<TGia>1</TGia> <!-- Exchange Rate -->
<HTTToan>TM/CK</HTTToan> <!-- Payment Method -->
<MSTTCGP></MSTTCGP> <!-- Provider Tax Code -->
<MSTDVNUNLHDon></MSTDVNUNLHDon> <!-- E-Invoice Provider -->
<THTToan>1</THTToan> <!-- Payment Status -->
</TTChung>
<!-- Invoice Content -->
<NDHDon>
<!-- Seller Information -->
<NBan>
<Ten>Công ty TNHH ABC</Ten> <!-- Company Name -->
<MST>0123456789</MST> <!-- Tax Code -->
<DChi>123 Nguyễn Huệ, Q1, TP.HCM</DChi> <!-- Address -->
<SDThoai>028-1234-5678</SDThoai> <!-- Phone -->
<DCTDTu>contact@abc.com.vn</DCTDTu> <!-- Email -->
<STKNHang>1234567890</STKNHang> <!-- Bank Account -->
<TNHang>Vietcombank</TNHang> <!-- Bank Name -->
<Fax></Fax> <!-- Fax -->
<Website>www.abc.com.vn</Website> <!-- Website -->
</NBan>
<!-- Buyer Information -->
<NMua>
<Ten>Khách hàng XYZ</Ten> <!-- Customer Name -->
<MST>9876543210</MST> <!-- Tax Code (B2B) -->
<DChi>456 Lê Lợi, Q1, TP.HCM</DChi> <!-- Address -->
<SDThoai>0901234567</SDThoai> <!-- Phone -->
<DCTDTu>customer@xyz.com</DCTDTu> <!-- Email -->
<HVTNMHang>Nguyễn Văn A</HVTNMHang> <!-- Contact Person -->
<STKNHang></STKNHang> <!-- Bank Account -->
<TNHang></TNHang> <!-- Bank Name -->
</NMua>
<!-- Invoice Items -->
<DSHHDVu>
<HHDVu>
<TChat>1</TChat> <!-- Item Nature: 1=Goods, 2=Service -->
<STT>1</STT> <!-- Line Number -->
<MHHDVu>SP001</MHHDVu> <!-- Product Code -->
<THHDVu>Sản phẩm A</THHDVu> <!-- Product Name -->
<DVTinh>Cái</DVTinh> <!-- Unit -->
<SLuong>2</SLuong> <!-- Quantity -->
<DGia>100000</DGia> <!-- Unit Price -->
<TLCKhau>0</TLCKhau> <!-- Discount Percent -->
<STCKhau>0</STCKhau> <!-- Discount Amount -->
<ThTien>200000</ThTien> <!-- Line Total (before VAT) -->
<TSuat>10%</TSuat> <!-- VAT Rate -->
</HHDVu>
<HHDVu>
<TChat>2</TChat>
<STT>2</STT>
<MHHDVu>DV001</MHHDVu>
<THHDVu>Dịch vụ B</THHDVu>
<DVTinh>Lần</DVTinh>
<SLuong>1</SLuong>
<DGia>50000</DGia>
<TLCKhau>10</TLCKhau>
<STCKhau>5000</STCKhau>
<ThTien>45000</ThTien>
<TSuat>10%</TSuat>
</HHDVu>
</DSHHDVu>
<!-- Totals Summary -->
<TToan>
<TgTCThue>245000</TgTCThue> <!-- Total before VAT -->
<TgTThue>24500</TgTThue> <!-- Total VAT -->
<TgTTTBSo>269500</TgTTTBSo> <!-- Grand Total (number) -->
<TgTTTBChu>Hai trăm sáu mươi chín nghìn năm trăm đồng</TgTTTBChu>
<!-- Grand Total (words) -->
<!-- VAT Summary by Rate -->
<THTTLTSuat>
<LTSuat>
<TSuat>10%</TSuat> <!-- VAT Rate -->
<ThTien>245000</ThTien> <!-- Taxable Amount -->
<TThue>24500</TThue> <!-- VAT Amount -->
</LTSuat>
</THTTLTSuat>
</TToan>
</NDHDon>
</DLHDon>
<!-- Digital Signatures -->
<DSCKS>
<NBan>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<!-- Seller's Digital Signature (PKCS#7) -->
</Signature>
</NBan>
</DSCKS>
</HDon>Field Reference
TTChung (General Information)
| Element | Required | Description | Format |
|---|---|---|---|
PBan | Yes | XML Version | "2.0.0" |
THDon | Yes | Invoice Type Name | Text |
KHMSHDon | Yes | Template Code | 1-6 |
KHHDon | Yes | Template Symbol | Max 6 chars |
SHDon | Yes | Invoice Number | 8 digits |
MHSo | Yes | Model Number | Pattern: ##GTKT#/### |
NLap | Yes | Issue Date | YYYY-MM-DD |
DVTTe | Yes | Currency | ISO 4217 (VND) |
TGia | Yes | Exchange Rate | Decimal |
HTTToan | Yes | Payment Method | TM/CK/TM-CK |
NBan (Seller)
| Element | Required | Description |
|---|---|---|
Ten | Yes | Company Name |
MST | Yes | Tax Code (10 or 13 digits) |
DChi | Yes | Address |
SDThoai | No | Phone Number |
DCTDTu | No | |
STKNHang | No | Bank Account |
TNHang | No | Bank Name |
NMua (Buyer)
| Element | Required | Description |
|---|---|---|
Ten | Yes | Customer Name |
MST | B2B Only | Tax Code |
DChi | No | Address |
HVTNMHang | No | Contact Person |
HHDVu (Line Item)
| Element | Required | Description |
|---|---|---|
TChat | Yes | Nature: 1=Goods, 2=Service |
STT | Yes | Line Number |
MHHDVu | No | Product Code |
THHDVu | Yes | Product Name |
DVTinh | Yes | Unit of Measure |
SLuong | Yes | Quantity |
DGia | Yes | Unit Price |
TLCKhau | No | Discount Percent |
STCKhau | No | Discount Amount |
ThTien | Yes | Line Total (before VAT) |
TSuat | Yes | VAT Rate |
TToan (Totals)
| Element | Required | Description |
|---|---|---|
TgTCThue | Yes | Total before VAT |
TgTThue | Yes | Total VAT |
TgTTTBSo | Yes | Grand Total (number) |
TgTTTBChu | Yes | Grand Total (words) |
VAT Rates
| Rate | Code | Description |
|---|---|---|
| 0% | 0% | Export, tax-free goods |
| 5% | 5% | Essential goods |
| 8% | 8% | Standard rate (temporary reduction) |
| 10% | 10% | Standard rate |
| KCT | KCT | Not subject to VAT |
| KKKNT | KKKNT | Non-taxable items |
XmlGeneratorService
typescript
@injectable()
class XmlGeneratorService {
/**
* Generate GDT-compliant XML from invoice data
*/
async generateXml(invoice: Invoice): Promise<string> {
const builder = new xml2js.Builder({
xmldec: { version: '1.0', encoding: 'UTF-8' },
renderOpts: { pretty: true, indent: ' ' },
});
const xmlObject = this.buildXmlObject(invoice);
return builder.buildObject(xmlObject);
}
private buildXmlObject(invoice: Invoice): object {
return {
HDon: {
DLHDon: {
TTChung: this.buildTTChung(invoice),
NDHDon: {
NBan: this.buildNBan(invoice),
NMua: this.buildNMua(invoice),
DSHHDVu: this.buildItems(invoice.items),
TToan: this.buildTToan(invoice),
},
},
// Signature added after signing
},
};
}
private buildTTChung(invoice: Invoice): object {
return {
PBan: '2.0.0',
THDon: this.getInvoiceTypeName(invoice.type),
KHMSHDon: invoice.templateCode,
KHHDon: invoice.templateSymbol,
SHDon: invoice.invoiceNumber,
NLap: this.formatDate(invoice.issueDate),
DVTTe: invoice.currency,
TGia: invoice.exchangeRate || 1,
HTTToan: invoice.paymentMethod,
};
}
private buildItems(items: InvoiceItem[]): object {
return {
HHDVu: items.map((item, index) => ({
TChat: item.isService ? '2' : '1',
STT: index + 1,
MHHDVu: item.productCode,
THHDVu: item.productName,
DVTinh: item.unit,
SLuong: item.quantity,
DGia: item.unitPrice,
TLCKhau: item.discountPercent || 0,
STCKhau: item.discountAmount || 0,
ThTien: item.totalBeforeVat,
TSuat: this.formatVatRate(item.vatRate),
})),
};
}
private formatVatRate(rate: number): string {
if (rate === 0) return '0%';
return `${rate}%`;
}
}Number to Vietnamese Words
typescript
/**
* Convert number to Vietnamese words for invoice
*/
function numberToVietnameseWords(num: number): string {
const ones = ['', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín'];
const tens = ['', 'mười', 'hai mươi', 'ba mươi', 'bốn mươi', 'năm mươi',
'sáu mươi', 'bảy mươi', 'tám mươi', 'chín mươi'];
if (num === 0) return 'không đồng';
let result = '';
let billions = Math.floor(num / 1000000000);
let millions = Math.floor((num % 1000000000) / 1000000);
let thousands = Math.floor((num % 1000000) / 1000);
let remainder = num % 1000;
if (billions > 0) {
result += readThreeDigits(billions) + ' tỷ ';
}
if (millions > 0) {
result += readThreeDigits(millions) + ' triệu ';
}
if (thousands > 0) {
result += readThreeDigits(thousands) + ' nghìn ';
}
if (remainder > 0) {
result += readThreeDigits(remainder);
}
return capitalizeFirst(result.trim()) + ' đồng';
}
// Example: 269500 -> "Hai trăm sáu mươi chín nghìn năm trăm đồng"XML Validation
Schema Validation
typescript
import * as libxmljs from 'libxmljs';
async function validateXml(xml: string, schemaPath: string): Promise<ValidationResult> {
const xmlDoc = libxmljs.parseXml(xml);
const xsdDoc = libxmljs.parseXml(
await fs.readFile(schemaPath, 'utf-8')
);
const isValid = xmlDoc.validate(xsdDoc);
const errors = xmlDoc.validationErrors;
return {
isValid,
errors: errors.map(e => ({
line: e.line,
message: e.message,
})),
};
}Required Field Validation
typescript
interface ValidationRule {
path: string;
required: boolean;
pattern?: RegExp;
maxLength?: number;
}
const validationRules: ValidationRule[] = [
{ path: 'HDon.DLHDon.TTChung.PBan', required: true, pattern: /^2\.0\.0$/ },
{ path: 'HDon.DLHDon.TTChung.SHDon', required: true, pattern: /^\d{8}$/ },
{ path: 'HDon.DLHDon.NDHDon.NBan.MST', required: true, pattern: /^\d{10}|\d{13}$/ },
{ path: 'HDon.DLHDon.NDHDon.NBan.Ten', required: true, maxLength: 400 },
{ path: 'HDon.DLHDon.NDHDon.NMua.Ten', required: true, maxLength: 400 },
];