5 days subscription interval, metadata
This commit is contained in:
@@ -269,3 +269,29 @@ impl DBProductPrice {
|
||||
Ok(prices)
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries a single price ID for a product by type.
|
||||
pub async fn unique_price_id_of_product_by_type<'a, E>(
|
||||
exec: E,
|
||||
r#type: &str,
|
||||
) -> Result<Option<DBProductPriceId>, DatabaseError>
|
||||
where
|
||||
E: sqlx::PgExecutor<'a>,
|
||||
{
|
||||
let maybe_row = sqlx::query!(
|
||||
"
|
||||
SELECT
|
||||
products_prices.id
|
||||
FROM
|
||||
products_prices
|
||||
INNER JOIN
|
||||
products ON products.metadata ->> 'type' = $1
|
||||
LIMIT 1
|
||||
",
|
||||
r#type,
|
||||
)
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
Ok(maybe_row.map(|x| DBProductPriceId(x.id)))
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ pub enum Price {
|
||||
#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PriceDuration {
|
||||
FiveDays,
|
||||
Monthly,
|
||||
Quarterly,
|
||||
Yearly,
|
||||
@@ -62,6 +63,7 @@ pub enum PriceDuration {
|
||||
impl PriceDuration {
|
||||
pub fn duration(&self) -> chrono::Duration {
|
||||
match self {
|
||||
PriceDuration::FiveDays => chrono::Duration::days(5),
|
||||
PriceDuration::Monthly => chrono::Duration::days(30),
|
||||
PriceDuration::Quarterly => chrono::Duration::days(90),
|
||||
PriceDuration::Yearly => chrono::Duration::days(365),
|
||||
@@ -70,6 +72,7 @@ impl PriceDuration {
|
||||
|
||||
pub fn from_string(string: &str) -> PriceDuration {
|
||||
match string {
|
||||
"five-days" => PriceDuration::FiveDays,
|
||||
"monthly" => PriceDuration::Monthly,
|
||||
"quarterly" => PriceDuration::Quarterly,
|
||||
"yearly" => PriceDuration::Yearly,
|
||||
@@ -82,6 +85,7 @@ impl PriceDuration {
|
||||
PriceDuration::Monthly => "monthly",
|
||||
PriceDuration::Quarterly => "quarterly",
|
||||
PriceDuration::Yearly => "yearly",
|
||||
PriceDuration::FiveDays => "five-days",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +94,7 @@ impl PriceDuration {
|
||||
PriceDuration::Monthly,
|
||||
PriceDuration::Quarterly,
|
||||
PriceDuration::Yearly,
|
||||
PriceDuration::FiveDays,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
@@ -152,6 +157,7 @@ impl SubscriptionStatus {
|
||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||
pub enum SubscriptionMetadata {
|
||||
Pyro { id: String, region: Option<String> },
|
||||
Medal { id: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
||||
@@ -950,14 +950,17 @@ pub async fn active_servers(
|
||||
let server_ids = servers
|
||||
.into_iter()
|
||||
.filter_map(|x| {
|
||||
x.metadata.as_ref().map(|metadata| match metadata {
|
||||
SubscriptionMetadata::Pyro { id, region } => ActiveServer {
|
||||
user_id: x.user_id.into(),
|
||||
server_id: id.clone(),
|
||||
price_id: x.price_id.into(),
|
||||
interval: x.interval,
|
||||
region: region.clone(),
|
||||
},
|
||||
x.metadata.as_ref().and_then(|metadata| match metadata {
|
||||
SubscriptionMetadata::Pyro { id, region } => {
|
||||
Some(ActiveServer {
|
||||
user_id: x.user_id.into(),
|
||||
server_id: id.clone(),
|
||||
price_id: x.price_id.into(),
|
||||
interval: x.interval,
|
||||
region: region.clone(),
|
||||
})
|
||||
}
|
||||
SubscriptionMetadata::Medal { .. } => None,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<ActiveServer>>();
|
||||
@@ -1846,6 +1849,7 @@ pub async fn stripe_webhook(
|
||||
"region": server_region,
|
||||
"source": source,
|
||||
"payment_interval": metadata.charge_item.subscription_interval.map(|x| match x {
|
||||
PriceDuration::FiveDays => 1,
|
||||
PriceDuration::Monthly => 1,
|
||||
PriceDuration::Quarterly => 3,
|
||||
PriceDuration::Yearly => 12,
|
||||
|
||||
@@ -4,9 +4,13 @@ use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::database::models::generate_user_subscription_id;
|
||||
use crate::database::models::product_item;
|
||||
use crate::database::models::user_subscription_item::DBUserSubscription;
|
||||
use crate::database::models::users_redeemals::{
|
||||
Offer, RedeemalLookupFields, Status, UserRedeemal,
|
||||
};
|
||||
use crate::models::v3::billing::{PriceDuration, SubscriptionStatus};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::guards::medal_key_guard;
|
||||
|
||||
@@ -64,7 +68,7 @@ pub async fn redeem(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _redeemal = match maybe_fields {
|
||||
let redeemal = match maybe_fields {
|
||||
None => return Err(ApiError::NotFound),
|
||||
Some(fields) => {
|
||||
if fields.redeemal_status.is_some() {
|
||||
@@ -88,5 +92,37 @@ pub async fn redeem(
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
// TODO: Provision server (send archon request) THEN add subscription to DB
|
||||
|
||||
let mut txn = pool.begin().await?;
|
||||
|
||||
// Find the Medal product price
|
||||
let maybe_price_id =
|
||||
product_item::unique_price_id_of_product_by_type(&mut *txn, "medal")
|
||||
.await?;
|
||||
|
||||
let Some(medal_price_id) = maybe_price_id else {
|
||||
return Ok(HttpResponse::NotImplemented()
|
||||
.body("Missing price ID for Medal subscription"));
|
||||
};
|
||||
|
||||
// Build a subscription using this price ID.
|
||||
let subscription = DBUserSubscription {
|
||||
id: generate_user_subscription_id(&mut txn).await?,
|
||||
user_id: redeemal.user_id,
|
||||
price_id: medal_price_id,
|
||||
interval: PriceDuration::FiveDays,
|
||||
created: Utc::now(),
|
||||
status: SubscriptionStatus::Unprovisioned,
|
||||
metadata: None, // TODO: Provision server, then add metadata
|
||||
};
|
||||
|
||||
// TODO: Insert a cancelled charge in 5 days time, `index_subscriptions` will unprovision
|
||||
// the subscription.
|
||||
|
||||
subscription.upsert(&mut txn).await?;
|
||||
|
||||
txn.commit().await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user