You've already forked AstralRinth
forked from didirus/AstralRinth
Payments/subscriptions support (#943)
* [wip] Payments/subscriptions support * finish * working payment flow * finish subscriptions, lint, clippy, etc * docker compose
This commit is contained in:
1314
src/routes/internal/billing.rs
Normal file
1314
src/routes/internal/billing.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@ use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator::Validate;
|
||||
@@ -217,6 +218,7 @@ impl TempUser {
|
||||
None
|
||||
},
|
||||
venmo_handle: None,
|
||||
stripe_customer_id: None,
|
||||
totp_secret: None,
|
||||
username,
|
||||
name: self.name,
|
||||
@@ -680,7 +682,6 @@ impl AuthProvider {
|
||||
pub id: String,
|
||||
pub email: String,
|
||||
pub name: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub picture: Option<String>,
|
||||
}
|
||||
|
||||
@@ -1523,6 +1524,7 @@ pub async fn create_account_with_password(
|
||||
paypal_country: None,
|
||||
paypal_email: None,
|
||||
venmo_handle: None,
|
||||
stripe_customer_id: None,
|
||||
totp_secret: None,
|
||||
username: new_account.username.clone(),
|
||||
name: Some(new_account.username),
|
||||
@@ -2157,6 +2159,7 @@ pub async fn set_email(
|
||||
redis: Data<RedisPool>,
|
||||
email: web::Json<SetEmail>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
stripe_client: Data<stripe::Client>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
email
|
||||
.0
|
||||
@@ -2197,6 +2200,22 @@ pub async fn set_email(
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(customer_id) = user
|
||||
.stripe_customer_id
|
||||
.as_ref()
|
||||
.and_then(|x| stripe::CustomerId::from_str(x).ok())
|
||||
{
|
||||
stripe::Customer::update(
|
||||
&stripe_client,
|
||||
&customer_id,
|
||||
stripe::UpdateCustomer {
|
||||
email: Some(&email.email),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let flow = Flow::ConfirmEmail {
|
||||
user_id: user.id.into(),
|
||||
confirm_email: email.email.clone(),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub(crate) mod admin;
|
||||
pub mod billing;
|
||||
pub mod flows;
|
||||
pub mod moderation;
|
||||
pub mod pats;
|
||||
@@ -17,6 +18,7 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
.configure(session::config)
|
||||
.configure(flows::config)
|
||||
.configure(pats::config)
|
||||
.configure(moderation::config),
|
||||
.configure(moderation::config)
|
||||
.configure(billing::config),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ pub async fn get_projects(
|
||||
ProjectStatus::Processing.as_str(),
|
||||
count.count as i64
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| database::models::ProjectId(m.id))) })
|
||||
.fetch(&**pool)
|
||||
.map_ok(|m| database::models::ProjectId(m.id))
|
||||
.try_collect::<Vec<database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ pub enum ApiError {
|
||||
NotFound,
|
||||
#[error("You are being rate-limited. Please wait {0} milliseconds. 0/{1} remaining.")]
|
||||
RateLimitError(u128, u32),
|
||||
#[error("Error while interacting with payment processor: {0}")]
|
||||
Stripe(#[from] stripe::StripeError),
|
||||
}
|
||||
|
||||
impl ApiError {
|
||||
@@ -163,6 +165,7 @@ impl ApiError {
|
||||
ApiError::Zip(..) => "zip_error",
|
||||
ApiError::Io(..) => "io_error",
|
||||
ApiError::RateLimitError(..) => "ratelimit_error",
|
||||
ApiError::Stripe(..) => "stripe_error",
|
||||
},
|
||||
description: self.to_string(),
|
||||
}
|
||||
@@ -198,6 +201,7 @@ impl actix_web::ResponseError for ApiError {
|
||||
ApiError::Zip(..) => StatusCode::BAD_REQUEST,
|
||||
ApiError::Io(..) => StatusCode::BAD_REQUEST,
|
||||
ApiError::RateLimitError(..) => StatusCode::TOO_MANY_REQUESTS,
|
||||
ApiError::Stripe(..) => StatusCode::FAILED_DEPENDENCY,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::ids::ImageId;
|
||||
use crate::models::reports::{ItemType, Report};
|
||||
use crate::models::reports::Report;
|
||||
use crate::models::v2::reports::LegacyReport;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::{v2_reroute, v3, ApiError};
|
||||
@@ -18,18 +17,6 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(report_get);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
pub struct CreateReport {
|
||||
pub report_type: String,
|
||||
pub item_id: String,
|
||||
pub item_type: ItemType,
|
||||
pub body: String,
|
||||
// Associations to uploaded images
|
||||
#[validate(length(max = 10))]
|
||||
#[serde(default)]
|
||||
pub uploaded_images: Vec<ImageId>,
|
||||
}
|
||||
|
||||
#[post("report")]
|
||||
pub async fn report_create(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -77,9 +77,9 @@ pub async fn organization_projects_get(
|
||||
possible_organization_id.map(|x| x as i64),
|
||||
info
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| crate::database::models::ProjectId(m.id))) })
|
||||
.try_collect::<Vec<crate::database::models::ProjectId>>()
|
||||
.fetch(&**pool)
|
||||
.map_ok(|m| database::models::ProjectId(m.id))
|
||||
.try_collect::<Vec<database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects_data =
|
||||
@@ -574,8 +574,8 @@ pub async fn organization_delete(
|
||||
",
|
||||
organization.id as database::models::ids::OrganizationId
|
||||
)
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|c| crate::database::models::TeamId(c.id))) })
|
||||
.fetch(&mut *transaction)
|
||||
.map_ok(|c| database::models::TeamId(c.id))
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use hex::ToHex;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use hyper::Method;
|
||||
use reqwest::Method;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -98,8 +98,8 @@ pub async fn random_projects_get(
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| db_ids::ProjectId(m.id))) })
|
||||
.fetch(&**pool)
|
||||
.map_ok(|m| db_ids::ProjectId(m.id))
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
@@ -430,8 +430,8 @@ pub async fn project_edit(
|
||||
",
|
||||
project_item.inner.team_id as db_ids::TeamId
|
||||
)
|
||||
.fetch_many(&mut *transaction)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|c| db_models::UserId(c.id))) })
|
||||
.fetch(&mut *transaction)
|
||||
.map_ok(|c| db_models::UserId(c.id))
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -260,11 +260,8 @@ pub async fn reports(
|
||||
",
|
||||
count.count as i64
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right()
|
||||
.map(|m| crate::database::models::ids::ReportId(m.id)))
|
||||
})
|
||||
.fetch(&**pool)
|
||||
.map_ok(|m| crate::database::models::ids::ReportId(m.id))
|
||||
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
|
||||
.await?
|
||||
} else {
|
||||
@@ -278,11 +275,8 @@ pub async fn reports(
|
||||
user.id.0 as i64,
|
||||
count.count as i64
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right()
|
||||
.map(|m| crate::database::models::ids::ReportId(m.id)))
|
||||
})
|
||||
.fetch(&**pool)
|
||||
.map_ok(|m| crate::database::models::ids::ReportId(m.id))
|
||||
.try_collect::<Vec<crate::database::models::ids::ReportId>>()
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -129,22 +129,19 @@ pub async fn filter_authorized_threads(
|
||||
&*project_thread_ids,
|
||||
user_id as database::models::ids::UserId,
|
||||
)
|
||||
.fetch_many(&***pool)
|
||||
.try_for_each(|e| {
|
||||
if let Some(row) = e.right() {
|
||||
check_threads.retain(|x| {
|
||||
let bool = x.project_id.map(|x| x.0) == Some(row.id);
|
||||
.fetch(&***pool)
|
||||
.map_ok(|row| {
|
||||
check_threads.retain(|x| {
|
||||
let bool = x.project_id.map(|x| x.0) == Some(row.id);
|
||||
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
|
||||
!bool
|
||||
});
|
||||
}
|
||||
|
||||
futures::future::ready(Ok(()))
|
||||
!bool
|
||||
});
|
||||
})
|
||||
.try_collect::<Vec<()>>()
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -165,22 +162,19 @@ pub async fn filter_authorized_threads(
|
||||
&*project_thread_ids,
|
||||
user_id as database::models::ids::UserId,
|
||||
)
|
||||
.fetch_many(&***pool)
|
||||
.try_for_each(|e| {
|
||||
if let Some(row) = e.right() {
|
||||
check_threads.retain(|x| {
|
||||
let bool = x.project_id.map(|x| x.0) == Some(row.id);
|
||||
.fetch(&***pool)
|
||||
.map_ok(|row| {
|
||||
check_threads.retain(|x| {
|
||||
let bool = x.project_id.map(|x| x.0) == Some(row.id);
|
||||
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
|
||||
!bool
|
||||
});
|
||||
}
|
||||
|
||||
futures::future::ready(Ok(()))
|
||||
!bool
|
||||
});
|
||||
})
|
||||
.try_collect::<Vec<()>>()
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -199,22 +193,19 @@ pub async fn filter_authorized_threads(
|
||||
&*report_thread_ids,
|
||||
user_id as database::models::ids::UserId,
|
||||
)
|
||||
.fetch_many(&***pool)
|
||||
.try_for_each(|e| {
|
||||
if let Some(row) = e.right() {
|
||||
check_threads.retain(|x| {
|
||||
let bool = x.report_id.map(|x| x.0) == Some(row.id);
|
||||
.fetch(&***pool)
|
||||
.map_ok(|row| {
|
||||
check_threads.retain(|x| {
|
||||
let bool = x.report_id.map(|x| x.0) == Some(row.id);
|
||||
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
if bool {
|
||||
return_threads.push(x.clone());
|
||||
}
|
||||
|
||||
!bool
|
||||
});
|
||||
}
|
||||
|
||||
futures::future::ready(Ok(()))
|
||||
!bool
|
||||
});
|
||||
})
|
||||
.try_collect::<Vec<()>>()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,11 +610,8 @@ pub async fn user_follows(
|
||||
",
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
.fetch_many(&**pool)
|
||||
.try_filter_map(|e| async {
|
||||
Ok(e.right()
|
||||
.map(|m| crate::database::models::ProjectId(m.mod_id)))
|
||||
})
|
||||
.fetch(&**pool)
|
||||
.map_ok(|m| crate::database::models::ProjectId(m.mod_id))
|
||||
.try_collect::<Vec<crate::database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -394,8 +394,8 @@ async fn version_create_inner(
|
||||
",
|
||||
builder.project_id as crate::database::models::ids::ProjectId
|
||||
)
|
||||
.fetch_many(&mut **transaction)
|
||||
.try_filter_map(|e| async { Ok(e.right().map(|m| models::ids::UserId(m.follower_id))) })
|
||||
.fetch(&mut **transaction)
|
||||
.map_ok(|m| models::ids::UserId(m.follower_id))
|
||||
.try_collect::<Vec<models::ids::UserId>>()
|
||||
.await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user