Định dạng XML Hóa đơn
Tổng quan
Hóa đơn điện tử Việt Nam phải tuân theo định dạng XML được quy định bởi Tổng cục Thuế (GDT). Tài liệu này mô tả cấu trúc XML, các trường bắt buộc và quy tắc xác thực.
Cấu trúc XML
Ví dụ XML Đầy đủ
xml
<?xml version="1.0" encoding="UTF-8"?>
<HDon>
<DLHDon>
<!-- Thông tin Chung -->
<TTChung>
<PBan>2.0.0</PBan> <!-- Phiên bản XML -->
<THDon>Hóa đơn GTGT</THDon> <!-- Tên Loại Hóa đơn -->
<KHMSHDon>1</KHMSHDon> <!-- Mã Mẫu số -->
<KHHDon>C25TAA</KHHDon> <!-- Ký hiệu Mẫu -->
<SHDon>00000001</SHDon> <!-- Số Hóa đơn -->
<MHSo>01GTKT0/001</MHSo> <!-- Mẫu số Hóa đơn -->
<NLap>2025-01-20</NLap> <!-- Ngày lập -->
<SBKe></SBKe> <!-- Số Bảng kê -->
<NBKe></NBKe> <!-- Ngày Bảng kê -->
<DVTTe>VND</DVTTe> <!-- Đơn vị Tiền tệ -->
<TGia>1</TGia> <!-- Tỷ giá -->
<HTTToan>TM/CK</HTTToan> <!-- Hình thức Thanh toán -->
<MSTTCGP></MSTTCGP> <!-- MST Tổ chức cung cấp giải pháp -->
<MSTDVNUNLHDon></MSTDVNUNLHDon> <!-- MST Đơn vị nhận ủy nhiệm lập hóa đơn -->
<THTToan>1</THTToan> <!-- Trạng thái Thanh toán -->
</TTChung>
<!-- Nội dung Hóa đơn -->
<NDHDon>
<!-- Thông tin Người bán -->
<NBan>
<Ten>Công ty TNHH ABC</Ten> <!-- Tên Công ty -->
<MST>0123456789</MST> <!-- Mã số Thuế -->
<DChi>123 Nguyễn Huệ, Q1, TP.HCM</DChi> <!-- Địa chỉ -->
<SDThoai>028-1234-5678</SDThoai> <!-- Điện thoại -->
<DCTDTu>contact@abc.com.vn</DCTDTu> <!-- Email -->
<STKNHang>1234567890</STKNHang> <!-- Số Tài khoản Ngân hàng -->
<TNHang>Vietcombank</TNHang> <!-- Tên Ngân hàng -->
<Fax></Fax> <!-- Fax -->
<Website>www.abc.com.vn</Website> <!-- Website -->
</NBan>
<!-- Thông tin Người mua -->
<NMua>
<Ten>Khách hàng XYZ</Ten> <!-- Tên Khách hàng -->
<MST>9876543210</MST> <!-- Mã số Thuế (B2B) -->
<DChi>456 Lê Lợi, Q1, TP.HCM</DChi> <!-- Địa chỉ -->
<SDThoai>0901234567</SDThoai> <!-- Điện thoại -->
<DCTDTu>customer@xyz.com</DCTDTu> <!-- Email -->
<HVTNMHang>Nguyễn Văn A</HVTNMHang> <!-- Họ và tên người mua hàng -->
<STKNHang></STKNHang> <!-- Số Tài khoản Ngân hàng -->
<TNHang></TNHang> <!-- Tên Ngân hàng -->
</NMua>
<!-- Danh sách Hàng hóa, Dịch vụ -->
<DSHHDVu>
<HHDVu>
<TChat>1</TChat> <!-- Tính chất: 1=Hàng hóa, 2=Dịch vụ -->
<STT>1</STT> <!-- Số thứ tự -->
<MHHDVu>SP001</MHHDVu> <!-- Mã Hàng hóa/Dịch vụ -->
<THHDVu>Sản phẩm A</THHDVu> <!-- Tên Hàng hóa/Dịch vụ -->
<DVTinh>Cái</DVTinh> <!-- Đơn vị tính -->
<SLuong>2</SLuong> <!-- Số lượng -->
<DGia>100000</DGia> <!-- Đơn giá -->
<TLCKhau>0</TLCKhau> <!-- Tỷ lệ Chiết khấu -->
<STCKhau>0</STCKhau> <!-- Số tiền Chiết khấu -->
<ThTien>200000</ThTien> <!-- Thành tiền (trước VAT) -->
<TSuat>10%</TSuat> <!-- Thuế suất -->
</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>
<!-- Tóm tắt Thanh toán -->
<TToan>
<TgTCThue>245000</TgTCThue> <!-- Tổng tiền chưa thuế -->
<TgTThue>24500</TgTThue> <!-- Tổng tiền thuế -->
<TgTTTBSo>269500</TgTTTBSo> <!-- Tổng tiền thanh toán bằng số -->
<TgTTTBChu>Hai trăm sáu mươi chín nghìn năm trăm đồng</TgTTTBChu>
<!-- Tổng tiền thanh toán bằng chữ -->
<!-- Tóm tắt VAT theo thuế suất -->
<THTTLTSuat>
<LTSuat>
<TSuat>10%</TSuat> <!-- Thuế suất -->
<ThTien>245000</ThTien> <!-- Thành tiền -->
<TThue>24500</TThue> <!-- Tiền thuế -->
</LTSuat>
</THTTLTSuat>
</TToan>
</NDHDon>
</DLHDon>
<!-- Chữ ký số -->
<DSCKS>
<NBan>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<!-- Chữ ký số của Người bán (PKCS#7) -->
</Signature>
</NBan>
</DSCKS>
</HDon>Tham khảo Trường
TTChung (Thông tin Chung)
| Phần tử | Bắt buộc | Mô tả | Định dạng |
|---|---|---|---|
PBan | Có | Phiên bản XML | "2.0.0" |
THDon | Có | Tên loại hóa đơn | Văn bản |
KHMSHDon | Có | Mã mẫu số hóa đơn | 1-6 |
KHHDon | Có | Ký hiệu mẫu hóa đơn | Tối đa 6 ký tự |
SHDon | Có | Số hóa đơn | 8 chữ số |
MHSo | Có | Mẫu số | Mẫu: ##GTKT#/### |
NLap | Có | Ngày lập | YYYY-MM-DD |
DVTTe | Có | Đơn vị tiền tệ | ISO 4217 (VND) |
TGia | Có | Tỷ giá | Thập phân |
HTTToan | Có | Hình thức thanh toán | TM/CK/TM-CK |
NBan (Người bán)
| Phần tử | Bắt buộc | Mô tả |
|---|---|---|
Ten | Có | Tên công ty |
MST | Có | Mã số thuế (10 hoặc 13 chữ số) |
DChi | Có | Địa chỉ |
SDThoai | Không | Số điện thoại |
DCTDTu | Không | |
STKNHang | Không | Số tài khoản ngân hàng |
TNHang | Không | Tên ngân hàng |
NMua (Người mua)
| Phần tử | Bắt buộc | Mô tả |
|---|---|---|
Ten | Có | Tên khách hàng |
MST | Chỉ B2B | Mã số thuế |
DChi | Không | Địa chỉ |
HVTNMHang | Không | Người liên hệ |
HHDVu (Hàng hóa Dịch vụ)
| Phần tử | Bắt buộc | Mô tả |
|---|---|---|
TChat | Có | Tính chất: 1=Hàng hóa, 2=Dịch vụ |
STT | Có | Số thứ tự |
MHHDVu | Không | Mã hàng hóa/dịch vụ |
THHDVu | Có | Tên hàng hóa/dịch vụ |
DVTinh | Có | Đơn vị tính |
SLuong | Có | Số lượng |
DGia | Có | Đơn giá |
TLCKhau | Không | Tỷ lệ chiết khấu |
STCKhau | Không | Số tiền chiết khấu |
ThTien | Có | Thành tiền (trước VAT) |
TSuat | Có | Thuế suất |
TToan (Thanh toán)
| Phần tử | Bắt buộc | Mô tả |
|---|---|---|
TgTCThue | Có | Tổng tiền chưa thuế |
TgTThue | Có | Tổng tiền thuế |
TgTTTBSo | Có | Tổng tiền thanh toán bằng số |
TgTTTBChu | Có | Tổng tiền thanh toán bằng chữ |
Thuế suất VAT
| Thuế suất | Mã | Mô tả |
|---|---|---|
| 0% | 0% | Xuất khẩu, hàng miễn thuế |
| 5% | 5% | Hàng hóa thiết yếu |
| 8% | 8% | Mức giảm (tạm thời) |
| 10% | 10% | Mức chuẩn |
| KCT | KCT | Không chịu thuế |
| KKKNT | KKKNT | Không kê khai, nộp thuế |
XmlGeneratorService
typescript
@injectable()
class XmlGeneratorService {
/**
* Tạo XML tuân thủ GDT từ dữ liệu hóa đơn
*/
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),
},
},
// Chữ ký được thêm sau khi ký
},
};
}
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}%`;
}
}Chuyển đổi Số sang Chữ Tiếng Việt
typescript
/**
* Chuyển đổi số sang chữ tiếng Việt cho hóa đơn
*/
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';
}
// Ví dụ: 269500 -> "Hai trăm sáu mươi chín nghìn năm trăm đồng"Xác thực XML
Xác thực Schema
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,
})),
};
}Xác thực Trường Bắt buộc
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 },
];