63 lines
2.5 KiB
Go
63 lines
2.5 KiB
Go
// Package tax provides the tax computation seam: a Provider interface
|
||
// (mirroring PaymentProvider and ShippingProvider patterns), the shared Compute
|
||
// formula, and a stateless Static provider for dev/test.
|
||
//
|
||
// SCALE: all tax rates are integer **basis points of a percent** — rate × 100,
|
||
// so 25% = 2500, 12.5% = 1250, 8.1% = 810. This is the single platform-wide
|
||
// scale (Price.VatRates, CartItem.Tax, OrderLine.tax_rate, the payment-provider
|
||
// wire). Integer basis points support fractional VAT rates (Ireland 13.5%,
|
||
// France 5.5%, Finland 25.5%) exactly without ever using a float.
|
||
package tax
|
||
|
||
// Provider computes taxes for orders and line items.
|
||
type Provider interface {
|
||
// Name returns the provider name for identification (logging, metrics).
|
||
Name() string
|
||
|
||
// Compute computes the VAT portion from a gross (inc-VAT) total and tax
|
||
// rate. Both total and return value are in minor currency units (öre).
|
||
// rateBp is basis points of a percent (2500 = 25%, 1250 = 12.5%).
|
||
//
|
||
// Ex-VAT-first integer math: exVat = total*10000/(10000+rateBp); tax = total - exVat.
|
||
// When rateBp <= 0 the result is 0 (untaxed / zero-rated items).
|
||
Compute(total int64, rateBp int) int64
|
||
|
||
// DefaultTaxRate returns the default VAT rate for a country in basis points
|
||
// (2500 = 25%). Used for deliveries and items where no explicit rate was
|
||
// recorded at add-to-cart time. Returns 0 if the country is unknown/untaxed.
|
||
DefaultTaxRate(country string) int
|
||
}
|
||
|
||
// Compute returns the VAT portion of an inc-VAT total using integer (cent)
|
||
// arithmetic only, ex-VAT-first so exVat + tax == total exactly and the result
|
||
// matches the payment-provider convention (no per-line rounding drift):
|
||
//
|
||
// exVat = total * 10000 / (10000 + rateBp)
|
||
// tax = total - exVat
|
||
//
|
||
// rateBp is basis points of a percent (2500 = 25%, 1250 = 12.5%) — the single
|
||
// platform-wide rate scale. When rateBp <= 0 the result is 0 (untaxed).
|
||
func Compute(total int64, rateBp int) int64 {
|
||
if rateBp <= 0 {
|
||
return 0
|
||
}
|
||
exVat := total * 10000 / int64(10000+rateBp)
|
||
return total - exVat
|
||
}
|
||
|
||
// Static is a stateless, always-available Provider that simply runs the
|
||
// formula without any country-specific rates. Use for local dev, tests,
|
||
// and as a no-dependency default.
|
||
type Static struct{}
|
||
|
||
var _ Provider = (*Static)(nil)
|
||
|
||
// NewStatic returns a new Static provider.
|
||
func NewStatic() *Static { return &Static{} }
|
||
|
||
func (p *Static) Name() string { return "static" }
|
||
func (p *Static) Compute(total int64, taxRate int) int64 {
|
||
return Compute(total, taxRate)
|
||
}
|
||
func (p *Static) DefaultTaxRate(_ string) int { return 0 }
|