use super::PROVIDER_KEY;
use super::StripePaymentProcessor;
use super::sync_plan_costs;
use crate::models::metadata;
use crate::{payments::Error, pbbson::Model};
use change_case::pascal_case;
use pbbson::bson::Bson;
use std::collections::HashMap;
use std::str::FromStr;
use stripe_product::ProductType;
use stripe_product::product::{CreateProduct, Features, RetrieveProduct};
pub(super) async fn sync<B: Clone + FromStr + Send + Sync + ToString>(
this: &StripePaymentProcessor<B>,
plan: &Model,
plan_metadata_field: &str,
services_bucket: B,
plans_bucket: B,
) -> Result<Model, Error> {
if let Some(product_id) = metadata::find_string(plan, plan_metadata_field, PROVIDER_KEY, "productId") {
let req = RetrieveProduct::new(product_id);
match req.send(&this.client).await {
Ok(_product) => return Ok(plan.clone()),
Err(_e) => {
}
}
}
let service = this
.config_store
.find(services_bucket, plan.get_str("serviceId")?)
.await?;
let service_display_name = get_service_display_name(&service)?;
let plan_display_name = get_plan_display_name(plan)?;
let name = format!("{service_display_name} - {plan_display_name}",);
let description = match plan.get_str("description") {
Ok(description) => description,
Err(_) => &plan_display_name,
};
let service_id = service.id()?;
let plan_id = plan.id()?;
let mut metadata = HashMap::from([
("serviceId".to_string(), service_id.clone()),
("planId".to_string(), plan_id.clone()),
]);
if let Some(service_name) = service.get("name") {
metadata.insert("serviceName".to_string(), service_name.to_string());
}
if let Some(plan_name) = plan.get("name") {
metadata.insert("planName".to_string(), plan_name.to_string());
}
let req = CreateProduct::new(name)
.active(true)
.description(description)
.marketing_features(get_marketing_features_from_bullets(plan))
.metadata(metadata)
.type_(ProductType::Service);
let product = req.send(&this.client).await?;
log::info!(plan_id, stripe_product_id = product.id.as_str(); "Created Stripe product");
let mut plan = plan.clone();
metadata::set(
&mut plan,
plan_metadata_field,
PROVIDER_KEY,
"productId",
Bson::String(product.id.to_string()),
);
sync_plan_costs::sync(this, &plan, plan_metadata_field).await?;
plan = this.config_store.update(plans_bucket, plan.clone()).await?;
let plan_id = plan.id()?;
log::info!(plan_id, stripe_product_id = product.id.as_str(); "Updated plan metadata to reference Stripe product");
Ok(plan.clone())
}
fn get_marketing_features_from_bullets(plan: &Model) -> Vec<Features> {
if let Some(Bson::Document(metadata)) = plan.get("metadata")
&& let Some(Bson::Array(bullets)) = metadata.get("bullets")
{
return bullets
.iter()
.map(|bullet| {
Features::new(match bullet {
Bson::String(s) => s.clone(),
_ => bullet.to_string(),
})
})
.collect();
}
vec![]
}
fn get_service_display_name(service: &Model) -> Result<String, Error> {
Ok(match service.get("metadata") {
Some(Bson::Document(metadata)) => match metadata.get_str("displayName") {
Ok(display_name) => display_name.to_string(),
_ => pascal_case(service.get_str("name")?),
},
_ => pascal_case(service.get_str("name")?),
})
}
fn get_plan_display_name(plan: &Model) -> Result<String, Error> {
Ok(pascal_case(plan.get_str("name")?))
}