mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-14 13:52:11 +00:00
refactor
This commit is contained in:
1 parent
b5bc532b80
commit
d10cb54c62
24 files changed
+40
-374
No files matched your search
@@ -1,3 +1,4 @@
|
||||
pub mod common;
|
||||
pub mod stripe_artcraft_webhook_handler;
|
||||
pub mod webhook_event_handlers;
|
||||
pub mod stripe_artcraft_webhook_error;
|
||||
pub (crate) mod webhook_event_enrichment;
|
||||
File renamed without changes.
+2
-11
@@ -2,16 +2,8 @@ use actix_web::web::{Bytes, Data, Json};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
|
||||
use crate::endpoints::webhook::common::enriched_webhook_event::EnrichedWebhookEvent;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::checkout_session::checkout_session_completed_handler::checkout_session_completed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_created_handler::customer_subscription_created_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_updated_handler::customer_subscription_updated_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::handle_webhook_event_enrichment::handle_webhook_event_enrichment;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::handle_webhook_payload::handle_webhook_payload;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::invoice::invoice_paid_handler::invoice_paid_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::invoice::invoice_payment_failed::invoice_payment_failed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::payment_intent::payment_intent_succeeded_handler::payment_intent_succeeded_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::handle_webhook_event_enrichment::handle_webhook_event_enrichment;
|
||||
use crate::fulfillment::transactionally_fulfill_artcraft_billing_event::transactionally_fulfill_artcraft_billing_action;
|
||||
use crate::utils::artcraft_stripe_config::ArtcraftStripeConfigWithClient;
|
||||
use crate::utils::verify_stripe_webhook_ip_address::verify_stripe_webhook_ip_address;
|
||||
@@ -26,7 +18,6 @@ use sqlx::pool::PoolConnection;
|
||||
use sqlx::{Acquire, MySql, MySqlConnection, MySqlPool, Transaction};
|
||||
use std::sync::Arc;
|
||||
use stripe_webhook::{Event, EventObject, Webhook};
|
||||
|
||||
/*
|
||||
For one-off payments (eg. credits packs), we see this typical sequence of events:
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crate::configs::stripe_artcraft_metadata_keys::STRIPE_ARTCRAFT_METADATA_USER_TOKEN;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::utils::expand_ids::expand_customer_id::expand_customer_id;
|
||||
use stripe_checkout::CheckoutSession;
|
||||
|
||||
File renamed without changes.
+1
-1
@@ -3,7 +3,7 @@ use std::ops::{Add, Sub};
|
||||
use chrono::{Duration, NaiveDateTime};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::SubscriptionSummary;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::subscription_event_extractor::SubscriptionSummary;
|
||||
use reusable_types::stripe::stripe_subscription_status::StripeSubscriptionStatus;
|
||||
|
||||
// Push back the expiration date.
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
use crate::configs::get_artcraft_product_by_stripe_id_and_env::get_artcraft_product_by_stripe_id_and_env;
|
||||
use crate::configs::stripe_artcraft_generic_product_info::StripeArtcraftGenericProductInfo;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use enums::common::subscription_namespace::SubscriptionNamespace;
|
||||
use log::error;
|
||||
use mysql_queries::queries::users::user::update::update_user_record_with_new_stripe_customer_id::{update_user_record_with_new_stripe_customer_id, update_user_record_with_new_stripe_customer_id_with_connection};
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
use crate::configs::get_artcraft_product_by_stripe_id_and_env::get_artcraft_product_by_stripe_id_and_env;
|
||||
use crate::configs::stripe_artcraft_generic_product_info::StripeArtcraftGenericProductInfo;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use enums::common::subscription_namespace::SubscriptionNamespace;
|
||||
use log::error;
|
||||
use mysql_queries::queries::users::user_subscriptions::get_user_subscription_by_stripe_subscription_id::{get_user_subscription_by_stripe_subscription_id, get_user_subscription_by_stripe_subscription_id_with_connection};
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
use crate::configs::get_artcraft_product_by_stripe_id_and_env::get_artcraft_product_by_stripe_id_and_env;
|
||||
use crate::configs::stripe_artcraft_generic_product_info::StripeArtcraftGenericProductInfo;
|
||||
use crate::configs::subscriptions::get_artcraft_subscription_by_slug_and_env::get_artcraft_subscription_by_slug_and_env;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use enums::common::subscription_namespace::SubscriptionNamespace;
|
||||
use log::{error, info};
|
||||
use mysql_queries::queries::users::user::update::update_user_record_with_new_stripe_customer_id::{update_user_record_with_new_stripe_customer_id, update_user_record_with_new_stripe_customer_id_with_connection};
|
||||
File renamed without changes.
+1
-1
@@ -106,7 +106,7 @@ pub fn subscription_summary_extractor(subscription: &Subscription) -> AnyhowResu
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::subscription_event_extractor::subscription_summary_extractor;
|
||||
use reusable_types::stripe::stripe_recurring_interval::StripeRecurringInterval;
|
||||
use reusable_types::stripe::stripe_subscription_status::StripeSubscriptionStatus;
|
||||
use stripe_shared::Subscription;
|
||||
+11
-12
@@ -2,17 +2,16 @@ use crate::endpoints::webhook::common::artcraft_billing_action::BillingAction::I
|
||||
use crate::endpoints::webhook::common::artcraft_billing_action::{BillingAction, IgnoreableEventType};
|
||||
use crate::endpoints::webhook::common::enriched_webhook_event::EnrichedWebhookEvent;
|
||||
use crate::endpoints::webhook::common::webhook_event_log_summary::WebhookEventLogSummary;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::checkout_session::checkout_session_completed_handler::checkout_session_completed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_created_handler::customer_subscription_created_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_deleted_handler::customer_subscription_deleted_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_updated_handler::customer_subscription_updated_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::ignore_known_unwanted_events::ignore_known_unwanted_events;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::invoice::invoice_paid_handler::invoice_paid_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::invoice::invoice_payment_failed::invoice_payment_failed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::payment_intent::payment_intent_succeeded_handler::payment_intent_succeeded_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::payment_intent::payment_intent_succeeded_handler_2::payment_intent_succeeded_handler_2;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::checkout_session::checkout_session_completed_handler::checkout_session_completed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::customer_subscription_created_handler::customer_subscription_created_handler;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::customer_subscription_deleted_handler::customer_subscription_deleted_handler;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::customer_subscription::customer_subscription_updated_handler::customer_subscription_updated_handler;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::ignore_known_unwanted_events::ignore_known_unwanted_events;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::invoice::invoice_paid_handler::invoice_paid_handler;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::invoice::invoice_payment_failed::invoice_payment_failed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::payment_intent::payment_intent_succeeded_extractor::payment_intent_succeeded_extractor;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use log::{info, warn};
|
||||
use reusable_types::server_environment::ServerEnvironment;
|
||||
use sqlx::pool::PoolConnection;
|
||||
@@ -189,7 +188,7 @@ pub async fn handle_webhook_event_enrichment(
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, payment_intent);
|
||||
|
||||
// NB: This is responsible for enabling one-off payments (eg. credits packs).
|
||||
return payment_intent_succeeded_handler_2(
|
||||
return payment_intent_succeeded_extractor(
|
||||
&payment_intent,
|
||||
server_environment,
|
||||
stripe_client,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use log::info;
|
||||
use stripe_webhook::{Event, EventObject};
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::utils::expand_ids::expand_customer_id::expand_customer_id;
|
||||
use crate::utils::expand_ids::expand_subscription_id::expand_subscription_id;
|
||||
use stripe_shared::{Invoice, InvoiceStatus};
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use crate::configs::stripe_artcraft_metadata_keys::STRIPE_ARTCRAFT_METADATA_USER_TOKEN;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::utils::expand_ids::expand_customer_id::expand_customer_id;
|
||||
use stripe_shared::Invoice;
|
||||
|
||||
File renamed without changes.
-2
@@ -1,8 +1,6 @@
|
||||
|
||||
mod ignore_known_unwanted_events;
|
||||
pub (crate) mod handle_webhook_event_enrichment;
|
||||
pub mod handle_webhook_payload;
|
||||
pub mod stripe_artcraft_webhook_error;
|
||||
pub(crate) mod checkout_session;
|
||||
pub(crate) mod customer_subscription;
|
||||
pub(crate) mod invoice;
|
||||
+1
@@ -0,0 +1 @@
|
||||
pub(crate) mod payment_intent_succeeded_extractor;
|
||||
+3
-3
@@ -4,8 +4,8 @@ use crate::configs::subscriptions::stripe_artcraft_subscription_info::StripeArtc
|
||||
use crate::endpoints::webhook::common::artcraft_billing_action::{BillingAction, IgnoreableEventType, WalletCreditsPurchaseEvent};
|
||||
use crate::endpoints::webhook::common::enriched_webhook_event::EnrichedWebhookEvent;
|
||||
use crate::endpoints::webhook::common::webhook_event_log_summary::WebhookEventLogSummary;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::endpoints::webhook::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_enrichment::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::fulfillment::credits_pack::complete_credits_pack_purchase::complete_credits_pack_purchase;
|
||||
use crate::requests::lookup_purchase_from_payment_intent_success::lookup_purchase_from_payment_intent_success;
|
||||
use crate::utils::expand_ids::expand_customer_id::expand_customer_id;
|
||||
@@ -21,7 +21,7 @@ use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallets::WalletToken;
|
||||
|
||||
// Handle event type: 'payment_intent.succeeded'
|
||||
pub async fn payment_intent_succeeded_handler_2(
|
||||
pub async fn payment_intent_succeeded_extractor(
|
||||
payment_intent: &PaymentIntent,
|
||||
server_environment: ServerEnvironment,
|
||||
stripe_client: &Client,
|
||||
File renamed without changes.
-194
@@ -1,194 +0,0 @@
|
||||
use crate::endpoints::webhook::webhook_event_handlers::checkout_session::checkout_session_completed_handler::checkout_session_completed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_created_handler::customer_subscription_created_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_deleted_handler::customer_subscription_deleted_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::customer_subscription::customer_subscription_updated_handler::customer_subscription_updated_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::ignore_known_unwanted_events::ignore_known_unwanted_events;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::invoice::invoice_paid_handler::invoice_paid_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::invoice::invoice_payment_failed::invoice_payment_failed_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::payment_intent::payment_intent_succeeded_handler::payment_intent_succeeded_handler;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use log::{info, warn};
|
||||
use reusable_types::server_environment::ServerEnvironment;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{MySql, Transaction};
|
||||
use stripe::Client;
|
||||
use stripe_webhook::{Event, EventObject};
|
||||
|
||||
pub async fn handle_webhook_payload(
|
||||
transaction: &mut Transaction<'_, MySql>,
|
||||
stripe_client: &Client,
|
||||
server_environment: ServerEnvironment,
|
||||
webhook_payload: Event,
|
||||
stripe_event_type: &str,
|
||||
) -> Result<StripeArtcraftWebhookSummary, StripeArtcraftWebhookError> {
|
||||
|
||||
if let Some(summary) = ignore_known_unwanted_events(&webhook_payload) {
|
||||
return Ok(summary);
|
||||
}
|
||||
|
||||
let mut unhandled_event_type = false;
|
||||
|
||||
let mut webhook_summary = StripeArtcraftWebhookSummary {
|
||||
maybe_user_token: None,
|
||||
maybe_event_entity_id: None,
|
||||
maybe_stripe_customer_id: None,
|
||||
action_was_taken: false,
|
||||
should_ignore_retry: false,
|
||||
};
|
||||
|
||||
/*
|
||||
You usually need these:
|
||||
1. Subscription events: to track contract state (active, canceled, past_due, trialing, etc.).
|
||||
2. Invoice events: to track actual money flow and handle edge cases like failed payments, dunning, or refunds.
|
||||
|
||||
Minimal Set to Cover Payment Lifecycle
|
||||
- invoice.paid → confirm credits or entitlements
|
||||
- invoice.payment_failed → trigger dunning or downgrade access
|
||||
- customer.subscription.deleted → stop service when canceled
|
||||
- customer.subscription.updated → react to upgrades, downgrades, trial end
|
||||
- (Optionally) checkout.session.completed → provision on signup
|
||||
*/
|
||||
|
||||
match webhook_payload.data.object {
|
||||
|
||||
// =============== CHECKOUT SESSIONS ===============
|
||||
|
||||
// TODO: Provision the subscription here.
|
||||
EventObject::CheckoutSessionCompleted(checkout_session) => {
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, checkout_session);
|
||||
// TODO: DO NOT USE TO PROVISION SERVICE - USE `invoice.paid`.
|
||||
//
|
||||
// Checkout session completion is ideal for provisioning the service after checkout,
|
||||
// but will not be used for monthly subscription renewals, which are handled by
|
||||
// invoice.paid events.
|
||||
//
|
||||
// - `payment_status = {paid, unpaid, no_payment_required}` will let us know
|
||||
// if the funds are in our account. NOTE (2): this will not be `paid` for free
|
||||
// trials, but `invoice.paid` events will fire for free trials. (2): Async
|
||||
// methods like ACH will have the status `unpaid` until the payment
|
||||
// clears.
|
||||
//
|
||||
// - `subscription` will contain the ID of the subscription in subscription mode.
|
||||
//
|
||||
// - `metadata` - user token, etc.
|
||||
//
|
||||
// After the subscription signup succeeds, the customer returns to your website at the success_url,
|
||||
// which initiates a checkout.session.completed webhooks. When you receive a checkout.session.completed
|
||||
// event, you can provision the subscription. Continue to provision each month (if billing monthly) as
|
||||
// you receive invoice.paid events. If you receive an invoice.payment_failed event, notify your customer
|
||||
// and send them to the customer portal to update their payment method.
|
||||
//
|
||||
// Still need to ask about this. - does stripe say we can provision service here?
|
||||
// Do we know if this happens for upgrades/downgrades/renewals?
|
||||
// What about one-off payments?
|
||||
webhook_summary = checkout_session_completed_handler(checkout_session)?;
|
||||
}
|
||||
|
||||
// =============== CUSTOMER SUBSCRIPTIONS ===============
|
||||
|
||||
EventObject::CustomerSubscriptionCreated(subscription) => {
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, subscription);
|
||||
// DO NOT USE TO PROVISION SERVICE.
|
||||
//
|
||||
// This can be used to upsert the subscription record, but may be `incomplete` and unpaid.
|
||||
// This is good for subscription state, renewal dates, etc.
|
||||
//
|
||||
// - `id` of the subscription object
|
||||
// - `customer` id of the stripe customer object
|
||||
// - `status` has a rich list of subscription states (active, etc.)
|
||||
// - `items.data[0].price`
|
||||
// - `items.data[0].plan`
|
||||
// - billing_cycle_anchor
|
||||
// - `metadata` - user token, etc. that were attached to the subscription
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// TODO: add `maybe_billing_cycle_anchor`
|
||||
//
|
||||
webhook_summary = customer_subscription_created_handler(
|
||||
&subscription,
|
||||
server_environment,
|
||||
transaction,
|
||||
).await?;
|
||||
}
|
||||
|
||||
EventObject::CustomerSubscriptionUpdated(subscription) => {
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, subscription);
|
||||
webhook_summary = customer_subscription_updated_handler(
|
||||
&subscription,
|
||||
server_environment,
|
||||
transaction,
|
||||
).await?;
|
||||
}
|
||||
|
||||
EventObject::CustomerSubscriptionDeleted(subscription) => {
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, subscription);
|
||||
webhook_summary = customer_subscription_deleted_handler(
|
||||
&subscription,
|
||||
server_environment,
|
||||
transaction,
|
||||
).await?;
|
||||
}
|
||||
|
||||
// =============== INVOICES ===============
|
||||
|
||||
EventObject::InvoiceCreated(_invoice) => {
|
||||
// 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
|
||||
}
|
||||
|
||||
EventObject::InvoicePaid(invoice) => {
|
||||
// TODO: How do we handle multiple subscriptions?
|
||||
// TODO: How do we handle inactive subscriptions vs. payment failed ones?
|
||||
// 'invoice.paid'
|
||||
// - 'customer' - the customer id
|
||||
// - 'status = paid'
|
||||
// - (optional) 'subscription_details.subscription' - the stripe subscription id (if subscription)
|
||||
// - (optional) 'subscription_details.metadata' - Artcraft user token, etc.
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, invoice);
|
||||
webhook_summary = invoice_paid_handler(&invoice)?;
|
||||
}
|
||||
|
||||
EventObject::InvoicePaymentFailed(invoice) => {
|
||||
// TODO: Halt service.
|
||||
// TODO: in wallet schema, hold a 'lifetime_total_credits_used' - no, that wastes space. `credits_used` then SUM() for reports
|
||||
// When we detect invoice payment failures, we need to disable
|
||||
// subscription services.
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, invoice);
|
||||
webhook_summary = invoice_payment_failed_handler(&invoice)?;
|
||||
}
|
||||
|
||||
// =============== PAYMENT INTENTS ===============
|
||||
|
||||
EventObject::PaymentIntentSucceeded(payment_intent) => {
|
||||
info!("Event: {}, data: {:?}", webhook_payload.type_, payment_intent);
|
||||
webhook_summary = payment_intent_succeeded_handler(
|
||||
&payment_intent,
|
||||
server_environment,
|
||||
stripe_client,
|
||||
transaction,
|
||||
).await?;
|
||||
}
|
||||
|
||||
// =============== Ignored ===============
|
||||
|
||||
_ => {
|
||||
unhandled_event_type = true;
|
||||
},
|
||||
}
|
||||
|
||||
// To play with the payload contents as JSON:
|
||||
// let json = serde_json::ser::to_string(&event_payload).unwrap();
|
||||
// info!("event payload as json: {}", json);
|
||||
|
||||
if unhandled_event_type {
|
||||
warn!("Unhandled Stripe webhook event type: {} ({:?})",
|
||||
&stripe_event_type,
|
||||
&webhook_payload.type_);
|
||||
}
|
||||
|
||||
Ok(webhook_summary)
|
||||
}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
pub(crate) mod payment_intent_succeeded_handler;
|
||||
pub(crate) mod payment_intent_succeeded_handler_2;
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
use crate::configs::get_artcraft_product_by_stripe_id_and_env::get_artcraft_product_by_stripe_id_and_env;
|
||||
use crate::configs::stripe_artcraft_generic_product_info::StripeArtcraftGenericProductInfo;
|
||||
use crate::configs::stripe_artcraft_metadata_keys::STRIPE_ARTCRAFT_METADATA_USER_TOKEN;
|
||||
use crate::configs::subscriptions::stripe_artcraft_subscription_info::StripeArtcraftSubscriptionInfo;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_summary::StripeArtcraftWebhookSummary;
|
||||
use crate::fulfillment::credits_pack::complete_credits_pack_purchase::complete_credits_pack_purchase;
|
||||
use crate::requests::lookup_purchase_from_payment_intent_success::lookup_purchase_from_payment_intent_success;
|
||||
use crate::utils::expand_ids::expand_customer_id::expand_customer_id;
|
||||
use log::{error, info, warn};
|
||||
use reusable_types::server_environment::ServerEnvironment;
|
||||
use sqlx::{MySql, Transaction};
|
||||
use stripe::Client;
|
||||
use stripe_checkout::checkout_session::ListCheckoutSession;
|
||||
use stripe_shared::PaymentIntent;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
// Handle event type: 'payment_intent.succeeded'
|
||||
pub async fn payment_intent_succeeded_handler(
|
||||
payment_intent: &PaymentIntent,
|
||||
server_environment: ServerEnvironment,
|
||||
stripe_client: &Client,
|
||||
transaction: &mut Transaction<'_, MySql>,
|
||||
) -> Result<StripeArtcraftWebhookSummary, StripeArtcraftWebhookError> {
|
||||
|
||||
let payment_intent_id = payment_intent.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 = payment_intent.customer
|
||||
.as_ref()
|
||||
.map(|c| expand_customer_id(c));
|
||||
|
||||
// NB: Our internal user token.
|
||||
let maybe_user_token = payment_intent.metadata.get(STRIPE_ARTCRAFT_METADATA_USER_TOKEN)
|
||||
.map(|t| t.to_string());
|
||||
|
||||
// TODO: Multiple ways to get this; better ways to get this
|
||||
let user_token = match &maybe_user_token {
|
||||
Some(token) => UserToken::new_from_str(token),
|
||||
None => {
|
||||
warn!("No user token found in payment intent metadata. Cannot proceed.");
|
||||
return Err(StripeArtcraftWebhookError::BadRequest("no user token in payment intent".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let mut should_ignore_retry = false;
|
||||
let mut action_was_taken = false;
|
||||
|
||||
// Payment intent events are bare. They have absolutely no context about what they were for.
|
||||
// No products, no checkout sessions, etc. We'll have to look them up on success.
|
||||
|
||||
let payment_succeeded = match payment_intent.status {
|
||||
stripe_shared::PaymentIntentStatus::Succeeded => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
||||
if payment_succeeded {
|
||||
info!("Payment intent succeeded. Looking up payment...");
|
||||
|
||||
|
||||
// TODO TODO TODO
|
||||
// TODO TODO TODO - DO NOT DO A TRANSACTION WITH HTTP REQUESTS!!! Move this logic out!
|
||||
// TODO TODO TODO
|
||||
// TODO: Summarize the webhook into an internal event, then process that in an atomic transaction.
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
warn!("DO NOT KEEP THIS REQUEST WITHIN A TRANSACTION - MOVE IT OUT!!!");
|
||||
|
||||
let purchase = lookup_purchase_from_payment_intent_success(&payment_intent_id, stripe_client)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error looking up purchase from payment intent {}: {:?}", &payment_intent_id, err);
|
||||
StripeArtcraftWebhookError::ServerError("error looking up purchase".to_string())
|
||||
})?;
|
||||
|
||||
if purchase.has_invoice {
|
||||
// Subscription purchase. Let `invoice.paid` event handle this instead.
|
||||
should_ignore_retry = true;
|
||||
action_was_taken = false;
|
||||
} else {
|
||||
match get_artcraft_product_by_stripe_id_and_env(&purchase.product_id, server_environment) {
|
||||
None => {
|
||||
error!("Could not find product for ID: {}", &purchase.product_id);
|
||||
return Err(StripeArtcraftWebhookError::ServerError("unknown product".to_string()));
|
||||
}
|
||||
Some(StripeArtcraftGenericProductInfo::Subscription(subscription)) => {
|
||||
info!("Do not handle subscriptions as one-off payments: {}", purchase.product_id);
|
||||
should_ignore_retry = true;
|
||||
action_was_taken = false;
|
||||
}
|
||||
Some(StripeArtcraftGenericProductInfo::CreditsPack(credits_pack)) => {
|
||||
info!("Fulfilling one-time payment...");
|
||||
complete_credits_pack_purchase(
|
||||
&user_token,
|
||||
credits_pack,
|
||||
purchase.quantity,
|
||||
transaction,
|
||||
).await?;
|
||||
|
||||
should_ignore_retry = true;
|
||||
action_was_taken = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(StripeArtcraftWebhookSummary {
|
||||
maybe_user_token,
|
||||
maybe_event_entity_id: Some(payment_intent_id),
|
||||
maybe_stripe_customer_id,
|
||||
action_was_taken,
|
||||
should_ignore_retry,
|
||||
})
|
||||
}
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
use crate::endpoints::webhook::webhook_event_handlers::stripe_artcraft_webhook_error::StripeArtcraftWebhookError;
|
||||
use crate::utils::expand_ids::expand_product_id::expand_product_id;
|
||||
use log::{error, warn};
|
||||
use stripe::{Client, RequestStrategy, StripeRequest};
|
||||
|
||||
Reference in new issue
Block a user