1use serde::{
2 Deserialize, Deserializer, Serialize, Serializer,
3 de::{self, Visitor},
4};
5use std::fmt;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum EventKey {
9 Account(AccountEventKey),
10 Sync(SyncEventKey),
11}
12
13impl EventKey {
14 pub fn as_str(&self) -> &'static str {
15 match self {
16 EventKey::Account(key) => key.as_str(),
17 EventKey::Sync(key) => key.as_str(),
18 }
19 }
20
21 pub fn to_subject(&self) -> String {
22 match self {
23 EventKey::Account(account_key) => match account_key {
24 AccountEventKey::Welcome => "Welcome to Safe — your AI guard against chargebacks".to_string(),
26 AccountEventKey::TierUpgrade => "You’ve been upgraded".to_string(),
27 AccountEventKey::ExistingChargebacks => "You have open chargebacks".to_string(),
28 AccountEventKey::HighChargebackRatioWarning => "Warning: chargeback rate rising".to_string(),
29 AccountEventKey::ProviderDisconnected => "Action needed: connection disconnected".to_string(),
30
31 AccountEventKey::BillingPaymentFailed => "Payment failed".to_string(),
33 AccountEventKey::BillingInvoiceReady => "Invoice ready".to_string(),
34 },
35
36 EventKey::Sync(sync_key) => match sync_key {
37 SyncEventKey::Connection(connection_key) => match connection_key {
39 ConnectionEventKey::Finished => "Sync complete — Safe is watching for disputes".to_string(),
40 ConnectionEventKey::Added => "Connection successful — monitoring enabled".to_string(),
41 },
42
43 SyncEventKey::Disputer(disputer_key) => match disputer_key {
45 DisputerEventKey::PendingCharges(pending_key) => match pending_key {
46 PendingChargeEventKey::Payment => "Pending charges payment processed".to_string(),
47 PendingChargeEventKey::LimitReached => "Pending charges limit reached".to_string(),
48 PendingChargeEventKey::Added => "New pending charge added".to_string(),
49 },
50 DisputerEventKey::Disputes(dispute_key) => match dispute_key {
51 DisputeEventKey::Created => "New dispute detected".to_string(),
52 DisputeEventKey::Won => "🎉 You won a chargeback".to_string(),
53 DisputeEventKey::Lost => "Outcome: dispute lost".to_string(),
54 },
55 DisputerEventKey::Evidences(evidence_key) => match evidence_key {
56 EvidencesEventKey::Submitted => "Dispute response submitted".to_string(),
57 },
58 },
59
60 SyncEventKey::ConnectionNudge(nudge_key) => match nudge_key {
62 ConnectionNudgeKey::Day1 => "Connect your account to start protection".to_string(),
63 ConnectionNudgeKey::Day2 => "Prevent disputes before they start".to_string(),
64 ConnectionNudgeKey::Day4 => "Win disputes automatically with Disputer".to_string(),
65 ConnectionNudgeKey::Day7 => "You’re close — finish connecting".to_string(),
66 ConnectionNudgeKey::Day30 => "Still here when you’re ready".to_string(),
67 },
68
69 SyncEventKey::Chargeback(cb_key) => match cb_key {
71 ChargebackEventKey::Detected => "New chargeback detected".to_string(),
72 },
73
74 SyncEventKey::Response(resp_key) => match resp_key {
76 ResponseEventKey::DeadlineApproaching => "Urgent: response deadline approaching".to_string(),
77 },
78
79 SyncEventKey::Stopper(stopper_key) => match stopper_key {
81 StopperEventKey::PreChargebackAlert => "At-risk transaction detected".to_string(),
82 StopperEventKey::AutoRefundExecuted => "Auto-refund executed".to_string(),
83 },
84
85 SyncEventKey::Rules(rules_key) => match rules_key {
87 RulesEventKey::Created => "Rule created".to_string(),
88 RulesEventKey::TriggeredAutoRefund => "Rule triggered: Auto-refund".to_string(),
89 },
90 },
91 }
92 }
93}
94
95impl Serialize for EventKey {
97 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
98 where
99 S: Serializer,
100 {
101 serializer.serialize_str(self.as_str())
102 }
103}
104
105impl<'de> Deserialize<'de> for EventKey {
107 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108 where
109 D: Deserializer<'de>,
110 {
111 struct EventKeyVisitor;
112
113 impl<'de> Visitor<'de> for EventKeyVisitor {
114 type Value = EventKey;
115
116 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
117 formatter.write_str("a valid event key string")
118 }
119
120 fn visit_str<E>(self, value: &str) -> Result<EventKey, E>
121 where
122 E: de::Error,
123 {
124 match value {
125 "account.welcome" => Ok(keys::ACCOUNT_WELCOME),
126 "account.tier_upgrade" => Ok(keys::ACCOUNT_TIER_UPGRADE),
127 "sync.connection.finished" => Ok(keys::SYNC_CONNECTION_FINISHED),
128 "sync.connection.added" => Ok(keys::SYNC_CONNECTION_ADDED),
129 "sync.disputer.pending_charges.payment" => Ok(keys::SYNC_DISPUTER_PENDING_CHARGES_PAYMENT),
130 "sync.disputer.pending_charges.limit_reached" => {
131 Ok(keys::SYNC_DISPUTER_PENDING_CHARGES_LIMIT_REACHED)
132 }
133 "sync.disputer.pending_charges.added" => Ok(keys::SYNC_DISPUTER_PENDING_CHARGES_ADDED),
134 "sync.disputer.disputes.created" => Ok(keys::SYNC_DISPUTER_DISPUTES_CREATED),
135 "sync.disputer.disputes.won" => Ok(keys::SYNC_DISPUTER_DISPUTES_WON),
136 "sync.disputer.disputes.lost" => Ok(keys::SYNC_DISPUTER_DISPUTES_LOST),
137 "sync.disputer.evidences.submitted" => Ok(keys::SYNC_DISPUTER_EVIDENCES_SUBMITTED),
138 "sync.connection.not_created.nudge_day_1" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY1),
139 "sync.connection.not_created.nudge_day_2" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY2),
141 "sync.connection.not_created.nudge_day_4" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY4),
142 "sync.connection.not_created.nudge_day_7" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY7),
143 "sync.connection.not_created.nudge_day_30" => Ok(keys::SYNC_CONNECTION_NUDGE_DAY30),
144
145 "sync.chargeback.detected" => Ok(keys::SYNC_CHARGEBACK_DETECTED),
147 "sync.response.deadline.approaching" => Ok(keys::SYNC_RESPONSE_DEADLINE_APPROACHING),
148
149 "sync.stopper.pre_chargeback_alert" => Ok(keys::SYNC_STOPPER_PRE_CHARGEBACK_ALERT),
151 "sync.stopper.auto_refund_executed" => Ok(keys::SYNC_STOPPER_AUTO_REFUND_EXECUTED),
152
153 "sync.rules.created" => Ok(keys::SYNC_RULES_CREATED),
155 "sync.rules.triggered.auto_refund" => Ok(keys::SYNC_RULES_TRIGGERED_AUTO_REFUND),
156
157 "account.existing_chargebacks" => Ok(keys::ACCOUNT_EXISTING_CHARGEBACKS),
159 "account.high_chargeback_ratio_warning"=> Ok(keys::ACCOUNT_HIGH_CHARGEBACK_RATIO_WARNING),
160 "account.provider_disconnected" => Ok(keys::ACCOUNT_PROVIDER_DISCONNECTED),
161
162 "billing.payment_failed" => Ok(keys::BILLING_PAYMENT_FAILED),
164 "billing.invoice_ready" => Ok(keys::BILLING_INVOICE_READY),
165
166 unknown => {
167 eprintln!("Unknown event key '{}', expected a known key", unknown);
169 Err(de::Error::custom(format!("Unknown event key: {}", unknown)))
170 }
171 }
172 }
173 }
174
175 deserializer.deserialize_str(EventKeyVisitor)
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
180pub enum AccountEventKey {
181 Welcome,
182 TierUpgrade,
183
184 ExistingChargebacks,
186 HighChargebackRatioWarning,
187 ProviderDisconnected,
188
189 BillingPaymentFailed,
191 BillingInvoiceReady,
192}
193
194impl AccountEventKey {
195 pub fn as_str(&self) -> &'static str {
196 match self {
197 AccountEventKey::Welcome => "account.welcome",
198 AccountEventKey::TierUpgrade => "account.tier_upgrade",
199 AccountEventKey::ExistingChargebacks => "account.existing_chargebacks",
200 AccountEventKey::HighChargebackRatioWarning => "account.high_chargeback_ratio_warning",
201 AccountEventKey::ProviderDisconnected => "account.provider_disconnected",
202 AccountEventKey::BillingPaymentFailed => "billing.payment_failed",
203 AccountEventKey::BillingInvoiceReady => "billing.invoice_ready",
204 }
205 }
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
209pub enum SyncEventKey {
210 Connection(ConnectionEventKey),
211 Disputer(DisputerEventKey),
212
213 ConnectionNudge(ConnectionNudgeKey),
215
216 Chargeback(ChargebackEventKey),
218 Response(ResponseEventKey),
219 Stopper(StopperEventKey),
220 Rules(RulesEventKey),
221}
222
223impl SyncEventKey {
224 pub fn as_str(&self) -> &'static str {
225 match self {
226 SyncEventKey::Connection(key) => key.as_str(),
227 SyncEventKey::Disputer(key) => key.as_str(),
228 SyncEventKey::ConnectionNudge(key) => key.as_str(),
229 SyncEventKey::Chargeback(key) => key.as_str(),
230 SyncEventKey::Response(key) => key.as_str(),
231 SyncEventKey::Stopper(key) => key.as_str(),
232 SyncEventKey::Rules(key) => key.as_str(),
233 }
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238pub enum ConnectionEventKey {
239 Finished,
240 Added,
241}
242
243impl ConnectionEventKey {
244 pub fn as_str(&self) -> &'static str {
245 match self {
246 ConnectionEventKey::Finished => "sync.connection.finished",
247 ConnectionEventKey::Added => "sync.connection.added",
248 }
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253pub enum DisputerEventKey {
254 PendingCharges(PendingChargeEventKey),
255 Disputes(DisputeEventKey),
256 Evidences(EvidencesEventKey),
257}
258
259impl DisputerEventKey {
260 pub fn as_str(&self) -> &'static str {
261 match self {
262 DisputerEventKey::PendingCharges(key) => key.as_str(),
263 DisputerEventKey::Disputes(key) => key.as_str(),
264 DisputerEventKey::Evidences(key) => key.as_str(),
265 }
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270pub enum DisputeEventKey {
271 Created,
272 Won,
273 Lost,
274}
275
276impl DisputeEventKey {
277 pub fn as_str(&self) -> &'static str {
278 match self {
279 DisputeEventKey::Created => "sync.disputer.disputes.created",
280 DisputeEventKey::Won => "sync.disputer.disputes.won",
281 DisputeEventKey::Lost => "sync.disputer.disputes.lost",
282 }
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
287pub enum PendingChargeEventKey {
288 Payment,
289 LimitReached,
290 Added,
291}
292
293impl PendingChargeEventKey {
294 pub fn as_str(&self) -> &'static str {
295 match self {
296 PendingChargeEventKey::Payment => "sync.disputer.pending_charges.payment",
297 PendingChargeEventKey::LimitReached => "sync.disputer.pending_charges.limit_reached",
298 PendingChargeEventKey::Added => "sync.disputer.pending_charges.added",
299 }
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
304pub enum EvidencesEventKey {
305 Submitted,
306}
307
308impl EvidencesEventKey {
309 pub fn as_str(&self) -> &'static str {
310 match self {
311 EvidencesEventKey::Submitted => "sync.disputer.evidences.submitted",
312 }
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
318pub enum ConnectionNudgeKey {
319 Day1,
320 Day2,
321 Day4,
322 Day7,
323 Day30,
324}
325
326impl ConnectionNudgeKey {
327 pub fn as_str(&self) -> &'static str {
328 match self {
329 ConnectionNudgeKey::Day1 => "sync.connection.not_created.nudge_day_1",
330 ConnectionNudgeKey::Day2 => "sync.connection.not_created.nudge_day_2",
331 ConnectionNudgeKey::Day4 => "sync.connection.not_created.nudge_day_4",
332 ConnectionNudgeKey::Day7 => "sync.connection.not_created.nudge_day_7",
333 ConnectionNudgeKey::Day30 => "sync.connection.not_created.nudge_day_30",
334 }
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
340pub enum ChargebackEventKey {
341 Detected,
342}
343impl ChargebackEventKey {
344 pub fn as_str(&self) -> &'static str {
345 match self {
346 ChargebackEventKey::Detected => "sync.chargeback.detected",
347 }
348 }
349}
350
351#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
353pub enum ResponseEventKey {
354 DeadlineApproaching,
355}
356impl ResponseEventKey {
357 pub fn as_str(&self) -> &'static str {
358 match self {
359 ResponseEventKey::DeadlineApproaching => "sync.response.deadline.approaching",
360 }
361 }
362}
363
364#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
366pub enum StopperEventKey {
367 PreChargebackAlert,
368 AutoRefundExecuted,
369}
370impl StopperEventKey {
371 pub fn as_str(&self) -> &'static str {
372 match self {
373 StopperEventKey::PreChargebackAlert => "sync.stopper.pre_chargeback_alert",
374 StopperEventKey::AutoRefundExecuted => "sync.stopper.auto_refund_executed",
375 }
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
381pub enum RulesEventKey {
382 Created,
383 TriggeredAutoRefund,
384}
385impl RulesEventKey {
386 pub fn as_str(&self) -> &'static str {
387 match self {
388 RulesEventKey::Created => "sync.rules.created",
389 RulesEventKey::TriggeredAutoRefund => "sync.rules.triggered.auto_refund",
390 }
391 }
392}
393
394pub mod keys {
395 use super::*;
396
397 pub const ACCOUNT_WELCOME: EventKey = EventKey::Account(AccountEventKey::Welcome);
398 pub const ACCOUNT_TIER_UPGRADE: EventKey = EventKey::Account(AccountEventKey::TierUpgrade);
399
400 pub const SYNC_CONNECTION_FINISHED: EventKey =
401 EventKey::Sync(SyncEventKey::Connection(ConnectionEventKey::Finished));
402
403 pub const SYNC_CONNECTION_ADDED: EventKey = EventKey::Sync(SyncEventKey::Connection(ConnectionEventKey::Added));
404
405 pub const SYNC_DISPUTER_PENDING_CHARGES_PAYMENT: EventKey = EventKey::Sync(SyncEventKey::Disputer(
406 DisputerEventKey::PendingCharges(PendingChargeEventKey::Payment),
407 ));
408 pub const SYNC_DISPUTER_PENDING_CHARGES_LIMIT_REACHED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
409 DisputerEventKey::PendingCharges(PendingChargeEventKey::LimitReached),
410 ));
411 pub const SYNC_DISPUTER_PENDING_CHARGES_ADDED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
412 DisputerEventKey::PendingCharges(PendingChargeEventKey::Added),
413 ));
414 pub const SYNC_DISPUTER_DISPUTES_CREATED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
415 DisputerEventKey::Disputes(DisputeEventKey::Created),
416 ));
417 pub const SYNC_DISPUTER_DISPUTES_WON: EventKey =
418 EventKey::Sync(SyncEventKey::Disputer(DisputerEventKey::Disputes(DisputeEventKey::Won)));
419 pub const SYNC_DISPUTER_DISPUTES_LOST: EventKey = EventKey::Sync(SyncEventKey::Disputer(
420 DisputerEventKey::Disputes(DisputeEventKey::Lost),
421 ));
422 pub const SYNC_DISPUTER_EVIDENCES_SUBMITTED: EventKey = EventKey::Sync(SyncEventKey::Disputer(
423 DisputerEventKey::Evidences(EvidencesEventKey::Submitted),
424 ));
425
426 pub const SYNC_CONNECTION_NUDGE_DAY1: EventKey = EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day1));
427
428 pub const SYNC_CONNECTION_NUDGE_DAY2: EventKey =
429 EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day2));
430 pub const SYNC_CONNECTION_NUDGE_DAY4: EventKey =
431 EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day4));
432 pub const SYNC_CONNECTION_NUDGE_DAY7: EventKey =
433 EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day7));
434 pub const SYNC_CONNECTION_NUDGE_DAY30: EventKey =
435 EventKey::Sync(SyncEventKey::ConnectionNudge(ConnectionNudgeKey::Day30));
436
437 pub const SYNC_CHARGEBACK_DETECTED: EventKey =
438 EventKey::Sync(SyncEventKey::Chargeback(ChargebackEventKey::Detected));
439
440 pub const SYNC_RESPONSE_DEADLINE_APPROACHING: EventKey =
441 EventKey::Sync(SyncEventKey::Response(ResponseEventKey::DeadlineApproaching));
442
443 pub const SYNC_STOPPER_PRE_CHARGEBACK_ALERT: EventKey =
444 EventKey::Sync(SyncEventKey::Stopper(StopperEventKey::PreChargebackAlert));
445 pub const SYNC_STOPPER_AUTO_REFUND_EXECUTED: EventKey =
446 EventKey::Sync(SyncEventKey::Stopper(StopperEventKey::AutoRefundExecuted));
447
448 pub const SYNC_RULES_CREATED: EventKey =
449 EventKey::Sync(SyncEventKey::Rules(RulesEventKey::Created));
450 pub const SYNC_RULES_TRIGGERED_AUTO_REFUND: EventKey =
451 EventKey::Sync(SyncEventKey::Rules(RulesEventKey::TriggeredAutoRefund));
452
453 pub const ACCOUNT_EXISTING_CHARGEBACKS: EventKey =
455 EventKey::Account(AccountEventKey::ExistingChargebacks);
456 pub const ACCOUNT_HIGH_CHARGEBACK_RATIO_WARNING: EventKey =
457 EventKey::Account(AccountEventKey::HighChargebackRatioWarning);
458 pub const ACCOUNT_PROVIDER_DISCONNECTED: EventKey =
459 EventKey::Account(AccountEventKey::ProviderDisconnected);
460
461 pub const BILLING_PAYMENT_FAILED: EventKey =
463 EventKey::Account(AccountEventKey::BillingPaymentFailed);
464 pub const BILLING_INVOICE_READY: EventKey =
465 EventKey::Account(AccountEventKey::BillingInvoiceReady);
466
467}