correlate 0.3.0

correlate is a standalone server that listens for Stripe webhook events and sends notification emails about successful orders.
use rocket::serde::Deserialize;
use validator::Validate;

use super::{customer::StripeCustomerDetails, date::StripeDate, StripeCurrency};

/// Charge status of a Stripe payment.
/// Represents the possible states of a payment after processing by Stripe.
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
#[serde(rename_all = "lowercase")]
pub enum StripeChargeStatus {
    /// Payment attempt has failed.
    /// This status indicates that the charge was unsuccessful and no funds were transferred.
    Failed,
    /// Payment is still being processed.
    /// The charge is in an intermediate state and may succeed or fail later.
    Pending,
    /// Payment was successful.
    /// The charge has been processed successfully and funds have been transferred.
    Succeeded,
}

/// Represents a Stripe Charge object from the Stripe API.
/// Contains payment details including amount, status, and customer information.
#[derive(Debug, Deserialize, Validate)]
#[serde(crate = "rocket::serde")]
pub struct StripeCharge<'s> {
    /// The unique identifier for the charge.
    #[validate(contains(pattern = "ch_"))]
    pub id: &'s str,
    /// The timestamp when the charge was created.
    pub created: StripeDate,

    /// Currency of the session
    pub currency: StripeCurrency,

    /// Payment status of the charge.
    ///
    /// `true` if the charge succeeded, or was successfully authorized. `false` otherwise.
    pub paid: bool,

    pub status: StripeChargeStatus,

    /// The amount charged in cents.
    pub amount: u32,

    /// Details about customer initiated attempted charge
    pub billing_details: StripeCustomerDetails,

    /// The statement descriptor that appears on the customer's credit card statement.
    pub calculated_statement_descriptor: Option<&'s str>,

    /// URL to the receipt for this charge.
    /// This may be null if the charge is not in a state that generates a receipt.
    pub receipt_url: Option<&'s str>,
}

impl<'s> StripeCharge<'s> {
    /// Returns whether this charge was successful.
    pub fn is_successful(&self) -> bool {
        matches!(self.status, StripeChargeStatus::Succeeded) && self.paid
    }

    /// Returns the formatted amount with currency symbol.
    ///
    /// # Examples
    ///
    /// ```
    /// let charge = StripeCharge { amount: 1000, currency: "usd", ... };
    /// assert_eq!(charge.formatted_amount(), "$10.00");
    /// ```
    pub fn formatted_amount(&self) -> String {
        let symbol = match self.currency {
            StripeCurrency::USD => "$",
            StripeCurrency::CAD => "CA$\u{a0}",
            StripeCurrency::EUR => "",
            StripeCurrency::GBP => "£",
        };

        let amount = self.amount as f64 / 100.0;
        format!("{}{:.2}", symbol, amount)
    }
}