From 02e7d1f4fa76be20eb338245c497d06721b2fbd3 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 4 Sep 2022 19:21:41 -0400 Subject: [PATCH] Stripe WIP --- crates/service/storyteller_web/Cargo.toml | 7 +- .../src/http_server/endpoints/mod.rs | 1 + .../src/http_server/endpoints/stripe/mod.rs | 3 + .../stripe/stripe_checkout_webhook.rs | 70 --------- .../endpoints/stripe/stripe_common.rs | 8 ++ .../stripe_create_checkout_session_handler.rs | 133 ++++++++++++++++++ crates/service/storyteller_web/src/routes.rs | 30 ++++ 7 files changed, 181 insertions(+), 71 deletions(-) diff --git a/crates/service/storyteller_web/Cargo.toml b/crates/service/storyteller_web/Cargo.toml index 3490abe193..d2f7b33fd9 100644 --- a/crates/service/storyteller_web/Cargo.toml +++ b/crates/service/storyteller_web/Cargo.toml @@ -104,7 +104,7 @@ rust-s3 = { version = "0.32.3" } subprocess = "0.2.6" tempdir = "0.3.7" -actix = "0.12.0" +#actix = "0.12.0" actix-cors = "0.6.0-beta.3" actix-files= "0.6.0-beta.8" actix-http = "3.0.0-beta.11" @@ -120,6 +120,11 @@ jwt = "0.13.0" # NB: Furthermore, the Tokio runtime is old, so I have to use an older Hyper. hyper = { version = "0.14.14" } +# Money +async-stripe = { version = "0.15.0", features = ["runtime-tokio-hyper"] } +#async-stripe = { version = "0.15.0", features = ["runtime-blocking"] } # TODO: Determine proper runtime to use +#dolladollabills = "0.2.0" # money from stripe + # Crypto bcrypt = "0.8" # password hashes hmac = "0.10.0" # cookies diff --git a/crates/service/storyteller_web/src/http_server/endpoints/mod.rs b/crates/service/storyteller_web/src/http_server/endpoints/mod.rs index be49be3664..2defbce7c4 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/mod.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/mod.rs @@ -8,6 +8,7 @@ pub mod leaderboard; pub mod misc; pub mod moderation; pub mod service; +pub mod stripe; pub mod tts; pub mod twitch; pub mod vocoders; diff --git a/crates/service/storyteller_web/src/http_server/endpoints/stripe/mod.rs b/crates/service/storyteller_web/src/http_server/endpoints/stripe/mod.rs index e69de29bb2..018e620644 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/stripe/mod.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/stripe/mod.rs @@ -0,0 +1,3 @@ +pub mod stripe_checkout_webhook; +pub mod stripe_create_checkout_session_handler; +pub mod stripe_common; \ No newline at end of file diff --git a/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_checkout_webhook.rs b/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_checkout_webhook.rs index 2e8df8dd11..e69de29bb2 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_checkout_webhook.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_checkout_webhook.rs @@ -1,70 +0,0 @@ -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::web::Path; -use actix_web::{web, HttpResponse, HttpRequest}; -use chrono::{DateTime, Utc}; -use crate::utils::session_checker::SessionChecker; -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; -use http_server_common::util::timer::MultiBenchmarkingTimer; -use log::warn; -use reusable_types::entity_visibility::EntityVisibility; -use sqlx::MySqlPool; -use std::fmt; -use actix_web::error::UrlencodedError::ContentType; - -// TODO: This is duplicated in query_user_CreateCheckoutSession -// TODO: This handler has embedded queries. - -#[derive(Serialize)] -pub struct CreateCheckoutSessionSuccessResponse { - pub success: bool, -} - -#[derive(Debug, Serialize)] -pub enum CreateCheckoutSessionError { - ServerError, -} - -impl ResponseError for CreateCheckoutSessionError { - fn status_code(&self) -> StatusCode { - match *self { - CreateCheckoutSessionError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR, - } - } - - fn error_response(&self) -> HttpResponse { - serialize_as_json_error(self) - } -} - -// NB: Not using derive_more::Display since Clion doesn't understand it. -impl fmt::Display for CreateCheckoutSessionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -pub async fn create_checkout_session_handler( - http_request: HttpRequest, - mysql_pool: web::Data, -) -> Result -{ - - let stripe_publishable_key = "pk_test_51KZvWXEU5se17Mekx6e0gw32ovDF78k2eeaGctLIs8oQVxN6DerEp1BDqh6gLMYKli4VsTZoVGHoxq5RmwKMlxtP00r59UEEad"; - let stripe_secret_key = "sk_test_51KZvWXEU5se17Mek11ZdnWr4frnyw7tWOPuvl3pRzL290zvkT6KqcbGUl3fcvc3hA8oH6lWXz2kGWXXUlNcCuDxB003uVT9JX0"; - - let product_fakeyou_basic_id = "prod_MMxi2J5y69VPbO"; - - - let response = CreateCheckoutSessionSuccessResponse { - success: true, - }; - - let body = serde_json::to_string(&response) - .map_err(|_e| CreateCheckoutSessionError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) -} diff --git a/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_common.rs b/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_common.rs index e69de29bb2..d659f80b64 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_common.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_common.rs @@ -0,0 +1,8 @@ + +// NB: These are "test" keys and not secret. +pub const STRIPE_PUBLISHABLE_KEY : &'static str = "pk_test_51KZvWXEU5se17Mekx6e0gw32ovDF78k2eeaGctLIs8oQVxN6DerEp1BDqh6gLMYKli4VsTZoVGHoxq5RmwKMlxtP00r59UEEad"; +pub const STRIPE_SECRET_KEY : &'static str = "sk_test_51KZvWXEU5se17Mek11ZdnWr4frnyw7tWOPuvl3pRzL290zvkT6KqcbGUl3fcvc3hA8oH6lWXz2kGWXXUlNcCuDxB003uVT9JX0"; + +// NB: These are "test" product IDs. +pub const PRODUCT_FAKEYOU_BASIC_ID : &'static str = "prod_MMxi2J5y69VPbO"; +pub const PRODUCT_FAKEYOU_BASIC_PRICE_ID : &'static str = "price_1LeDnKEU5se17MekVr1iYYNf"; diff --git a/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_create_checkout_session_handler.rs b/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_create_checkout_session_handler.rs index e69de29bb2..fb2ee0b5e1 100644 --- a/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_create_checkout_session_handler.rs +++ b/crates/service/storyteller_web/src/http_server/endpoints/stripe/stripe_create_checkout_session_handler.rs @@ -0,0 +1,133 @@ +use std::collections::HashMap; +use actix_web::error::ResponseError; +use actix_web::error::UrlencodedError::ContentType; +use actix_web::http::{header, StatusCode}; +use actix_web::web::Path; +use actix_web::{web, HttpResponse, HttpRequest}; +use chrono::{DateTime, Utc}; +use crate::http_server::endpoints::stripe::stripe_common::{PRODUCT_FAKEYOU_BASIC_PRICE_ID, STRIPE_SECRET_KEY}; +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; +use http_server_common::util::timer::MultiBenchmarkingTimer; +use log::{error, warn}; +use reusable_types::entity_visibility::EntityVisibility; +use sqlx::MySqlPool; +use std::fmt; +use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionLineItems}; + +// TODO: This is duplicated in query_user_CreateCheckoutSession +// TODO: This handler has embedded queries. + +#[derive(Serialize)] +pub struct CreateCheckoutSessionSuccessResponse { + pub success: bool, +} + +#[derive(Debug, Serialize)] +pub enum CreateCheckoutSessionError { + ServerError, +} + +impl ResponseError for CreateCheckoutSessionError { + fn status_code(&self) -> StatusCode { + match *self { + CreateCheckoutSessionError::ServerError=> StatusCode::INTERNAL_SERVER_ERROR, + } + } + + fn error_response(&self) -> HttpResponse { + serialize_as_json_error(self) + } +} + +// NB: Not using derive_more::Display since Clion doesn't understand it. +impl fmt::Display for CreateCheckoutSessionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +pub async fn stripe_create_checkout_session_handler( + http_request: HttpRequest, + mysql_pool: web::Data, +) -> Result +{ + //let stripe = Client::new(STRIPE_SECRET_KEY); + //let checkout = stripe.checkout(); + + let stripe_client = stripe::Client::new(STRIPE_SECRET_KEY); + + let mut metadata = HashMap::new(); + metadata.insert("user_token".to_string(), "U:TEST".to_string()); + + /*let params = CreateCheckoutSession { + after_expiration: None, + allow_promotion_codes: None, + automatic_tax: None, + billing_address_collection: None, + cancel_url: "http://localhost:12345/stripe/checkout/cancel", + client_reference_id: None, + consent_collection: None, + customer: None, + customer_creation: None, + customer_email: None, + customer_update: None, + discounts: None, + expand: &[], + expires_at: None, + line_items: None, + locale: None, + metadata: Some(metadata), + mode: Some(CheckoutSessionMode::Subscription), + payment_intent_data: None, + payment_method_options: None, + payment_method_types: None, + phone_number_collection: None, + setup_intent_data: None, + shipping_address_collection: None, + shipping_options: None, + submit_type: None, + subscription_data: None, + success_url: "http://localhost:12345/stripe/checkout/success", + tax_id_collection: None + };*/ + + + let checkout_session = { + let mut params = CreateCheckoutSession::new("http://localhost:12345/stripe/checkout/cancel", "http://localhost:12345/stripe/checkout/success"); + + params.mode = Some(CheckoutSessionMode::Subscription); + params.line_items = Some(vec![ + CreateCheckoutSessionLineItems { + price: Some(PRODUCT_FAKEYOU_BASIC_PRICE_ID.to_string()), + quantity: Some(1), + ..Default::default() + } + ]); + params.expand = &["line_items", "line_items.data.price.product"]; + + CheckoutSession::create(&stripe_client, params) + .await + .map_err(|e| { + error!("Error: {:?}", e); + CreateCheckoutSessionError::ServerError + })? + }; + + let url = checkout_session.url.expect("must have url"); + + //let response = CreateCheckoutSessionSuccessResponse { + // success: true, + //}; + + //let body = serde_json::to_string(&response) + // .map_err(|_e| CreateCheckoutSessionError::ServerError)?; + + //Ok(HttpResponse::Ok() + // .content_type("application/json") + // .body(body)) + + Ok(HttpResponse::Found() + .append_header((header::LOCATION, url.to_string())) + .finish()) +} diff --git a/crates/service/storyteller_web/src/routes.rs b/crates/service/storyteller_web/src/routes.rs index bf2a2457b2..2713875f15 100644 --- a/crates/service/storyteller_web/src/routes.rs +++ b/crates/service/storyteller_web/src/routes.rs @@ -92,6 +92,7 @@ use crate::http_server::endpoints::w2l::set_w2l_template_mod_approval::set_w2l_t use users_component::default_routes::add_suggested_api_v1_account_creation_and_session_routes; use users_component::endpoints::edit_profile_handler::edit_profile_handler; use users_component::endpoints::get_profile_handler::get_profile_handler; +use crate::http_server::endpoints::stripe::stripe_create_checkout_session_handler::stripe_create_checkout_session_handler; pub fn add_routes (app: App) -> App where @@ -116,6 +117,7 @@ pub fn add_routes (app: App) -> App app = add_twitch_routes(app); /* /twitch */ // TODO: MAYBE TEMPORARY app = add_investor_demo_routes(app); /* /demo_mode */ // TODO: DEFINITELY TEMPORARY app = add_flag_routes(app); /* /flag */ + app = add_stripe_routes(app); /* /flag */ // From components app = add_suggested_api_v1_account_creation_and_session_routes(app); // /create_account, /session, /login, /logout @@ -730,6 +732,34 @@ fn add_flag_routes (app: App) -> App ) } +// ==================== STRIPE ROUTES ==================== + +fn add_stripe_routes (app: App) -> App + where + B: MessageBody, + T: ServiceFactory< + ServiceRequest, + Config = (), + Response = ServiceResponse, + Error = Error, + InitError = (), + >, +{ + app.service(web::scope("/stripe") + .service(web::scope("/checkout") + .service(web::resource("/begin") + .route(web::get().to(stripe_create_checkout_session_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + //.service(web::resource("/webhook") + // .route(web::get().to()) + // .route(web::head().to(|| HttpResponse::Ok())) + //) + ) + ) +} + + // ==================== TWITCH ROUTES ==================== // TODO: Maybe move these into an "oauth-gateway" type http service.