Premium plans config (just getting started on this)

This commit is contained in:
Brandon Thomas committed 2022-10-21 19:18:37 -04:00
1 parent 1ca54927d7
commit 4e4dbf0688
7 files changed
+330 -1

No files matched your search

@@ -70,7 +70,6 @@ pub async fn stripe_webhook_handler(
StripeWebhookError::BadRequest
})?;
// Events can be re-sent, so we need to make handling them idempotent.
let stripe_event_id = webhook_payload.id.to_string();
@@ -1 +1,2 @@
pub mod plans;
pub mod static_api_tokens;
@@ -0,0 +1,4 @@
pub mod plan;
pub mod plan_builder;
pub mod plan_category;
pub mod plan_list;
@@ -0,0 +1,77 @@
use chrono::Duration;
use crate::configs::plans::plan_builder::PlanBuilder;
use crate::configs::plans::plan_category::PlanCategory;
/// A Plan is either a free or premium plan.
/// Each plan corresponds to a certain level of service.
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct Plan {
/// Name of the plan
plan_name: String,
/// Whether the plan is free, paid, or loyalty-based
plan_category: PlanCategory,
/// Non-authoritative cost of the plan per month in USD equivalent.
/// This is mostly for documentation. Stripe will remain the source
/// of truth.
cost_per_month_dollars: Option<u32>,
/// For lookup against Stripe product ID
stripe_product_id: Option<String>,
/// For lookup against Stripe price ID
stripe_price_id: Option<String>,
/// Whether this plan is only active in development
is_development_plan: bool,
// ========== Features for TTS ==========
tts_base_priority_level: u8,
tts_max_duration: Duration,
tts_can_generate_mp3: bool,
tts_can_upload_private_models: bool,
tts_can_share_private_models: bool,
// ========== Features for Voice Conversion ==========
vc_max_concurrent_models: u32,
vc_pre_recorded_time_limit: Duration,
vc_pre_recorded_time_is_unlimited: bool,
vc_real_time_time_limit: Duration,
vc_real_time_time_is_unlimited: bool,
// ========== Features for W2L ==========
w2l_max_duration: Duration,
w2l_can_turn_off_watermark: bool,
}
impl Plan {
pub fn from_builder(builder: &PlanBuilder) -> Self {
Self {
plan_name: builder.plan_name.clone(),
plan_category: builder.plan_category,
cost_per_month_dollars: builder.cost_per_month_dollars.clone(),
stripe_product_id: builder.stripe_product_id.clone(),
stripe_price_id: builder.stripe_price_id.clone(),
is_development_plan: builder.is_development_plan,
tts_base_priority_level: builder.tts_base_priority_level,
tts_max_duration: builder.tts_max_duration,
tts_can_generate_mp3: builder.tts_can_generate_mp3,
tts_can_upload_private_models: builder.tts_can_upload_private_models,
tts_can_share_private_models: builder.tts_can_share_private_models,
vc_max_concurrent_models: builder.vc_max_concurrent_models,
vc_pre_recorded_time_limit: builder.vc_pre_recorded_time_limit,
vc_pre_recorded_time_is_unlimited: builder.vc_pre_recorded_time_is_unlimited,
vc_real_time_time_limit: builder.vc_real_time_time_limit,
vc_real_time_time_is_unlimited: builder.vc_real_time_time_is_unlimited,
w2l_max_duration: builder.w2l_max_duration,
w2l_can_turn_off_watermark: builder.w2l_can_turn_off_watermark,
}
}
}
@@ -0,0 +1,173 @@
use chrono::Duration;
use crate::configs::plans::plan::Plan;
use crate::configs::plans::plan_category::PlanCategory;
const TTS_DEFAULT_PRIORITY_LEVEL : u8 = 0;
const TTS_DEFAULT_DURATION_SECONDS : i64 = 12;
const VC_DEFAULT_MAX_CONCURRENT_MODELS : u32 = 5;
const VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS : i64 = 20;
const VC_DEFAULT_REAL_TIME_TIME_LIMIT_SECONDS : i64 = 20;
const W2L_DEFAULT_TIME_LIMIT_SECONDS : i64 = 20;
#[derive(Clone)]
pub struct PlanBuilder {
pub plan_name: String,
pub plan_category: PlanCategory,
pub cost_per_month_dollars: Option<u32>,
pub stripe_product_id: Option<String>,
pub stripe_price_id: Option<String>,
pub is_development_plan: bool,
// ========== Features for TTS ==========
pub tts_base_priority_level: u8,
pub tts_max_duration: Duration,
pub tts_can_generate_mp3: bool,
pub tts_can_upload_private_models: bool,
pub tts_can_share_private_models: bool,
// ========== Features for Voice Conversion ==========
pub vc_max_concurrent_models: u32,
pub vc_pre_recorded_time_limit: Duration,
pub vc_pre_recorded_time_is_unlimited: bool,
pub vc_real_time_time_limit: Duration,
pub vc_real_time_time_is_unlimited: bool,
// ========== Features for W2L ==========
pub w2l_max_duration: Duration,
pub w2l_can_turn_off_watermark: bool,
}
impl PlanBuilder {
pub fn new(plan_name: &str) -> Self {
// NB: Not using default() since that seems dangerous when we want to be explicit.
PlanBuilder {
plan_name : plan_name.to_string(),
plan_category : PlanCategory::Free,
cost_per_month_dollars: None,
stripe_product_id: None,
stripe_price_id: None,
is_development_plan: false,
// TTS
tts_base_priority_level : TTS_DEFAULT_PRIORITY_LEVEL,
tts_max_duration: Duration::seconds(TTS_DEFAULT_DURATION_SECONDS),
tts_can_generate_mp3 : false,
tts_can_upload_private_models: false,
tts_can_share_private_models : false,
// VC
vc_max_concurrent_models: VC_DEFAULT_MAX_CONCURRENT_MODELS,
vc_pre_recorded_time_limit: Duration::seconds(VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS),
vc_pre_recorded_time_is_unlimited: false,
vc_real_time_time_limit: Duration::seconds(VC_DEFAULT_REAL_TIME_TIME_LIMIT_SECONDS),
vc_real_time_time_is_unlimited: false,
// W2L
w2l_max_duration: Duration::seconds(W2L_DEFAULT_TIME_LIMIT_SECONDS),
w2l_can_turn_off_watermark: false
}
}
pub fn build(&self) -> Plan {
Plan::from_builder(self)
}
pub fn plan_category(mut self, value: PlanCategory) -> Self {
self.plan_category = value;
self
}
pub fn cost_per_month_dollars(mut self, value: u32) -> Self {
self.cost_per_month_dollars = Some(value);
self
}
pub fn stripe_product_id(mut self, value: &str) -> Self {
self.stripe_product_id = Some(value.to_string());
self
}
pub fn stripe_price_id(mut self, value: &str) -> Self {
self.stripe_price_id = Some(value.to_string());
self
}
pub fn is_development_plan(mut self, value: bool) -> Self {
self.is_development_plan = value;
self
}
pub fn tts_base_priority_level(mut self, value: u8) -> Self {
self.tts_base_priority_level = value;
self
}
pub fn tts_max_duration_seconds(mut self, value: i64) -> Self {
self.tts_max_duration = Duration::seconds(value);
self
}
pub fn tts_can_generate_mp3(mut self, value: bool) -> Self {
self.tts_can_generate_mp3 = value;
self
}
pub fn tts_can_upload_private_models(mut self, value: bool) -> Self {
self.tts_can_upload_private_models = value;
self
}
pub fn tts_can_share_private_models(mut self, value: bool) -> Self {
self.tts_can_share_private_models = value;
self
}
pub fn vc_max_concurrent_models(mut self, value: u32) -> Self {
self.vc_max_concurrent_models = value;
self
}
pub fn vc_pre_recorded_time_limit_seconds(mut self, value: i64) -> Self {
self.vc_pre_recorded_time_limit = Duration::seconds(value);
self
}
pub fn vc_pre_recorded_time_is_unlimited(mut self, value: bool) -> Self {
self.vc_pre_recorded_time_is_unlimited = value;
if value {
self.vc_pre_recorded_time_limit = Duration::seconds(60 * 60 * 24 * 7);
}
self
}
pub fn vc_real_time_time_limit_seconds(mut self, value: i64) -> Self {
self.vc_real_time_time_limit = Duration::seconds(value);
self
}
pub fn vc_real_time_time_is_unlimited(mut self, value: bool) -> Self {
self.vc_real_time_time_is_unlimited = value;
if value {
self.vc_real_time_time_limit = Duration::seconds(60 * 60 * 24 * 7);
}
self
}
pub fn w2l_time_limit_seconds(mut self, value: i64) -> Self {
self.w2l_max_duration = Duration::seconds(value);
self
}
pub fn w2l_can_turn_off_watermark(mut self, value: bool) -> Self {
self.w2l_can_turn_off_watermark = value;
self
}
}
@@ -0,0 +1,11 @@
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum PlanCategory {
/// Free plans cost nothing and are the lowest tiers of service.
Free,
/// Certain users will get premium features for free by contributing to FakeYou.
LoyaltyReward,
/// Paid plans come with various variable premium perks according to plan level.
Paid,
}
@@ -0,0 +1,64 @@
use std::collections::{HashMap, HashSet};
use once_cell::sync::Lazy;
use tts_common::priority::{FAKEYOU_ANONYMOUS_PRIORITY_LEVEL, FAKEYOU_LOGGED_IN_PRIORITY_LEVEL};
use crate::configs::plans::plan::Plan;
use crate::configs::plans::plan_builder::PlanBuilder;
use crate::configs::plans::plan_category::PlanCategory;
pub static FREE_LOGGED_OUT_PLAN : Lazy<Plan> = Lazy::new(|| {
PlanBuilder::new("free_logged_out")
.plan_category(PlanCategory::Free)
.tts_base_priority_level(FAKEYOU_ANONYMOUS_PRIORITY_LEVEL)
.build()
});
pub static FREE_LOGGED_IN_PLAN : Lazy<Plan> = Lazy::new(|| {
PlanBuilder::new("free_logged_in")
.plan_category(PlanCategory::Free)
.tts_base_priority_level(FAKEYOU_LOGGED_IN_PRIORITY_LEVEL)
.build()
});
/// This plan is for users that create models for us but don't pay.
pub static LOYALTY_PLAN : Lazy<Plan> = Lazy::new(|| {
PlanBuilder::new("loyalty_plan")
.plan_category(PlanCategory::LoyaltyReward)
.tts_base_priority_level(2)
.build()
});
pub static PREMIUM_PLANS : Lazy<HashSet<Plan>> = Lazy::new(|| {
let mut plans = HashSet::new();
// ========== English plans ==========
plans.insert(PlanBuilder::new("fakeyou_en_basic")
.plan_category(PlanCategory::Paid)
.cost_per_month_dollars(5)
.tts_base_priority_level(10)
.build());
plans.insert(PlanBuilder::new("fakeyou_en_pro")
.plan_category(PlanCategory::Paid)
.cost_per_month_dollars(15)
.tts_base_priority_level(20)
.build());
// ========== Spanish plans ==========
plans.insert(PlanBuilder::new("fakeyou_es_basic")
.plan_category(PlanCategory::Paid)
.cost_per_month_dollars(3)
.tts_base_priority_level(5)
.build());
plans.insert(PlanBuilder::new("fakeyou_es_pro")
.plan_category(PlanCategory::Paid)
.cost_per_month_dollars(7)
.tts_base_priority_level(11)
.build());
plans
});