Stripe WIP

This commit is contained in:
Brandon Thomas committed 2022-09-10 04:26:24 -04:00
1 parent 360a389e3d
commit 02e7d1f4fa
7 files changed
+181 -71

No files matched your search

+6 -1
View File
@@ -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
@@ -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;
@@ -0,0 +1,3 @@
pub mod stripe_checkout_webhook;
pub mod stripe_create_checkout_session_handler;
pub mod stripe_common;
@@ -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<MySqlPool>,
) -> Result<HttpResponse, CreateCheckoutSessionError>
{
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))
}
@@ -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";
@@ -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<MySqlPool>,
) -> Result<HttpResponse, CreateCheckoutSessionError>
{
//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())
}
@@ -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<T, B> (app: App<T, B>) -> App<T, B>
where
@@ -116,6 +117,7 @@ pub fn add_routes<T, B> (app: App<T, B>) -> App<T, B>
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<T, B> (app: App<T, B>) -> App<T, B>
)
}
// ==================== STRIPE ROUTES ====================
fn add_stripe_routes<T, B> (app: App<T, B>) -> App<T, B>
where
B: MessageBody,
T: ServiceFactory<
ServiceRequest,
Config = (),
Response = ServiceResponse<B>,
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.