From 3efe1afa48cbd2f439a4c0620718c5bfbdd549e4 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 1 Nov 2022 23:10:12 -0400 Subject: [PATCH] Migrations to change/rename some columns --- .../up.sql | 5 +- .../down.sql | 11 ++ .../up.sql | 11 ++ .../down.sql | 7 ++ .../up.sql | 7 ++ .../user_tables/user_subscriptions.sql | 102 ++++++++++++++++++ .../{ => user_tables}/users.sql | 0 .../customer_subscription/common.rs | 4 +- .../customer_subscription_created_handler.rs | 88 +++++++-------- .../customer_subscription_deleted_handler.rs | 64 +++++------ .../customer_subscription_updated_handler.rs | 70 ++++++------ .../subscription_event_extractor.rs | 4 +- ..._subscription_by_stripe_subscription_id.rs | 24 ++--- .../list_active_user_subscriptions.rs | 15 ++- .../upsert_user_subscription_by_stripe_id.rs | 30 +++--- 15 files changed, 291 insertions(+), 151 deletions(-) create mode 100644 _migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/down.sql create mode 100644 _migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/up.sql create mode 100644 _migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/down.sql create mode 100644 _migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/up.sql create mode 100644 _migrations_squashed/user_tables/user_subscriptions.sql rename _migrations_squashed/{ => user_tables}/users.sql (100%) diff --git a/_migrations/2022-09-17-173657_user_subscriptions/up.sql b/_migrations/2022-09-17-173657_user_subscriptions/up.sql index 0fa67b2956..d04a53cd87 100644 --- a/_migrations/2022-09-17-173657_user_subscriptions/up.sql +++ b/_migrations/2022-09-17-173657_user_subscriptions/up.sql @@ -14,16 +14,17 @@ CREATE TABLE user_subscriptions ( -- The internal user associated with the subscription. -- This is nullable since it's possible we mess up and don't set it on the Stripe objects. -- We want the latitude to correct mistakes in the future. (Hopefully this doesn't bite me later.) + -- **** NB/NOTE: THIS COLUMN WAS RENAMED TO `user_token` and made NON NULLABLE. **** maybe_user_token VARCHAR(32) DEFAULT NULL, -- If we host multiple sites with distinct categories, this will enable us to segregate -- subscriptions. This is effectively a subscriptions namespace. - -- TODO: Rename 'namespace' + -- **** NB/NOTE: THIS COLUMN WAS RENAMED TO `subscription_namespace` **** subscription_category VARCHAR(32) NOT NULL, -- This is the identifier for the actual type of subscription. -- These will not be defined in the database, but rather the source code. - -- TODO: Rename 'slug' + -- **** NB/NOTE: THIS COLUMN WAS RENAMED TO `subscription_product_slug` **** subscription_product_key VARCHAR(32) NOT NULL, -- ========== STRIPE DATA ========== diff --git a/_migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/down.sql b/_migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/down.sql new file mode 100644 index 0000000000..1891346498 --- /dev/null +++ b/_migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/down.sql @@ -0,0 +1,11 @@ +-- noinspection SqlDialectInspectionForFile +-- noinspection SqlNoDataSourceInspectionForFile +-- noinspection SqlResolveForFile + +ALTER TABLE user_subscriptions CHANGE subscription_namespace subscription_category VARCHAR(32) NOT NULL; + +ALTER TABLE user_subscriptions CHANGE subscription_product_slug subscription_product_key VARCHAR(32) NOT NULL; + +ALTER TABLE user_subscriptions RENAME INDEX index_subscription_namespace TO index_subscription_category; + +ALTER TABLE user_subscriptions RENAME INDEX index_subscription_product_slug TO index_subscription_product_key; diff --git a/_migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/up.sql b/_migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/up.sql new file mode 100644 index 0000000000..62f3c636bb --- /dev/null +++ b/_migrations/2022-11-02-024322_alter_table_user_subscriptions_rename_columns/up.sql @@ -0,0 +1,11 @@ +-- noinspection SqlDialectInspectionForFile +-- noinspection SqlNoDataSourceInspectionForFile +-- noinspection SqlResolveForFile + +ALTER TABLE user_subscriptions CHANGE subscription_category subscription_namespace VARCHAR(32) NOT NULL; + +ALTER TABLE user_subscriptions CHANGE subscription_product_key subscription_product_slug VARCHAR(32) NOT NULL; + +ALTER TABLE user_subscriptions RENAME INDEX index_subscription_category TO index_subscription_namespace; + +ALTER TABLE user_subscriptions RENAME INDEX index_subscription_product_key TO index_subscription_product_slug; diff --git a/_migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/down.sql b/_migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/down.sql new file mode 100644 index 0000000000..03a5ed2822 --- /dev/null +++ b/_migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/down.sql @@ -0,0 +1,7 @@ +-- noinspection SqlDialectInspectionForFile +-- noinspection SqlNoDataSourceInspectionForFile +-- noinspection SqlResolveForFile + +ALTER TABLE user_subscriptions CHANGE user_token maybe_user_token VARCHAR(255) DEFAULT NULL; + +ALTER TABLE user_subscriptions RENAME INDEX fk_user_token TO fk_maybe_user_token; diff --git a/_migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/up.sql b/_migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/up.sql new file mode 100644 index 0000000000..4fb862e79d --- /dev/null +++ b/_migrations/2022-11-02-024525_alter_table_user_subscriptions_make_user_non_nullable/up.sql @@ -0,0 +1,7 @@ +-- noinspection SqlDialectInspectionForFile +-- noinspection SqlNoDataSourceInspectionForFile +-- noinspection SqlResolveForFile + +ALTER TABLE user_subscriptions CHANGE maybe_user_token user_token VARCHAR(255) NOT NULL; + +ALTER TABLE user_subscriptions RENAME INDEX fk_maybe_user_token TO fk_user_token; diff --git a/_migrations_squashed/user_tables/user_subscriptions.sql b/_migrations_squashed/user_tables/user_subscriptions.sql new file mode 100644 index 0000000000..fb92810ff7 --- /dev/null +++ b/_migrations_squashed/user_tables/user_subscriptions.sql @@ -0,0 +1,102 @@ +-- noinspection SqlDialectInspectionForFile +-- noinspection SqlNoDataSourceInspectionForFile +-- noinspection SqlResolveForFile + +CREATE TABLE user_subscriptions ( + -- Not used for anything except replication. + id BIGINT(20) NOT NULL AUTO_INCREMENT, + + -- Effective "primary key" (PUBLIC) + token VARCHAR(32) NOT NULL, + + -- ========== INTERNAL SUBSCRIPTION DETAILS ========== + + -- The internal user associated with the subscription. + -- This is nullable since it's possible we mess up and don't set it on the Stripe objects. + -- We want the latitude to correct mistakes in the future. (Hopefully this doesn't bite me later.) + user_token VARCHAR(32) DEFAULT NULL, + + -- If we host multiple sites with distinct categories, this will enable us to segregate + -- subscriptions. This is effectively a subscriptions namespace. + subscription_namespace VARCHAR(32) NOT NULL, + + -- This is the identifier for the actual type of subscription. + -- These will not be defined in the database, but rather the source code. + subscription_product_slug VARCHAR(32) NOT NULL, + + -- ========== STRIPE DATA ========== + + -- The stripe IDs (nullable in case we can't associate or if we use paypal) + + -- You can safely assume object IDs we generate will never exceed 255 characters, + -- but you should be able to handle IDs of up to that length. If for example you’re using MySQL, + -- you should store IDs in a VARCHAR(255) COLLATE utf8_bin column (the COLLATE configuration ensures + -- case-sensitivity in lookups). + -- https://stackoverflow.com/a/61809494 + + -- When we receive a webhook update, we'll look it up by this key. + -- This has a UNIQUE index. + maybe_stripe_subscription_id VARCHAR(255) DEFAULT NULL, + + maybe_stripe_customer_id VARCHAR(255) DEFAULT NULL, + + maybe_stripe_product_id VARCHAR(255) DEFAULT NULL, + + maybe_stripe_price_id VARCHAR(255) DEFAULT NULL, + + -- How frequently the subscription is billed and updated + maybe_stripe_recurring_interval VARCHAR(32) DEFAULT NULL, + + -- Subscription object status enum as string + maybe_stripe_subscription_status VARCHAR(32) DEFAULT NULL, + + maybe_stripe_is_production BOOLEAN DEFAULT NULL, + + -- ========== VECTOR CLOCK ========== + + -- Incremented with every update. + version INT NOT NULL DEFAULT 0, + + -- ========== RECORD TIMESTAMPS ========== + + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted_at TIMESTAMP DEFAULT NULL, + + -- ========== SUBSCRIPTION TIMESTAMPS ========== + + -- When the subscription was first created (Stripe's subscription "start_date"). + -- This may predate the Stripe object `created` timestamp due to backdating. + subscription_start_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- We'll use this to determine if the subscription is active. + -- + -- Always compare against MySQL's clock rather than the app's clock so that we + -- don't get weird clock skew behaviors across multiple requests. + -- + -- Technically the user can have multiple active subscriptions of the same type, but we'll + -- try to prevent creation of new subscriptions of the same type. + subscription_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- Billing periods. + -- Maybe useful for debugging. + current_billing_period_start_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + current_billing_period_end_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- Subscription cancellation (future and past) + maybe_cancel_at TIMESTAMP DEFAULT NULL, + maybe_canceled_at TIMESTAMP DEFAULT NULL, + + -- ========== INDICES ========== + PRIMARY KEY (id), + UNIQUE KEY (token), + UNIQUE KEY (maybe_stripe_subscription_id), + KEY index_subscription_namespace (subscription_namespace), + KEY index_subscription_product_slug (subscription_product_slug), + KEY index_maybe_stripe_customer_id (maybe_stripe_customer_id), + KEY index_maybe_stripe_product_id (maybe_stripe_product_id), + KEY index_maybe_stripe_subscription_status (maybe_stripe_subscription_status), + KEY fk_user_token (user_token), + KEY index_subscription_expires_at (subscription_expires_at) + +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; diff --git a/_migrations_squashed/users.sql b/_migrations_squashed/user_tables/users.sql similarity index 100% rename from _migrations_squashed/users.sql rename to _migrations_squashed/user_tables/users.sql diff --git a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/common.rs b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/common.rs index c6cb45fec9..177c319247 100644 --- a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/common.rs +++ b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/common.rs @@ -1,8 +1,8 @@ /// Subscription categories are used to namespace subscriptions. /// Integrations should always provide a default even if this isn't used. -pub const UNKNOWN_SUBSCRIPTION_CATEGORY : &'static str = "unknown"; +pub const UNKNOWN_SUBSCRIPTION_NAMESPACE: &'static str = "unknown"; /// The integrated system has internal unique string key identifiers for /// each subscription plan. -pub const UNKNOWN_SUBSCRIPTION_PRODUCT_KEY : &'static str = "unknown"; +pub const UNKNOWN_SUBSCRIPTION_PRODUCT_SLUG: &'static str = "unknown"; diff --git a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs index 25832bcbac..a22e6829ba 100644 --- a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs +++ b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_created_handler.rs @@ -1,5 +1,5 @@ use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date; -use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::common::{UNKNOWN_SUBSCRIPTION_CATEGORY, UNKNOWN_SUBSCRIPTION_PRODUCT_KEY}; +use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::common::{UNKNOWN_SUBSCRIPTION_NAMESPACE, UNKNOWN_SUBSCRIPTION_PRODUCT_SLUG}; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::stripe_webhook_error::StripeWebhookError; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::stripe_webhook_summary::StripeWebhookSummary; @@ -56,57 +56,59 @@ pub async fn customer_subscription_created_handler( StripeWebhookError::ServerError // NB: This was probably *our* fault. })?; - let mut subscription_category = UNKNOWN_SUBSCRIPTION_CATEGORY; - let mut subscription_product_key = UNKNOWN_SUBSCRIPTION_PRODUCT_KEY; + let mut subscription_namespace = UNKNOWN_SUBSCRIPTION_NAMESPACE; + let mut subscription_product_slug = UNKNOWN_SUBSCRIPTION_PRODUCT_SLUG; if let Some(ref internal_product) = maybe_internal_subscription_product { - subscription_category = &internal_product.subscription_category; - subscription_product_key = &internal_product.subscription_product_key; + subscription_namespace = &internal_product.subscription_category; + subscription_product_slug = &internal_product.subscription_product_key; } - // TODO: record cancel_at (future_cancel_at), canceled_at, ended_at (if subscription ended, when it ended), start_date + if let Some(user_token) = summary.user_token.as_deref() { + // TODO: record cancel_at (future_cancel_at), canceled_at, ended_at (if subscription ended, when it ended), start_date - let upsert = UpsertUserSubscription { - stripe_subscription_id: &summary.stripe_subscription_id, - maybe_user_token: summary.user_token.as_deref(), - subscription_category, - subscription_product_key, - maybe_stripe_customer_id: Some(&summary.stripe_customer_id), - maybe_stripe_product_id: Some(&summary.stripe_product_id), - maybe_stripe_price_id: Some(&summary.stripe_price_id), - maybe_stripe_recurring_interval: Some(summary.subscription_interval), - maybe_stripe_subscription_status: Some(summary.stripe_subscription_status), - maybe_stripe_is_production: Some(summary.stripe_is_production), - subscription_start_at: summary.subscription_start_date, - current_billing_period_start_at: summary.current_billing_period_start, - current_billing_period_end_at: summary.current_billing_period_end, - subscription_expires_at: calculate_subscription_end_date(&summary), - maybe_cancel_at: summary.maybe_cancel_at, - maybe_canceled_at: summary.maybe_canceled_at, - }; - - let _r = upsert.upsert(mysql_pool) - .await - .map_err(|err| { - error!("Mysql error: {:?}", err); - StripeWebhookError::ServerError - })?; - - if let Some(user_token) = summary.user_token.as_deref() { - // TODO: Should we care if a user accidentally gets two stripe customer IDs and this - // overwrites one of them? - update_user_record_with_new_stripe_customer_id( - mysql_pool, + let upsert = UpsertUserSubscription { + stripe_subscription_id: &summary.stripe_subscription_id, user_token, - Some(&summary.stripe_customer_id)) - .await - .map_err(|err| { + subscription_namespace, + subscription_product_slug, + maybe_stripe_customer_id: Some(&summary.stripe_customer_id), + maybe_stripe_product_id: Some(&summary.stripe_product_id), + maybe_stripe_price_id: Some(&summary.stripe_price_id), + maybe_stripe_recurring_interval: Some(summary.subscription_interval), + maybe_stripe_subscription_status: Some(summary.stripe_subscription_status), + maybe_stripe_is_production: Some(summary.stripe_is_production), + subscription_start_at: summary.subscription_start_date, + current_billing_period_start_at: summary.current_billing_period_start, + current_billing_period_end_at: summary.current_billing_period_end, + subscription_expires_at: calculate_subscription_end_date(&summary), + maybe_cancel_at: summary.maybe_cancel_at, + maybe_canceled_at: summary.maybe_canceled_at, + }; + + let _r = upsert.upsert(mysql_pool) + .await + .map_err(|err| { error!("Mysql error: {:?}", err); StripeWebhookError::ServerError - })?; - } + })?; + + + // TODO: Should we care if a user accidentally gets two stripe customer IDs and this + // overwrites one of them? + update_user_record_with_new_stripe_customer_id( + mysql_pool, + user_token, + Some(&summary.stripe_customer_id)) + .await + .map_err(|err| { + error!("Mysql error: {:?}", err); + StripeWebhookError::ServerError + })?; + + result.action_was_taken = true; + } - result.action_was_taken = true; result.should_ignore_retry = true; Ok(result) diff --git a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs index 44c3401e89..3ab29bec5b 100644 --- a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs +++ b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_deleted_handler.rs @@ -1,5 +1,5 @@ use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date; -use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::common::{UNKNOWN_SUBSCRIPTION_CATEGORY, UNKNOWN_SUBSCRIPTION_PRODUCT_KEY}; +use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::common::{UNKNOWN_SUBSCRIPTION_NAMESPACE, UNKNOWN_SUBSCRIPTION_PRODUCT_SLUG}; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::stripe_webhook_error::StripeWebhookError; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::stripe_webhook_summary::StripeWebhookSummary; @@ -36,12 +36,12 @@ pub async fn customer_subscription_deleted_handler( StripeWebhookError::ServerError // NB: This was probably *our* fault. })?; - let mut subscription_category = UNKNOWN_SUBSCRIPTION_CATEGORY; - let mut subscription_product_key = UNKNOWN_SUBSCRIPTION_PRODUCT_KEY; + let mut subscription_namespace = UNKNOWN_SUBSCRIPTION_NAMESPACE; + let mut subscription_product_slug = UNKNOWN_SUBSCRIPTION_PRODUCT_SLUG; if let Some(ref internal_product) = maybe_internal_subscription_product { - subscription_category = &internal_product.subscription_category; - subscription_product_key = &internal_product.subscription_product_key; + subscription_namespace = &internal_product.subscription_category; + subscription_product_slug = &internal_product.subscription_product_key; } // NB: It's possible to receive events out of order. @@ -66,33 +66,37 @@ pub async fn customer_subscription_deleted_handler( // NB: Even if we haven't received a record before, we should still be able to "tombstone" it // once we detect the deletion. if should_process_update { - let upsert = UpsertUserSubscription { - stripe_subscription_id: &summary.stripe_subscription_id, - maybe_user_token: summary.user_token.as_deref(), - subscription_category, - subscription_product_key, - maybe_stripe_customer_id: Some(&summary.stripe_customer_id), - maybe_stripe_product_id: Some(&summary.stripe_product_id), - maybe_stripe_price_id: Some(&summary.stripe_price_id), - maybe_stripe_recurring_interval: Some(summary.subscription_interval), - maybe_stripe_subscription_status: Some(summary.stripe_subscription_status), - maybe_stripe_is_production: Some(summary.stripe_is_production), - subscription_start_at: summary.subscription_start_date, - current_billing_period_start_at: summary.current_billing_period_start, - current_billing_period_end_at: summary.current_billing_period_end, - subscription_expires_at: calculate_subscription_end_date(&summary), - maybe_cancel_at: summary.maybe_cancel_at, - maybe_canceled_at: summary.maybe_canceled_at, - }; + if let Some(user_token) = summary.user_token.as_deref() { - let _r = upsert.upsert(mysql_pool) - .await - .map_err(|err| { - error!("Mysql error: {:?}", err); - StripeWebhookError::ServerError - })?; + let upsert = UpsertUserSubscription { + stripe_subscription_id: &summary.stripe_subscription_id, + user_token, + subscription_namespace, + subscription_product_slug, + maybe_stripe_customer_id: Some(&summary.stripe_customer_id), + maybe_stripe_product_id: Some(&summary.stripe_product_id), + maybe_stripe_price_id: Some(&summary.stripe_price_id), + maybe_stripe_recurring_interval: Some(summary.subscription_interval), + maybe_stripe_subscription_status: Some(summary.stripe_subscription_status), + maybe_stripe_is_production: Some(summary.stripe_is_production), + subscription_start_at: summary.subscription_start_date, + current_billing_period_start_at: summary.current_billing_period_start, + current_billing_period_end_at: summary.current_billing_period_end, + subscription_expires_at: calculate_subscription_end_date(&summary), + maybe_cancel_at: summary.maybe_cancel_at, + maybe_canceled_at: summary.maybe_canceled_at, + }; + + let _r = upsert.upsert(mysql_pool) + .await + .map_err(|err| { + error!("Mysql error: {:?}", err); + StripeWebhookError::ServerError + })?; + + action_was_taken = true; + } - action_was_taken = true; should_ignore_retry = true; } diff --git a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs index a74a48994f..20f7aa452f 100644 --- a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs +++ b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/customer_subscription_updated_handler.rs @@ -1,5 +1,5 @@ use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::calculate_subscription_end_date::calculate_subscription_end_date; -use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::common::{UNKNOWN_SUBSCRIPTION_CATEGORY, UNKNOWN_SUBSCRIPTION_PRODUCT_KEY}; +use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::common::{UNKNOWN_SUBSCRIPTION_NAMESPACE, UNKNOWN_SUBSCRIPTION_PRODUCT_SLUG}; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::customer_subscription::subscription_event_extractor::subscription_summary_extractor; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::stripe_webhook_error::StripeWebhookError; use crate::stripe::http_endpoints::webhook::webhook_event_handlers::stripe_webhook_summary::StripeWebhookSummary; @@ -37,12 +37,12 @@ pub async fn customer_subscription_updated_handler( StripeWebhookError::ServerError // NB: This was probably *our* fault. })?; - let mut subscription_category = UNKNOWN_SUBSCRIPTION_CATEGORY; - let mut subscription_product_key = UNKNOWN_SUBSCRIPTION_PRODUCT_KEY; + let mut subscription_namespace = UNKNOWN_SUBSCRIPTION_NAMESPACE; + let mut subscription_product_slug = UNKNOWN_SUBSCRIPTION_PRODUCT_SLUG; if let Some(ref internal_product) = maybe_internal_subscription_product { - subscription_category = &internal_product.subscription_category; - subscription_product_key = &internal_product.subscription_product_key; + subscription_namespace = &internal_product.subscription_category; + subscription_product_slug = &internal_product.subscription_product_key; } // NB: It's possible to receive events out of order. @@ -65,33 +65,34 @@ pub async fn customer_subscription_updated_handler( } if should_process_update { - let upsert = UpsertUserSubscription { - stripe_subscription_id: &summary.stripe_subscription_id, - maybe_user_token: summary.user_token.as_deref(), - subscription_category, - subscription_product_key, - maybe_stripe_customer_id: Some(&summary.stripe_customer_id), - maybe_stripe_product_id: Some(&summary.stripe_product_id), - maybe_stripe_price_id: Some(&summary.stripe_price_id), - maybe_stripe_recurring_interval: Some(summary.subscription_interval), - maybe_stripe_subscription_status: Some(summary.stripe_subscription_status), - maybe_stripe_is_production: Some(summary.stripe_is_production), - subscription_start_at: summary.subscription_start_date, - current_billing_period_start_at: summary.current_billing_period_start, - current_billing_period_end_at: summary.current_billing_period_end, - subscription_expires_at: calculate_subscription_end_date(&summary), - maybe_cancel_at: summary.maybe_cancel_at, - maybe_canceled_at: summary.maybe_canceled_at, - }; - - let _r = upsert.upsert(mysql_pool) - .await - .map_err(|err| { - error!("Mysql error: {:?}", err); - StripeWebhookError::ServerError - })?; - if let Some(user_token) = summary.user_token.as_deref() { + + let upsert = UpsertUserSubscription { + stripe_subscription_id: &summary.stripe_subscription_id, + user_token: &user_token, + subscription_namespace, + subscription_product_slug, + maybe_stripe_customer_id: Some(&summary.stripe_customer_id), + maybe_stripe_product_id: Some(&summary.stripe_product_id), + maybe_stripe_price_id: Some(&summary.stripe_price_id), + maybe_stripe_recurring_interval: Some(summary.subscription_interval), + maybe_stripe_subscription_status: Some(summary.stripe_subscription_status), + maybe_stripe_is_production: Some(summary.stripe_is_production), + subscription_start_at: summary.subscription_start_date, + current_billing_period_start_at: summary.current_billing_period_start, + current_billing_period_end_at: summary.current_billing_period_end, + subscription_expires_at: calculate_subscription_end_date(&summary), + maybe_cancel_at: summary.maybe_cancel_at, + maybe_canceled_at: summary.maybe_canceled_at, + }; + + let _r = upsert.upsert(mysql_pool) + .await + .map_err(|err| { + error!("Mysql error: {:?}", err); + StripeWebhookError::ServerError + })?; + // TODO: Should we care if a user accidentally gets two stripe customer IDs and this // overwrites one of them? update_user_record_with_new_stripe_customer_id( @@ -103,12 +104,13 @@ pub async fn customer_subscription_updated_handler( error!("Mysql error: {:?}", err); StripeWebhookError::ServerError })?; - } - action_was_taken = true; - should_ignore_retry = true; + action_was_taken = true; + } } + should_ignore_retry = true; + Ok(StripeWebhookSummary { maybe_user_token: summary.user_token, maybe_event_entity_id: Some(summary.stripe_subscription_id), diff --git a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs index 110852fe2c..17a000fa6c 100644 --- a/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs +++ b/crates/component_subsystem/billing/src/stripe/http_endpoints/webhook/webhook_event_handlers/customer_subscription/subscription_event_extractor.rs @@ -104,8 +104,8 @@ pub fn subscription_summary_extractor(subscription: &Subscription) -> AnyhowResu subscription_start_date: start_date, current_billing_period_start: period_start, current_billing_period_end: period_end, - maybe_cancel_at: maybe_cancel_at, - maybe_canceled_at: maybe_canceled_at, + maybe_cancel_at, + maybe_canceled_at, }) } diff --git a/crates/lib/database_queries/src/queries/users/user_subscriptions/get_user_subscription_by_stripe_subscription_id.rs b/crates/lib/database_queries/src/queries/users/user_subscriptions/get_user_subscription_by_stripe_subscription_id.rs index 6074b3a3d7..7485ffdd4a 100644 --- a/crates/lib/database_queries/src/queries/users/user_subscriptions/get_user_subscription_by_stripe_subscription_id.rs +++ b/crates/lib/database_queries/src/queries/users/user_subscriptions/get_user_subscription_by_stripe_subscription_id.rs @@ -8,9 +8,9 @@ use sqlx::MySqlPool; pub struct UserSubscription { pub token: String, - pub maybe_user_token: Option, - pub subscription_category: String, - pub subscription_product_key: String, + pub user_token: String, + pub subscription_namespace: String, + pub subscription_product_slug: String, pub maybe_stripe_subscription_id: Option, pub maybe_stripe_product_id: Option, pub maybe_stripe_customer_id: Option, @@ -36,9 +36,9 @@ pub async fn get_user_subscription_by_stripe_subscription_id( r#" SELECT token, - maybe_user_token, - subscription_category, - subscription_product_key, + user_token, + subscription_namespace, + subscription_product_slug, maybe_stripe_subscription_id, maybe_stripe_customer_id, maybe_stripe_product_id, @@ -65,9 +65,9 @@ WHERE Ok(r) => { Ok(Some(UserSubscription { token: r.token, - maybe_user_token: r.maybe_user_token, - subscription_category: r.subscription_category, - subscription_product_key: r.subscription_product_key, + user_token: r.user_token, + subscription_namespace: r.subscription_namespace, + subscription_product_slug: r.subscription_product_slug, maybe_stripe_subscription_id: r.maybe_stripe_subscription_id, maybe_stripe_product_id: r.maybe_stripe_product_id, maybe_stripe_customer_id: r.maybe_stripe_customer_id, @@ -84,9 +84,9 @@ WHERE struct RawUserSubscriptionFromDb { pub token: String, - pub maybe_user_token: Option, - pub subscription_category: String, - pub subscription_product_key: String, + pub user_token: String, + pub subscription_namespace: String, + pub subscription_product_slug: String, pub maybe_stripe_subscription_id: Option, pub maybe_stripe_product_id: Option, pub maybe_stripe_customer_id: Option, diff --git a/crates/lib/database_queries/src/queries/users/user_subscriptions/list_active_user_subscriptions.rs b/crates/lib/database_queries/src/queries/users/user_subscriptions/list_active_user_subscriptions.rs index 693ce8c763..6bb00f68f7 100644 --- a/crates/lib/database_queries/src/queries/users/user_subscriptions/list_active_user_subscriptions.rs +++ b/crates/lib/database_queries/src/queries/users/user_subscriptions/list_active_user_subscriptions.rs @@ -25,16 +25,15 @@ pub async fn list_active_user_subscriptions( RawActiveUserSubscription, r#" SELECT - maybe_user_token as user_token, - subscription_category as subscription_namespace, - subscription_product_key as subscription_product_slug, + user_token, + subscription_namespace, + subscription_product_slug, subscription_expires_at FROM user_subscriptions WHERE - maybe_user_token IS NOT NULL - AND maybe_user_token = ? + user_token = ? AND subscription_expires_at > CURRENT_TIMESTAMP ORDER BY id ASC "#, @@ -46,9 +45,7 @@ WHERE let records = records.into_iter() .map(|record : RawActiveUserSubscription | { ActiveUserSubscription { - user_token: record.user_token.unwrap_or("".to_string()), - - + user_token: record.user_token, subscription_namespace: record.subscription_namespace, subscription_product_slug: record.subscription_product_slug, subscription_expires_at: record.subscription_expires_at, @@ -60,7 +57,7 @@ WHERE } struct RawActiveUserSubscription { - user_token: Option, + user_token: String, subscription_namespace: String, subscription_product_slug: String, subscription_expires_at: DateTime, diff --git a/crates/lib/database_queries/src/queries/users/user_subscriptions/upsert_user_subscription_by_stripe_id.rs b/crates/lib/database_queries/src/queries/users/user_subscriptions/upsert_user_subscription_by_stripe_id.rs index 62e163c90a..79a28c1d61 100644 --- a/crates/lib/database_queries/src/queries/users/user_subscriptions/upsert_user_subscription_by_stripe_id.rs +++ b/crates/lib/database_queries/src/queries/users/user_subscriptions/upsert_user_subscription_by_stripe_id.rs @@ -14,13 +14,13 @@ pub struct UpsertUserSubscription<'a> { pub stripe_subscription_id: &'a str, /// Internal user token - pub maybe_user_token: Option<&'a str>, + pub user_token: &'a str, /// The platform key, eg. "fakeyou", "storyteller_stream", "symphonia", etc. - pub subscription_category: &'a str, + pub subscription_namespace: &'a str, /// The name of the product the user is subscribing to within the category. - pub subscription_product_key: &'a str, + pub subscription_product_slug: &'a str, pub maybe_stripe_customer_id: Option<&'a str>, @@ -67,9 +67,9 @@ impl <'a> UpsertUserSubscription<'a> { INSERT INTO user_subscriptions SET token = ?, - maybe_user_token = ?, - subscription_category = ?, - subscription_product_key = ?, + user_token = ?, + subscription_namespace = ?, + subscription_product_slug = ?, maybe_stripe_subscription_id = ?, maybe_stripe_customer_id = ?, @@ -91,10 +91,8 @@ SET version = version + 1 ON DUPLICATE KEY UPDATE - maybe_user_token = COALESCE(NULLIF(?, ''), maybe_user_token), - - subscription_category = ?, - subscription_product_key = ?, + subscription_namespace = ?, + subscription_product_slug= ?, maybe_stripe_product_id = ?, maybe_stripe_price_id = ?, @@ -112,9 +110,9 @@ ON DUPLICATE KEY UPDATE "#, // Insert token, - self.maybe_user_token, - self.subscription_category, - self.subscription_product_key, + self.user_token, + self.subscription_namespace, + self.subscription_product_slug, self.stripe_subscription_id, self.maybe_stripe_customer_id, @@ -134,10 +132,8 @@ ON DUPLICATE KEY UPDATE self.maybe_canceled_at, // Upsert - self.maybe_user_token, - - self.subscription_category, - self.subscription_product_key, + self.subscription_namespace, + self.subscription_product_slug, self.maybe_stripe_product_id, self.maybe_stripe_price_id,