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(),
));
}
};
let req = ListPrice::new()
.lookup_keys(vec![cost_id.clone()])
.product(stripe_product_id.clone());
let prices = req.send(&this.client).await?.data;
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,
}
}