Shipping System
Architecture
Section titled “Architecture”Checkout Service ↓Shipping Provider Factory ├── Weight-Based Provider ├── Manual Provider ├── DHL Provider ├── FedEx Provider └── UPS Provider ↓Shipping Rates → Client selects → Order finalizedProvider trait
Section titled “Provider trait”#[async_trait]pub trait ShippingProvider: Send + Sync { fn id(&self) -> &str; fn name(&self) -> &str; fn is_available(&self) -> bool; async fn get_rates(&self, from: &Address, to: &Address, package: &Package, options: &RateOptions) -> Result<Vec<ShippingRate>>; // ...}Weight-based provider
Section titled “Weight-based provider”Default provider. Calculates rates based on:
- Total cart weight (from product weights)
- Destination country
- Zone-based rates per 0.5kg
Weight rounds up to nearest 0.5kg.
Zone rates
Section titled “Zone rates”| Zone | Rate/0.5kg |
|---|---|
| US | $5 (base $5) |
| EU | $4.50 + VAT |
| CN | Free |
| HK/TW/MO/SG | $2.50 |
| Default | $5 |
Full zone table in Shipping guide.
EU VAT
Section titled “EU VAT”For EU destinations, VAT is calculated on underdeclared values:
- Wheels: $10/item → $2 VAT
- Bushings: $2/item → $0.40 VAT
- Rate: 20%
Package weight
Section titled “Package weight”Product weights are fetched from the database. Products without weight default to 0.5kg.
let weights = cart_service.get_product_weights(&product_ids).await?;let total_weight: Decimal = items.iter() .map(|item| { let w = weights.get(&item.product_id).copied().unwrap_or(dec!(0.5)); Decimal::from(item.quantity) * w }) .sum();Factory clone
Section titled “Factory clone”The ShippingProviderFactory must be cloned for the rate aggregator. Each provider is re-created in the clone (providers are stateless).