Skip to main content

world_tax/
types.rs

1//! Tax calculation types and structures.
2//!
3//! This module contains the core types used for tax calculations across different
4//! tax systems including VAT, GST, HST, and other regional tax schemes. It provides
5//! the fundamental data structures and enums needed to represent tax scenarios,
6//! trade agreements, and calculation rules.
7
8use crate::errors::InputValidationError;
9use log::debug;
10use serde::{Deserialize, Deserializer, Serialize};
11use std::collections::HashMap;
12use strum_macros::Display;
13use typeshare::typeshare;
14
15/// Represents different types of tax systems used globally.
16#[typeshare]
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum TaxSystemType {
20    /// Value Added Tax - Common in EU and many other countries
21    Vat,
22    /// Goods and Services Tax
23    Gst,
24    /// Provincial Sales Tax (Canadian)
25    Pst,
26    /// Harmonized Sales Tax (Canadian)
27    Hst,
28    /// Quebec Sales Tax
29    Qst,
30    /// No tax system applicable
31    None,
32}
33
34/// Defines the type of transaction between parties.
35#[typeshare]
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
37#[serde(rename_all = "lowercase")]
38pub enum TransactionType {
39    /// Business to Business transaction
40    B2B,
41    /// Business to Consumer transaction
42    B2C,
43}
44
45/// Specifies how tax should be calculated for a given transaction.
46#[typeshare]
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum TaxCalculationType {
50    /// Use origin tax rate; below threshold
51    Origin,
52    /// Use destination tax rate; above threshold
53    Destination,
54    /// Buyer pays tax in their country
55    ReverseCharge,
56    /// Tax applies but at zero rate
57    ZeroRated,
58    /// No tax applies
59    Exempt,
60    /// Tax status unknown
61    None,
62    /// Calculation depends on threshold
63    ThresholdBased,
64}
65
66/// Represents different types of taxes that can be applied.
67#[typeshare]
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
69#[serde(tag = "type", content = "content")]
70#[serde(rename_all = "snake_case")]
71pub enum TaxType {
72    /// Value Added Tax with specific rate
73    VAT(VatRate),
74    /// Goods and Services Tax
75    GST,
76    /// Harmonized Sales Tax
77    HST,
78    /// Provincial Sales Tax
79    PST,
80    /// Quebec Sales Tax
81    QST,
82    /// US State Sales Tax
83    StateSalesTax,
84}
85
86/// Different rates that can be applied for Value Added Tax.
87#[typeshare]
88#[derive(Debug, Clone, Display, Serialize, Deserialize, PartialEq)]
89#[serde(rename_all = "snake_case")]
90pub enum VatRate {
91    /// Standard VAT rate
92    Standard,
93    /// Reduced VAT rate
94    Reduced,
95    /// Alternative reduced VAT rate
96    ReducedAlt,
97    /// Super-reduced VAT rate
98    SuperReduced,
99    /// Zero-rated goods/services
100    Zero,
101    /// VAT exempt
102    Exempt,
103    /// Reverse charge applies
104    ReverseCharge,
105}
106
107/// Defines the type of trade agreement between regions.
108#[typeshare]
109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110#[serde(rename_all = "snake_case")]
111pub enum TradeAgreementType {
112    /// Agreement between multiple countries in a customs union (e.g., EU)
113    CustomsUnion,
114    /// Agreement between states within a federal system
115    FederalState,
116}
117
118/// Override options for trade agreement application.
119#[typeshare]
120#[derive(Debug, Clone, Serialize, Deserialize)]
121#[serde(tag = "type", content = "content")]
122pub enum TradeAgreementOverride {
123    /// Explicitly use a specific agreement (e.g., "EU", "USMCA")
124    UseAgreement(String),
125    /// Force no trade agreement to be applied
126    NoAgreement,
127}
128
129/// Specifies which types of goods/services an agreement applies to.
130#[typeshare]
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct AppliesTo {
133    /// Whether the agreement applies to physical goods
134    pub physical_goods: bool,
135    /// Whether the agreement applies to digital goods
136    pub digital_goods: bool,
137    /// Whether the agreement applies to services
138    pub services: bool,
139}
140
141/// Represents a trade agreement between regions or states.
142#[typeshare]
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct TradeAgreement {
145    /// Name of the trade agreement
146    pub name: String,
147    /// Type of the trade agreement
148    pub r#type: TradeAgreementType,
149    /// List of member regions/states
150    pub members: Vec<String>,
151    /// Whether agreement applies by default
152    pub default_applicable: bool,
153    /// Types of goods/services covered
154    pub applies_to: AppliesTo,
155    /// Tax rules under this agreement
156    pub tax_rules: TaxRules,
157}
158
159impl TradeAgreement {
160    /// Returns true if this is a federal-level agreement
161    pub fn is_federal(&self) -> bool {
162        self.r#type == TradeAgreementType::FederalState
163    }
164
165    /// Returns true if this is an international agreement
166    pub fn is_international(&self) -> bool {
167        self.r#type == TradeAgreementType::CustomsUnion
168    }
169}
170
171/// Configuration for tax calculation rules based on various thresholds and conditions.
172#[typeshare]
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct TaxRuleConfig {
175    /// Default tax calculation type
176    pub r#type: TaxCalculationType,
177    /// Tax calculation type for amounts below threshold
178    pub below_threshold: Option<TaxCalculationType>,
179    /// Tax calculation type for amounts above threshold
180    pub above_threshold: Option<TaxCalculationType>,
181    /// Monetary threshold for standard goods
182    pub threshold: Option<u32>,
183    /// Tax calculation type for digital products below threshold
184    pub below_threshold_digital_products: Option<TaxCalculationType>,
185    /// Tax calculation type for digital products above threshold
186    pub above_threshold_digital_products: Option<TaxCalculationType>,
187    /// Monetary threshold for digital products
188    pub threshold_digital_products: Option<u32>,
189    /// Whether a resale certificate is required for special treatment
190    pub requires_resale_certificate: Option<bool>,
191}
192
193impl TaxRuleConfig {
194    /// Determines the tax calculation type based on the amount and threshold
195    ///
196    /// # Arguments
197    /// * `amount` - The transaction amount
198    /// * `ignore_threshold` - Whether to ignore threshold-based calculations
199    pub fn by_threshold(&self, amount: u32, ignore_threshold: bool) -> &TaxCalculationType {
200        let has_threshold = self.below_threshold.is_some()
201            && self.above_threshold.is_some()
202            && self.threshold.is_some();
203        if has_threshold {
204            let rule_threshold: u32 = self.threshold.unwrap();
205            if amount < rule_threshold && !ignore_threshold {
206                return self.below_threshold.as_ref().unwrap();
207            } else {
208                return self.above_threshold.as_ref().unwrap();
209            }
210        }
211        &self.r#type
212    }
213
214    /// Determines the tax calculation type for digital products based on amount and threshold
215    pub fn by_digital_product_threshold(
216        &self,
217        amount: u32,
218        ignore_threshold: bool,
219    ) -> &TaxCalculationType {
220        let has_threshold = self.below_threshold_digital_products.is_some()
221            && self.above_threshold_digital_products.is_some()
222            && self.threshold_digital_products.is_some();
223        if has_threshold {
224            let rule_threshold: u32 = self.threshold_digital_products.unwrap();
225            if amount < rule_threshold && !ignore_threshold {
226                return self.below_threshold_digital_products.as_ref().unwrap();
227            } else {
228                return self.above_threshold_digital_products.as_ref().unwrap();
229            }
230        }
231        &self.r#type
232    }
233
234    /// Determines the appropriate tax calculation type based on product type and amount
235    pub fn by_threshold_or_digital_product_threshold(
236        &self,
237        amount: u32,
238        is_digital_product_or_service: bool,
239        ignore_threshold: bool,
240    ) -> &TaxCalculationType {
241        if is_digital_product_or_service {
242            return self.by_digital_product_threshold(amount, ignore_threshold);
243        }
244        self.by_threshold(amount, ignore_threshold)
245    }
246
247    /// Determines if the transaction qualifies for reseller treatment
248    pub fn is_reseller(&self, has_resale_certificate: bool) -> bool {
249        if self.requires_resale_certificate.is_some() {
250            return self.requires_resale_certificate.unwrap() && has_resale_certificate;
251        }
252        false
253    }
254}
255
256/// Collection of tax rules for different transaction scenarios
257#[typeshare]
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct TaxRules {
260    /// Rules for internal B2B transactions
261    pub internal_b2b: Option<TaxRuleConfig>,
262    /// Rules for internal B2C transactions
263    pub internal_b2c: Option<TaxRuleConfig>,
264    /// Rules for external exports
265    pub external_export: TaxRuleConfig,
266}
267
268/// Product-specific tax rules configuration
269#[typeshare]
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct ProductRules {
272    /// Default tax rule to apply
273    pub default: String,
274    /// Tax rules for specific products
275    pub specific_products: HashMap<String, String>,
276}
277
278/// Represents tax information for a state/province
279#[typeshare]
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct State {
282    /// Standard tax rate for the state
283    pub standard_rate: f64,
284    /// Type of tax system used in the state
285    #[serde(rename = "type")]
286    pub tax_type: TaxSystemType,
287}
288
289/// Custom deserializer for handling rate values that might be boolean or numeric
290fn deserialize_rate<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
291where
292    D: Deserializer<'de>,
293{
294    #[derive(Deserialize)]
295    #[serde(untagged)]
296    enum RateValue {
297        Number(f64),
298        Boolean(bool),
299    }
300
301    match Option::<RateValue>::deserialize(deserializer)? {
302        Some(RateValue::Number(rate)) => Ok(Some(rate)),
303        Some(RateValue::Boolean(false)) => Ok(None),
304        Some(RateValue::Boolean(true)) => Ok(None),
305        None => Ok(None),
306    }
307}
308
309/// Represents tax information for a country
310#[typeshare]
311#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct Country {
313    /// Type of tax system used in the country
314    #[serde(rename = "type")]
315    pub tax_type: TaxSystemType,
316    /// Currency code for the country
317    pub currency: String,
318    /// Standard tax rate
319    pub standard_rate: f64,
320    /// Reduced tax rate if applicable
321    #[serde(default, deserialize_with = "deserialize_rate")]
322    pub reduced_rate: Option<f64>,
323    /// Alternative reduced tax rate if applicable
324    #[serde(default, deserialize_with = "deserialize_rate")]
325    pub reduced_rate_alt: Option<f64>,
326    /// Super-reduced tax rate if applicable
327    #[serde(default, deserialize_with = "deserialize_rate")]
328    pub super_reduced_rate: Option<f64>,
329    /// Parking rate if applicable
330    #[serde(default, deserialize_with = "deserialize_rate")]
331    pub parking_rate: Option<f64>,
332    /// Full name of the VAT system
333    pub vat_name: Option<String>,
334    /// Abbreviation of the VAT system name
335    pub vat_abbr: Option<String>,
336    /// Tax information for states/provinces if applicable
337    pub states: Option<HashMap<String, State>>,
338}
339
340/// Represents a geographical region for tax purposes
341///
342/// # Examples
343///
344/// ```
345/// # use world_tax::types::Region;
346/// // Create a region for France (no sub-region)
347/// let france = Region::new("FR".to_string(), None).unwrap();
348///
349/// // Create a region for California, USA
350/// let california = Region::new("US".to_string(), Some("US-CA".to_string())).unwrap();
351/// ```
352#[typeshare]
353#[derive(Debug, Clone)]
354pub struct Region {
355    /// ISO 3166-1 alpha-2 country code
356    pub country: String,
357    /// Optional ISO 3166-2 region code
358    pub region: Option<String>,
359}
360
361impl Region {
362    /// Creates a new Region with validation
363    pub fn new(country: String, region: Option<String>) -> Result<Self, InputValidationError> {
364        Self::validate(&country, &region)?;
365        Ok(Self { country, region })
366    }
367
368    /// Validates country and region codes against ISO standards
369    fn validate(country: &str, region: &Option<String>) -> Result<(), InputValidationError> {
370        let country_info = rust_iso3166::from_alpha2(country)
371            .ok_or_else(|| InputValidationError::InvalidCountryCode(country.to_string()))?;
372
373        debug!("Found country: {}", country_info.name);
374
375        if let Some(region_code) = region {
376            let _ = country_info
377                .subdivisions()
378                .ok_or_else(|| InputValidationError::UnexpectedRegionCode(region_code.clone()))?;
379
380            let region_info = rust_iso3166::iso3166_2::from_code(region_code)
381                .ok_or_else(|| InputValidationError::InvalidRegionCode(region_code.clone()))?;
382
383            debug!("Found region: {}", region_info.name);
384        }
385
386        Ok(())
387    }
388}
389
390/// Represents a complete tax calculation scenario
391#[typeshare]
392#[derive(Debug, Clone)]
393pub struct TaxScenario {
394    /// Region where the seller is located
395    pub source_region: Region,
396    /// Region where the buyer is located
397    pub destination_region: Region,
398    /// Type of transaction (B2B or B2C)
399    pub transaction_type: TransactionType,
400    /// How the tax should be calculated
401    // pub calculation_type: TaxCalculationType,
402    /// Optional override for trade agreement application
403    pub trade_agreement_override: Option<TradeAgreementOverride>,
404    /// Whether the product/service is digital
405    pub is_digital_product_or_service: bool,
406    /// Whether the buyer has a resale certificate (relevant for B2B in US)
407    pub has_resale_certificate: bool,
408    /// Whether to ignore thresholds in calculations
409    pub ignore_threshold: bool,
410    /// Specific VAT rate to apply if applicable
411    pub vat_rate: Option<VatRate>,
412}
413
414/// Represents a specific tax rate and its characteristics.
415#[typeshare]
416#[derive(Debug, Serialize, Deserialize)]
417pub struct TaxRate {
418    /// The numerical tax rate as a decimal (e.g., 0.20 for 20%)
419    pub rate: f64,
420    /// The type of tax (VAT, GST, etc.)
421    pub tax_type: TaxType,
422    /// Whether this tax compounds on top of other taxes
423    pub compound: bool,
424}