Hệ thống Thuế
1. Tổng quan
| Thuộc tính | Giá trị |
|---|---|
| Status | Ổn định (input định giá canonical) |
| Owner | pricing-team |
| Services | TaxSetService (CRUD), PricingTaxCalculatorService (v1), TaxCalculatorService (v2) |
| Controllers / Routes | TaxController /taxes, TaxSetController /tax-sets, TaxTypeController /tax-types |
| Depends on | schema Tax, TaxSet, TaxType (@nx/core) |
Quản lý tính thuế thông qua TaxSets (bộ chứa cho mỗi variant hoặc merchant), Taxes (mục phần trăm / cố định / per-unit), và TaxTypes (danh mục phạm vi hệ thống hoặc merchant như VAT). Thuế áp theo thứ tự ưu tiên với cửa sổ thời gian, tính inclusive/exclusive, compound thuế-trên-thuế, điều kiện số lượng, và thuế cấp order cho các set phạm vi merchant.
Danh mục service và identity card: xem Pricing Service index. Tham chiếu REST: trực tiếp tại
/v1/api/pricing/doc/openapi.json.
2. Mô hình Dữ liệu
2.1. Chế độ Thuế
| Chế độ | percentage | amount | Tính toán |
|---|---|---|---|
| Percentage | set (vd 0.1) | null | tax = taxableAmount × percentage |
| Fixed | null | set (vd 5000) | tax = amount |
| Combined | set | set | tax = (taxableAmount × percentage) + amount |
2.2. Phạm vi TaxType
| Phạm vi | merchantId | Mô tả |
|---|---|---|
| System | null | Loại thuế toàn nền tảng (VAT, GST) - từ dữ liệu seed |
| Merchant | set | Loại thuế tuỳ chỉnh do một merchant cụ thể tạo |
2.3. Thuế Inclusive vs Exclusive
| Loại | isInclusive | Hành vi |
|---|---|---|
| Exclusive | false (mặc định) | Thuế cộng lên trên giá - tăng tổng |
| Inclusive | true | Thuế nhúng trong giá - back-calc, không tăng tổng |
Công thức back-calc inclusive:
| Chế độ | Công thức |
|---|---|
| Percentage | taxAmount = price − (price / (1 + rate)) |
| Fixed amount | taxAmount = fixedAmount (trừ khỏi giá, không cộng thêm) |
Lưu ý: Thuế inclusive chỉ áp ở phạm vi
ITEM.
2.4. Phạm vi Thuế
Mỗi thuế có trường scope kiểm soát nơi nó áp dụng:
| Phạm vi | Áp dụng cho | Cho phép trên |
|---|---|---|
ITEM (mặc định) | Tổng phụ của mỗi product variant | Bất kỳ TaxSet |
ORDER | Tổng phụ order sau khi mọi item được định giá | Chỉ TaxSet của Merchant |
Ràng buộc: Thuế phạm vi
ORDERphải exclusive (isInclusive = false). Hệ thống throw lỗi validation nếu vi phạm quy tắc này.
2.5. Cờ Hành vi Thuế
| Trường | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
isInclusive | boolean | false | Thuế nhúng trong giá (inclusive) hoặc cộng lên trên (exclusive) |
isCompound | boolean | true | Cộng dồn trên thuế tích luỹ từ các nhóm priority trước |
shouldApplyOnDiscounted | boolean | true | Dùng tổng phụ đã giảm làm base (true) hoặc tổng phụ gốc (false) |
scope | ITEM | ORDER | ITEM | Áp ở cấp item hay cấp order |
minQuantity | integer? | null | Bỏ qua thuế này nếu số lượng item dưới ngưỡng |
maxQuantity | integer? | null | Bỏ qua thuế này nếu số lượng item vượt ngưỡng |
2.6. Điều kiện theo Số lượng
Thuế có thể bị bỏ qua có điều kiện dựa trên số lượng order:
| Điều kiện | Kết quả |
|---|---|
minQuantity đặt và quantity < minQuantity | Thuế bị bỏ qua |
maxQuantity đặt và quantity > maxQuantity | Thuế bị bỏ qua |
| Không đặt gì | Thuế luôn áp |
3. Luồng Tính Thuế
3.1. Kết quả Tính toán
interface TaxCalculationResult {
taxSetId: string;
totalTax: string; // Sum of all applied exclusive taxes
appliedTaxes: AppliedTaxResponse[];
}
interface AppliedTaxResponse {
taxId: string;
amount: string; // Individual tax amount
taxableBase: string; // Base amount used for calculation
taxTypeId: string;
isInclusive: boolean; // Whether the tax is inclusive
isVat: boolean; // true if taxType.type === 'VAT'
isCompound: boolean; // Whether the tax compounds on previous groups
}3.2. Thuế Cấp Order
Sau khi mọi thuế cấp item được tính, hệ thống áp thuế cấp order khi merchantId được cung cấp trong context:
Cấu trúc OrderTaxesResponse:
interface OrderTaxesResponse {
totalOrderTax: string;
totalExclusiveOrderTax: string;
totalInclusiveOrderTax: string; // Always "0.0000" - ORDER taxes must be exclusive
appliedOrderTaxes: AppliedTaxResponse[];
}Lưu ý:
totalInclusiveOrderTaxluôn là"0.0000"vì thuế phạm vi ORDER bắt buộc exclusive (isInclusive = false).
4. Thiết lập VAT Tự động
Khi tạo một product variant, hệ thống thuế tự động tạo một TaxSet với VAT 10% mặc định:
5. Thao tác Repository
5.1. TaxRepository
| Phương thức | Mô tả |
|---|---|
findActivated() | Nạp thuế theo taxSetId, lọc theo effectiveDate, sắp theo priority |
findByTaxTypeId() | Tìm thuế dùng một tax type cụ thể |
findFutureTaxes() | Tìm thuế có effectiveFrom trong tương lai |
findExpiredTaxes() | Tìm thuế có effectiveTo trong quá khứ |
5.2. TaxSetRepository
| Phương thức | Mô tả |
|---|---|
findActivated() | Tìm TaxSet đã activate cho product variant (strict/non-strict) |
findActiveTaxSetByPrincipal() | Tìm theo principalId + principalType kèm taxes |
deactivateAllForPrincipal() | Vô hiệu mọi TaxSet trước khi activate cái mới |
5.3. TaxTypeRepository
| Phương thức | Mô tả |
|---|---|
findSystemTaxTypes() | Loại hệ thống (merchantId = null) |
findByMerchantId() | Loại riêng merchant |
findAvailableTaxTypes() | Loại hệ thống + merchant kết hợp |
6. API Controller
TaxController /taxes và TaxTypeController /tax-types là CRUD chuẩn. TaxSetController /tax-sets thêm một tuỳ chỉnh PATCH /{id}/aggregate (cập nhật set + taxes của nó trong một lệnh gọi). Tham chiếu request/response đầy đủ được render trực tiếp từ /v1/api/pricing/doc/openapi.json - không bảo trì thủ công ở đây.
7. Ví dụ Thực tế
7.1. Kịch bản 1: Thuế Phần trăm Đơn giản (VAT)
Quy tắc Kinh doanh: "Áp dụng VAT 10% cho tất cả sản phẩm"
Bước 1: Tạo Tax Set (Tự động khi Tạo Biến thể Sản phẩm)
Khi tạo biến thể sản phẩm, hệ thống tự động tạo:
- TaxSet với trạng thái
ACTIVATED - Thuế VAT mặc định với
percentage: 0.1(10%)
Bước 2: Tính Thuế
// Internal API call from PricingService
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-001',
taxableAmount: '100000', // Giá cơ sở sau giảm giá
context: {
traceId: 'trace-001',
calculatingAt: '2026-02-25T10:00:00Z'
}
});Kết quả:
{
"taxSetId": "taxset-001",
"totalTax": "10000.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-001",
"amount": "10000.0000"
}
]
}Tính toán: 100,000 × 0.1 = 10,000
7.2. Kịch bản 2: Thuế Số tiền Cố định (Phí Dịch vụ)
Quy tắc Kinh doanh: "Thêm phí dịch vụ 5,000đ vào mọi giao dịch"
Bước 1: Tạo Thuế Phí Dịch vụ
POST /taxes
Content-Type: application/json
Authorization: Bearer <token>
{
"taxSetId": "taxset-001",
"taxTypeId": "taxtype-service-fee",
"amount": "5000",
"percentage": null,
"effectiveFrom": "2026-02-01T00:00:00Z",
"effectiveTo": null,
"priority": 1,
"status": "ACTIVATED"
}Bước 2: Tính Thuế với VAT + Phí Dịch vụ
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-001',
taxableAmount: '100000',
context: { traceId: 'trace-002' }
});Kết quả:
{
"taxSetId": "taxset-001",
"totalTax": "15000.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-001",
"amount": "10000.0000"
},
{
"taxId": "tax-service-fee-001",
"amount": "5000.0000"
}
]
}Tính toán:
- VAT:
100,000 × 0.1 = 10,000 - Phí Dịch vụ:
5,000(cố định) - Tổng:
10,000 + 5,000 = 15,000
7.3. Kịch bản 3: Thuế Kết hợp (Phần trăm + Cố định)
Quy tắc Kinh doanh: "Áp thuế xa xỉ 8% + phí môi trường 10,000đ trên sản phẩm cao cấp"
Bước 1: Tạo Thuế Kết hợp
POST /taxes
Content-Type: application/json
{
"taxSetId": "taxset-premium-001",
"taxTypeId": "taxtype-luxury",
"percentage": "0.08",
"amount": "10000",
"effectiveFrom": "2026-01-01T00:00:00Z",
"priority": 2,
"status": "ACTIVATED"
}Bước 2: Tính Thuế (VAT + Xa xỉ + Môi trường)
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-premium-001',
taxableAmount: '500000',
context: { traceId: 'trace-003' }
});Kết quả:
{
"taxSetId": "taxset-premium-001",
"totalTax": "100000.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-001",
"amount": "50000.0000"
},
{
"taxId": "tax-luxury-001",
"amount": "50000.0000"
}
]
}Tính toán:
- VAT (priority 0):
500,000 × 0.1 = 50,000 - Xa xỉ + Môi trường (priority 2):
(500,000 × 0.08) + 10,000 = 40,000 + 10,000 = 50,000 - Tổng:
50,000 + 50,000 = 100,000
7.4. Kịch bản 4: Định giá Tax-Inclusive vs Tax-Exclusive
Quy tắc Kinh doanh: "Giá hiển thị đã bao gồm VAT (tax-inclusive), nhưng tính thuế riêng để báo cáo"
Cách Tax-Exclusive (Triển khai Hiện tại):
// 1. Calculate fare (base price)
const fareResult = await pricingFareCalculatorService.selectFare({
productVariantId: 'pv-001',
context: { quantity: '1' }
});
// fareResult.selectedFare.amount = '100000'
// 2. Calculate tax on fare amount
const taxResult = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-001',
taxableAmount: '100000', // Use fare amount as taxable base
context: { traceId: 'trace-004' }
});
// taxResult.totalTax = '10000'
// 3. Final price
const finalPrice = {
subtotal: '100000', // Pre-tax amount
tax: '10000', // Tax amount
total: '110000' // Customer pays this
};Cách Tax-Inclusive (Tính Ngược):
// Given: Tax-inclusive price = 110,000 VND (includes 10% VAT)
// Calculate: Tax amount and pre-tax amount
const taxInclusivePrice = 110000;
const vatRate = 0.1;
// Reverse formula: preTaxAmount = inclusivePrice / (1 + vatRate)
const preTaxAmount = taxInclusivePrice / (1 + vatRate);
// preTaxAmount = 110,000 / 1.1 = 100,000
const taxAmount = taxInclusivePrice - preTaxAmount;
// taxAmount = 110,000 - 100,000 = 10,000
const breakdown = {
displayPrice: '110000', // What customer sees
subtotal: '100000', // Pre-tax amount
tax: '10000', // Tax amount
total: '110000' // Same as display price
};7.5. Kịch bản 5: Sắp xếp Thuế theo Priority
Quy tắc Kinh doanh: "Áp thuế theo thứ tự cụ thể: VAT (priority 0) → Phí dịch vụ (priority 1) → Thuế xa xỉ (priority 2)"
Bước 1: Tạo Thuế với Priority Khác nhau
# VAT (highest priority)
POST /taxes
{
"taxSetId": "taxset-001",
"taxTypeId": "taxtype-vat",
"percentage": "0.1",
"priority": 0,
"status": "ACTIVATED"
}
# Service Fee (medium priority)
POST /taxes
{
"taxSetId": "taxset-001",
"taxTypeId": "taxtype-service",
"amount": "5000",
"priority": 1,
"status": "ACTIVATED"
}
# Luxury Tax (lowest priority)
POST /taxes
{
"taxSetId": "taxset-001",
"taxTypeId": "taxtype-luxury",
"percentage": "0.05",
"priority": 2,
"status": "ACTIVATED"
}Bước 2: Tính Thuế (Áp theo Thứ tự Priority)
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-001',
taxableAmount: '200000',
context: { traceId: 'trace-005' }
});Kết quả:
{
"taxSetId": "taxset-001",
"totalTax": "35000.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-001",
"amount": "20000.0000"
},
{
"taxId": "tax-service-001",
"amount": "5000.0000"
},
{
"taxId": "tax-luxury-001",
"amount": "10000.0000"
}
]
}Tính toán (theo thứ tự priority):
- VAT (priority 0):
200,000 × 0.1 = 20,000 - Phí Dịch vụ (priority 1):
5,000(cố định) - Thuế Xa xỉ (priority 2):
200,000 × 0.05 = 10,000 - Tổng:
20,000 + 5,000 + 10,000 = 35,000
Lưu ý: Thuế cùng priority chia sẻ cùng base tích luỹ. Thuế có isCompound = true cộng dồn trên thuế tích luỹ từ mọi nhóm priority thấp hơn. Xem Kịch bản 7.10 để có ví dụ đầy đủ.
7.6. Kịch bản 6: Thay đổi Thuế theo Thời gian (Tăng Thuế suất)
Quy tắc Kinh doanh: "VAT tăng từ 10% lên 12% bắt đầu 2026-04-01"
Bước 1: Cập nhật Thuế VAT Hiện có để Hết hạn
PUT /taxes/tax-vat-001
Content-Type: application/json
{
"effectiveTo": "2026-03-31T23:59:59Z"
}Bước 2: Tạo Thuế VAT Mới với Ngày Hiệu lực Tương lai
POST /taxes
Content-Type: application/json
{
"taxSetId": "taxset-001",
"taxTypeId": "taxtype-vat",
"percentage": "0.12",
"effectiveFrom": "2026-04-01T00:00:00Z",
"effectiveTo": null,
"priority": 0,
"status": "ACTIVATED"
}Bước 3: Tính Thuế Trước Chuyển đổi (2026-03-30)
const resultBefore = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-001',
taxableAmount: '100000',
context: {
traceId: 'trace-006',
calculatingAt: '2026-03-30T10:00:00Z'
}
});Kết quả:
{
"taxSetId": "taxset-001",
"totalTax": "10000.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-001",
"amount": "10000.0000"
}
]
}Tính toán: 100,000 × 0.1 = 10,000 (thuế suất cũ)
Bước 4: Tính Thuế Sau Chuyển đổi (2026-04-02)
const resultAfter = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-001',
taxableAmount: '100000',
context: {
traceId: 'trace-007',
calculatingAt: '2026-04-02T10:00:00Z'
}
});Kết quả:
{
"taxSetId": "taxset-001",
"totalTax": "12000.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-002",
"amount": "12000.0000"
}
]
}Tính toán: 100,000 × 0.12 = 12,000 (thuế suất mới)
7.7. Kịch bản 7: Thuế Tuỳ chỉnh Riêng Merchant
Quy tắc Kinh doanh: "Merchant 'ABC Corp' thêm phí xử lý tuỳ chỉnh 2% vào sản phẩm của họ"
Bước 1: Tạo Tax Type Riêng Merchant
POST /tax-types
Content-Type: application/json
Authorization: Bearer <merchant-abc-token>
{
"type": "HANDLING_FEE",
"name": "ABC Handling Fee",
"merchantId": "merchant-abc-001",
"status": "ACTIVATED"
}Phản hồi:
{
"id": "taxtype-abc-handling",
"type": "HANDLING_FEE",
"merchantId": "merchant-abc-001",
"status": "ACTIVATED"
}Bước 2: Thêm Thuế Tuỳ chỉnh vào Product Variant
POST /taxes
Content-Type: application/json
{
"taxSetId": "taxset-abc-product-001",
"taxTypeId": "taxtype-abc-handling",
"percentage": "0.02",
"priority": 3,
"status": "ACTIVATED"
}Bước 3: Tính Thuế (VAT Hệ thống + Phí Xử lý Merchant)
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-abc-001',
taxableAmount: '150000',
context: { traceId: 'trace-008' }
});Kết quả:
{
"taxSetId": "taxset-abc-product-001",
"totalTax": "18000.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-001",
"amount": "15000.0000"
},
{
"taxId": "tax-abc-handling-001",
"amount": "3000.0000"
}
]
}Tính toán:
- VAT Hệ thống (priority 0):
150,000 × 0.1 = 15,000 - Phí Xử lý Merchant (priority 3):
150,000 × 0.02 = 3,000 - Tổng:
15,000 + 3,000 = 18,000
7.8. Kịch bản 8: Xử lý Lỗi - Cấu hình Thuế Không hợp lệ
Quy tắc Kinh doanh: "Thuế PHẢI có percentage HOẶC amount (hoặc cả hai)"
Thử Tạo Thuế Không hợp lệ:
POST /taxes
Content-Type: application/json
{
"taxSetId": "taxset-001",
"taxTypeId": "taxtype-invalid",
"percentage": null,
"amount": null,
"status": "ACTIVATED"
}Thử Tính toán:
try {
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-001',
taxableAmount: '100000',
context: { traceId: 'trace-009' }
});
} catch (error) {
console.error(error);
}Phản hồi Lỗi:
{
"statusCode": 500,
"message": "[PricingTaxCalculatorService][_validateTaxConfiguration] Invalid tax configuration: tax must have either percentage or amount value | Tax ID: tax-invalid-001 | Tax Set ID: taxset-001",
"details": {
"taxId": "tax-invalid-001",
"taxSetId": "taxset-001",
"percentage": null,
"amount": null
}
}7.9. Kịch bản 9: VAT Inclusive
Quy tắc Kinh doanh: "Sản phẩm có giá 110,000đ đã bao gồm VAT 10% - trích thuế nhúng để báo cáo"
Thuế có isInclusive: true và percentage: 0.1. Hệ thống back-calc thuế nhúng mà không tăng tổng.
Cấu hình thuế:
POST /taxes
Content-Type: application/json
{
"taxSetId": "taxset-inclusive-001",
"taxTypeId": "taxtype-vat",
"percentage": "0.1",
"isInclusive": true,
"priority": 0,
"status": "ACTIVATED"
}Tính thuế trên giá inclusive:
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-inclusive-001',
taxableAmount: '110000', // Display price (already includes VAT)
context: { traceId: 'trace-009' }
});Kết quả:
{
"taxSetId": "taxset-inclusive-001",
"totalTax": "0.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-inclusive-001",
"amount": "10000.0000",
"taxableBase": "110000.0000",
"isInclusive": true,
"isVat": true,
"isCompound": true
}
]
}Tính toán: 110,000 − (110,000 / (1 + 0.1)) = 110,000 − 100,000 = 10,000
Quan trọng:
totalTaxlà0vì thuế inclusive KHÔNG cộng vào tổng - thuế đã nhúng trong giá hiển thị.amounttrongappliedTaxeschỉ phục vụ báo cáo/phân tích.
7.10. Kịch bản 10: Thuế Compound (Thuế-trên-Thuế)
Quy tắc Kinh doanh: "Áp VAT 10% (priority 0), rồi phí dịch vụ 2% trên giá đã gồm VAT (priority 1, compound)"
| Thuế | Priority | Thuế suất | isCompound |
|---|---|---|---|
| VAT | 0 | 10% | true |
| Phí Dịch vụ | 1 | 2% | true |
Cấu hình thuế:
# VAT - priority 0
POST /taxes
{
"taxSetId": "taxset-compound-001",
"taxTypeId": "taxtype-vat",
"percentage": "0.1",
"isCompound": true,
"priority": 0,
"status": "ACTIVATED"
}
# Service Charge - priority 1, compounds on VAT
POST /taxes
{
"taxSetId": "taxset-compound-001",
"taxTypeId": "taxtype-service",
"percentage": "0.02",
"isCompound": true,
"priority": 1,
"status": "ACTIVATED"
}Tính thuế compound:
const result = await pricingTaxCalculatorService.calculateTax({
productVariantId: 'pv-compound-001',
taxableAmount: '100000',
context: { traceId: 'trace-010' }
});Kết quả:
{
"taxSetId": "taxset-compound-001",
"totalTax": "12200.0000",
"appliedTaxes": [
{
"taxId": "tax-vat-001",
"amount": "10000.0000",
"taxableBase": "100000.0000",
"isInclusive": false,
"isVat": true,
"isCompound": true
},
{
"taxId": "tax-service-001",
"amount": "2200.0000",
"taxableBase": "110000.0000",
"isInclusive": false,
"isVat": false,
"isCompound": true
}
]
}Tính toán (thuật toán compound):
- Priority 0 - VAT:
taxableBase = 100,000(base, không tích luỹ trước) →tax = 100,000 × 0.1 = 10,000→cumulativeTax = 10,000 - Priority 1 - Phí Dịch vụ:
isCompound = true→taxableBase = 100,000 + 10,000 = 110,000→tax = 110,000 × 0.02 = 2,200 - Tổng:
10,000 + 2,200 = 12,200
7.11. Kịch bản 11: Thuế Cấp Order (Phí Dịch vụ Merchant)
Quy tắc Kinh doanh: "Merchant áp phí dịch vụ nền tảng 1% trên tổng phụ order sau mọi thuế item"
Merchant có một TaxSet với thuế phạm vi ORDER. Sau khi mọi item được định giá, hệ thống tính phí này trên tổng phụ order.
Cấu hình thuế TaxSet Merchant:
POST /taxes
Content-Type: application/json
{
"taxSetId": "taxset-merchant-order-001",
"taxTypeId": "taxtype-platform-fee",
"percentage": "0.01",
"scope": "ORDER",
"isInclusive": false,
"priority": 0,
"status": "ACTIVATED"
}Context simulation với merchantId:
const result = await simulationService.calculate({
items: [/* ... */],
context: {
merchantId: 'merchant-abc-001', // Triggers order-level tax lookup
traceId: 'trace-011'
}
});Kết quả thuế cấp order (SimulationCalculateResponse.orderTaxes):
{
"totalOrderTax": "5000.0000",
"totalExclusiveOrderTax": "5000.0000",
"totalInclusiveOrderTax": "0.0000",
"appliedOrderTaxes": [
{
"taxId": "tax-platform-fee-001",
"amount": "5000.0000",
"taxableBase": "500000.0000",
"isInclusive": false,
"isVat": false,
"isCompound": true
}
]
}Tính toán: orderSubtotal (500,000) × 0.01 = 5,000
Lưu ý:
totalInclusiveOrderTaxluôn là"0.0000"vì thuế phạm vi ORDER bắt buộc exclusive theo thiết kế.
8. Tài liệu Liên quan
- Pricing Service - Tổng quan package
- Fare System - Tính fare
- Cost Tracking - Quản lý chi phí
- Promotions - Hệ thống khuyến mãi