checkout_core 0.0.147

core traits and structs for the checkout_controller crate
Documentation
use async_trait::async_trait;
use rust_decimal::{prelude::FromPrimitive, Decimal};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

use crate::checkout::Checkout;
use crate::error::Error;
use crate::money::Money;
use crate::shipping::Fulfillment;

// #[derive(Clone, Serialize, Deserialize, JsonSchema)]
// #[serde(tag = "type", rename_all = "snake_case")]
// pub enum TaxValue {
//     Percentage(Decimal),
//     Absolute(Money),
// }

// #[derive(Clone, Serialize, Deserialize, JsonSchema)]
// pub struct Tax {
//     pub title: String,
//     pub value: TaxValue,
// }

#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Tax {
    Percentage { title: String, value: Decimal },
    Absolute { title: String, value: Money },
}

impl Tax {
    pub fn new_percentage(title: &str, value: &str) -> Self {
        Self::Percentage {
            title: title.to_string(),
            value: Decimal::from_str(value).unwrap(),
        }
    }

    pub fn new_absolute(title: &str, value: Money) -> Self {
        Self::Absolute {
            title: title.to_string(),
            value,
        }
    }
}

#[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub struct LineItem {
    pub title: String,
    pub quantity: u64,
    pub price: Money,
    pub discount: Money,
}

#[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub struct Invoice {
    pub line_items: Vec<LineItem>,
    pub shipping: Option<LineItem>,
    pub line_item_taxes: Vec<Tax>,
    pub shipping_taxes: Vec<Tax>,
    pub subtotal: Money,
    pub shipping_total: Money,
    pub total_line_item_taxes: Money,
    pub total_shipping_taxes: Money,
    pub total_taxes: Money,
    pub total: Money,
    pub initial_charge_amount: Money,
}

#[async_trait]
pub trait InvoiceCalculator {
    async fn line_item_taxes<P: Sync + Send>(
        &mut self,
        _co: &Checkout<P>,
    ) -> Result<Vec<Tax>, Error> {
        Ok(vec![])
    }

    async fn shipping_taxes<P: Sync + Send>(
        &mut self,
        _co: &Checkout<P>,
    ) -> Result<Vec<Tax>, Error> {
        Ok(vec![])
    }

    async fn initial_charge_ratio<P: Sync + Send>(
        &mut self,
        _co: &Checkout<P>,
    ) -> Result<Decimal, Error> {
        Ok(Decimal::from_str("1").unwrap())
    }

    async fn generate_invoice<P: Sync + Send>(
        &mut self,
        co: &Checkout<P>,
    ) -> Result<Invoice, Error> {
        let line_items: Vec<LineItem> = co
            .items
            .iter()
            .map(|i| LineItem {
                title: i.title.clone(),
                price: i.price.clone(),
                quantity: i.quantity.clone(),
                discount: i.discount.clone(),
            })
            .collect();

        let shipping = match &co.fulfillment {
            Some(ft) => match ft {
                Fulfillment::Pickup => None,
                Fulfillment::Shipping { quote } => Some(LineItem {
                    title: String::from("Shipping"),
                    quantity: 1,
                    price: quote.price.clone(),
                    discount: quote.discount.clone(),
                }),
            },
            None => None,
        };

        let subtotal = line_items
            .iter()
            .fold(Money::new(co.currency.clone(), "0"), |t, li| {
                t.add(
                    &li.price
                        .sub(&li.discount)
                        .mul(&Decimal::from_u64(li.quantity).unwrap()),
                )
            });

        let shipping_total = match &shipping {
            Some(s) => s.price.mul(&Decimal::from_u64(s.quantity).unwrap()),
            None => Money::new(co.currency.clone(), "0"),
        };

        let line_item_taxes = self.line_item_taxes(co).await?;
        let total_line_item_taxes = line_item_taxes.iter().fold(
            Money::new(co.currency.clone(), "0"),
            |t, tax| match &tax {
                Tax::Absolute { title: _, value } => t.add(value),
                Tax::Percentage { title: _, value } => t.add(&subtotal.mul(value)),
            },
        );

        let shipping_taxes = self.shipping_taxes(co).await?;
        let total_shipping_taxes =
            shipping_taxes
                .iter()
                .fold(Money::new(co.currency.clone(), "0"), |t, tax| match &tax {
                    Tax::Absolute { title: _, value } => t.add(value),
                    Tax::Percentage { title: _, value } => t.add(&shipping_total.mul(value)),
                });

        let total_taxes = total_line_item_taxes.add(&total_shipping_taxes);
        let total = subtotal.add(&shipping_total).add(&total_taxes);
        let initial_charge_amount = total.mul(&self.initial_charge_ratio(co).await?);

        Ok(Invoice {
            line_items,
            shipping,
            line_item_taxes,
            shipping_taxes,
            subtotal,
            shipping_total,
            total_line_item_taxes,
            total_shipping_taxes,
            total_taxes,
            total,
            initial_charge_amount,
        })
    }
}