From 6face4ab4fc4da76f7d3fa0ca723fed39f07f49d Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 29 Jan 2026 02:39:54 -0500 Subject: [PATCH] undo change --- ...ft_create_subscription_checkout_handler.rs | 148 +++++++++++++++++- .../endpoints/users/create_account_handler.rs | 2 +- .../handle_new_sso_account_for_new_user.rs | 2 +- .../src/util/email_to_gravatar.rs | 34 ---- .../web/storyteller_web/src/util/mod.rs | 1 - 5 files changed, 142 insertions(+), 45 deletions(-) delete mode 100644 crates/service/web/storyteller_web/src/util/email_to_gravatar.rs diff --git a/crates/service/plugins/billing_artcraft/src/endpoints/checkout/stripe_artcraft_create_subscription_checkout_handler.rs b/crates/service/plugins/billing_artcraft/src/endpoints/checkout/stripe_artcraft_create_subscription_checkout_handler.rs index 02e757fc21..175ac01020 100644 --- a/crates/service/plugins/billing_artcraft/src/endpoints/checkout/stripe_artcraft_create_subscription_checkout_handler.rs +++ b/crates/service/plugins/billing_artcraft/src/endpoints/checkout/stripe_artcraft_create_subscription_checkout_handler.rs @@ -24,6 +24,7 @@ use user_traits_component::traits::internal_session_cache_purge::InternalSession //use utoipa::ToSchema; +// /// Create a Stripe Checkout session and return the redirect URL in Json. // #[utoipa::path( // get, // tag = "Stripe (Artcraft)", @@ -35,10 +36,6 @@ use user_traits_component::traits::internal_session_cache_purge::InternalSession // (status = 200, description = "Success Delete", body = CreateCheckoutSessionSuccessResponse), // ), // )] - - -/// Create a Stripe Checkout session *with* user signup and return the redirect URL in Json. -/// If the user is already logged in, we just attach it to that record instead. pub async fn stripe_artcraft_create_subscription_session_handler( http_request: HttpRequest, request: Json, @@ -85,10 +82,145 @@ pub async fn stripe_artcraft_create_subscription_session_handler( // NB: Our integration relies on an internal user token being present. let user_metadata = match maybe_user_metadata { None => return Err(CommonWebError::NotAuthorized), - Some(user_metadata) => { - - user_exists_case - }, + Some(user_metadata) => user_metadata, + }; + + // NB: Currently the stripe customer id field in the `users` table is only for FakeYou subscriptions, + // so we need to look up any existing Artcraft subscription separately. This is needed to pre-fill + // the Stripe billing form. + let maybe_active_subscription = find_subscription_for_owner_user_using_connection( + &user_metadata.user_token_typed, + PaymentsNamespace::Artcraft, + &mut mysql_connection + ).await.map_err(|err| { + error!("Error looking up user's ({}) existing subscription: {:?}", &user_metadata.user_token_typed, err); + CommonWebError::ServerError // NB: This was probably *our* fault. + })?; + + if maybe_active_subscription.is_some() { + return Err(CommonWebError::BadInputWithSimpleMessage( + "user already has an active subscription plan; use the portal to update the plan" + .to_string())) + } + + // NB: This works, but it feels really weird to have the customer's old email shown in a + // completely immutable state in the stripe checkout form. + // + // let maybe_inactive_subscription = find_possibly_inactive_first_subscription_for_owner_user_using_connection( + // &user_metadata.user_token_typed, + // PaymentsNamespace::Artcraft, + // &mut mysql_connection + // ).await.map_err(|err| { + // error!("Error looking up user's ({}) possibly existing inactive subscription: {:?}", + // &user_metadata.user_token_typed, err); + // CommonWebError::ServerError // NB: This was probably *our* fault. + // })?; + // let maybe_existing_inactive_stripe_customer_id = maybe_inactive_subscription.as_ref() + // .map(|sub| sub.stripe_customer_id.as_str()); + + let mut maybe_existing_stripe_customer_id = None; + + let result = find_user_stripe_customer_link_using_connection( + &user_metadata.user_token_typed, + PaymentsNamespace::Artcraft, + &mut mysql_connection + ).await; + + // NB: Fail silently. + if let Ok(Some(link)) = result { + maybe_existing_stripe_customer_id = CustomerId::from_str(&link.stripe_customer_id).ok(); + } else if let Err(err) = result { + warn!("Error looking up user's ({}) existing stripe customer link: {:?}", &user_metadata.user_token_typed, err); + } + + let success_url = stripe_config.checkout_success_url.clone(); + let cancel_url = stripe_config.checkout_cancel_url.clone(); + + let checkout_session = { + // `client_reference_id` + // Stripe Docs: + // A unique string to reference the Checkout Session. + // This can be a customer ID, a cart ID, or similar, and can be used to reconcile the session + // with your internal systems. + // + // Our Notes: + // This gets reported back in the Checkout Session (and related webhooks) as + // `client_reference_id`. Passing the same ID on multiple checkouts does not unify or + // cross-correlate customers and only seems to be metadata for the checkout session itself. + // This is probably only useful for tracking checkout session engagement. + //params.client_reference_id = Some("SOME_INTERNAL_ID"); + + // `customer_email` + // Stripe Docs: + // If provided, this value will be used when the Customer object is created. If not provided, + // customers will be asked to enter their email address. Use this parameter to prefill + // customer data if you already have an email on file. To access information about the + // customer once a session is complete, use the customer field. + // + // Our Notes: + // This does not look up previous customers with the same email and will not unify or + // cross-correlate customers. By default the field will be un-editable in the checkout flow + // if this is specified. + //params.customer_email = Some("email@example.com"); + + let mut metadata = HashMap::new(); + + metadata.insert(STRIPE_ARTCRAFT_METADATA_USER_TOKEN.to_string(), user_metadata.user_token.to_string()); + + if let Some(username) = user_metadata.username.as_deref() { + metadata.insert(STRIPE_ARTCRAFT_METADATA_USERNAME.to_string(), username.to_string()); + } + + if let Some(user_email) = user_metadata.user_email.as_deref() { + metadata.insert(STRIPE_ARTCRAFT_METADATA_EMAIL.to_string(), user_email.to_string()); + } + + let mut checkout_builder = CreateCheckoutSession::new() + .success_url(&success_url) + .cancel_url(&cancel_url) + .mode(CheckoutSessionMode::Subscription) + .line_items(vec![ + CreateCheckoutSessionLineItems { + price: Some(price_id.to_string()), + quantity: Some(1), + ..Default::default() + } + ]) + .saved_payment_method_options(CreateCheckoutSessionSavedPaymentMethodOptions { + allow_redisplay_filters: Some(vec![ + CreateCheckoutSessionSavedPaymentMethodOptionsAllowRedisplayFilters::Always, + CreateCheckoutSessionSavedPaymentMethodOptionsAllowRedisplayFilters::Limited, + CreateCheckoutSessionSavedPaymentMethodOptionsAllowRedisplayFilters::Unspecified, + ]), + // The user can choose to tick a checkbox that saves their card for redisplay later. + payment_method_save: Some(CreateCheckoutSessionSavedPaymentMethodOptionsPaymentMethodSave::Enabled), + }) + .allow_promotion_codes(true) // Allow promo codes / coupons + .automatic_tax(CreateCheckoutSessionAutomaticTax { + enabled: true, // This will ask for the customer's location + liability: None, + }) + .metadata(metadata.clone()) + .subscription_data(CreateCheckoutSessionSubscriptionData { + metadata: Some(metadata), + ..Default::default() + }) + ; + + if let Some(customer_id) = maybe_existing_stripe_customer_id { + info!("Adding existing stripe customer id to checkout session: {}", customer_id.as_str()); + checkout_builder = checkout_builder.customer(customer_id); + } + + let checkout_session = checkout_builder + .send(&stripe_config.client) + .await + .map_err(|err| { + error!("Stripe Error: {:?}", err); + CommonWebError::ServerError + })?; + + checkout_session }; let url = checkout_session.url.ok_or(CommonWebError::ServerError)?; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs index 6e1aa03339..ebffc27c53 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/create_account_handler.rs @@ -12,7 +12,6 @@ use crate::http_server::session::http::http_user_session_manager::HttpUserSessio use crate::http_server::validations::is_reserved_username::is_reserved_username; use crate::http_server::validations::validate_passwords::validate_passwords; use crate::http_server::validations::validate_username::validate_username; -use crate::util::email_to_gravatar::email_to_gravatar; use crate::util::enroll_in_studio::enroll_in_studio; use actix_web::error::ResponseError; use actix_web::http::StatusCode; @@ -29,6 +28,7 @@ use password::bcrypt_hash_password::bcrypt_hash_password; use sqlx::MySqlPool; use tokens::tokens::user_sessions::UserSessionToken; use user_input_common::check_for_slurs::contains_slurs; +use users::email::email_to_gravatar::email_to_gravatar; use utoipa::ToSchema; #[derive(ToSchema, Deserialize)] diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs index b15b8c4d73..32af6c897a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/users/google_sso/handle_new_sso_account_for_new_user.rs @@ -2,7 +2,6 @@ use crate::http_server::endpoints::users::google_sso::google_sso_handler::Google use crate::http_server::endpoints::users::google_sso::handle_new_sso_account::NewSsoAccountInfo; use crate::http_server::requests::get_request_signup_source::{get_request_signup_source, get_request_signup_source_enum}; use crate::http_server::session::lookup::user_session_feature_flags::UserSessionFeatureFlags; -use crate::util::email_to_gravatar::email_to_gravatar; use actix_web::HttpRequest; use enums::by_table::users::user_feature_flag::UserFeatureFlag; use google_sign_in::claims::claims::Claims; @@ -14,6 +13,7 @@ use mysql_queries::queries::users::user::create::create_account_from_google_sso: use mysql_queries::utils::transactor::Transactor; use sqlx::pool::PoolConnection; use sqlx::{Acquire, MySql}; +use users::email::email_to_gravatar::email_to_gravatar; use users::username::generate_random_username::generate_random_username; pub struct CreateArgs<'a> { diff --git a/crates/service/web/storyteller_web/src/util/email_to_gravatar.rs b/crates/service/web/storyteller_web/src/util/email_to_gravatar.rs deleted file mode 100644 index 7d227e00b2..0000000000 --- a/crates/service/web/storyteller_web/src/util/email_to_gravatar.rs +++ /dev/null @@ -1,34 +0,0 @@ -use md5::{Digest, Md5}; - -pub fn email_to_gravatar(email_address: &str) -> String { - let email = email_address.trim().to_lowercase(); - - let mut hasher = Md5::new(); - hasher.update(email); - let hash = hasher.finalize(); - let gravatar_hash = format!("{:x}", hash); - - gravatar_hash -} - -#[cfg(test)] -pub mod tests { - use crate::util::email_to_gravatar::email_to_gravatar; - - #[test] - fn test_gravatar() { - // Email 1 - assert_eq!(email_to_gravatar("example@example.com"), "23463b99b62a72f26ed677cc556c44e8".to_string()); - assert_eq!(email_to_gravatar("EXAMPLE@EXAMPLE.COM"), "23463b99b62a72f26ed677cc556c44e8".to_string()); - assert_eq!(email_to_gravatar(" example@example.com \n "), "23463b99b62a72f26ed677cc556c44e8".to_string()); - - // Email 2 - assert_eq!(email_to_gravatar("echelon@gmail.com"), "37e7e18f19d8f654a1ace405c846a9de".to_string()); - assert_eq!(email_to_gravatar("ECHELON@GMAIL.COM"), "37e7e18f19d8f654a1ace405c846a9de".to_string()); - assert_eq!(email_to_gravatar(" ECHELON@GMAIL.COM "), "37e7e18f19d8f654a1ace405c846a9de".to_string()); - - // Misc - assert_eq!(email_to_gravatar(""), "d41d8cd98f00b204e9800998ecf8427e".to_string()); - assert_eq!(email_to_gravatar(" "), "d41d8cd98f00b204e9800998ecf8427e".to_string()); - } -} \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/util/mod.rs b/crates/service/web/storyteller_web/src/util/mod.rs index 7980fe4e25..3dbe31fbd1 100644 --- a/crates/service/web/storyteller_web/src/util/mod.rs +++ b/crates/service/web/storyteller_web/src/util/mod.rs @@ -5,7 +5,6 @@ pub mod canonicalize_email_for_users_table; pub mod check_creator_tokens; pub mod cleaners; pub mod delete_role_disambiguation; -pub mod email_to_gravatar; pub mod encrypted_sort_id; pub mod enroll_in_studio; pub mod http_download_url_to_bytes;