use std::future::Future;
use std::pin::Pin;
use autumn_web::webhook::WebhookEndpointConfig;
use serde::{Deserialize, Serialize};
use crate::error::BillingError;
use crate::event::{BillingEvent, SubscriptionSnapshot};
use crate::model::ProviderId;
pub type ProviderFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, BillingError>> + Send + 'a>>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct HostedSession {
pub id: ProviderId,
pub url: String,
}
impl HostedSession {
#[must_use]
pub fn new(id: impl Into<ProviderId>, url: impl Into<String>) -> Self {
Self {
id: id.into(),
url: url.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct CustomerRequest {
pub local_customer_id: String,
pub user_id: String,
pub email: Option<String>,
}
impl CustomerRequest {
#[must_use]
pub fn new(local_customer_id: impl Into<String>, user_id: impl Into<String>) -> Self {
Self {
local_customer_id: local_customer_id.into(),
user_id: user_id.into(),
email: None,
}
}
#[must_use]
pub fn with_email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct CheckoutRequest {
pub provider_customer_id: ProviderId,
pub local_customer_id: String,
pub provider_price_id: ProviderId,
pub quantity: i64,
pub success_url: String,
pub cancel_url: String,
}
impl CheckoutRequest {
#[must_use]
pub fn new(
provider_customer_id: impl Into<ProviderId>,
local_customer_id: impl Into<String>,
provider_price_id: impl Into<ProviderId>,
success_url: impl Into<String>,
cancel_url: impl Into<String>,
) -> Self {
Self {
provider_customer_id: provider_customer_id.into(),
local_customer_id: local_customer_id.into(),
provider_price_id: provider_price_id.into(),
quantity: 1,
success_url: success_url.into(),
cancel_url: cancel_url.into(),
}
}
#[must_use]
pub const fn with_quantity(mut self, quantity: i64) -> Self {
self.quantity = quantity;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PortalRequest {
pub provider_customer_id: ProviderId,
pub return_url: String,
}
impl PortalRequest {
#[must_use]
pub fn new(provider_customer_id: impl Into<ProviderId>, return_url: impl Into<String>) -> Self {
Self {
provider_customer_id: provider_customer_id.into(),
return_url: return_url.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
#[non_exhaustive]
pub enum PaymentAttemptOutcome {
Paid,
Declined {
reason: String,
},
AlreadyPaid,
}
pub trait BillingProvider: Send + Sync + 'static {
fn name(&self) -> &'static str;
fn webhook_endpoint(
&self,
name: &str,
path: &str,
) -> Result<WebhookEndpointConfig, BillingError>;
fn create_customer(&self, request: CustomerRequest) -> ProviderFuture<'_, ProviderId>;
fn create_checkout(&self, request: CheckoutRequest) -> ProviderFuture<'_, HostedSession>;
fn create_portal(&self, request: PortalRequest) -> ProviderFuture<'_, HostedSession>;
fn parse_event(&self, raw: &[u8]) -> Result<BillingEvent, BillingError>;
fn retry_invoice_payment<'a>(
&'a self,
invoice: &'a ProviderId,
idempotency_key: &'a str,
) -> ProviderFuture<'a, PaymentAttemptOutcome>;
fn cancel_subscription<'a>(&'a self, subscription: &'a ProviderId) -> ProviderFuture<'a, ()>;
fn fetch_subscription<'a>(
&'a self,
_subscription: &'a ProviderId,
) -> ProviderFuture<'a, Option<SubscriptionSnapshot>> {
Box::pin(async { Ok(None) })
}
fn report_usage<'a>(
&'a self,
_subscription: &'a ProviderId,
_quantity: i64,
) -> ProviderFuture<'a, ()> {
Box::pin(async { Err(BillingError::Unsupported("usage reporting")) })
}
}