correlate 0.3.0

correlate is a standalone server that listens for Stripe webhook events and sends notification emails about successful orders.
use super::{CHECKOUT_ENDPOINT, STRIPE_API};
use crate::StripeConfig;
use base64::{engine::general_purpose::STANDARD, Engine};
use rocket::{response::status::BadRequest, serde::Deserialize};

/// Line item info for a Stripe Checkout order
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct StripeCheckoutLineItem {
    #[serde(alias = "id")]
    pub _id: String,
    pub currency: String,
    pub amount_total: u32,
    pub amount_subtotal: u32,
    pub description: String,
    pub quantity: u32,
}

/// List of line items details for a Stripe Checkout order
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct StripeCheckoutLineItems {
    pub data: Vec<StripeCheckoutLineItem>,
}

impl StripeCheckoutLineItems {
    /// Gets a list of all the line items from a checkout order
    pub fn get_line_items(&self) -> Vec<(&String, u32, &String, u32, u32)> {
        self.data
            .iter()
            .map(|item| {
                (
                    &item.description,
                    item.quantity,
                    &item.currency,
                    item.amount_subtotal,
                    item.amount_total,
                )
            })
            .collect()
    }
}

pub fn get_basic_auth_header(user: &str, pass: &str) -> String {
    let creds = String::from(user) + ":" + pass;
    String::from("Basic ") + &STANDARD.encode(creds.as_bytes())
}

/// Retrieves product line item details for a checkout session.
/// It requires the checkout session ID and Stripe secret key. If the key is
/// missing it will provide an error notifying the need for the secret key.
///
/// Arguments:
///   * `line_item_id` - The Stripe checkout session ID to query
///   * `config` - Optional Stripe configuration containing the secret key
///
/// Returns:
///   * Ok(StripeCheckoutLineItems) - The line items from the checkout session
///   * Err(BadRequest) - If there's an error with the request or configuration
pub fn find_order_info(
    line_item_id: &str,
    config: &Option<StripeConfig>,
) -> Result<StripeCheckoutLineItems, BadRequest<String>> {
    if let Some(c) = &config {
        let api_url = format!("{STRIPE_API}{CHECKOUT_ENDPOINT}/{line_item_id}/line_items");
        let mut result = match ureq::get(&api_url)
            .header("Authorization", &get_basic_auth_header(&c.secret, ""))
            .call()
        {
            Err(ureq::Error::StatusCode(code)) => {
                return if code == 401 {
                    Err(BadRequest(
                        "Error fetching product info. Try setting the correct secret key."
                            .to_owned(),
                    ))
                } else {
                    Err(BadRequest("Error fetching product info.".to_owned()))
                }
            }
            Err(err) => return Err(BadRequest(format!("Error fetching product info: {}", err))),
            Ok(res) => res,
        };

        match result.body_mut().read_json::<StripeCheckoutLineItems>() {
            Err(err) => Err(BadRequest(format!(
                "Encountered error while parsing product info from JSON: {}",
                err
            ))),
            Ok(res) => Ok(res),
        }
    } else {
        Err(BadRequest(
            "Stripe secret key needs to be configured in order to find product information."
                .to_owned(),
        ))
    }
}