use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TransactionType {
Quote,
Payment,
Escrow,
Service,
}
impl std::fmt::Display for TransactionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Quote => write!(f, "quote"),
Self::Payment => write!(f, "payment"),
Self::Escrow => write!(f, "escrow"),
Self::Service => write!(f, "service"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TrustTier {
Sandbox,
Standard,
Verified,
Enterprise,
}
impl Default for TrustTier {
fn default() -> Self {
Self::Sandbox
}
}
impl std::fmt::Display for TrustTier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sandbox => write!(f, "sandbox"),
Self::Standard => write!(f, "standard"),
Self::Verified => write!(f, "verified"),
Self::Enterprise => write!(f, "enterprise"),
}
}
}
#[derive(Debug, Clone)]
pub struct TierRequirements {
pub min_transactions: u64,
pub min_avg_score: Decimal,
pub max_unresolved_disputes: Option<u64>,
pub max_dispute_rate: Option<Decimal>,
}
impl TrustTier {
#[must_use]
pub const fn requirements(self) -> TierRequirements {
match self {
Self::Sandbox => TierRequirements {
min_transactions: 0,
min_avg_score: Decimal::ZERO,
max_unresolved_disputes: None,
max_dispute_rate: None,
},
Self::Standard => TierRequirements {
min_transactions: 5,
min_avg_score: dec!(3.5),
max_unresolved_disputes: None,
max_dispute_rate: None,
},
Self::Verified => TierRequirements {
min_transactions: 25,
min_avg_score: dec!(4.0),
max_unresolved_disputes: Some(0),
max_dispute_rate: None,
},
Self::Enterprise => TierRequirements {
min_transactions: 100,
min_avg_score: dec!(4.5),
max_unresolved_disputes: None,
max_dispute_rate: Some(dec!(0.02)),
},
}
}
#[must_use]
pub fn is_at_least(self, other: Self) -> bool {
self >= other
}
#[must_use]
pub fn compute_tier(
total_transactions: u64,
average_score: Decimal,
unresolved_disputes: u64,
dispute_rate: Decimal,
) -> Self {
for &tier in &[Self::Enterprise, Self::Verified, Self::Standard] {
let reqs = tier.requirements();
if total_transactions < reqs.min_transactions {
continue;
}
if average_score < reqs.min_avg_score {
continue;
}
if let Some(max_disputes) = reqs.max_unresolved_disputes {
if unresolved_disputes > max_disputes {
continue;
}
}
if let Some(max_rate) = reqs.max_dispute_rate {
if dispute_rate >= max_rate {
continue;
}
}
return tier;
}
Self::Sandbox
}
#[must_use]
pub const fn all() -> &'static [Self] {
&[Self::Sandbox, Self::Standard, Self::Verified, Self::Enterprise]
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn default_tier_is_sandbox() {
assert_eq!(TrustTier::default(), TrustTier::Sandbox);
}
#[test]
fn tier_ordering() {
assert!(TrustTier::Sandbox < TrustTier::Standard);
assert!(TrustTier::Standard < TrustTier::Verified);
assert!(TrustTier::Verified < TrustTier::Enterprise);
}
#[test]
fn is_at_least() {
assert!(TrustTier::Enterprise.is_at_least(TrustTier::Sandbox));
assert!(TrustTier::Verified.is_at_least(TrustTier::Verified));
assert!(!TrustTier::Sandbox.is_at_least(TrustTier::Standard));
}
#[test]
fn display_tiers() {
assert_eq!(TrustTier::Sandbox.to_string(), "sandbox");
assert_eq!(TrustTier::Standard.to_string(), "standard");
assert_eq!(TrustTier::Verified.to_string(), "verified");
assert_eq!(TrustTier::Enterprise.to_string(), "enterprise");
}
#[test]
fn compute_tier_sandbox_default() {
assert_eq!(TrustTier::compute_tier(0, Decimal::ZERO, 0, Decimal::ZERO), TrustTier::Sandbox);
}
#[test]
fn compute_tier_sandbox_insufficient_transactions() {
assert_eq!(TrustTier::compute_tier(4, dec!(4.5), 0, Decimal::ZERO), TrustTier::Sandbox);
}
#[test]
fn compute_tier_sandbox_low_score() {
assert_eq!(TrustTier::compute_tier(10, dec!(3.0), 0, Decimal::ZERO), TrustTier::Sandbox);
}
#[test]
fn compute_tier_standard() {
assert_eq!(TrustTier::compute_tier(5, dec!(3.5), 0, Decimal::ZERO), TrustTier::Standard);
}
#[test]
fn compute_tier_standard_boundary() {
assert_eq!(TrustTier::compute_tier(5, dec!(3.5), 0, Decimal::ZERO), TrustTier::Standard);
assert_eq!(TrustTier::compute_tier(5, dec!(3.49), 0, Decimal::ZERO), TrustTier::Sandbox);
}
#[test]
fn compute_tier_verified() {
assert_eq!(TrustTier::compute_tier(25, dec!(4.0), 0, Decimal::ZERO), TrustTier::Verified);
}
#[test]
fn compute_tier_verified_blocked_by_disputes() {
assert_eq!(TrustTier::compute_tier(25, dec!(4.0), 1, Decimal::ZERO), TrustTier::Standard);
}
#[test]
fn compute_tier_enterprise() {
assert_eq!(TrustTier::compute_tier(100, dec!(4.5), 0, dec!(0.01)), TrustTier::Enterprise);
}
#[test]
fn compute_tier_enterprise_boundary() {
assert_ne!(TrustTier::compute_tier(100, dec!(4.5), 0, dec!(0.02)), TrustTier::Enterprise);
assert_eq!(TrustTier::compute_tier(100, dec!(4.5), 0, dec!(0.019)), TrustTier::Enterprise);
}
#[test]
fn compute_tier_enterprise_high_dispute_rate_falls_to_verified() {
assert_eq!(TrustTier::compute_tier(100, dec!(4.5), 0, dec!(0.05)), TrustTier::Verified);
}
#[test]
fn transaction_type_display() {
assert_eq!(TransactionType::Quote.to_string(), "quote");
assert_eq!(TransactionType::Payment.to_string(), "payment");
assert_eq!(TransactionType::Escrow.to_string(), "escrow");
assert_eq!(TransactionType::Service.to_string(), "service");
}
#[test]
fn all_tiers_returns_ascending() {
let tiers = TrustTier::all();
assert_eq!(tiers.len(), 4);
assert_eq!(tiers[0], TrustTier::Sandbox);
assert_eq!(tiers[3], TrustTier::Enterprise);
}
#[test]
fn requirements_sandbox() {
let r = TrustTier::Sandbox.requirements();
assert_eq!(r.min_transactions, 0);
}
#[test]
fn requirements_standard() {
let r = TrustTier::Standard.requirements();
assert_eq!(r.min_transactions, 5);
assert_eq!(r.min_avg_score, dec!(3.5));
}
#[test]
fn requirements_verified() {
let r = TrustTier::Verified.requirements();
assert_eq!(r.min_transactions, 25);
assert_eq!(r.min_avg_score, dec!(4.0));
assert_eq!(r.max_unresolved_disputes, Some(0));
}
#[test]
fn requirements_enterprise() {
let r = TrustTier::Enterprise.requirements();
assert_eq!(r.min_transactions, 100);
assert_eq!(r.min_avg_score, dec!(4.5));
assert_eq!(r.max_dispute_rate, Some(dec!(0.02)));
}
}