use std::collections::HashMap;
pub trait StripeEvent: Send + Sync + 'static {
fn from_raw(event: &stripe::Event) -> Option<Self>
where
Self: Sized;
}
#[derive(Debug, Clone)]
pub struct StripeSubscriptionUpdated {
pub event_id: String,
pub subscription_id: String,
pub customer_id: String,
}
impl StripeEvent for StripeSubscriptionUpdated {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::CustomerSubscriptionUpdated {
return None;
}
match &event.data.object {
stripe::EventObject::Subscription(sub) => Some(Self {
event_id: event.id.to_string(),
subscription_id: sub.id.to_string(),
customer_id: sub.customer.id().to_string(),
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeSubscriptionDeleted {
pub event_id: String,
pub subscription_id: String,
pub customer_id: String,
}
impl StripeEvent for StripeSubscriptionDeleted {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::CustomerSubscriptionDeleted {
return None;
}
match &event.data.object {
stripe::EventObject::Subscription(sub) => Some(Self {
event_id: event.id.to_string(),
subscription_id: sub.id.to_string(),
customer_id: sub.customer.id().to_string(),
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeCheckoutCompleted {
pub event_id: String,
pub session_id: String,
pub payment_intent_id: Option<String>,
pub amount_total_cents: i64,
pub currency: String,
pub metadata: HashMap<String, String>,
pub customer_email: Option<String>,
}
impl StripeEvent for StripeCheckoutCompleted {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::CheckoutSessionCompleted {
return None;
}
match &event.data.object {
stripe::EventObject::CheckoutSession(session) => Some(Self {
event_id: event.id.to_string(),
session_id: session.id.to_string(),
payment_intent_id: session.payment_intent.as_ref().map(|e| e.id().to_string()),
amount_total_cents: session.amount_total.unwrap_or(0),
currency: session.currency.map(|c| c.to_string()).unwrap_or_default(),
metadata: session.metadata.clone().unwrap_or_default(),
customer_email: session.customer_email.clone(),
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeInvoicePaid {
pub event_id: String,
pub invoice_id: String,
pub customer_id: String,
}
impl StripeEvent for StripeInvoicePaid {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::InvoicePaid {
return None;
}
match &event.data.object {
stripe::EventObject::Invoice(inv) => {
let customer_id = inv.customer.as_ref().map(|e| e.id().to_string())?;
Some(Self {
event_id: event.id.to_string(),
invoice_id: inv.id.to_string(),
customer_id,
})
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeConnectPaymentSucceeded {
pub event_id: String,
pub payment_intent_id: String,
pub connect_account_id: String,
}
impl StripeEvent for StripeConnectPaymentSucceeded {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::PaymentIntentSucceeded {
return None;
}
match &event.data.object {
stripe::EventObject::PaymentIntent(pi) => {
let connect_account_id = event.account.as_ref()?.to_string();
Some(Self {
event_id: event.id.to_string(),
payment_intent_id: pi.id.to_string(),
connect_account_id,
})
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeCheckoutExpired {
pub event_id: String,
pub session_id: String,
pub metadata: HashMap<String, String>,
}
impl StripeEvent for StripeCheckoutExpired {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::CheckoutSessionExpired {
return None;
}
match &event.data.object {
stripe::EventObject::CheckoutSession(session) => Some(Self {
event_id: event.id.to_string(),
session_id: session.id.to_string(),
metadata: session.metadata.clone().unwrap_or_default(),
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripePaymentIntentFailed {
pub event_id: String,
pub payment_intent_id: String,
pub session_id: Option<String>,
pub failure_code: Option<String>,
pub failure_message: Option<String>,
pub metadata: HashMap<String, String>,
}
impl StripeEvent for StripePaymentIntentFailed {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::PaymentIntentPaymentFailed {
return None;
}
match &event.data.object {
stripe::EventObject::PaymentIntent(pi) => {
let failure_code = pi
.last_payment_error
.as_ref()
.and_then(|e| e.code.as_ref())
.map(|c| c.to_string());
let failure_message = pi
.last_payment_error
.as_ref()
.and_then(|e| e.message.clone());
let session_id = pi.metadata.get("checkout_session_id").cloned();
Some(Self {
event_id: event.id.to_string(),
payment_intent_id: pi.id.to_string(),
session_id,
failure_code,
failure_message,
metadata: pi.metadata.clone(),
})
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeChargeRefunded {
pub event_id: String,
pub charge_id: String,
pub payment_intent_id: Option<String>,
pub amount_refunded_cents: i64,
pub metadata: HashMap<String, String>,
}
impl StripeEvent for StripeChargeRefunded {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::ChargeRefunded {
return None;
}
match &event.data.object {
stripe::EventObject::Charge(charge) => Some(Self {
event_id: event.id.to_string(),
charge_id: charge.id.to_string(),
payment_intent_id: charge.payment_intent.as_ref().map(|e| e.id().to_string()),
amount_refunded_cents: charge.amount_refunded,
metadata: charge.metadata.clone(),
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeChargeDisputeCreated {
pub event_id: String,
pub charge_id: String,
pub payment_intent_id: Option<String>,
pub dispute_reason: String,
pub amount_cents: i64,
}
impl StripeEvent for StripeChargeDisputeCreated {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::ChargeDisputeCreated {
return None;
}
match &event.data.object {
stripe::EventObject::Dispute(dispute) => Some(Self {
event_id: event.id.to_string(),
charge_id: dispute.charge.id().to_string(),
payment_intent_id: dispute.payment_intent.as_ref().map(|e| e.id().to_string()),
dispute_reason: dispute.reason.clone(),
amount_cents: dispute.amount,
}),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct StripeConnectAccountUpdated {
pub event_id: String,
pub account_id: String,
pub charges_enabled: bool,
pub payouts_enabled: bool,
pub details_submitted: bool,
}
impl StripeEvent for StripeConnectAccountUpdated {
fn from_raw(event: &stripe::Event) -> Option<Self> {
if event.type_ != stripe::EventType::AccountUpdated {
return None;
}
match &event.data.object {
stripe::EventObject::Account(acct) => Some(Self {
event_id: event.id.to_string(),
account_id: acct.id.to_string(),
charges_enabled: acct.charges_enabled.unwrap_or(false),
payouts_enabled: acct.payouts_enabled.unwrap_or(false),
details_submitted: acct.details_submitted.unwrap_or(false),
}),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn _assert_clone_send_sync<T: Clone + Send + Sync>() {}
fn _assert_stripe_event<T: StripeEvent>() {}
#[test]
fn events_are_clone_send_sync() {
_assert_clone_send_sync::<StripeSubscriptionUpdated>();
_assert_clone_send_sync::<StripeSubscriptionDeleted>();
_assert_clone_send_sync::<StripeCheckoutCompleted>();
_assert_clone_send_sync::<StripeCheckoutExpired>();
_assert_clone_send_sync::<StripeInvoicePaid>();
_assert_clone_send_sync::<StripePaymentIntentFailed>();
_assert_clone_send_sync::<StripeChargeRefunded>();
_assert_clone_send_sync::<StripeChargeDisputeCreated>();
_assert_clone_send_sync::<StripeConnectAccountUpdated>();
_assert_clone_send_sync::<StripeConnectPaymentSucceeded>();
}
#[test]
fn all_event_types_implement_stripe_event() {
_assert_stripe_event::<StripeSubscriptionUpdated>();
_assert_stripe_event::<StripeSubscriptionDeleted>();
_assert_stripe_event::<StripeCheckoutCompleted>();
_assert_stripe_event::<StripeCheckoutExpired>();
_assert_stripe_event::<StripeInvoicePaid>();
_assert_stripe_event::<StripePaymentIntentFailed>();
_assert_stripe_event::<StripeChargeRefunded>();
_assert_stripe_event::<StripeChargeDisputeCreated>();
_assert_stripe_event::<StripeConnectAccountUpdated>();
_assert_stripe_event::<StripeConnectPaymentSucceeded>();
}
}