use autumn_web::AppState;
use autumn_web::notifications::{MemoryNotificationStore, Notifications};
use serde_json::{Value, json};
use crate::BillingService;
use crate::model::{Customer, Invoice, Subscription};
pub const KIND_PAYMENT_FAILED: &str = "billing.payment_failed";
pub const KIND_PAYMENT_RECOVERED: &str = "billing.payment_recovered";
pub const KIND_DUNNING_EXHAUSTED: &str = "billing.dunning_exhausted";
pub const KIND_SUBSCRIPTION_CANCELED: &str = "billing.subscription_canceled";
pub(crate) async fn send(state: &AppState, recipient: i64, kind: &str, payload: Value) {
let notifications = state.extension_or_insert_with::<Notifications>(|| default_for(state));
if let Err(error) = notifications.notify(recipient, kind, payload).await {
tracing::warn!(
kind,
recipient,
error = %error,
"🍂 Autumn Billing: notification not stored"
);
}
}
pub(crate) async fn send_to_customer(
state: &AppState,
service: &BillingService,
customer: &Customer,
kind: &str,
payload: Value,
) {
let recipient = customer
.user_id
.as_deref()
.and_then(|user_id| service.hooks().recipient_for(user_id));
if let Some(recipient) = recipient {
send(state, recipient, kind, payload).await;
} else {
tracing::debug!(
kind,
customer_id = %customer.id,
"🍂 Autumn Billing: no notification recipient for customer; skipped"
);
}
}
fn default_for(state: &AppState) -> Notifications {
#[cfg(feature = "db")]
if let Some(pool) = autumn_web::db::DbState::pool(state) {
return Notifications::new(autumn_web::notifications::DbNotificationStore::new(
pool.clone(),
));
}
Notifications::new(MemoryNotificationStore::new())
}
fn money(value: &crate::money::Money) -> Value {
serde_json::to_value(value).unwrap_or(Value::Null)
}
pub(crate) fn payment_failed_payload(
invoice: &Invoice,
attempt: Option<i64>,
reason: Option<&str>,
) -> Value {
json!({
"invoice_id": invoice.id,
"provider_invoice_id": invoice.provider_invoice_id,
"subscription_id": invoice.subscription_id,
"amount_due": money(&invoice.amount_due),
"attempt": attempt,
"provider_attempt_count": invoice.attempt_count,
"reason": reason,
})
}
pub(crate) fn payment_recovered_payload(invoice: &Invoice) -> Value {
json!({
"invoice_id": invoice.id,
"provider_invoice_id": invoice.provider_invoice_id,
"subscription_id": invoice.subscription_id,
"amount_paid": money(&invoice.amount_paid),
})
}
pub(crate) fn dunning_exhausted_payload(invoice: &Invoice, attempts: i64, action: &str) -> Value {
json!({
"invoice_id": invoice.id,
"provider_invoice_id": invoice.provider_invoice_id,
"subscription_id": invoice.subscription_id,
"amount_due": money(&invoice.amount_due),
"attempts": attempts,
"action": action,
})
}
pub(crate) fn subscription_canceled_payload(subscription: &Subscription) -> Value {
json!({
"subscription_id": subscription.id,
"provider_subscription_id": subscription.provider_subscription_id,
"plan_id": subscription.plan_id,
})
}