Skip to content

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

AttributeValue
Package@nx/t-van
StatusActive
PurposeDirect CQT integration for tax compliance
FeaturesTax 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 provider query parameter
  • Compliance ready - Meet Decree 123/2020 and 70/2025 requirements

Quick Start

1. Configure your application

typescript
// 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

bash
# 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=false

Architecture

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.

typescript
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:

json
{
  "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.

typescript
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

http
# 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

http
# 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=10

Returns { 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:

typescript
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

typescript
// 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 TypeDescription
00001_CITIZEN_IDENTITY_CARDLookup by citizen identity card
00003_TAX_CODELookup by tax code
00130_IDENTITY_CARD_TAX_PAYERLookup tax payer by identity card
00132_PERSONAL_TAX_CODELookup 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.

typescript
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

typescript
// 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

typescript
// 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 },
});

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.