1use std::str::FromStr;
6
7use serde::{Deserialize, Serialize};
8
9use crate::stripe::error::{StripeWebhookError, StripeWebhookResult};
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(rename_all = "snake_case")]
14pub enum StripeEventType {
15 #[serde(rename = "customer.created")]
17 CustomerCreated,
18
19 #[serde(rename = "customer.subscription.created")]
21 SubscriptionCreated,
22 #[serde(rename = "customer.subscription.updated")]
23 SubscriptionUpdated,
24 #[serde(rename = "customer.subscription.deleted")]
25 SubscriptionDeleted,
26
27 #[serde(rename = "invoice.payment_succeeded")]
29 InvoicePaymentSucceeded,
30 #[serde(rename = "invoice.payment_failed")]
31 InvoicePaymentFailed,
32
33 #[serde(other)]
35 Unknown,
36}
37
38impl FromStr for StripeEventType {
39 type Err = std::convert::Infallible;
40
41 fn from_str(s: &str) -> Result<Self, Self::Err> {
42 Ok(match s {
43 "customer.created" => Self::CustomerCreated,
44 "customer.subscription.created" => Self::SubscriptionCreated,
45 "customer.subscription.updated" => Self::SubscriptionUpdated,
46 "customer.subscription.deleted" => Self::SubscriptionDeleted,
47 "invoice.payment_succeeded" => Self::InvoicePaymentSucceeded,
48 "invoice.payment_failed" => Self::InvoicePaymentFailed,
49 _ => Self::Unknown,
50 })
51 }
52}
53
54impl StripeEventType {
55 pub fn as_str(&self) -> &'static str {
57 match self {
58 Self::CustomerCreated => "customer.created",
59 Self::SubscriptionCreated => "customer.subscription.created",
60 Self::SubscriptionUpdated => "customer.subscription.updated",
61 Self::SubscriptionDeleted => "customer.subscription.deleted",
62 Self::InvoicePaymentSucceeded => "invoice.payment_succeeded",
63 Self::InvoicePaymentFailed => "invoice.payment_failed",
64 Self::Unknown => "unknown",
65 }
66 }
67
68 pub fn is_known(&self) -> bool {
70 !matches!(self, Self::Unknown)
71 }
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct StripeEvent {
77 pub id: String,
79
80 #[serde(rename = "type")]
82 pub event_type: String,
83
84 pub created: i64,
86
87 #[serde(default)]
89 pub api_version: Option<String>,
90
91 pub livemode: bool,
93
94 #[serde(default)]
96 pub pending_webhooks: u32,
97
98 pub data: EventData,
100
101 #[serde(default)]
103 pub request: Option<EventRequest>,
104}
105
106impl StripeEvent {
107 pub fn from_bytes(bytes: &[u8]) -> StripeWebhookResult<Self> {
109 serde_json::from_slice(bytes).map_err(|e| StripeWebhookError::InvalidPayload(e.to_string()))
110 }
111
112 pub fn typed_event_type(&self) -> StripeEventType {
114 StripeEventType::from_str(&self.event_type).unwrap()
116 }
117
118 pub fn as_subscription(&self) -> StripeWebhookResult<SubscriptionEvent> {
120 match self.typed_event_type() {
121 StripeEventType::SubscriptionCreated
122 | StripeEventType::SubscriptionUpdated
123 | StripeEventType::SubscriptionDeleted => {
124 let subscription: Subscription =
125 serde_json::from_value(self.data.object.clone())
126 .map_err(|e| StripeWebhookError::InvalidPayload(e.to_string()))?;
127
128 Ok(SubscriptionEvent {
129 event_id: self.id.clone(),
130 event_type: self.typed_event_type(),
131 subscription,
132 previous_attributes: self.data.previous_attributes.clone(),
133 })
134 }
135 _ => Err(StripeWebhookError::InvalidPayload(format!(
136 "Event {} is not a subscription event",
137 self.event_type
138 ))),
139 }
140 }
141
142 pub fn as_invoice(&self) -> StripeWebhookResult<InvoiceEvent> {
144 match self.typed_event_type() {
145 StripeEventType::InvoicePaymentSucceeded | StripeEventType::InvoicePaymentFailed => {
146 let invoice: Invoice = serde_json::from_value(self.data.object.clone())
147 .map_err(|e| StripeWebhookError::InvalidPayload(e.to_string()))?;
148
149 Ok(InvoiceEvent {
150 event_id: self.id.clone(),
151 event_type: self.typed_event_type(),
152 invoice,
153 })
154 }
155 _ => Err(StripeWebhookError::InvalidPayload(format!(
156 "Event {} is not an invoice event",
157 self.event_type
158 ))),
159 }
160 }
161
162 pub fn as_customer(&self) -> StripeWebhookResult<CustomerEvent> {
164 match self.typed_event_type() {
165 StripeEventType::CustomerCreated => {
166 let customer: Customer = serde_json::from_value(self.data.object.clone())
167 .map_err(|e| StripeWebhookError::InvalidPayload(e.to_string()))?;
168
169 Ok(CustomerEvent {
170 event_id: self.id.clone(),
171 event_type: self.typed_event_type(),
172 customer,
173 })
174 }
175 _ => Err(StripeWebhookError::InvalidPayload(format!(
176 "Event {} is not a customer event",
177 self.event_type
178 ))),
179 }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct EventData {
186 pub object: serde_json::Value,
188
189 #[serde(default)]
191 pub previous_attributes: Option<serde_json::Value>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct EventRequest {
197 pub id: Option<String>,
199 pub idempotency_key: Option<String>,
201}
202
203#[derive(Debug, Clone)]
209pub struct SubscriptionEvent {
210 pub event_id: String,
212 pub event_type: StripeEventType,
214 pub subscription: Subscription,
216 pub previous_attributes: Option<serde_json::Value>,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct Subscription {
223 pub id: String,
225 pub customer: String,
227 pub status: SubscriptionStatus,
229 pub current_period_start: i64,
231 pub current_period_end: i64,
233 #[serde(default)]
235 pub cancel_at_period_end: bool,
236 pub canceled_at: Option<i64>,
238 pub ended_at: Option<i64>,
240 pub trial_end: Option<i64>,
242 pub items: SubscriptionItems,
244 #[serde(default)]
246 pub metadata: serde_json::Value,
247 pub livemode: bool,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253#[serde(rename_all = "snake_case")]
254pub enum SubscriptionStatus {
255 Active,
256 PastDue,
257 Unpaid,
258 Canceled,
259 Incomplete,
260 IncompleteExpired,
261 Trialing,
262 Paused,
263 #[serde(other)]
264 Unknown,
265}
266
267impl SubscriptionStatus {
268 pub fn is_active(&self) -> bool {
270 matches!(self, Self::Active | Self::Trialing)
271 }
272
273 pub fn requires_payment_action(&self) -> bool {
275 matches!(self, Self::PastDue | Self::Unpaid | Self::Incomplete)
276 }
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct SubscriptionItems {
282 pub data: Vec<SubscriptionItem>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct SubscriptionItem {
289 pub id: String,
291 pub price: Price,
293 #[serde(default = "default_quantity")]
295 pub quantity: u32,
296}
297
298fn default_quantity() -> u32 {
299 1
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct Price {
305 pub id: String,
307 pub product: String,
309 pub unit_amount: Option<i64>,
311 pub currency: String,
313 pub recurring: Option<Recurring>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct Recurring {
320 pub interval: String,
322 pub interval_count: u32,
324}
325
326#[derive(Debug, Clone)]
332pub struct InvoiceEvent {
333 pub event_id: String,
335 pub event_type: StripeEventType,
337 pub invoice: Invoice,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct Invoice {
344 pub id: String,
346 pub customer: String,
348 pub subscription: Option<String>,
350 pub status: InvoiceStatus,
352 pub amount_due: i64,
354 pub amount_paid: i64,
356 pub amount_remaining: i64,
358 pub currency: String,
360 pub billing_reason: Option<String>,
362 pub customer_email: Option<String>,
364 pub hosted_invoice_url: Option<String>,
366 pub invoice_pdf: Option<String>,
368 pub payment_intent: Option<String>,
370 pub created: i64,
372 pub period_start: i64,
374 pub period_end: i64,
376 pub livemode: bool,
378}
379
380#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
382#[serde(rename_all = "snake_case")]
383pub enum InvoiceStatus {
384 Draft,
385 Open,
386 Paid,
387 Uncollectible,
388 Void,
389 #[serde(other)]
390 Unknown,
391}
392
393#[derive(Debug, Clone)]
399pub struct CustomerEvent {
400 pub event_id: String,
402 pub event_type: StripeEventType,
404 pub customer: Customer,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
410pub struct Customer {
411 pub id: String,
413 pub email: Option<String>,
415 pub name: Option<String>,
417 pub description: Option<String>,
419 pub created: i64,
421 #[serde(default)]
423 pub metadata: serde_json::Value,
424 pub livemode: bool,
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431
432 #[test]
433 fn test_event_type_parsing() {
434 assert_eq!(
435 StripeEventType::from_str("customer.subscription.created").unwrap(),
436 StripeEventType::SubscriptionCreated
437 );
438 assert_eq!(
439 StripeEventType::from_str("invoice.payment_succeeded").unwrap(),
440 StripeEventType::InvoicePaymentSucceeded
441 );
442 assert_eq!(
443 StripeEventType::from_str("unknown.event").unwrap(),
444 StripeEventType::Unknown
445 );
446 }
447
448 #[test]
449 fn test_subscription_status() {
450 assert!(SubscriptionStatus::Active.is_active());
451 assert!(SubscriptionStatus::Trialing.is_active());
452 assert!(!SubscriptionStatus::Canceled.is_active());
453
454 assert!(SubscriptionStatus::PastDue.requires_payment_action());
455 assert!(!SubscriptionStatus::Active.requires_payment_action());
456 }
457
458 #[test]
459 fn test_parse_subscription_event() {
460 let json = r#"{
461 "id": "evt_1234567890",
462 "type": "customer.subscription.created",
463 "created": 1614556800,
464 "livemode": false,
465 "pending_webhooks": 1,
466 "data": {
467 "object": {
468 "id": "sub_1234567890",
469 "customer": "cus_1234567890",
470 "status": "active",
471 "current_period_start": 1614556800,
472 "current_period_end": 1617235200,
473 "cancel_at_period_end": false,
474 "items": {
475 "data": [{
476 "id": "si_1234567890",
477 "price": {
478 "id": "price_1234567890",
479 "product": "prod_1234567890",
480 "unit_amount": 2000,
481 "currency": "usd",
482 "recurring": {
483 "interval": "month",
484 "interval_count": 1
485 }
486 },
487 "quantity": 1
488 }]
489 },
490 "metadata": {},
491 "livemode": false
492 }
493 }
494 }"#;
495
496 let event = StripeEvent::from_bytes(json.as_bytes()).unwrap();
497 assert_eq!(event.id, "evt_1234567890");
498 assert_eq!(
499 event.typed_event_type(),
500 StripeEventType::SubscriptionCreated
501 );
502
503 let sub_event = event.as_subscription().unwrap();
504 assert_eq!(sub_event.subscription.id, "sub_1234567890");
505 assert_eq!(sub_event.subscription.status, SubscriptionStatus::Active);
506 }
507
508 #[test]
509 fn test_parse_invoice_event() {
510 let json = r#"{
511 "id": "evt_invoice_1234",
512 "type": "invoice.payment_succeeded",
513 "created": 1614556800,
514 "livemode": false,
515 "pending_webhooks": 1,
516 "data": {
517 "object": {
518 "id": "in_1234567890",
519 "customer": "cus_1234567890",
520 "subscription": "sub_1234567890",
521 "status": "paid",
522 "amount_due": 2000,
523 "amount_paid": 2000,
524 "amount_remaining": 0,
525 "currency": "usd",
526 "created": 1614556800,
527 "period_start": 1614556800,
528 "period_end": 1617235200,
529 "livemode": false
530 }
531 }
532 }"#;
533
534 let event = StripeEvent::from_bytes(json.as_bytes()).unwrap();
535 let invoice_event = event.as_invoice().unwrap();
536
537 assert_eq!(invoice_event.invoice.id, "in_1234567890");
538 assert_eq!(invoice_event.invoice.status, InvoiceStatus::Paid);
539 assert_eq!(invoice_event.invoice.amount_paid, 2000);
540 }
541}