diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs index 7f71b8d9f7..a10e0f8457 100644 --- a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs @@ -3,6 +3,7 @@ use crate::stripe::helpers::expand_customer_id::expand_customer_id; use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError; use log::{error, warn}; use stripe::Subscription; +use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor; /// Handle event type: 'customer.subscription.created' /// Sent when the subscription is created. The subscription status may be incomplete if customer @@ -10,20 +11,17 @@ use stripe::Subscription; /// default_incomplete. For more details, read about subscription payment behavior. pub fn customer_subscription_created_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> { - let stripe_subscription_id = subscription.id.to_string(); - let stripe_subscription_status = subscription.status.to_string(); + let json = serde_json::ser::to_string(&subscription).unwrap(); + error!("\n\n subscription.created ----> {}\n\n", json); - // NB: We'll need this to send them to the "customer portal", which is how they can modify or - // cancel their subscriptions. - let maybe_stripe_customer_id = expand_customer_id(&subscription.customer); - // NB: Our internal user token. - // NB: This *should* be present if checkout flow attached metadata to the subscription. - let maybe_user_token = subscription.metadata.get(METADATA_USER_TOKEN) - .map(|t| t.to_string()); + let summary = subscription_summary_extractor(subscription) + .map_err(|err| { + error!("Error extracting subscription from 'customer.subscription.created' payload: {:?}", err); + StripeWebhookError::ServerError // NB: This was probably *our* fault. + })?; - error!(">>> customer.subscription.created: {:?}, {:?}, {:?}, {:?}", - stripe_subscription_id, maybe_stripe_customer_id, maybe_user_token, stripe_subscription_status); + warn!(">>> subscription.created = {:?}", summary); Ok(()) } diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs index 2442283a21..c44af05852 100644 --- a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs @@ -3,23 +3,22 @@ use crate::stripe::helpers::expand_customer_id::expand_customer_id; use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError; use log::{error, warn}; use stripe::Subscription; +use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor; /// Handle event type: 'customer.subscription.deleted' /// Sent when a customer’s subscription ends. pub fn customer_subscription_deleted_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> { - let stripe_subscription_id = subscription.id.to_string(); + let json = serde_json::ser::to_string(&subscription).unwrap(); + error!("\n\n subscription.deleted ----> {}\n\n", json); - // NB: We'll need this to send them to the "customer portal", which is how they can modify or - // cancel their subscriptions. - let maybe_stripe_customer_id = expand_customer_id(&subscription.customer); + let summary = subscription_summary_extractor(subscription) + .map_err(|err| { + error!("Error extracting subscription from 'customer.subscription.deleted' payload: {:?}", err); + StripeWebhookError::ServerError // NB: This was probably *our* fault. + })?; - // NB: Our internal user token. - let maybe_user_token = subscription.metadata.get(METADATA_USER_TOKEN) - .map(|t| t.to_string()); - - error!(">>> customer.subscription.deleted: {:?}, {:?}, {:?}", - stripe_subscription_id, maybe_stripe_customer_id, maybe_user_token); + warn!(">>> subscription.deleted = {:?}", summary); Ok(()) } diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs index 4e1003e1c6..d6267e4a95 100644 --- a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs @@ -5,27 +5,20 @@ use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookEr use log::{error, warn}; use stripe::{Subscription, SubscriptionInterval}; use container_common::anyhow_result::AnyhowResult; +use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor; /// Handle event type: 'customer.subscription.updated' pub fn customer_subscription_updated_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> { + //let json = serde_json::ser::to_string(&subscription).unwrap(); + //error!("\n\n subscription.updated ----> {}\n\n", json); - let json = serde_json::ser::to_string(&subscription).unwrap(); + let summary = subscription_summary_extractor(subscription) + .map_err(|err| { + error!("Error extracting subscription from 'customer.subscription.updated' payload: {:?}", err); + StripeWebhookError::ServerError // NB: This was probably *our* fault. + })?; - error!("\n\n subscription.updated ----> {}\n\n", json); - - let stripe_subscription_id = subscription.id.to_string(); - let stripe_subscription_status = subscription.status.to_string(); - - // NB: We'll need this to send them to the "customer portal", which is how they can modify or - // cancel their subscriptions. - let maybe_stripe_customer_id = expand_customer_id(&subscription.customer); - - // NB: Our internal user token. - let maybe_user_token = subscription.metadata.get(METADATA_USER_TOKEN) - .map(|t| t.to_string()); - - error!(">>> customer.subscription.updated: {:?}, {:?}, {:?}, {:?}", - stripe_subscription_id, maybe_stripe_customer_id, maybe_user_token, stripe_subscription_status); + warn!(">>> subscription.updated = {:?}", summary); Ok(()) } diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs index d2304c9b2a..45116ba33c 100644 --- a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs @@ -6,6 +6,7 @@ use crate::stripe::helpers::common_metadata_keys::METADATA_USER_TOKEN; use crate::stripe::helpers::expand_customer_id::expand_customer_id; use crate::stripe::helpers::expand_product_id::expand_product_id; +#[derive(Clone, Debug)] pub struct SubscriptionSummary { pub user_token: Option, @@ -85,7 +86,46 @@ mod tests { use crate::stripe::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor; #[test] - fn test_subscription_summary_extractor() { + fn test_subscription_summary_extractor_on_create_event() { + // NB: Actual raw test data from Stripe to our webhook + let json = r#" + {"id":"sub_1LhA3MEU5se17MekeWvmTNyk","automatic_tax":{"enabled":false}, + "billing_cycle_anchor":1662979188,"cancel_at_period_end":false, + "collection_method":"charge_automatically","created":1662979188,"current_period_end":1665571188, + "current_period_start":1662979188,"customer":"cus_MQ03py0gWUh0Ox","default_tax_rates":[], + "items":{"data":[{"id":"si_MQ03hadVNfRnaS","created":1662979188,"deleted":false,"metadata":{}, + "price":{"id":"price_1LeDnKEU5se17MekVr1iYYNf","active":true,"billing_scheme":"per_unit", + "created":1662278586,"currency":"usd","deleted":false,"livemode":false,"metadata":{}, + "product":"prod_MMxi2J5y69VPbO","recurring":{"interval":"month","interval_count":1, + "usage_type":"licensed"},"tax_behavior":"exclusive","type":"recurring","unit_amount":700, + "unit_amount_decimal":"700"},"quantity":1,"subscription":"sub_1LhA3MEU5se17MekeWvmTNyk", + "tax_rates":[]}],"has_more":false,"total_count":1, + "url":"/v1/subscription_items?subscription=sub_1LhA3MEU5se17MekeWvmTNyk"}, + "latest_invoice":"in_1LhA3MEU5se17MekUqOhZ9cu","livemode":false, + "metadata":{"email":"email@email.com","username":"username","user_token":"U:TOKEN"}, + "payment_settings":{"save_default_payment_method":"off"},"start_date":1662979188, + "status":"incomplete"} + "#; + + let subscription = serde_json::from_str::(json).unwrap(); + + let summary= subscription_summary_extractor(&subscription).unwrap(); + + assert_eq!(summary.user_token, Some("U:TOKEN".to_string())); + assert_eq!(summary.stripe_subscription_id, "sub_1LhA3MEU5se17MekeWvmTNyk".to_string()); + assert_eq!(summary.stripe_subscription_status, SubscriptionStatus::Incomplete); + assert_eq!(summary.subscription_is_active, false); + assert_eq!(summary.stripe_customer_id, "cus_MQ03py0gWUh0Ox".to_string()); + assert_eq!(summary.stripe_product_id, "prod_MMxi2J5y69VPbO".to_string()); + assert_eq!(summary.stripe_price_id, "price_1LeDnKEU5se17MekVr1iYYNf".to_string()); + assert_eq!(summary.subscription_interval, RecurringInterval::Month); + assert_eq!(summary.stripe_is_production, false); + assert_eq!(summary.subscription_period_start.to_string(), "2022-09-12 10:39:48".to_string()); + assert_eq!(summary.subscription_period_end.to_string(), "2022-10-12 10:39:48".to_string()); + } + + #[test] + fn test_subscription_summary_extractor_on_update_event() { // NB: Actual raw test data from Stripe to our webhook let json = r#" {"id":"sub_1Lh1wvEU5se17Mekx72OzAzs","automatic_tax":{"enabled":false}, @@ -124,4 +164,45 @@ mod tests { assert_eq!(summary.subscription_period_start.to_string(), "2022-09-12 02:00:37".to_string()); assert_eq!(summary.subscription_period_end.to_string(), "2022-10-12 02:00:37".to_string()); } + + #[test] + fn test_subscription_summary_extractor_on_control_panel_immediate_delete_event() { + // NB: Actual raw test data from Stripe to our webhook + let json = r#" + {"id":"sub_1LhA3MEU5se17MekeWvmTNyk","automatic_tax":{"enabled":false}, + "billing_cycle_anchor":1662979188,"cancel_at_period_end":false,"canceled_at":1662979740, + "collection_method":"charge_automatically","created":1662979188,"current_period_end":1665571188, + "current_period_start":1662979188,"customer":"cus_MQ03py0gWUh0Ox", + "default_payment_method":"pm_1LhA3LEU5se17MekViNvoYAx","default_tax_rates":[], + "ended_at":1662979740,"items":{"data":[{"id":"si_MQ03hadVNfRnaS","created":1662979188, + "deleted":false,"metadata":{},"price":{"id":"price_1LeDnKEU5se17MekVr1iYYNf","active":true, + "billing_scheme":"per_unit","created":1662278586,"currency":"usd","deleted":false, + "livemode":false,"metadata":{},"product":"prod_MMxi2J5y69VPbO", + "recurring":{"interval":"month","interval_count":1,"usage_type":"licensed"}, + "tax_behavior":"exclusive","type":"recurring","unit_amount":700,"unit_amount_decimal":"700"}, + "quantity":1,"subscription":"sub_1LhA3MEU5se17MekeWvmTNyk","tax_rates":[]}],"has_more":false, + "total_count":1, + "url":"/v1/subscription_items?subscription=sub_1LhA3MEU5se17MekeWvmTNyk"}, + "latest_invoice":"in_1LhA3MEU5se17MekUqOhZ9cu","livemode":false, + "metadata":{"email":"email@email.com","username":"username","user_token":"U:TOKEN"}, + "payment_settings":{"save_default_payment_method":"off"},"start_date":1662979188, + "status":"canceled"} + "#; + + let subscription = serde_json::from_str::(json).unwrap(); + + let summary= subscription_summary_extractor(&subscription).unwrap(); + + assert_eq!(summary.user_token, Some("U:TOKEN".to_string())); + assert_eq!(summary.stripe_subscription_id, "sub_1LhA3MEU5se17MekeWvmTNyk".to_string()); + assert_eq!(summary.stripe_subscription_status, SubscriptionStatus::Canceled); + assert_eq!(summary.subscription_is_active, false); + assert_eq!(summary.stripe_customer_id, "cus_MQ03py0gWUh0Ox".to_string()); + assert_eq!(summary.stripe_product_id, "prod_MMxi2J5y69VPbO".to_string()); + assert_eq!(summary.stripe_price_id, "price_1LeDnKEU5se17MekVr1iYYNf".to_string()); + assert_eq!(summary.subscription_interval, RecurringInterval::Month); + assert_eq!(summary.stripe_is_production, false); + assert_eq!(summary.subscription_period_start.to_string(), "2022-09-12 10:39:48".to_string()); + assert_eq!(summary.subscription_period_end.to_string(), "2022-10-12 10:39:48".to_string()); + } } \ No newline at end of file