add: coin purchases + donator badge
This commit is contained in:
parent
fd529d3847
commit
44f9edd67e
21 changed files with 345 additions and 38 deletions
|
@ -1,13 +1,20 @@
|
|||
use std::time::Duration;
|
||||
use axum::{http::HeaderMap, response::IntoResponse, Extension, Json};
|
||||
use std::{str::FromStr, time::Duration};
|
||||
use axum::{
|
||||
extract::Query,
|
||||
http::HeaderMap,
|
||||
response::{IntoResponse, Redirect},
|
||||
Extension, Json,
|
||||
};
|
||||
use tetratto_core::model::{
|
||||
auth::{Notification, User},
|
||||
economy::{CoinTransfer, CoinTransferMethod},
|
||||
moderation::AuditLogEntry,
|
||||
permissions::{FinePermission, SecondaryPermission},
|
||||
ApiReturn, Error,
|
||||
};
|
||||
use stripe::{EventObject, EventType};
|
||||
use crate::State;
|
||||
use crate::{get_user_from_token, State, cookie::CookieJar};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub async fn stripe_webhook(
|
||||
Extension(data): Extension<State>,
|
||||
|
@ -131,7 +138,7 @@ pub async fn stripe_webhook(
|
|||
if let Err(e) = data
|
||||
.create_audit_log_entry(AuditLogEntry::new(
|
||||
0,
|
||||
format!("invoice tier update failed: stripe {customer_id}"),
|
||||
format!("invoice user update failed: stripe {customer_id}"),
|
||||
))
|
||||
.await
|
||||
{
|
||||
|
@ -469,3 +476,202 @@ pub async fn stripe_webhook(
|
|||
payload: (),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub enum ProductIDAlias {
|
||||
Coins100,
|
||||
Coins400,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateCheckoutSessionProps {
|
||||
pub product: ProductIDAlias,
|
||||
}
|
||||
|
||||
pub async fn create_stupid_fucking_checkout_session(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Json(props): Json<CreateCheckoutSessionProps>,
|
||||
) -> impl IntoResponse {
|
||||
let data = &(data.read().await);
|
||||
let user = match get_user_from_token!(jar, data.0) {
|
||||
Some(ua) => ua,
|
||||
None => return Json(Error::NotAllowed.into()),
|
||||
};
|
||||
|
||||
let stripe_cnf = match data.0.0.0.stripe {
|
||||
Some(ref c) => c,
|
||||
None => return Json(Error::MiscError("Disabled".to_string()).into()),
|
||||
};
|
||||
|
||||
let stripe_client = match data.3 {
|
||||
Some(ref x) => x,
|
||||
None => return Json(Error::MiscError("Disabled".to_string()).into()),
|
||||
};
|
||||
|
||||
let session = match stripe::CheckoutSession::create(
|
||||
&stripe_client,
|
||||
stripe::CreateCheckoutSession {
|
||||
customer_creation: if user.stripe_id.is_empty() {
|
||||
Some(stripe::CheckoutSessionCustomerCreation::Always)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
customer: if user.stripe_id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(stripe::CustomerId::from_str(&user.stripe_id).unwrap())
|
||||
},
|
||||
line_items: Some(vec![stripe::CreateCheckoutSessionLineItems {
|
||||
quantity: Some(1),
|
||||
adjustable_quantity: Some(
|
||||
stripe::CreateCheckoutSessionLineItemsAdjustableQuantity {
|
||||
enabled: false,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
price: Some(match props.product {
|
||||
ProductIDAlias::Coins100 => stripe_cnf.price_ids.coins_100.clone(),
|
||||
ProductIDAlias::Coins400 => stripe_cnf.price_ids.coins_400.clone(),
|
||||
}),
|
||||
..Default::default()
|
||||
}]),
|
||||
client_reference_id: Some(&user.id.to_string()),
|
||||
mode: Some(stripe::CheckoutSessionMode::Payment),
|
||||
ui_mode: Some(stripe::CheckoutSessionUiMode::Hosted),
|
||||
success_url: Some(&format!(
|
||||
"{}/api/v1/service_hooks/stripe/checkout/success?session_id={{CHECKOUT_SESSION_ID}}",
|
||||
data.0.0.0.host
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
||||
};
|
||||
|
||||
Json(ApiReturn {
|
||||
ok: true,
|
||||
message: "Success".to_string(),
|
||||
payload: session.url.unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CheckoutSessionSuccessProps {
|
||||
pub session_id: String,
|
||||
}
|
||||
|
||||
/// By this point, we can assume the customer has properly paid.
|
||||
///
|
||||
/// This endpoint will just read the purchase, apply the purchase, and then redirect home.
|
||||
pub async fn handle_stupid_fucking_checkout_success_session(
|
||||
jar: CookieJar,
|
||||
Extension(data): Extension<State>,
|
||||
Query(props): Query<CheckoutSessionSuccessProps>,
|
||||
) -> std::result::Result<impl IntoResponse, Json<ApiReturn<()>>> {
|
||||
let data = &(data.read().await);
|
||||
let mut user = match get_user_from_token!(jar, data.0) {
|
||||
Some(ua) => ua,
|
||||
None => return Err(Json(Error::NotAllowed.into())),
|
||||
};
|
||||
|
||||
if user.checkouts.contains(&props.session_id) {
|
||||
return Err(Json(
|
||||
Error::MiscError("You can only do this once".to_string()).into(),
|
||||
));
|
||||
}
|
||||
|
||||
let stripe_cnf = match data.0.0.0.stripe {
|
||||
Some(ref c) => c,
|
||||
None => return Err(Json(Error::MiscError("Disabled".to_string()).into())),
|
||||
};
|
||||
|
||||
let stripe_client = match data.3 {
|
||||
Some(ref x) => x,
|
||||
None => return Err(Json(Error::MiscError("Disabled".to_string()).into())),
|
||||
};
|
||||
|
||||
let session = match stripe::CheckoutSession::retrieve(
|
||||
&stripe_client,
|
||||
&match stripe::CheckoutSessionId::from_str(&props.session_id) {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return Err(Json(
|
||||
Error::MiscError("Invalid session ID".to_string()).into(),
|
||||
));
|
||||
}
|
||||
},
|
||||
&[&"line_items"],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(x) => x,
|
||||
Err(e) => return Err(Json(Error::MiscError(e.to_string()).into())),
|
||||
};
|
||||
|
||||
let price_id = session
|
||||
.line_items
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.data
|
||||
.get(0)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.price
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.id
|
||||
.to_string();
|
||||
|
||||
if price_id == stripe_cnf.price_ids.coins_100 {
|
||||
if let Err(e) = data
|
||||
.0
|
||||
.create_transfer(
|
||||
&mut CoinTransfer::new(
|
||||
data.0.0.0.system_user,
|
||||
user.id,
|
||||
100,
|
||||
CoinTransferMethod::Transfer,
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
} else if price_id == stripe_cnf.price_ids.coins_400 {
|
||||
if let Err(e) = data
|
||||
.0
|
||||
.create_transfer(
|
||||
&mut CoinTransfer::new(
|
||||
data.0.0.0.system_user,
|
||||
user.id,
|
||||
400,
|
||||
CoinTransferMethod::Transfer,
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
} else {
|
||||
tracing::error!(
|
||||
"received an invalid stripe price id, please check config.stripe.price_ids"
|
||||
);
|
||||
|
||||
return Err(Json(
|
||||
Error::MiscError("Unknown price ID".to_string()).into(),
|
||||
));
|
||||
}
|
||||
|
||||
user.checkouts.push(props.session_id);
|
||||
if let Err(e) = data.0.update_user_checkouts(user.id, user.checkouts).await {
|
||||
return Err(Json(e.into()));
|
||||
}
|
||||
|
||||
Ok(Redirect::to("/wallet"))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue