use rustlavel_core::Json;
use rustlavel_payment::Money;
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cycle {
Monthly,
Yearly,
Days(u32),
}
impl Cycle {
pub fn length(self) -> Duration {
let days = match self {
Cycle::Monthly => 30,
Cycle::Yearly => 365,
Cycle::Days(days) => days.max(1),
};
Duration::from_secs(u64::from(days) * 86_400)
}
pub fn as_str(self) -> String {
match self {
Cycle::Monthly => "monthly".to_string(),
Cycle::Yearly => "yearly".to_string(),
Cycle::Days(days) => format!("days:{days}"),
}
}
pub fn parse(text: &str) -> Option<Cycle> {
match text {
"monthly" => Some(Cycle::Monthly),
"yearly" => Some(Cycle::Yearly),
other => other.strip_prefix("days:")?.parse().ok().map(Cycle::Days),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Plan {
pub id: String,
pub name: String,
pub price: Money,
pub credits_per_cycle: i64,
pub cycle: Cycle,
pub grace: Duration,
}
impl Plan {
pub fn new(id: impl Into<String>, name: impl Into<String>, price: Money, credits_per_cycle: i64) -> Plan {
Plan {
id: id.into(),
name: name.into(),
price,
credits_per_cycle,
cycle: Cycle::Monthly,
grace: Duration::from_secs(3 * 86_400),
}
}
pub fn cycle(mut self, cycle: Cycle) -> Plan {
self.cycle = cycle;
self
}
pub fn grace(mut self, grace: Duration) -> Plan {
self.grace = grace;
self
}
pub fn to_json(&self) -> Json {
Json::object([
("id", Json::from(self.id.as_str())),
("name", Json::from(self.name.as_str())),
("price", self.price.to_json()),
("credits_per_cycle", Json::from(self.credits_per_cycle)),
("cycle", Json::from(self.cycle.as_str())),
("grace_seconds", Json::from(self.grace.as_secs() as i64)),
])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cycles_round_trip_and_are_fixed_lengths() {
for cycle in [Cycle::Monthly, Cycle::Yearly, Cycle::Days(7)] {
assert_eq!(Cycle::parse(&cycle.as_str()), Some(cycle));
}
assert_eq!(Cycle::Monthly.length(), Duration::from_secs(30 * 86_400));
assert_eq!(Cycle::Days(0).length(), Duration::from_secs(86_400), "a zero-day cycle would invoice forever");
assert_eq!(Cycle::parse("weekly"), None);
}
}