open-standards/openpay/crates/op-tax/src/native.rs
Transaction Science 3fe109d58f ingest batch 4: 9 crates · table-stakes completers across all three workspaces
OpenPay (3 crates, ~13k LOC, 175 tests)
  op-connect           sub-merchant onboarding + KYB + split-payments + payouts + 1099-K
  op-3ds2              3DS2.3 SCA processor (AReq/ARes/CReq/CRes/RReq/RRes + exemptions)
  op-tax               US/EU/UK/AU/CA/IN tax with Avalara/Stripe/CCH/Vertex/TaxJar adapters

Smart Byte (3 crates, ~7k LOC, 57 tests)
  smart-byte-libp2p    libp2p transport (kad/gossipsub/mdns/autonat/relay/dcutr/identify/ping/req_resp)
  smart-byte-crdt      LWW/GCounter/PnCounter/OrSet/LwwMap/RgaList/2P-Set + HLC + vector clock + Automerge/Yjs interop
  smart-byte-keri-witness  KERI key-rotation cycle + witness layer + watchers + recovery + delegation

EOC (3 crates, ~8k LOC, 108 tests)
  eoc-tools            function-calling across Anthropic/OpenAI/Google/Mistral/Cohere + parallel exec + sandbox
  eoc-multimodal       vision (CLIP/SigLIP/ColPali) + audio (Whisper/MMS) + video sampling + modality router
  eoc-spec-decode      vanilla + SpS-with-temp + lookahead speculative decoding + EAGLE/Medusa stubs

All three workspaces compile cleanly. Forbidden-unsafe, no unwrap outside tests.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-24 04:00:04 -04:00

547 lines
20 KiB
Rust

