use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::{self, Visitor},
};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventKey {
Account(AccountEventKey),
Sync(SyncEventKey),
}
impl EventKey {
pub fn as_str(&self) -> &'static str {
match self {
EventKey::Account(key) => key.as_str(),
EventKey::Sync(key) => key.as_str(),
}
}
pub fn to_subject(&self) -> String {
match self {
EventKey::Account(account_key) => match account_key {
AccountEventKey::Welcome => "Welcome to Safe — your AI guard against chargebacks".to_string(),
AccountEventKey::TierUpgrade => "You’ve been upgraded".to_string(),
AccountEventKey::ExistingChargebacks => "You have open chargebacks".to_string(),
AccountEventKey::HighChargebackRatioWarning => "Warning: chargeback rate rising".to_string(),
AccountEventKey::ProviderDisconnected => "Action needed: connection disconnected".to_string(),
AccountEventKey::BillingPaymentFailed => "Payment failed".to_string(),
AccountEventKey::BillingInvoiceReady => "Invoice ready".to_string(),
},
EventKey::Sync(sync_key) => match sync_key {
SyncEventKey::Connection(connection_key) => match connection_key {
ConnectionEventKey::Finished => "Sync complete — Safe is watching for disputes".to_string(),
ConnectionEventKey::Added => "Connection successful — monitoring enabled".to_string(),
},
SyncEventKey::Disputer(disputer_key) => match disputer_key {
DisputerEventKey::PendingCharges(pending_key) => match pending_key {
PendingChargeEventKey::Payment => "Pending charges payment processed".to_string(),
PendingChargeEventKey::LimitReached => "Pending charges limit reached".to_string(),
PendingChargeEventKey::Added => "New pending charge added".to_string(),
},
DisputerEventKey::Disputes(dispute_key) => match dispute_key {
DisputeEventKey::Created => "New dispute detected".to_string(),
DisputeEventKey::Won => "🎉 You won a chargeback".to_string(),
DisputeEventKey::Lost => "Outcome: dispute lost".to_string(),
},
DisputerEventKey::Evidences(evidence_key) => match evidence_key {
EvidencesEventKey::Submitted => "Dispute response submitted".to_string(),
},
},
SyncEventKey::ConnectionNudge(nudge_key) => match nudge_key {
ConnectionNudgeKey::Day1 => "Connect your account to start protection".to_string(),
ConnectionNudgeKey::Day2 => "Prevent disputes before they start".to_string(),
ConnectionNudgeKey::Day4 => "Win disputes automatically with Disputer".to_string(),
ConnectionNudgeKey::Day7 => "You’re close — finish connecting".to_string(),
ConnectionNudgeKey::Day30 => "Still here when you’re ready".to_string(),
},
SyncEventKey::Chargeback(cb_key) => match cb_key {
ChargebackEventKey::Detected => "New chargeback detected".to_string(),
},
SyncEventKey::Response(resp_key) => match resp_key {
ResponseEventKey::DeadlineApproaching => "Urgent: response deadline approaching".to_string(),
},
SyncEventKey::Stopper(stopper_key) => match stopper_key {
StopperEventKey::PreChargebackAlert => "At-risk transaction detected".to_string(),
StopperEventKey::AutoRefundExecuted => "Auto-refund executed".to_string(),
},
SyncEventKey::Rules(rules_key) => match rules_key {
RulesEventKey::Created => "Rule created".to_string(),
RulesEventKey::TriggeredAutoRefund => "Rule triggered: Auto-refund".to_string(),
},
},
}
}
}
impl Serialize for EventKey {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for EventKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct EventKeyVisitor;
impl<'de> Visitor<'de> for EventKeyVisitor {
type Value = EventKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a valid event key string")
}
fn visit_str<E>(self, value: &str) -> Result<EventKey, E>
where
E: de::Error,
{
match value {
"account.welcome" => Ok(keys::ACCOUNT_WELCOME),
"account.tier_upgrade" => Ok(keys::ACCOUNT_TIER_UPGRADE),
"sync.connection.finished" => Ok(keys::SYNC_CONNECTION_FINISHED),
"sync.connection.added" => Ok(keys::SYNC_CONNECTION_ADDED),
"sync.disputer.pending_charges.payment" => Ok(keys::SYNC_DISPUTER_PENDING_CHARGES_PAYMENT),
"sync.disputer.pending_charges.limit_reached" => {
Ok(keys::SYNC_DISPUTER_PENDING_CHARGES_LIMIT_REACHED)
}
"sync.disputer.pending_charges.added" => Ok(keys::SYNC_DISPUTER_PENDING_CHARGES_ADDED),
"sync.disputer.disputes.created" => Ok(keys::SYNC_DISPUTER_DISPUTES_CREATED),
"sync.disputer.disputes.won" => Ok(keys::SYNC_DISPUTER_DISPUTES_WON),
"sync.disputer.disputes.lost" => Ok(keys::SYNC_DISPUTER_DISPUTES_LOST),
"sync.disputer.evidences.submitted" => Ok(keys::SYNC_DISPUTER_EVIDENCES_SUBMITTED),
"sync.connection.not_created.nudge_day_1" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY1),
"sync.connection.not_created.nudge_day_2" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY2),
"sync.connection.not_created.nudge_day_4" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY4),
"sync.connection.not_created.nudge_day_7" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY7),
"sync.connection.not_created.nudge_day_30" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY30),
"sync.chargeback.detected" => Ok(keys::SYNC_CHARGEBACK_DETECTED),
"sync.response.deadline.approaching" => Ok(keys::SYNC_RESPONSE_DEADLINE_APPROACHING),
"sync.stopper.pre_chargeback_alert" => Ok(keys::SYNC_STOPPER_PRE_CHARGEBACK_ALERT),
"sync.stopper.auto_refund_executed" => Ok(keys::SYNC_STOPPER_AUTO_REFUND_EXECUTED),
"sync.rules.created" => Ok(keys::SYNC_RULES_CREATED),
"sync.rules.triggered.auto_refund" => Ok(keys::SYNC_RULES_TRIGGERED_AUTO_REFUND),
"account.existing_chargebacks" => Ok(keys::ACCOUNT_EXISTING_CHARGEBACKS),
"account.high_chargeback_ratio_warning"=> Ok(keys::ACCOUNT_HIGH_CHARGEBACK_RATIO_WARNING),
"account.provider_disconnected" => Ok(keys::ACCOUNT_PROVIDER_DISCONNECTED),
"billing.payment_failed" => Ok(keys::BILLING_PAYMENT_FAILED),
"billing.invoice_ready" => Ok(keys::BILLING_INVOICE_READY),
unknown => {
eprintln!("Unknown event key '{}', expected a known key", unknown);
Err(de::Error::custom(format!("Unknown event key: {}", unknown)))
}
}
}
}
deserializer.deserialize_str(EventKeyVisitor)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AccountEventKey {
Welcome,
TierUpgrade,
ExistingChargebacks,
HighChargebackRatioWarning,
ProviderDisconnected,
BillingPaymentFailed,
BillingInvoiceReady,
}
impl AccountEventKey {
pub fn as_str(&self) -> &'static str {
match self {
AccountEventKey::Welcome => "account.welcome",
AccountEventKey::TierUpgrade => "account.tier_upgrade",
AccountEventKey::ExistingChargebacks => "account.existing_chargebacks",
AccountEventKey::HighChargebackRatioWarning => "account.high_chargeback_ratio_warning",
AccountEventKey::ProviderDisconnected => "account.provider_disconnected",
AccountEventKey::BillingPaymentFailed => "billing.payment_failed",
AccountEventKey::BillingInvoiceReady => "billing.invoice_ready",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SyncEventKey {
Connection(ConnectionEventKey),
Disputer(DisputerEventKey),
ConnectionNudge(ConnectionNudgeKey),
Chargeback(ChargebackEventKey),
Response(ResponseEventKey),
Stopper(StopperEventKey),
Rules(RulesEventKey),
}
impl SyncEventKey {
pub fn as_str(&self) -> &'static str {
match self {
SyncEventKey::Connection(key) => key.as_str(),
SyncEventKey::Disputer(key) => key.as_str(),
SyncEventKey::ConnectionNudge(key) => key.as_str(),
SyncEventKey::Chargeback(key) => key.as_str(),
SyncEventKey::Response(key) => key.as_str(),
SyncEventKey::Stopper(key) => key.as_str(),
SyncEventKey::Rules(key) => key.as_str(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionEventKey {
Finished,
Added,
}
impl ConnectionEventKey {
pub fn as_str(&self) -> &'static str {
match self {
ConnectionEventKey::Finished => "sync.connection.finished",
ConnectionEventKey::Added => "sync.connection.added",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DisputerEventKey {
PendingCharges(PendingChargeEventKey),
Disputes(DisputeEventKey),
Evidences(EvidencesEventKey),
}
impl DisputerEventKey {
pub fn as_str(&self) -> &'static str {
match self {
DisputerEventKey::PendingCharges(key) => key.as_str(),
DisputerEventKey::Disputes(key) => key.as_str(),
DisputerEventKey::Evidences(key) => key.as_str(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DisputeEventKey {
Created,
Won,
Lost,
}
impl DisputeEventKey {
pub fn as_str(&self) -> &'static str {
match self {
DisputeEventKey::Created => "sync.disputer.disputes.created",
DisputeEventKey::Won => "sync.disputer.disputes.won",
DisputeEventKey::Lost => "sync.disputer.disputes.lost",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PendingChargeEventKey {
Payment,
LimitReached,
Added,
}
impl PendingChargeEventKey {
pub fn as_str(&self) -> &'static str {
match self {
PendingChargeEventKey::Payment => "sync.disputer.pending_charges.payment",
PendingChargeEventKey::LimitReached => "sync.disputer.pending_charges.limit_reached",
PendingChargeEventKey::Added => "sync.disputer.pending_charges.added",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EvidencesEventKey {
Submitted,
}
impl EvidencesEventKey {
pub fn as_str(&self) -> &'static str {
match self {
EvidencesEventKey::Submitted => "sync.disputer.evidences.submitted",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ConnectionNudgeKey {
Day1,
Day2,
Day4,
Day7,
Day30,
}
impl ConnectionNudgeKey {
pub fn as_str(&self) -> &'static str {
match self {
ConnectionNudgeKey::Day1 => "sync.connection.not_created.nudge_day_1",
ConnectionNudgeKey::Day2 => "sync.connection.not_created.nudge_day_2",
ConnectionNudgeKey::Day4 => "sync.connection.not_created.nudge_day_4",
ConnectionNudgeKey::Day7 => "sync.connection.not_created.nudge_day_7",
ConnectionNudgeKey::Day30 => "sync.connection.not_created.nudge_day_30",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChargebackEventKey {
Detected,
}
impl ChargebackEventKey {
pub fn as_str(&self) -> &'static str {
match self {
ChargebackEventKey::Detected => "sync.chargeback.detected",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ResponseEventKey {
DeadlineApproaching,
}
impl ResponseEventKey {
pub fn as_str(&self) -> &'static str {
match self {
ResponseEventKey::DeadlineApproaching => "sync.response.deadline.approaching",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum StopperEventKey {
PreChargebackAlert,
AutoRefundExecuted,
}
impl StopperEventKey {
pub fn as_str(&self) -> &'static str {
match self {
StopperEventKey::PreChargebackAlert => "sync.stopper.pre_chargeback_alert",
StopperEventKey::AutoRefundExecuted => "sync.stopper.auto_refund_executed",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RulesEventKey {
Created,
TriggeredAutoRefund,
}
impl RulesEventKey {
pub fn as_str(&self) -> &'static str {
match self {
RulesEventKey::Created => "sync.rules.created",
RulesEventKey::TriggeredAutoRefund => "sync.rules.triggered.auto_refund",
}
}
}
pub mod keys {
use super::*;
pub const ACCOUNT_WELCOME: EventKey = EventKey::Account(AccountEventKey::Welcome);
pub const ACCOUNT_TIER_UPGRADE: EventKey = EventKey::Account(AccountEventKey::TierUpgrade);
pub const SYNC_CONNECTION_FINISHED: EventKey =
EventKey::Sync(SyncEventKey::Connection(ConnectionEventKey::Finished));
pub const SYNC_CONNECTION_ADDED: EventKey = EventKey::Sync(SyncEventKey::Connection(ConnectionEventKey::Added));
pub const SYNC_DISPUTER_PENDING_CHARGES_PAYMENT: EventKey = EventKey::Sync(SyncEventKey::Disputer(
DisputerEventKey::PendingCharges(PendingChargeEventKey::Payment),
));
pub const SYNC_DISPUTER_PENDING_CHARGES_LIMIT_REACHED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
DisputerEventKey::PendingCharges(PendingChargeEventKey::LimitReached),
));
pub const SYNC_DISPUTER_PENDING_CHARGES_ADDED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
DisputerEventKey::PendingCharges(PendingChargeEventKey::Added),
));
pub const SYNC_DISPUTER_DISPUTES_CREATED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
DisputerEventKey::Disputes(DisputeEventKey::Created),
));
pub const SYNC_DISPUTER_DISPUTES_WON: EventKey =
EventKey::Sync(SyncEventKey::Disputer(DisputerEventKey::Disputes(DisputeEventKey::Won)));
pub const SYNC_DISPUTER_DISPUTES_LOST: EventKey = EventKey::Sync(SyncEventKey::Disputer(
DisputerEventKey::Disputes(DisputeEventKey::Lost),
));
pub const SYNC_DISPUTER_EVIDENCES_SUBMITTED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
DisputerEventKey::Evidences(EvidencesEventKey::Submitted),
));
pub const SYNC_CONNECTION_NUDGE_DAY1: EventKey = EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day1));
pub const SYNC_CONNECTION_NUDGE_DAY2: EventKey =
EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day2));
pub const SYNC_CONNECTION_NUDGE_DAY4: EventKey =
EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day4));
pub const SYNC_CONNECTION_NUDGE_DAY7: EventKey =
EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day7));
pub const SYNC_CONNECTION_NUDGE_DAY30: EventKey =
EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day30));
pub const SYNC_CHARGEBACK_DETECTED: EventKey =
EventKey::Sync(SyncEventKey::Chargeback(ChargebackEventKey::Detected));
pub const SYNC_RESPONSE_DEADLINE_APPROACHING: EventKey =
EventKey::Sync(SyncEventKey::Response(ResponseEventKey::DeadlineApproaching));
pub const SYNC_STOPPER_PRE_CHARGEBACK_ALERT: EventKey =
EventKey::Sync(SyncEventKey::Stopper(StopperEventKey::PreChargebackAlert));
pub const SYNC_STOPPER_AUTO_REFUND_EXECUTED: EventKey =
EventKey::Sync(SyncEventKey::Stopper(StopperEventKey::AutoRefundExecuted));
pub const SYNC_RULES_CREATED: EventKey =
EventKey::Sync(SyncEventKey::Rules(RulesEventKey::Created));
pub const SYNC_RULES_TRIGGERED_AUTO_REFUND: EventKey =
EventKey::Sync(SyncEventKey::Rules(RulesEventKey::TriggeredAutoRefund));
pub const ACCOUNT_EXISTING_CHARGEBACKS: EventKey =
EventKey::Account(AccountEventKey::ExistingChargebacks);
pub const ACCOUNT_HIGH_CHARGEBACK_RATIO_WARNING: EventKey =
EventKey::Account(AccountEventKey::HighChargebackRatioWarning);
pub const ACCOUNT_PROVIDER_DISCONNECTED: EventKey =
EventKey::Account(AccountEventKey::ProviderDisconnected);
pub const BILLING_PAYMENT_FAILED: EventKey =
EventKey::Account(AccountEventKey::BillingPaymentFailed);
pub const BILLING_INVOICE_READY: EventKey =
EventKey::Account(AccountEventKey::BillingInvoiceReady);
}