use super::PROVIDER_KEY;
use super::StripePaymentProcessor;
use super::sync_plan_costs;
use crate::models::metadata;
use crate::{payments::Error, pbbson::Model};
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> {
let req = RetrieveProduct::new(plan.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").map_err(|e| Error::internal(e.to_string()))?,
)
.await
.map_err(|e| Error::internal(e.to_string()))?;
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 service_id = service.id().map_err(|e| Error::internal(e.to_string()))?;
let plan_id = plan.id().map_err(|e| Error::internal(e.to_string()))?;
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(&plan_display_name)
.id(plan_id.clone())
.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();
let plan_has_changed = {
if let Some(product_id) = metadata::find_string(&plan, plan_metadata_field, PROVIDER_KEY, "productId")
&& product_id == product.id.as_str()
{
true
} else {
metadata::set(
&mut plan,
plan_metadata_field,
PROVIDER_KEY,
"productId",
Bson::String(product.id.to_string()),
);
true
}
};
sync_plan_costs::sync(this, &plan, plan_metadata_field).await?;
if plan_has_changed {
let new_plan = this
.config_store
.update(plans_bucket, plan.clone())
.await
.map_err(|e| Error::internal(e.to_string()))?;
new_plan.clone_into(&mut plan);
let plan_id = plan.id().map_err(|e| Error::internal(e.to_string()))?;
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(),
_ => service
.get_str("name")
.map_err(|e| Error::internal(e.to_string()))?
.to_string(),
},
_ => service
.get_str("name")
.map_err(|e| Error::internal(e.to_string()))?
.to_string(),
})
}
fn get_plan_display_name(plan: &Model) -> Result<String, Error> {
Ok(match plan.get_str("description") {
Ok(description) => description.to_string(),
_ => plan
.get_str("name")
.map_err(|e| Error::internal(e.to_string()))?
.to_string(),
})
}