use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::model::{InvoiceStatus, ProviderId, SubscriptionStatus};
use crate::money::Money;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct BillingEvent {
pub id: String,
pub occurred_at: DateTime<Utc>,
pub kind: BillingEventKind,
}
impl BillingEvent {
#[must_use]
pub fn new(id: impl Into<String>, occurred_at: DateTime<Utc>, kind: BillingEventKind) -> Self {
Self {
id: id.into(),
occurred_at,
kind,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[non_exhaustive]
pub enum BillingEventKind {
CheckoutCompleted(CheckoutSnapshot),
SubscriptionChanged(SubscriptionSnapshot),
SubscriptionDeleted(SubscriptionSnapshot),
InvoicePaymentFailed(InvoiceSnapshot),
InvoicePaid(InvoiceSnapshot),
Ignored {
event_type: String,
},
}
impl BillingEventKind {
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::CheckoutCompleted(_) => "checkout_completed",
Self::SubscriptionChanged(_) => "subscription_changed",
Self::SubscriptionDeleted(_) => "subscription_deleted",
Self::InvoicePaymentFailed(_) => "invoice_payment_failed",
Self::InvoicePaid(_) => "invoice_paid",
Self::Ignored { .. } => "ignored",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct CheckoutSnapshot {
pub provider_customer_id: ProviderId,
pub local_customer_ref: Option<String>,
pub email: Option<String>,
pub provider_subscription_id: Option<ProviderId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SubscriptionSnapshot {
pub provider_subscription_id: ProviderId,
pub provider_customer_id: ProviderId,
pub provider_price_id: Option<ProviderId>,
pub status: SubscriptionStatus,
pub quantity: i64,
pub current_period_end: Option<DateTime<Utc>>,
pub cancel_at_period_end: bool,
}
impl SubscriptionSnapshot {
#[must_use]
pub fn new(
provider_subscription_id: impl Into<ProviderId>,
provider_customer_id: impl Into<ProviderId>,
status: SubscriptionStatus,
) -> Self {
Self {
provider_subscription_id: provider_subscription_id.into(),
provider_customer_id: provider_customer_id.into(),
provider_price_id: None,
status,
quantity: 1,
current_period_end: None,
cancel_at_period_end: false,
}
}
#[must_use]
pub fn with_price(mut self, price_id: impl Into<ProviderId>) -> Self {
self.provider_price_id = Some(price_id.into());
self
}
#[must_use]
pub const fn with_period_end(mut self, end: DateTime<Utc>) -> Self {
self.current_period_end = Some(end);
self
}
#[must_use]
pub const fn with_quantity(mut self, quantity: i64) -> Self {
self.quantity = quantity;
self
}
#[must_use]
pub const fn with_cancel_at_period_end(mut self, cancel: bool) -> Self {
self.cancel_at_period_end = cancel;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct InvoiceSnapshot {
pub provider_invoice_id: ProviderId,
pub provider_customer_id: ProviderId,
pub provider_subscription_id: Option<ProviderId>,
pub status: InvoiceStatus,
pub amount_due: Money,
pub amount_paid: Money,
pub attempt_count: i64,
pub next_payment_attempt: Option<DateTime<Utc>>,
}
impl InvoiceSnapshot {
#[must_use]
pub fn new(
provider_invoice_id: impl Into<ProviderId>,
provider_customer_id: impl Into<ProviderId>,
status: InvoiceStatus,
amount_due: Money,
) -> Self {
Self {
provider_invoice_id: provider_invoice_id.into(),
provider_customer_id: provider_customer_id.into(),
provider_subscription_id: None,
status,
amount_due,
amount_paid: Money::zero(amount_due.currency()),
attempt_count: 0,
next_payment_attempt: None,
}
}
#[must_use]
pub fn with_subscription(mut self, id: impl Into<ProviderId>) -> Self {
self.provider_subscription_id = Some(id.into());
self
}
#[must_use]
pub const fn with_amount_paid(mut self, amount: Money) -> Self {
self.amount_paid = amount;
self
}
#[must_use]
pub const fn with_attempt_count(mut self, count: i64) -> Self {
self.attempt_count = count;
self
}
#[must_use]
pub const fn with_next_payment_attempt(mut self, at: DateTime<Utc>) -> Self {
self.next_payment_attempt = Some(at);
self
}
}