diff --git a/crates/component_subsystem/billing/src/stripe/http_endpoints/stripe_webhook_handler.rs b/crates/component_subsystem/billing/src/stripe/http_endpoints/stripe_webhook_handler.rs index efce4d72d3..01f0009504 100644 --- a/crates/component_subsystem/billing/src/stripe/http_endpoints/stripe_webhook_handler.rs +++ b/crates/component_subsystem/billing/src/stripe/http_endpoints/stripe_webhook_handler.rs @@ -5,8 +5,9 @@ use actix_web::web::{Bytes, Path}; use actix_web::{web, HttpResponse, HttpRequest}; use chrono::{DateTime, Utc}; use crate::stripe::stripe_config::StripeConfig; -use crate::stripe::webhook_event_handlers::checkout_session_completed_handler::checkout_session_completed_handler; +use crate::stripe::webhook_event_handlers::checkout_session::checkout_session_completed_handler::checkout_session_completed_handler; use crate::stripe::webhook_event_handlers::customer::customer_created_handler::customer_created_handler; +use crate::stripe::webhook_event_handlers::customer::customer_deleted_handler::customer_deleted_handler; use crate::stripe::webhook_event_handlers::customer::customer_updated_handler::customer_updated_handler; use crate::stripe::webhook_event_handlers::customer_subscription::customer_subscription_created_handler::customer_subscription_created_handler; use crate::stripe::webhook_event_handlers::customer_subscription::customer_subscription_deleted_handler::customer_subscription_deleted_handler; @@ -14,6 +15,7 @@ use crate::stripe::webhook_event_handlers::customer_subscription::customer_subsc use crate::stripe::webhook_event_handlers::invoice::invoice_paid_handler::invoice_paid_handler; use crate::stripe::webhook_event_handlers::invoice::invoice_payment_failed::invoice_payment_failed_handler; use crate::stripe::webhook_event_handlers::invoice::invoice_payment_succeeded_handler::invoice_payment_succeeded_handler; +use crate::stripe::webhook_event_handlers::invoice::invoice_updated_handler::invoice_updated_handler; use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError; use http_server_common::request::get_request_header_optional::get_request_header_optional; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; @@ -23,7 +25,6 @@ use sqlx::MySqlPool; use std::collections::HashMap; use std::fmt; use stripe::{EventObject, EventType, PaymentIntentStatus, Webhook}; -use crate::stripe::webhook_event_handlers::customer::customer_deleted_handler::customer_deleted_handler; #[derive(Serialize)] pub struct StripeWebhookSuccessResponse { @@ -104,9 +105,9 @@ pub async fn stripe_webhook_handler( } } - // EventType::CheckoutSessionExpired - // EventType::CheckoutSessionAsyncPaymentFailed - // EventType::CheckoutSessionAsyncPaymentSucceeded + // EventType::CheckoutSessionExpired => {} + // EventType::CheckoutSessionAsyncPaymentFailed => {} + // EventType::CheckoutSessionAsyncPaymentSucceeded => {} // =============== CUSTOMERS =============== @@ -143,9 +144,10 @@ pub async fn stripe_webhook_handler( let _r = customer_subscription_deleted_handler(&subscription)?; } } - // EventType::CustomerSubscriptionPendingUpdateApplied - // EventType::CustomerSubscriptionPendingUpdateExpired - // EventType::CustomerSubscriptionTrialWillEnd + + // EventType::CustomerSubscriptionPendingUpdateApplied => {} + // EventType::CustomerSubscriptionPendingUpdateExpired => {} + // EventType::CustomerSubscriptionTrialWillEnd => {} // =============== INVOICES =============== @@ -153,9 +155,9 @@ pub async fn stripe_webhook_handler( // TODO: We need to respond to this so we don't hold payments up by 72 hours! // See: https://stripe.com/docs/billing/subscriptions/webhooks } - EventType::InvoicePaymentSucceeded => { + EventType::InvoiceUpdated => { if let EventObject::Invoice(invoice) = webhook_payload.data.object { - let _r = invoice_payment_succeeded_handler(&invoice)?; + let _r = invoice_updated_handler(&invoice)?; } } EventType::InvoicePaid => { @@ -163,24 +165,29 @@ pub async fn stripe_webhook_handler( let _r = invoice_paid_handler(&invoice)?; } } + EventType::InvoicePaymentSucceeded => { + if let EventObject::Invoice(invoice) = webhook_payload.data.object { + let _r = invoice_payment_succeeded_handler(&invoice)?; + } + } EventType::InvoicePaymentFailed => { if let EventObject::Invoice(invoice) = webhook_payload.data.object { let _r = invoice_payment_failed_handler(&invoice)?; } } - // EventType::InvoiceDeleted - // EventType::InvoiceFinalizationFailed - // EventType::InvoiceFinalized - // EventType::InvoiceItemCreated - // EventType::InvoiceItemDeleted - // EventType::InvoiceItemUpdated - // EventType::InvoiceMarkedUncollectible - // EventType::InvoicePaymentActionRequired - // EventType::InvoiceSent - // EventType::InvoiceUpcoming - // EventType::InvoiceUpdated - // EventType::InvoiceVoided + // EventType::InvoiceDeleted => {} + // EventType::InvoiceFinalizationFailed => {} + // EventType::InvoiceFinalized => {} + // EventType::InvoiceItemCreated => {} + // EventType::InvoiceItemDeleted => {} + // EventType::InvoiceItemUpdated => {} + // EventType::InvoiceMarkedUncollectible => {} + // EventType::InvoicePaymentActionRequired => {} + // EventType::InvoiceSent => {} + // EventType::InvoiceUpcoming => {} + // EventType::InvoiceUpdated => {} + // EventType::InvoiceVoided => {} // =============== PAYMENT INTENTS =============== @@ -188,6 +195,66 @@ pub async fn stripe_webhook_handler( if let EventObject::PaymentIntent(payment_intent) = webhook_payload.data.object { } } + + // EventType::PaymentIntentAmountCapturableUpdated => {} + // EventType::PaymentIntentCanceled => {} + // EventType::PaymentIntentCreated => {} + // EventType::PaymentIntentPartiallyFunded => {} + // EventType::PaymentIntentPaymentFailed => {} + // EventType::PaymentIntentProcessing => {} + // EventType::PaymentIntentRequiresAction => {} + // EventType::PaymentIntentRequiresCapture => {} + + // =============== Ignored =============== + + // Ignoring these types: + // Account* (6), + // ApplicationFee* (2), + // BalanceAvailable (1), + // BillingPortal* (2), + // CapabilityCreated (1), + // Charge* (13), + // [CheckoutSession* handled above], + // Coupon* (3), + // CreditNote* (3), + // CustomerDiscount* (3), + // CustomerSource* (4), + // [CustomerSubscription* handled above], + // CreditTax* (3), + // FileCreated (1), + // IdentityVerification* (6), + // [Invoice* handled above], + // IssuingAuthorization* (3), + // IssuingCard* (2), + // IssuingCardholder* (2), + // IssuingDispute* (2), + // IssuingTransaction* (2), + // MandateUpdated (1), + // Order* (6), + // [PaymentIntent* handled above], + // PaymentLink* (2), + // PaymentMethod* (4), + // Payout* (5), + // Person* (3), + // Plan* (3), + // Price* (3), + // Product* (3), + // PromotionCode* (2), + // Quote* (4), + // RadarEarlyFraudWarning* (2), + // Recipient* (3), + // ReportingReport* (3), + // Review* (2), + // SetupIntent* (5), + // SigmaScheduledQueryRunCreated (1), + // Sku (3), + // Source* (7), + // TaxRate* (2), + // TerminalReader* (2), + // TestHelpersTestClock* (5), + // Topup* (5), + // Transfer* (5), + _ => {}, } diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/checkout_session_completed_handler.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/checkout_session/checkout_session_completed_handler.rs similarity index 100% rename from crates/component_subsystem/billing/src/stripe/webhook_event_handlers/checkout_session_completed_handler.rs rename to crates/component_subsystem/billing/src/stripe/webhook_event_handlers/checkout_session/checkout_session_completed_handler.rs diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/checkout_session/mod.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/checkout_session/mod.rs new file mode 100644 index 0000000000..cd65a66a86 --- /dev/null +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/checkout_session/mod.rs @@ -0,0 +1 @@ +pub (crate) mod checkout_session_completed_handler; \ No newline at end of file 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 ce343f6cef..7f71b8d9f7 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 @@ -5,22 +5,25 @@ use log::{error, warn}; use stripe::Subscription; /// Handle event type: 'customer.subscription.created' +/// Sent when the subscription is created. The subscription status may be incomplete if customer +/// authentication is required to complete the payment or if you set payment_behavior to +/// 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(); // 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 = subscription.customer - .as_ref() - .map(|c| expand_customer_id(c)); + 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()); - error!(">>> customer.subscription.created: {:?}, {:?}, {:?}", - stripe_subscription_id, maybe_stripe_customer_id, maybe_user_token); + error!(">>> customer.subscription.created: {:?}, {:?}, {:?}, {:?}", + stripe_subscription_id, maybe_stripe_customer_id, maybe_user_token, stripe_subscription_status); 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 b3070821b3..2442283a21 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 @@ -5,15 +5,14 @@ use log::{error, warn}; use stripe::Subscription; /// 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(); // 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 = subscription.customer - .as_ref() - .map(|c| expand_customer_id(c)); + 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) 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 20faa9efb9..74ba67abb4 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 @@ -8,19 +8,18 @@ use stripe::Subscription; pub fn customer_subscription_updated_handler(subscription: &Subscription) -> Result<(), StripeWebhookError> { 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 = subscription.customer - .as_ref() - .map(|c| expand_customer_id(c)); + 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); + error!(">>> customer.subscription.updated: {:?}, {:?}, {:?}, {:?}", + stripe_subscription_id, maybe_stripe_customer_id, maybe_user_token, stripe_subscription_status); Ok(()) } diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/invoice/invoice_updated_handler.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/invoice/invoice_updated_handler.rs new file mode 100644 index 0000000000..76ce65fe23 --- /dev/null +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/invoice/invoice_updated_handler.rs @@ -0,0 +1,31 @@ +use container_common::anyhow_result::AnyhowResult; +use crate::stripe::helpers::common_metadata_keys::METADATA_USER_TOKEN; +use crate::stripe::helpers::expand_customer_id::expand_customer_id; +use crate::stripe::webhook_event_handlers::stripe_webhook_error::StripeWebhookError; +use log::warn; +use stripe::Invoice; + +// Handle event type: 'invoice.updated' +// Sent when a payment succeeds or fails. +// If payment is successful the `paid` attribute is set to true and the `status` is `paid`. +// If payment fails, `paid` is set to false and the `status` remains `open`. +// Payment failures also trigger a invoice.payment_failed event. +pub fn invoice_updated_handler(invoice: &Invoice) -> Result<(), StripeWebhookError> { + + let is_paid = invoice.paid; + let invoice_status = invoice.status; + + // 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 = invoice.customer + .as_ref() + .map(|c| expand_customer_id(c)); + + // NB: Our internal user token. + let maybe_user_token = invoice.metadata.get(METADATA_USER_TOKEN) + .map(|t| t.to_string()); + + warn!(">>> invoice.updated: {:?}, {:?}, {:?}, {:?}", is_paid, invoice_status, maybe_stripe_customer_id, maybe_user_token); + + Ok(()) +} diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/invoice/mod.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/invoice/mod.rs index ad4a1eb7eb..45e6b69660 100644 --- a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/invoice/mod.rs +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/invoice/mod.rs @@ -1,3 +1,4 @@ pub (crate) mod invoice_paid_handler; pub (crate) mod invoice_payment_failed; pub (crate) mod invoice_payment_succeeded_handler; +pub (crate) mod invoice_updated_handler; diff --git a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/mod.rs b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/mod.rs index d444a3ffaa..4706b90d40 100644 --- a/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/mod.rs +++ b/crates/component_subsystem/billing/src/stripe/webhook_event_handlers/mod.rs @@ -1,6 +1,6 @@ pub mod stripe_webhook_error; -pub (crate) mod checkout_session_completed_handler; +pub (crate) mod checkout_session; pub (crate) mod customer; pub (crate) mod customer_subscription; pub (crate) mod invoice;