saas-rs-sdk 0.6.4

The SaaS RS SDK
use super::{PROVIDER_KEY, StripePaymentProcessor};
use crate::models::metadata;
use crate::payments::Error;
use pbbson::Model;
use pbbson::bson::{Bson, Document};
use std::collections::HashMap;
use std::str::FromStr;
use stripe_product::price::{CreatePrice, CreatePriceRecurring, CreatePriceRecurringInterval, ListPrice};
use stripe_product::{Price, PriceBillingScheme};
use stripe_types::Currency;

pub(super) async fn sync<B: Clone + FromStr + Send + Sync + ToString>(
    this: &StripePaymentProcessor<B>,
    plan: &Model,
    plan_metadata_field: &str,
) -> Result<Vec<Price>, Error> {
    let costs = get_plan_metadata_costs(plan);
    let mut prices = vec![];
    for cost in costs.iter() {
        let price = sync_cost(this, plan, plan_metadata_field, cost).await?;
        prices.push(price);
    }
    Ok(prices)
}

fn get_plan_metadata_costs(plan: &Model) -> Vec<Document> {
    if let Some(Bson::Document(metadata)) = plan.get("metadata")
        && let Some(Bson::Array(costs)) = metadata.get("costs")
    {
        return costs
            .iter()
            .filter_map(|cost| match cost {
                Bson::Document(cost) => Some(cost.clone()),
                _ => None,
            })
            .collect();
    }
    vec![]
}

async fn sync_cost<B: Clone + FromStr + Send + Sync + ToString>(
    this: &StripePaymentProcessor<B>,
    plan: &Model,
    plan_metadata_field: &str,
    cost: &Document,
) -> Result<Price, Error> {
    let plan_id = plan.id()?;
    let service_id = match plan.get_str("serviceId") {
        Ok(id) => id.to_string(),
        _ => {
            return Err(Error::FailedPrecondition(
                "A service plan requires a serviceId reference".to_string(),
            ));
        }
    };
    let cost_id = match cost.get_str("id") {
        Ok(id) => id.to_string(),
        _ => {
            return Err(Error::FailedPrecondition(
                "A service plan cost requires an id".to_string(),
            ));
        }
    };
    let stripe_product_id = match metadata::find_string(plan, plan_metadata_field, PROVIDER_KEY, "productId") {
        Some(stripe_product_id) => stripe_product_id,
        _ => {
            return Err(Error::FailedPrecondition(
                "Service plan metadata requires a stripe.productId reference".to_string(),
            ));
        }
    };

    // Find existing Stripe price for plan cost
    let req = ListPrice::new()
        .lookup_keys(vec![cost_id.clone()])
        .product(stripe_product_id.clone());
    let prices = req.send(&this.client).await?.data;

    // Create new price
    let price = match prices.first() {
        Some(price) => price.clone(),
        None => {
            let metadata = HashMap::from([
                ("serviceId".to_string(), service_id),
                ("planId".to_string(), plan_id.clone()),
            ]);
            let mut req = CreatePrice::new(Currency::USD)
                .billing_scheme(PriceBillingScheme::PerUnit)
                .lookup_key(cost_id.clone())
                .metadata(metadata)
                .product(stripe_product_id.clone())
                .unit_amount(get_plan_price_in_cents(cost));
            if let Some(Bson::String(unit)) = cost.get("unit") {
                req = req.nickname(unit);
            }
            if let Some(Bson::String(recurring_interval)) = cost.get("recurringInterval") {
                let interval = {
                    if recurring_interval == "month" {
                        CreatePriceRecurringInterval::Month
                    } else if recurring_interval == "day" {
                        CreatePriceRecurringInterval::Day
                    } else if recurring_interval == "week" {
                        CreatePriceRecurringInterval::Week
                    } else if recurring_interval == "year" {
                        CreatePriceRecurringInterval::Year
                    } else {
                        let msg = format!("Unsupported service plan cost recurrance interval: {recurring_interval}");
                        return Err(Error::internal(msg));
                    }
                };
                let mut recurring = CreatePriceRecurring::new(CreatePriceRecurringInterval::Month);
                recurring.interval = interval;
                req = req.recurring(recurring);
            }
            let price = req.send(&this.client).await?;
            log::info!(plan_id = plan_id.as_str(), stripe_product_id = stripe_product_id.as_str(); "Created Stripe price");
            price
        }
    };

    Ok(price)
}

fn get_plan_price_in_cents(plan_cost: &Document) -> i64 {
    match plan_cost.get("amount") {
        Some(Bson::Document(amount)) => match amount.get("usd") {
            Some(Bson::Double(usd)) => *usd as i64 * 100,
            Some(Bson::Int32(usd)) => *usd as i64 * 100,
            Some(Bson::Int64(usd)) => usd * 100,
            _ => 0,
        },
        _ => 0,
    }
}