Documentation
¶
Overview ¶
Package vnsalary provides an exact Gross-to-Net and Net-to-Gross salary and personal income tax (PIT / Thuế TNCN) calculator according to Vietnamese labor and taxation regulations.
The library determines statutory parameters based on the income payment date (Date): taxable income from salaries is determined when the income is paid (Law on PIT No. 109/2025/QH15, Article 8.3; previously Circular 111/2013/TT-BTC, Article 8.2.b).
All monetary values are represented as integer Vietnamese Dong (VND int64) with half-up integer rounding independently applied to mandatory insurance items and progressive tax brackets. No floating-point arithmetic (zero float64) and zero external dependencies (Go Standard Library only).
Index ¶
- Constants
- Variables
- type AnnualDeductions
- type AnnualOptions
- type AnnualResult
- type BracketLine
- type Deductions
- type EmployerCost
- type EmployerOptions
- type ExemptIncome
- type Extras
- type FlatResult
- type Insurance
- type Options
- type Payment
- type Policy
- type PolicyRef
- type Region
- type Result
- type TaxBracket
- type VND
- type Warning
Examples ¶
Constants ¶
const ( // MaxGross is the maximum supported salary amount (100 trillion VND) to guard against int64 overflow. MaxGross VND = 100_000_000_000_000 // MaxDependents is the maximum allowed number of dependents. MaxDependents = 50 // MaxAnnualPayments is the maximum number of payments Annual accepts for one tax year. MaxAnnualPayments = 1000 // FirstAnnualYear is the first tax year Annual supports: the policy data // starts on 2024-07-01, so 2025 is the first year it covers in full. FirstAnnualYear = 2025 )
const ( // WarnOvertimeTaxedBeforeExemption: before tax period 2026 only the part of // overtime pay above normal-hours pay was exempt (Circular 111/2013/TT-BTC // Art. 3.1.i). The library cannot split Extras.Overtime, so it taxes all of // it; put the exempt part in Extras.OtherExempt instead. WarnOvertimeTaxedBeforeExemption = "overtime_taxed_before_2026" // WarnMealCapUnverified: the meal allowance cap for this payment date could // not be confirmed from a legal text; the policy's cap is applied. WarnMealCapUnverified = "meal_cap_unverified" // WarnInsuranceSalaryIncludesAllowances: InsuranceBase is nil, so MealCash // and OtherExempt are included in the insurance salary. Set InsuranceBase if // the employment contract excludes them. WarnInsuranceSalaryIncludesAllowances = "insurance_salary_includes_allowances" )
Warning codes for Options.Extras.
const WarnDependentsIgnoredNonResident = "dependents_ignored_non_resident"
WarnDependentsIgnoredNonResident is reported when Dependents is set for a non-resident, who has no family deductions; the dependents are ignored.
const WarnHealthEducationNotDeductible = "health_education_not_deductible"
WarnHealthEducationNotDeductible is reported when Healthcare or Education is given for a tax year before 2026, when these expenses were not deductible; they are ignored.
const WarnInsuranceSalaryBelowRegionalMinimum = "insurance_salary_below_regional_minimum"
WarnInsuranceSalaryBelowRegionalMinimum is reported when the salary used for insurance (InsuranceBase, or gross when InsuranceBase is nil) is positive but below the regional minimum wage. A full-time employee must be paid at least that minimum (Labour Code 2019, Art. 90.2), so a lower insurance salary usually means a part-time contract or an input mistake. It is a warning, not an error: the legal floor for the social insurance salary itself is the reference level (Law on Social Insurance 41/2024/QH15, Art. 31.1), which is lower than the regional minimum.
Variables ¶
var ( // ErrInvalidAmount indicates that a negative amount was supplied for gross or net salary. ErrInvalidAmount = errors.New("vnsalary: amount must be non-negative") // ErrAmountTooLarge indicates that an amount exceeds the supported maximum (100 trillion VND). ErrAmountTooLarge = errors.New("vnsalary: amount exceeds supported maximum (100,000,000,000,000 VND)") // ErrInvalidDependents indicates that the dependents count is out of range [0, 50]. ErrInvalidDependents = errors.New("vnsalary: dependents count must be between 0 and 50") // ErrInvalidRegion indicates that the minimum wage region is outside [1, 4]. ErrInvalidRegion = errors.New("vnsalary: region must be between 1 and 4") // ErrDateOutOfRange indicates that the effective date is prior to July 01, 2024. ErrDateOutOfRange = errors.New("vnsalary: date must be on or after 2024-07-01") // ErrDateZero indicates that a zero time value was provided for the payment date. ErrDateZero = errors.New("vnsalary: date cannot be zero") // ErrExtrasExceedGross indicates that Options.Extras add up to more than gross; // gross is the total paid in the month and must include them. ErrExtrasExceedGross = errors.New("vnsalary: extras exceed gross (gross must include them)") // ErrInsuranceExceedsGross indicates that the mandatory employee insurance contribution exceeds the gross salary. ErrInsuranceExceedsGross = errors.New("vnsalary: insurance contribution exceeds gross salary") // ErrInvalidAccidentRate indicates that EmployerOptions.AccidentPerMille is outside [0, 10]. ErrInvalidAccidentRate = errors.New("vnsalary: accident insurance rate must be between 0 and 10 per mille") // ErrYearOutOfRange indicates that Annual was asked for a tax year before FirstAnnualYear. ErrYearOutOfRange = errors.New("vnsalary: tax year must be 2025 or later") // ErrPaymentOutsideYear indicates that a payment passed to Annual is dated outside the tax year. ErrPaymentOutsideYear = errors.New("vnsalary: payment date is outside the tax year") // ErrTooManyPayments indicates that Annual received more than MaxAnnualPayments payments. ErrTooManyPayments = errors.New("vnsalary: too many payments (maximum 1000 per tax year)") // ErrInvalidDependentMonths indicates that AnnualOptions.DependentMonths is outside [0, MaxDependents*12]. ErrInvalidDependentMonths = errors.New("vnsalary: dependent months must be between 0 and 600") // ErrNetUnreachable indicates that no integer gross amount maps to this exact net salary due to half-up discretization. ErrNetUnreachable = errors.New("vnsalary: net salary is unreachable due to integer rounding") )
Functions ¶
This section is empty.
Types ¶
type AnnualDeductions ¶ added in v0.4.0
type AnnualDeductions struct {
Personal VND `json:"personal"` // 12 months of the personal deduction, whatever the months worked
Dependents VND `json:"dependents"` // DependentMonths × the monthly dependent deduction
Charity VND `json:"charity"`
Healthcare VND `json:"healthcare"` // After the yearly cap
Education VND `json:"education"` // After the yearly cap
Total VND `json:"total"`
}
AnnualDeductions itemizes the deductions applied at finalization.
type AnnualOptions ¶ added in v0.4.0
type AnnualOptions struct {
// DependentMonths is the number of months claimed for all dependents
// together (2 dependents for the whole year = 24). At finalization a
// dependent counts from the month the duty of support arose, even if
// registered later in the year (Decree 253/2026/NĐ-CP Art. 48.2.a).
DependentMonths int
Charity VND // Contributions to licensed charity, humanitarian and study-promotion funds (Decree 253/2026/NĐ-CP Art. 49.1)
Healthcare VND // Medical expenses of the taxpayer and dependents; capped by Policy.HealthcareDeductionCap
Education VND // Tuition of the taxpayer and dependents; capped by Policy.EducationDeductionCap
}
AnnualOptions holds the yearly deductions claimed at finalization. The zero value claims only the personal deduction.
type AnnualResult ¶ added in v0.4.0
type AnnualResult struct {
Year int `json:"year"`
TaxableIncome VND `json:"taxable_income"` // Sum of the payments' taxable income
Insurance VND `json:"insurance"` // Sum of the payments' compulsory insurance
Deductions AnnualDeductions `json:"deductions"`
AssessableIncome VND `json:"assessable_income"` // Taxable income minus insurance and deductions, not below zero
Tax VND `json:"tax"` // Tax due for the year on the yearly schedule
Brackets []BracketLine `json:"brackets"`
Withheld VND `json:"withheld"` // Sum of the payments' withheld tax
Balance VND `json:"balance"` // Tax - Withheld: positive = to pay, negative = refundable
Policy PolicyRef `json:"policy"`
Warnings []Warning `json:"warnings,omitempty"`
}
AnnualResult is the PIT finalization (quyết toán thuế TNCN) of a tax year.
func Annual ¶ added in v0.4.0
func Annual(year int, payments []Payment, opts AnnualOptions) (AnnualResult, error)
Annual computes the PIT finalization of a resident individual's salary income for a tax year: the yearly tax on the sum of all payments, compared with the tax already withheld.
Monthly withholding and the yearly tax differ when income is uneven (a bonus month), when the taxpayer changed employers or did not work the whole year (the personal deduction always counts 12 months at finalization, Decree 253/2026/NĐ-CP Art. 48.1.b), or when dependents were registered late. Pass the payments from every payer in the year, including those withheld at the 10% flat rate.
The yearly schedule is the monthly schedule with every limit multiplied by 12 (Law on PIT 109/2025/QH15 Art. 9.2). Claiming Charity, Healthcare or Education from tax period 2026 means the taxpayer must file the finalization personally instead of authorizing the employer (Decree 253/2026/NĐ-CP Art. 51.3).
Example ¶
Changed employer in 2026: 40M a month from January to June, no income in July, 25M a month from August. Each employer withheld with a monthly personal deduction; at finalization the personal deduction counts all 12 months, so part of the tax is refunded.
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
var payments []vnsalary.Payment
for month := 1; month <= 12; month++ {
gross := vnsalary.VND(40_000_000)
switch {
case month == 7:
continue
case month > 7:
gross = 25_000_000
}
paid := time.Date(2026, time.Month(month), 28, 0, 0, 0, 0, time.UTC)
res, err := vnsalary.GrossToNet(gross, vnsalary.Options{Region: vnsalary.Region1, Date: paid})
if err != nil {
fmt.Println(err)
return
}
payments = append(payments, res.Payment(paid))
}
res, err := vnsalary.Annual(2026, payments, vnsalary.AnnualOptions{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Personal deduction:", res.Deductions.Personal)
fmt.Println("Tax for the year: ", res.Tax)
fmt.Println("Tax withheld: ", res.Withheld)
fmt.Println("Balance: ", res.Balance)
}
Output: Personal deduction: 186000000 Tax for the year: 8067500 Tax withheld: 10898750 Balance: -2831250
type BracketLine ¶
type BracketLine struct {
Bracket int `json:"bracket"` // Tax bracket index (1, 2, ...)
TaxablePart VND `json:"taxable_part"` // Income portion assessed within this bracket
RatePercent int `json:"rate_percent"` // Tax rate percentage (5, 10, 15, ...)
Tax VND `json:"tax"` // Tax amount accrued at this bracket
}
BracketLine details personal income tax paid at a specific progressive bracket.
type Deductions ¶
type Deductions struct {
Personal VND `json:"personal"` // Taxpayer personal deduction (giảm trừ bản thân)
Dependents VND `json:"dependents"` // Qualifying dependents deduction (giảm trừ người phụ thuộc)
Total VND `json:"total"` // Total deduction amount
}
Deductions itemizes personal and family tax relief deductions (giảm trừ gia cảnh).
type EmployerCost ¶
type EmployerCost struct {
Social VND `json:"social"` // Social insurance (BHXH): 17% + the accident rate, 17.5% by default (14% pension/death, 3% sickness/maternity, 0.5% occupational accident unless EmployerOptions.AccidentPerMille)
Health VND `json:"health"` // Health insurance (BHYT) 3%
Unemployment VND `json:"unemployment"` // Unemployment insurance (BHTN) 1%, none for a foreigner
TotalInsurance VND `json:"total_insurance"` // Total employer contribution (21.5% by default; 20.5% for a foreigner; less with a lower accident rate)
UnionFee VND `json:"union_fee,omitempty"` // Trade union fee (2%), when EmployerOptions.UnionFee is set
TotalCost VND `json:"total_cost"` // Total employer cost = Gross + TotalInsurance + UnionFee
}
EmployerCost itemizes statutory employer contributions and total company expenditure.
type EmployerOptions ¶ added in v0.6.0
type EmployerOptions struct {
// AccidentPerMille overrides the occupational accident insurance rate, in
// thousandths (3 = 0.3%, approved for some employers under Decree
// 58/2020/NĐ-CP); nil = the policy rate (0.5%). Allowed: 0 to 10.
AccidentPerMille *int64
// UnionFee adds the trade union fee (2% of the social insurance salary,
// Law on Trade Unions 50/2024/QH15 Art. 29.1.b) to EmployerCost.TotalCost.
// Employers pay it whether or not they have a grassroots union.
UnionFee bool
}
EmployerOptions are settings of the employer that change its cost but not the employee's net.
Example ¶
An employer approved for the 0.3% accident insurance rate, including the 2% trade union fee in its cost. The employee's net is unchanged.
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
rate := int64(3) // 0.3%
res, err := vnsalary.GrossToNet(30_000_000, vnsalary.Options{
Region: vnsalary.Region1,
Date: time.Date(2026, 8, 10, 0, 0, 0, 0, time.UTC),
Employer: vnsalary.EmployerOptions{AccidentPerMille: &rate, UnionFee: true},
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Employer social insurance:", res.Employer.Social)
fmt.Println("Trade union fee: ", res.Employer.UnionFee)
fmt.Println("Total employer cost: ", res.Employer.TotalCost)
}
Output: Employer social insurance: 5190000 Trade union fee: 600000 Total employer cost: 36990000
type ExemptIncome ¶ added in v0.3.0
type ExemptIncome struct {
Overtime VND `json:"overtime"`
Meal VND `json:"meal"`
Other VND `json:"other"`
Total VND `json:"total"`
}
ExemptIncome itemizes the parts of gross excluded from taxable income (thu nhập không tính thuế).
type Extras ¶ added in v0.3.0
type Extras struct {
Overtime VND // Overtime and night-work pay within the legal hour limits; fully tax-exempt from tax period 2026 (pay for hours beyond the limits belongs in gross only)
MealCash VND // Cash meal allowance; tax-exempt up to Policy.MealCashCap
OtherExempt VND // Other allowances exempt under the employer's regulation (phone, uniform, travel...); the caller is responsible for their limits
Bonus VND // Bonus paid this month; taxable, outside the insurance salary
}
Extras are amounts already included in gross (gross is the total paid in the month) that the law treats differently from base salary. The zero value reproduces the calculation of earlier versions.
When InsuranceBase is nil, the insurance salary is gross minus Overtime and Bonus: both vary from month to month, so they are outside the salary "paid regularly and stably" (Law on Social Insurance 41/2024/QH15 Art. 31.1.b). MealCash and OtherExempt stay in the insurance salary; set InsuranceBase if the employment contract excludes them.
type FlatResult ¶ added in v0.5.0
type FlatResult struct {
Amount VND `json:"amount"` // Payment before tax
Threshold VND `json:"threshold"` // Per-payment amount from which withholding is required
Required bool `json:"required"` // Amount >= Threshold
Withheld VND `json:"withheld"` // 10% of Amount when Required, otherwise 0
Net VND `json:"net"` // Amount - Withheld
Policy PolicyRef `json:"policy"`
}
FlatResult is the 10% withholding on one payment to a resident without a labour contract or with one under 3 months.
func FlatWithholding ¶ added in v0.5.0
func FlatWithholding(amount VND, date time.Time) (FlatResult, error)
FlatWithholding computes the PIT a payer withholds from one payment of salary, wages, fees or other income to a resident individual without a labour contract or with one under 3 months, including payments after the contract ended: 10% of the whole payment when it reaches the threshold of the payment date (Decree 253/2026/NĐ-CP Art. 50.2 from 2026-07-01: 5,000,000; Circular 111/2013/TT-BTC Art. 25.1.i before: 2,000,000), without family deductions.
Below the threshold withholding is not required; from 2026-07-01 the payer may still withhold 10% when the individual asks (Art. 50.2). An individual whose only income is of this kind and who expects no tax after deductions may give the payer a commitment to defer withholding; that case is not modelled. Compulsory insurance, which depends on the contract, is not computed. The tax withheld is a prepayment: pass FlatResult.Payment to Annual to settle the year.
Example ¶
A one-off payment of 8,000,000 in September 2026 to someone without a labour contract: 10% is withheld because the payment reaches the 5,000,000 threshold. With no other income in the year, finalization refunds it.
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
paid := time.Date(2026, 9, 10, 0, 0, 0, 0, time.UTC)
res, err := vnsalary.FlatWithholding(8_000_000, paid)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Withheld:", res.Withheld, "Net:", res.Net)
annual, err := vnsalary.Annual(2026, []vnsalary.Payment{res.Payment(paid)}, vnsalary.AnnualOptions{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Balance at finalization:", annual.Balance)
}
Output: Withheld: 800000 Net: 7200000 Balance at finalization: -800000
type Insurance ¶
type Insurance struct {
Social VND `json:"social"` // Social insurance (BHXH) 8%
Health VND `json:"health"` // Health insurance (BHYT) 1.5%
Unemployment VND `json:"unemployment"` // Unemployment insurance (BHTN) 1%
Total VND `json:"total"` // Total employee insurance contribution
}
Insurance itemizes mandatory employee contributions.
type Options ¶
type Options struct {
InsuranceBase *VND // nil = based on gross salary; non-nil = contractual insurance salary (lương đóng bảo hiểm)
Dependents int // Qualifying dependents count (0..50) (số người phụ thuộc)
Region Region // Statutory minimum wage region (1..4) (vùng lương tối thiểu)
Date time.Time // Salary payment date; selects the policy in force (cash basis, Law on PIT 109/2025/QH15 Art. 8.3)
Extras Extras // Parts of gross treated differently from base salary; zero value = none
// Foreigner marks a foreign national: no unemployment insurance (BHTN) for
// the employee or the employer, because that insurance covers Vietnamese
// citizens only (Law on Employment 74/2025/QH15 Art. 2.1; Law 38/2013/QH13
// Art. 3.1 before 2026). Social and health insurance stay: a foreign
// employee on a contract of 12 months or more pays social insurance (Law on
// Social Insurance 41/2024/QH15 Art. 2.2). Its exemptions (intra-company
// transfer, retirement age at signing, treaties) are not modelled; set
// InsuranceBase to 0 in those cases only if health insurance is also not due.
Foreigner bool
// NonResident marks a non-resident for PIT: 20% of taxable income from
// work in Vietnam, without family or insurance deductions (Decree
// 253/2026/NĐ-CP Art. 64; Law on PIT 2007 Art. 26 before 2026). A resident
// is present in Vietnam 183 days or more in the calendar year or in 12
// consecutive months, or has a regular place to stay there (Decree
// 253/2026/NĐ-CP Art. 4); the caller decides. Insurance is unchanged.
NonResident bool
// Employer holds employer-side settings that change EmployerCost only;
// the zero value keeps earlier results.
Employer EmployerOptions
}
Options specifies optional calculation parameters.
type Payment ¶ added in v0.4.0
type Payment struct {
Date time.Time // Payment date; the payment belongs to the tax year of this date (cash basis)
TaxableIncome VND // Taxable income (thu nhập chịu thuế): gross minus exempt income
Insurance VND // Compulsory insurance paid by the employee (BHXH, BHYT, BHTN)
TaxWithheld VND // PIT withheld by the payer, at the progressive rates or the 10% flat rate
}
Payment is one salary payment of a tax year, as calculated by GrossToNet or as printed on a payslip or a withholding certificate.
type Policy ¶
type Policy struct {
EffectiveFrom time.Time
Ref PolicyRef
// Base salary (lương cơ sở / mức tham chiếu) and statutory ceiling for social & health insurance (20 x base salary)
BaseSalary VND
MaxSocialHealthSalary VND
// Regional minimum wages for Regions I, II, III, IV (index 1..4) used to cap unemployment insurance (20 x regional min)
RegionalMinSalaries [5]VND
// Monthly personal family deductions (giảm trừ gia cảnh)
PersonalDeduction VND // Personal taxpayer deduction (giảm trừ bản thân)
DependentDeduction VND // Deduction per qualifying dependent (giảm trừ người phụ thuộc)
// Progressive PIT schedule (biểu thuế lũy tiến từng phần)
Brackets []TaxBracket
// Salary PIT schedule of non-residents: one bracket, a flat rate on
// taxable income without deductions (see Options.NonResident).
NonResidentBrackets []TaxBracket
// Income excluded from PIT (thu nhập không tính thuế), see Extras.
MealCashCap VND // Cash meal allowance excluded from taxable income up to this amount per month
MealCashCapVerified bool // False when the cap for this period could not be confirmed from a legal text
OvertimeFullyExempt bool // Overtime and night-work pay within the legal limits is fully excluded (from tax period 2026)
// Yearly caps on medical and tuition expenses of the taxpayer and dependents
// deductible at annual finalization (from tax period 2026); zero = not deductible.
HealthcareDeductionCap VND
EducationDeductionCap VND
// Payments of this amount or more to a resident without a labour contract
// or with one under 3 months are withheld at 10% (see FlatWithholding).
FlatWithholdingThreshold VND
// Employer occupational accident and disease insurance (TNLĐ-BNN), in
// thousandths of the social insurance salary (5 = 0.5%); part of the
// employer's social insurance. EmployerOptions.AccidentPerMille overrides it.
AccidentPerMille int64
// Trade union fee (kinh phí công đoàn) paid by the employer, in
// thousandths of the social insurance salary (20 = 2%); added to
// EmployerCost only when EmployerOptions.UnionFee is set.
UnionFeePerMille int64
}
Policy contains all statutory parameters effective for a given period.
func PolicyAt ¶
PolicyAt returns the statutory policy parameter set effective at the given payment date. The returned value is an independent deep copy: modifying it will not affect any subsequent calculations. Returns ErrDateOutOfRange if date is prior to July 01, 2024.
func (Policy) MaxUnemploymentSalary ¶
MaxUnemploymentSalary returns the statutory cap for unemployment insurance (20 x regional minimum wage).
func (Policy) RegionalMinSalary ¶
RegionalMinSalary returns the statutory minimum wage for the specified region.
type PolicyRef ¶
type PolicyRef struct {
EffectiveFrom string `json:"effective_from"`
Description string `json:"description"` // Description in English
DescriptionVI string `json:"description_vi"` // Description in Vietnamese (mô tả tiếng Việt)
Sources []string `json:"sources"` // Official statutory legal documents in Vietnamese
}
PolicyRef provides references to statutory decrees and enacted legislation.
type Region ¶
type Region int
Region represents the statutory minimum wage region (Vùng lương tối thiểu: 1, 2, 3, 4).
type Result ¶
type Result struct {
Gross VND `json:"gross"`
Net VND `json:"net"`
Insurance Insurance `json:"insurance"`
TaxableIncome VND `json:"taxable_income"` // Total taxable income (thu nhập chịu thuế)
AssessableIncome VND `json:"assessable_income"` // Income assessed for PIT after insurance and deductions (thu nhập tính thuế)
Deductions Deductions `json:"deductions"`
Tax VND `json:"tax"`
Brackets []BracketLine `json:"brackets"`
Employer EmployerCost `json:"employer"`
Policy PolicyRef `json:"policy"`
Exempt *ExemptIncome `json:"exempt,omitempty"` // Exempt parts of gross; set only when Options.Extras is used
BonusTax VND `json:"bonus_tax,omitempty"` // Tax caused by Extras.Bonus: tax with the bonus minus tax without it
Warnings []Warning `json:"warnings,omitempty"` // Inputs computed as given but worth checking; never change amounts
}
Result holds the comprehensive breakdown of a salary calculation.
Example (Warnings) ¶
Warnings flag inputs that are computed as given but worth checking; they never change the amounts.
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
opts := vnsalary.Options{
Region: vnsalary.Region1,
Date: time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC),
}
res, err := vnsalary.GrossToNet(3_000_000, opts)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
for _, w := range res.Warnings {
fmt.Println(w.Code)
}
fmt.Printf("Net: %d VND\n", res.Net)
}
Output: insurance_salary_below_regional_minimum Net: 2685000 VND
func GrossToNet ¶
GrossToNet converts Gross salary to Net take-home pay and itemizes all deductions.
Example ¶
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
opts := vnsalary.Options{
Dependents: 1,
Region: vnsalary.Region1,
Date: time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC),
}
res, err := vnsalary.GrossToNet(35_000_000, opts)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("Gross: %d VND\n", res.Gross)
fmt.Printf("BHXH (8%%): %d VND\n", res.Insurance.Social)
fmt.Printf("BHYT (1.5%%): %d VND\n", res.Insurance.Health)
fmt.Printf("BHTN (1%%): %d VND\n", res.Insurance.Unemployment)
fmt.Printf("Total Insurance: %d VND\n", res.Insurance.Total)
fmt.Printf("Tax: %d VND\n", res.Tax)
fmt.Printf("Net: %d VND\n", res.Net)
}
Output: Gross: 35000000 VND BHXH (8%): 2800000 VND BHYT (1.5%): 525000 VND BHTN (1%): 350000 VND Total Insurance: 3675000 VND Tax: 481250 VND Net: 30843750 VND
Example (Allowances) ¶
Overtime pay and allowances inside gross are excluded from taxable income under the rules in force on the payment date.
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
opts := vnsalary.Options{
Dependents: 1,
Region: vnsalary.Region1,
Date: time.Date(2026, 8, 10, 0, 0, 0, 0, time.UTC),
Extras: vnsalary.Extras{
Overtime: 5_000_000, // fully exempt from tax period 2026
MealCash: 1_500_000, // exempt up to 1,200,000 from 2026-07-01
OtherExempt: 800_000, // phone, uniform... under the employer's regulation
},
}
res, err := vnsalary.GrossToNet(55_000_000, opts)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("Exempt: overtime %d + meal %d + other %d = %d VND\n",
res.Exempt.Overtime, res.Exempt.Meal, res.Exempt.Other, res.Exempt.Total)
fmt.Printf("Taxable income: %d VND\n", res.TaxableIncome)
fmt.Printf("Net: %d VND\n", res.Net)
}
Output: Exempt: overtime 5000000 + meal 1200000 + other 800000 = 7000000 VND Taxable income: 48000000 VND Net: 48145000 VND
Example (Bonus) ¶
A bonus is part of the gross paid in the month. It is taxed in that month but is not part of the insurance salary; BonusTax is the tax the bonus adds.
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
opts := vnsalary.Options{
Region: vnsalary.Region1,
Date: time.Date(2026, 8, 10, 0, 0, 0, 0, time.UTC),
Extras: vnsalary.Extras{Bonus: 20_000_000}, // included in the 50M gross
}
res, err := vnsalary.GrossToNet(50_000_000, opts)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("Insurance: %d VND (on 30M salary, not the bonus)\n", res.Insurance.Total)
fmt.Printf("Tax: %d VND, of which caused by the bonus: %d VND\n", res.Tax, res.BonusTax)
fmt.Printf("Net: %d VND\n", res.Net)
}
Output: Insurance: 3150000 VND (on 30M salary, not the bonus) Tax: 2770000 VND, of which caused by the bonus: 2135000 VND Net: 44080000 VND
func NetToGross ¶
NetToGross solves the minimum integer Gross salary that yields the exact Net salary.
Step 1: Analytical piecewise linear interpolation on the interval [bps[i], bps[i+1]] containing net to derive gApprox. Step 2: Ascending scan within +/- searchRadius (50 VND) around gApprox; the first match is then extended downward to the smallest gross with the same net. Step 3 (Fallback): Monotonic binary search on [segmentLow, segmentHigh] to guarantee complete coverage.
Example ¶
package main
import (
"fmt"
"time"
"github.com/HuynhNhatKhanh/vnsalary"
)
func main() {
opts := vnsalary.Options{
Dependents: 1,
Region: vnsalary.Region1,
Date: time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC),
}
res, err := vnsalary.NetToGross(30_843_750, opts)
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("Net: %d VND -> Gross: %d VND\n", res.Net, res.Gross)
}
Output: Net: 30843750 VND -> Gross: 35000000 VND
type TaxBracket ¶
type TaxBracket struct {
Limit VND // Upper limit of assessable income in this bracket (e.g. 5,000,000 VND; math.MaxInt64 for the top bracket)
RateNumerator int64 // Tax rate numerator (e.g. 5)
RateDenom int64 // Tax rate denominator (e.g. 100)
}
TaxBracket represents a single bracket in the progressive personal income tax schedule.
type VND ¶
type VND int64
VND is an amount in Vietnamese Dong as a whole number. Salaries are non-negative; differences between two results (for example in a policy comparison) may be negative.
func RoundToThousand ¶
RoundToThousand rounds a non-negative VND amount to the nearest thousand (1,000 VND) half-up. Convenient for payroll systems with cash-disbursement rounding policies.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
vnsalary
command
Command vnsalary converts Vietnamese gross and net salaries from the command line, using the github.com/HuynhNhatKhanh/vnsalary library.
|
Command vnsalary converts Vietnamese gross and net salaries from the command line, using the github.com/HuynhNhatKhanh/vnsalary library. |