Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::money_amount::{money, money_from_value, money_to_value, Money}; ← from money.amount ^1.0.0 · built alongside by fune
use super::retail_sales_tax_us_data::{STATE_RATES, STATE_RATES_HISTORY}; ← this capability’s own data, compiled from data/state-rates.json into the same file by fune build
fn is_iso_date(value: &str) -> bool {
let bytes = value.as_bytes();
bytes.len() == 10
&& bytes[4] == b'-'
&& bytes[7] == b'-'
&& bytes
.iter()
.enumerate()
.all(|(i, b)| i == 4 || i == 7 || b.is_ascii_digit())
}
fn state_rate(state: &str, on_date: &str) -> i64 {
if !STATE_RATES.iter().any(|r| r.state == state) {
panic!("unknown US state \"{}\"", state);
}
let mut best: Option<(&str, i64)> = None;
for r in STATE_RATES {
if r.state != state || on_date < r.valid_from {
continue;
}
if let Some(to) = r.valid_to {
if on_date > to {
continue;
}
}
if best.map_or(true, |(from, _)| r.valid_from > from) {
best = Some((r.valid_from, r.rate_per_million));
}
}
if let Some((_, rate)) = best {
return rate;
}
if STATE_RATES_HISTORY != "full" {
let mut earliest: Option<&str> = None;
for r in STATE_RATES {
if r.state == state && earliest.map_or(true, |e| r.valid_from < e) {
earliest = Some(r.valid_from);
}
}
if let Some(earliest) = earliest {
if on_date < earliest {
panic!(
"no sales tax rate for {} on {}: this build was installed with history={}, so it only carries rates from {}. Reinstall with history=full for older sales.",
state, on_date, STATE_RATES_HISTORY, earliest
);
}
}
}
panic!("no sales tax rate for {} on {}", state, on_date)
}
/// US sales tax: the state's base rate on the date of sale plus the caller's
/// local rate, applied once and rounded half up to the cent.
///
/// # Panics
/// Panics on a malformed date, a non-USD amount, an unknown state, a local
/// rate outside 0..1000000, or a date no rate covers.
pub fn sales_tax_us(amount: &Money, state: &str, local_rate_per_million: i64, on_date: &str) -> UsSalesTax {
if !is_iso_date(on_date) {
panic!("onDate must be an ISO date (YYYY-MM-DD), received \"{}\"", on_date);
}
if amount.currency != "USD" {
panic!("US sales tax is charged in USD, received {}", amount.currency);
}
if !(0..1000000).contains(&local_rate_per_million) {
panic!(
"localRatePerMillion must be from 0 to 999999, received {}",
local_rate_per_million
);
}
let state_rate_per_million = state_rate(state, on_date);
let combined = state_rate_per_million + local_rate_per_million;
let tax = money(round_div(amount.minor * combined, 1000000, "half-up"), "USD");
UsSalesTax {
state: state.to_string(),
state_rate_per_million,
local_rate_per_million,
combined_rate_per_million: combined,
taxable: amount.clone(),
total: money(amount.minor + tax.minor, "USD"),
tax,
}
}
pub fn us_sales_tax_to_value(t: &UsSalesTax) -> Value {
Value::obj(vec![
("state", Value::str(&t.state)),
("stateRatePerMillion", Value::Int(t.state_rate_per_million)),
("localRatePerMillion", Value::Int(t.local_rate_per_million)),
("combinedRatePerMillion", Value::Int(t.combined_rate_per_million)),
("taxable", money_to_value(&t.taxable)),
("tax", money_to_value(&t.tax)),
("total", money_to_value(&t.total)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
us_sales_tax_to_value(&sales_tax_us(
&money_from_value(&args[0]),
args[1].as_str(),
args[2].as_i64(),
args[3].as_str(),
))
}