T-VAN Integration
T-VAN (Tax Value Added Network) integration provides direct connection to Vietnam's Tax Authority (CQT) for tax information queries and invoice message lookups.
Overview
| Attribute | Value |
|---|---|
| Package | @nx/t-van |
| Status | Active |
| Purpose | Direct CQT integration for tax compliance |
| Features | Tax info lookup, Invoice message query |
Key Features
- Tax information lookup - Query business tax registration details from CQT
- Invoice message query - List and paginate e-invoice processing messages from the provider
- Multi-provider support - Select a registered T-VAN provider via a
providerquery parameter - Compliance ready - Meet Decree 123/2020 and 70/2025 requirements
Quick Start
1. Configure your application
// application.ts
import { NxTVanComponent, TVanBindingKeys, ITVanOptions } from '@nx/t-van';
class MyApplication extends BaseApplication {
preConfigure() {
// Configure T-VAN clients
this.bind<ITVanOptions>({ key: TVanBindingKeys.TVAN_CLIENT_OPTIONS })
.toValue({
enableControllers: true,
clients: [
{
name: 'default',
provider: 'VNPAY',
apiKey: process.env.TVAN_API_KEY,
secretKey: process.env.TVAN_SECRET_KEY,
taxCode: process.env.COMPANY_TAX_CODE,
isProduction: process.env.NODE_ENV === 'production',
},
],
});
// Load T-VAN component
this.component(NxTVanComponent);
}
}2. Set environment variables
# T-VAN Configuration
APP_ENV_TVAN_API_KEY=your-api-key
APP_ENV_TVAN_SECRET_KEY=your-secret-key
APP_ENV_TVAN_TAX_CODE=0123456789
APP_ENV_TVAN_PROVIDER=VNPAY
APP_ENV_TVAN_IS_PRODUCTION=falseArchitecture
Component Structure
Integration Flow
API Reference
TVanService exposes exactly two operations: queryTaxInformation and queryInvoiceMessages. Both take an opts object with an optional provider, the request params, and optional options.
Query Tax Information
Look up business registration details from CQT. The request takes a taxCodes array (max 10 per provider request); larger arrays are auto-batched when fetchAll is enabled.
import { inject } from '@venizia/ignis';
import { TVanService, TVanTaxInfoQueryTypes } from '@nx/t-van';
class TaxController {
constructor(
@inject({ key: 'services.TVanService' })
private tvanService: TVanService,
) {}
async lookupTaxInfo(taxCodes: string[]) {
const response = await this.tvanService.queryTaxInformation({
provider: 'vnpay',
params: {
type: TVanTaxInfoQueryTypes.TAX_CODE,
taxCodes,
},
options: { fetchAll: true, continueOnError: true },
});
return response.data; // TaxInfo[]
}
}Response Example:
{
"data": [
{
"taxCode": "0102182292",
"fullName": "Tên doanh nghiệp",
"type": "0100",
"status": "00",
"issuedDate": "2020-01-01T00:00:00",
"department": "...",
"managingTaxAuthority": "...",
"chapterLevel": "...",
"chapter": "555",
"updatedDate": "2024-01-01T00:00:00",
"addressLine": "Địa chỉ doanh nghiệp",
"cityCode": "01TTT",
"districtCode": "006HH",
"wardsCode": "1010937",
"fullAddress": "Địa chỉ chi tiết của doanh nghiệp",
"reason": null
}
]
}Query Invoice Messages
List the e-invoice processing messages a provider holds for a given invoice. This is a paginated lookup - it does not verify invoice authenticity with CQT.
async listInvoiceMessages() {
const response = await this.tvanService.queryInvoiceMessages({
provider: 'vnpay',
params: {
messageTypeCode: 200,
messageCode: 'V0102182292A68308EFF92148B0A26',
taxPayerTaxCode: '1801545696-999',
invoiceNumberSymbol: '1',
invoiceSymbol: 'C22THC',
invoiceNumber: 13,
invoiceIssuerTaxCode: '0101352495',
fromDate: '2022-04-25T14:00:00',
toDate: '2022-04-26T14:00:00',
page: 0,
size: 10,
},
options: { fetchAll: false },
});
return response; // { messages, currentPage, pageSize, totalPages, totalItems }
}REST API Endpoints
When enableControllers: true, these endpoints are mounted under the /t-van controller path.
Tax Information Endpoint
# Query tax information (batching is built into the taxCodes array)
POST /t-van/tax-information?provider=vnpay
Content-Type: application/json
{
"type": "00003_TAX_CODE",
"taxCodes": ["0100231226-999", "0100240615", "0100256887"]
}Returns { "result": [ ...TaxInfo ] }. Optional query flags: fetchAll=true auto-batches arrays larger than the provider limit; continueOnError=true tolerates individual failed batches when fetchAll is on.
Invoice Messages Endpoint
# List invoice messages (paginated)
GET /t-van/invoices?provider=vnpay&messageTypeCode=200&messageCode=V0102182292A68308EFF92148B0A26&taxPayerTaxCode=1801545696-999&invoiceNumberSymbol=1&invoiceSymbol=C22THC&invoiceNumber=13&invoiceIssuerTaxCode=0101352495&fromDate=2022-04-25T14:00:00&toDate=2022-04-26T14:00:00&page=0&size=10Returns { messages, currentPage, pageSize, totalPages, totalItems }. Add fetchAll=true to auto-aggregate every page into a single messages array.
Provider Selection
The optional provider query parameter (e.g. vnpay) selects which registered client handles the request. When omitted, the default client is used. There is no client-selection header or client parameter.
Multi-Client Configuration
Support multiple T-VAN providers or accounts:
this.bind<ITVanOptions>({ key: TVanBindingKeys.TVAN_CLIENT_OPTIONS })
.toValue({
enableControllers: true,
clients: [
// Primary provider
{
name: 'primary',
provider: 'VNPAY',
apiKey: process.env.VNPAY_API_KEY,
isDefault: true,
},
// Backup provider
{
name: 'backup',
provider: 'VNPAY',
apiKey: process.env.VNPAY_BACKUP_API_KEY,
},
],
});Selecting a Provider
// Use the default client
const info = await this.tvanService.queryTaxInformation({ params });
// Route through a specific provider
const info = await this.tvanService.queryTaxInformation({
provider: 'vnpay',
params,
});Tax Info Query Types
The type field selects the lookup mode for queryTaxInformation:
| Query Type | Description |
|---|---|
00001_CITIZEN_IDENTITY_CARD | Lookup by citizen identity card |
00003_TAX_CODE | Lookup by tax code |
00130_IDENTITY_CARD_TAX_PAYER | Lookup tax payer by identity card |
00132_PERSONAL_TAX_CODE | Lookup by personal tax code |
The status field in each TaxInfo record is a provider-defined code (e.g. 00); BANA passes it through unchanged - interpret it against the provider's documentation.
Error Handling
T-VAN does not define its own error catalogue. Failures are surfaced from the provider response (HTTP status + provider error payload) via getError, and provider/options validation is enforced before the call. A request with an empty taxCodes array is rejected with Tax codes array cannot be empty before reaching the provider.
try {
const { data } = await this.tvanService.queryTaxInformation({
provider: 'vnpay',
params: { type: TVanTaxInfoQueryTypes.TAX_CODE, taxCodes },
});
return data;
} catch (error) {
// Inspect the provider response payload and service logs
this.logger.error('[lookupTaxInfo] T-VAN query failed: %s', error);
throw error;
}Vietnam Tax Compliance
See T-VAN Compliance for supported providers, when to use T-VAN, and Decree 123/2020 and 70/2025 requirements.
Best Practices
Caching Tax Info
// Tax info doesn't change frequently - cache it
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
async getTaxInfoCached(taxCode: string) {
const cached = await this.cache.get(`tax:${taxCode}`);
if (cached) return cached;
const { data } = await this.tvanService.queryTaxInformation({
provider: 'vnpay',
params: { type: TVanTaxInfoQueryTypes.TAX_CODE, taxCodes: [taxCode] },
});
await this.cache.set(`tax:${taxCode}`, data[0], CACHE_TTL);
return data[0];
}Batch Operations
// queryTaxInformation accepts up to 10 tax codes per provider request;
// enable fetchAll to auto-batch larger arrays
const taxCodes = orders.map(o => o.customerTaxCode).filter(Boolean);
const uniqueTaxCodes = [...new Set(taxCodes)];
const { data } = await this.tvanService.queryTaxInformation({
provider: 'vnpay',
params: { type: TVanTaxInfoQueryTypes.TAX_CODE, taxCodes: uniqueTaxCodes },
options: { fetchAll: true, continueOnError: true },
});Related
- Tax & Invoice Module - Business requirements
- IIAPI Integration - E-Invoice management
- System Overview - Vietnam tax compliance context