//! Money moving: what a discount is, what a promo code is for, and the state //! machines a transaction and a subscription each walk. use super::str_enum::impl_str_enum; use serde::{Deserialize, Serialize}; // --- Discount codes --- #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum DiscountType { Percentage, Fixed, } impl_str_enum!(DiscountType { Percentage => "percentage", Fixed => "fixed", }); // --- Promo codes --- #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum CodePurpose { Discount, FreeAccess, FreeTrial, } impl_str_enum!(CodePurpose { Discount => "discount", FreeAccess => "free_access", FreeTrial => "free_trial", }); // --- Transactions --- #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum TransactionStatus { Pending, Completed, /// In-flight: a refund has been claimed (`completed -> refunding`) and sent to /// Stripe, but the `refund.created` webhook has not yet finalized it. Guards /// against double-submit on shared-cart PaymentIntents. Refunding, Refunded, /// Present in the DB `CHECK` since the initial schema but never written by /// the app (stale pending transactions are deleted, not failed). Kept as a /// variant so the enum can decode any legacy/manual `'failed'` row instead of /// fail-closed-poisoning the whole query, and so the enum-drift test's /// variant set matches the column constraint. Failed, } impl_str_enum!(TransactionStatus { Pending => "pending", Completed => "completed", Refunding => "refunding", Refunded => "refunded", Failed => "failed", }); impl TransactionStatus { /// Badge vocabulary (charter: `docs/design-system.md`). A refund is over /// and needs nobody, so it is neutral rather than red. pub fn badge_status(self) -> crate::types::BadgeStatus { use crate::types::BadgeStatus; match self { Self::Completed => BadgeStatus::Live, Self::Pending | Self::Refunding => BadgeStatus::Pending, Self::Failed => BadgeStatus::Failed, Self::Refunded => BadgeStatus::Ended, } } } // --- Subscriptions --- #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum SubscriptionStatus { #[serde(rename = "active")] Active, #[serde(rename = "trialing")] Trialing, #[serde(rename = "incomplete")] Incomplete, #[serde(rename = "incomplete_expired")] IncompleteExpired, #[serde(rename = "past_due")] PastDue, #[serde(rename = "canceled")] Canceled, #[serde(rename = "unpaid")] Unpaid, } impl_str_enum!(SubscriptionStatus { Active => "active", Trialing => "trialing", Incomplete => "incomplete", IncompleteExpired => "incomplete_expired", PastDue => "past_due", Canceled => "canceled", Unpaid => "unpaid", }); impl SubscriptionStatus { /// Badge vocabulary (charter: `docs/design-system.md`). A trial is live /// because the subscriber has access; a cancellation is over and needs /// nobody, so it is neutral rather than red. pub fn badge_status(self) -> crate::types::BadgeStatus { use crate::types::BadgeStatus; match self { Self::Active | Self::Trialing => BadgeStatus::Live, Self::Incomplete => BadgeStatus::Pending, Self::IncompleteExpired | Self::PastDue | Self::Unpaid => BadgeStatus::Failed, Self::Canceled => BadgeStatus::Ended, } } } // --- Project Pricing --- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum PricingKind { #[default] Free, BuyOnce, Pwyw, Subscription, } impl_str_enum!(PricingKind { Free => "free", BuyOnce => "buy_once", Pwyw => "pwyw", Subscription => "subscription", }); /// Discriminator for checkout session types stored in Stripe metadata. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum CheckoutType { Guest, Subscription, Tip, FanPlus, CreatorTier, Cart, SynckitAppSub, } impl_str_enum!(CheckoutType { Guest => "guest", Subscription => "subscription", Tip => "tip", FanPlus => "fan_plus", CreatorTier => "creator_tier", Cart => "cart", SynckitAppSub => "synckit_app_sub", });