//! [`NativeCalculator`] — the bundled in-process tax engine.
//!
//! Uses a [`RateTable`] and does its own compounding. No external
//! dependency, no network call. Operators on small / single-
//! jurisdiction deployments can run the whole tax surface from this
//! one struct; everyone else swaps it for one of the commercial
//! adapters.
//!
//! ## Compounding rules
//!
//! - **VAT / GST** are *replace-style*. We pick exactly one layer —
//! the most specific `Vat` or `Gst` entry in the ship-to jurisdiction
//! chain — and apply it once. EU member states levy VAT at the
//! country level; subnational layers (e.g. German *Länder*, French
//! *régions*) do not have independent VAT authority.
//! - **Sales / Use** are *additive*. We walk the ship-to chain from
//! broadest to narrowest and sum every rate we find. US state +
//! county + city + special district stacks this way.
//! - **Excise** is additive on top of either of the above. Most
//! alcohol / tobacco / fuel taxes appear here.
//! - **Import duty** is additive at country level when
//! `ship_from.country != ship_to.country`.
//!
//! ## Inclusive vs exclusive math
//!
//! For an exclusive base (US sales tax): `tax = amount * rate`.
//!
//! For an inclusive base (EU VAT consumer price): the line `amount`
//! already contains the tax. We back it out:
//! `net = amount / (1 + rate)`
//! `tax = amount - net`
//!
//! Either way the per-jurisdiction `taxable_amount` reported on the
//! breakdown is the *net* amount the rate was applied to, so
//! downstream reconciliation always sees consistent numbers.
use async_trait::async_trait;
use chrono::Utc;
use op_core::Money;
use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;
use std::collections::BTreeMap;
use crate::calculator::{
CustomerType, JurisdictionTax, LineTaxBreakdown, TaxCalculator, TaxContext, TaxResult,
TaxableLine,
};
use crate::error::{Error, Result};
use crate::exemption::{self, ExemptionCertificate};
use crate::jurisdiction::Jurisdiction;
use crate::rate_table::{RateKind, RateTable, TaxBase, TaxRate};
/// In-process tax calculator backed by a [`RateTable`].
pub struct NativeCalculator {
/// The rate data. Cloned on construction; the calculator owns
/// its table so swapping in a new snapshot is a new calculator.
pub table: RateTable,
}
impl NativeCalculator {
/// Construct from a rate table.
#[must_use]
pub const fn new(table: RateTable) -> Self {
Self { table }
}
/// Construct using the bundled starter table. Convenience for
/// tests and small deployments.
#[must_use]
pub fn bundled() -> Self {
Self::new(RateTable::bundled())
}
/// Decide whether the buyer qualifies for EU reverse-charge on
/// this line: cross-border B2B inside the EU.
fn is_reverse_charge(line: &TaxableLine, ctx: &TaxContext) -> bool {
let CustomerType::Business { tax_id } = &ctx.customer_type else {
return false;
};
if tax_id.is_empty() {
return false;
}
let to_eu = line.ship_to.is_eu_member();
let Some(from) = &line.ship_from else {
return false;
};
let from_eu = from.is_eu_member();
// Cross-border (different countries) AND both in EU.
to_eu && from_eu && from.country != line.ship_to.country
}
/// Find the first exemption certificate that applies to this line.
fn matching_exemption<'c>(
line: &TaxableLine,
ctx: &'c TaxContext,
) -> Option<&'c ExemptionCertificate> {
ctx.exemption_certs
.iter()
.find(|c| exemption::applies(c, line, ctx))
}
/// Apply any per-category caps / floors that aren't expressible
/// as a plain rate. Currently:
///
/// - NY clothing exemption: only items under $110 qualify. Above
/// the cap, fall back to the NY-state TangibleGoods rate.
///
/// Returns a list of `(jurisdiction, TaxRate)` overrides to use in
/// place of the natural lookup at that jurisdiction. An empty
/// vector means "no overrides — use the table as-is."
fn category_overrides(
&self,
line: &TaxableLine,
) -> Vec<(Jurisdiction, TaxRate)> {
let mut overrides = Vec::new();
if line.category == crate::category::ProductTaxCategory::Clothing {
let ny = Jurisdiction::region("US", "NY");
// $110 cap = 11_000 minor units USD.
if line.ship_to.ancestors().contains(&ny)
&& line.amount.minor_units >= 11_000
&& let Some(rate) = self
.table
.lookup(&ny, &crate::category::ProductTaxCategory::TangibleGoods)
{
overrides.push((ny, rate.clone()));
}
}
overrides
}
/// Compute the tax breakdown for one line.
fn calc_line(&self, line: &TaxableLine, ctx: &TaxContext) -> Result<LineTaxBreakdown> {
// 1. Nexus filter. If the seller has not declared nexus
// *anywhere*, we skip the filter entirely (operator opt-
// out for development / freight-from-elsewhere flows).
// Otherwise any line shipping to a non-nexused jurisdiction
// is zero-rated.
if !ctx.nexus_jurisdictions.is_empty()
&& !ctx
.nexus_jurisdictions
.iter()
.any(|n| line.ship_to.ancestors().contains(n))
{
return Ok(zero_line(
&line.line_id,
line.amount,
Some("no nexus in destination jurisdiction"),
));
}
// 2. Reverse charge — EU B2B cross-border zero-rates at PoS.
if Self::is_reverse_charge(line, ctx) {
return Ok(zero_line(
&line.line_id,
line.amount,
Some("EU B2B reverse charge"),
));
}
// 3. Exemption certificate.
if let Some(cert) = Self::matching_exemption(line, ctx) {
return Ok(zero_line(
&line.line_id,
line.amount,
Some(&format!("exemption certificate {}", cert.id)),
));
}
// 4. Compound the rate stack.
let overrides = self.category_overrides(line);
let mut layers: Vec<(Jurisdiction, TaxRate)> = Vec::new();
let mut have_vat_or_gst = false;
for j in line.ship_to.ancestors() {
// Check override first.
if let Some((_, r)) = overrides.iter().find(|(oj, _)| oj == &j) {
push_layer(&mut layers, j, r.clone(), &mut have_vat_or_gst);
continue;
}
let Some(rate) = self.table.lookup(&j, &line.category) else {
continue;
};
push_layer(&mut layers, j, rate.clone(), &mut have_vat_or_gst);
}
// 5. Import duty — additive country-level when crossing borders.
if let Some(from) = &line.ship_from
&& from.country != line.ship_to.country
{
let country_j = Jurisdiction::country(&line.ship_to.country.0);
if let Some(r) = self
.table
.lookup(&country_j, &line.category)
.filter(|r| r.kind == RateKind::ImportDuty)
{
layers.push((country_j, r.clone()));
}
}
if layers.is_empty() {
return Err(Error::NoRate {
jurisdiction: line.ship_to.to_string(),
category: line.category.tag().to_owned(),
});
}
// 6. Determine inclusive / exclusive base. If any layer is
// inclusive (VAT/GST), the line amount is treated as gross
// and we back the tax out. Otherwise (US sales tax), we
// apply each rate to the line net amount.
let any_inclusive = layers.iter().any(|(_, r)| r.base == TaxBase::Inclusive);
let line_amount_dec = decimal_from_minor(line.amount);
let mut per_jurisdiction = Vec::with_capacity(layers.len());
let mut effective_rate = Decimal::ZERO;
let mut total_tax_dec = Decimal::ZERO;
let net_amount_dec;
if any_inclusive {
// VAT/GST inclusive — sum the inclusive layers' rates and
// back out tax in a single pass. Per the VAT directive, only
// one VAT/GST rate applies; any additional layers are excise
// on top of net.
let inclusive_rate: Decimal = layers
.iter()
.filter(|(_, r)| r.base == TaxBase::Inclusive)
.map(|(_, r)| r.rate)
.sum();
let one = Decimal::ONE;
let net = line_amount_dec
.checked_div(one + inclusive_rate)
.ok_or(Error::Overflow)?;
let inclusive_tax = line_amount_dec - net;
net_amount_dec = net;
// Allocate inclusive tax proportionally across inclusive layers
// (typically just one entry — VAT or GST).
for (j, r) in &layers {
if r.base != TaxBase::Inclusive {
continue;
}
let layer_share = if inclusive_rate.is_zero() {
Decimal::ZERO
} else {
inclusive_tax * (r.rate / inclusive_rate)
};
let layer_minor = decimal_to_minor_round_half_up(layer_share)?;
per_jurisdiction.push(JurisdictionTax {
jurisdiction: j.clone(),
rate: r.rate,
amount: Money::from_minor(layer_minor, line.amount.currency),
kind: r.kind,
});
effective_rate += r.rate;
total_tax_dec += layer_share;
}
// Additive (excise / import duty) layers stack on top of NET.
for (j, r) in &layers {
if r.base == TaxBase::Inclusive {
continue;
}
let layer_tax = net * r.rate;
let layer_minor = decimal_to_minor_round_half_up(layer_tax)?;
per_jurisdiction.push(JurisdictionTax {
jurisdiction: j.clone(),
rate: r.rate,
amount: Money::from_minor(layer_minor, line.amount.currency),
kind: r.kind,
});
effective_rate += r.rate;
total_tax_dec += layer_tax;
}
} else {
// US sales-tax style — all layers additive, exclusive base.
net_amount_dec = line_amount_dec;
for (j, r) in &layers {
let layer_tax = line_amount_dec * r.rate;
let layer_minor = decimal_to_minor_round_half_up(layer_tax)?;
per_jurisdiction.push(JurisdictionTax {
jurisdiction: j.clone(),
rate: r.rate,
amount: Money::from_minor(layer_minor, line.amount.currency),
kind: r.kind,
});
effective_rate += r.rate;
total_tax_dec += layer_tax;
}
}
let total_tax_minor = decimal_to_minor_round_half_up(total_tax_dec)?;
let net_minor = decimal_to_minor_round_half_up(net_amount_dec)?;
Ok(LineTaxBreakdown {
line_id: line.line_id.clone(),
taxable_amount: Money::from_minor(net_minor, line.amount.currency),
tax_amount: Money::from_minor(total_tax_minor, line.amount.currency),
effective_rate,
jurisdiction_layers: per_jurisdiction,
exemption_reason: None,
})
}
}
fn push_layer(
layers: &mut Vec<(Jurisdiction, TaxRate)>,
j: Jurisdiction,
r: TaxRate,
have_vat_or_gst: &mut bool,
) {
if matches!(r.kind, RateKind::Vat | RateKind::Gst) {
if *have_vat_or_gst {
// VAT/GST is replace-style: a more-specific layer would
// overwrite the broader one. Since we walk broadest-first,
// we replace the existing VAT layer with this narrower one.
layers.retain(|(_, existing)| {
!matches!(existing.kind, RateKind::Vat | RateKind::Gst)
});
}
*have_vat_or_gst = true;
}
layers.push((j, r));
}
fn zero_line(
line_id: &str,
amount: Money,
reason: Option<&str>,
) -> LineTaxBreakdown {
LineTaxBreakdown {
line_id: line_id.to_owned(),
taxable_amount: amount,
tax_amount: Money::from_minor(0, amount.currency),
effective_rate: Decimal::ZERO,
jurisdiction_layers: Vec::new(),
exemption_reason: reason.map(str::to_owned),
}
}
fn decimal_from_minor(m: Money) -> Decimal {
Decimal::from(m.minor_units)
}
fn decimal_to_minor_round_half_up(d: Decimal) -> Result<i64> {
let rounded = d.round_dp_with_strategy(0, rust_decimal::RoundingStrategy::MidpointAwayFromZero);
rounded.to_i64().ok_or(Error::Overflow)
}
#[async_trait]
impl TaxCalculator for NativeCalculator {
async fn calculate(&self, lines: &[TaxableLine], ctx: &TaxContext) -> Result<TaxResult> {
if lines.is_empty() {
return Err(Error::Config("calculate called with zero lines".into()));
}
let currency = lines[0].amount.currency;
let mut total_tax = Money::from_minor(0, currency);
let mut per_line: BTreeMap<String, LineTaxBreakdown> = BTreeMap::new();
let mut by_jurisdiction: BTreeMap<(Jurisdiction, RateKind), JurisdictionTax> =
BTreeMap::new();
for line in lines {
if line.amount.currency != currency {
return Err(Error::Money(op_core::Error::CurrencyMismatch));
}
let bd = self.calc_line(line, ctx)?;
total_tax = total_tax.checked_add(bd.tax_amount)?;
for layer in &bd.jurisdiction_layers {
let key = (layer.jurisdiction.clone(), layer.kind);
by_jurisdiction
.entry(key)
.and_modify(|j| {
if let Ok(sum) = j.amount.checked_add(layer.amount) {
j.amount = sum;
}
})
.or_insert_with(|| layer.clone());
}
per_line.insert(bd.line_id.clone(), bd);
}
let jurisdictions_charged = by_jurisdiction.into_values().collect();
Ok(TaxResult {
total_tax,
per_line,
jurisdictions_charged,
calculator: "native".to_owned(),
calculated_at: Utc::now(),
})
}
fn name(&self) -> &'static str {
"native"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::category::ProductTaxCategory;
use chrono::NaiveDate;
use op_core::Currency;
use std::collections::HashSet;
fn ctx_consumer() -> TaxContext {
TaxContext {
transaction_date: NaiveDate::parse_from_str("2026-06-15", "%Y-%m-%d").unwrap(),
customer_type: CustomerType::Consumer,
exemption_certs: vec![],
nexus_jurisdictions: HashSet::new(),
}
}
#[tokio::test]
async fn us_sales_tax_compounds_state_city_district() {
let calc = NativeCalculator::bundled();
let line = TaxableLine {
line_id: "L1".into(),
// $100.00
amount: Money::from_minor(10_000, Currency::USD),
category: ProductTaxCategory::TangibleGoods,
ship_from: None,
ship_to: Jurisdiction::full("US", "WA", "Seattle", "KingTransit"),
};
let r = calc.calculate(&[line], &ctx_consumer()).await.unwrap();
// WA 6.5 + Seattle 3.85 + KingTransit 0.9 = 11.25% on $100 = $11.25
assert_eq!(r.total_tax.minor_units, 1125);
let bd = r.per_line.get("L1").unwrap();
assert_eq!(bd.jurisdiction_layers.len(), 3);
}
#[tokio::test]
async fn eu_vat_inclusive_backs_out_correctly() {
let calc = NativeCalculator::bundled();
// €100.00 gross with 20% French VAT.
// Net = 100/1.20 = 83.333...; tax = 16.666... ; rounded = 16.67
let line = TaxableLine {
line_id: "L1".into(),
amount: Money::from_minor(10_000, Currency::EUR),
category: ProductTaxCategory::TangibleGoods,
ship_from: None,
ship_to: Jurisdiction::country("FR"),
};
let r = calc.calculate(&[line], &ctx_consumer()).await.unwrap();
assert_eq!(r.total_tax.minor_units, 1667);
let bd = r.per_line.get("L1").unwrap();
assert_eq!(bd.taxable_amount.minor_units, 8333);
assert_eq!(bd.jurisdiction_layers.len(), 1);
assert_eq!(bd.jurisdiction_layers[0].kind, RateKind::Vat);
}
#[tokio::test]
async fn vat_replace_not_additive() {
// Even if a region-level entry existed, VAT only compounds once.
let mut t = RateTable::bundled();
t = t.with(
Jurisdiction::region("FR", "75"),
ProductTaxCategory::TangibleGoods,
TaxRate::vat(Decimal::new(2500, 4)), // hypothetical Paris VAT 25%
);
let calc = NativeCalculator::new(t);
let line = TaxableLine {
line_id: "L1".into(),
amount: Money::from_minor(10_000, Currency::EUR),
category: ProductTaxCategory::TangibleGoods,
ship_from: None,
ship_to: Jurisdiction::locality("FR", "75", "Paris"),
};
let r = calc.calculate(&[line], &ctx_consumer()).await.unwrap();
// Should be exactly ONE VAT layer (Paris 25%, replacing FR 20%).
let bd = r.per_line.get("L1").unwrap();
let vat_layers = bd
.jurisdiction_layers
.iter()
.filter(|l| l.kind == RateKind::Vat)
.count();
assert_eq!(vat_layers, 1);
// Tax = 100 - 100/1.25 = 20.00
assert_eq!(r.total_tax.minor_units, 2000);
}
#[tokio::test]
async fn nexus_filter_zero_rates_outside_nexus() {
let calc = NativeCalculator::bundled();
let mut ctx = ctx_consumer();
// Seller has nexus only in WA. NY shipment should be zero-rated.
ctx.nexus_jurisdictions.insert(Jurisdiction::region("US", "WA"));
let line = TaxableLine {
line_id: "L1".into(),
amount: Money::from_minor(10_000, Currency::USD),
category: ProductTaxCategory::TangibleGoods,
ship_from: None,
ship_to: Jurisdiction::region("US", "NY"),
};
let r = calc.calculate(&[line], &ctx).await.unwrap();
assert_eq!(r.total_tax.minor_units, 0);
let bd = r.per_line.get("L1").unwrap();
assert!(bd.exemption_reason.as_deref().unwrap().contains("nexus"));
}
#[tokio::test]
async fn ny_clothing_under_110_exempt() {
let calc = NativeCalculator::bundled();
// $50 shirt to NY — should be zero-rated (state + city override).
let line = TaxableLine {
line_id: "L1".into(),
amount: Money::from_minor(5_000, Currency::USD),
category: ProductTaxCategory::Clothing,
ship_from: None,
ship_to: Jurisdiction::region("US", "NY"),
};
let r = calc.calculate(&[line], &ctx_consumer()).await.unwrap();
// Only the NY-state override (0%) applies — no city in this jurisdiction.
assert_eq!(r.total_tax.minor_units, 0);
}
#[tokio::test]
async fn ny_clothing_over_110_taxed() {
let calc = NativeCalculator::bundled();
// $200 jacket to NY state. Override kicks back to TangibleGoods rate (4%).
let line = TaxableLine {
line_id: "L1".into(),
amount: Money::from_minor(20_000, Currency::USD),
category: ProductTaxCategory::Clothing,
ship_from: None,
ship_to: Jurisdiction::region("US", "NY"),
};
let r = calc.calculate(&[line], &ctx_consumer()).await.unwrap();
// 4% of $200 = $8.00
assert_eq!(r.total_tax.minor_units, 800);
}
#[tokio::test]
async fn returns_no_rate_for_unknown_jurisdiction() {
let calc = NativeCalculator::bundled();
let line = TaxableLine {
line_id: "L1".into(),
amount: Money::from_minor(10_000, Currency::USD),
category: ProductTaxCategory::TangibleGoods,
ship_from: None,
ship_to: Jurisdiction::country("XX"), // unknown country
};
assert!(matches!(
calc.calculate(&[line], &ctx_consumer()).await,
Err(Error::NoRate { .. })
));
}
}