stripe/resources/
webhook_events.rs

1use std::collections::HashMap;
2
3use chrono::Utc;
4#[cfg(feature = "webhook-events")]
5use hmac::{Hmac, Mac};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8#[cfg(feature = "webhook-events")]
9use sha2::Sha256;
10use smart_default::SmartDefault;
11
12use crate::error::WebhookError;
13use crate::resources::*;
14
15#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq, Hash, SmartDefault)]
16pub enum EventType {
17    #[serde(rename = "account.application.authorized")]
18    AccountApplicationAuthorized,
19    #[serde(rename = "account.application.deauthorized")]
20    AccountApplicationDeauthorized,
21    #[serde(rename = "account.external_account.created")]
22    AccountExternalAccountCreated,
23    #[serde(rename = "account.external_account.deleted")]
24    AccountExternalAccountDeleted,
25    #[serde(rename = "account.external_account.updated")]
26    AccountExternalAccountUpdated,
27    #[serde(rename = "account.updated")]
28    AccountUpdated,
29    #[serde(rename = "application_fee.created")]
30    ApplicationFeeCreated,
31    #[serde(rename = "application_fee.refund.updated")]
32    ApplicationFeeRefundUpdated,
33    #[serde(rename = "application_fee.refunded")]
34    ApplicationFeeRefunded,
35    #[serde(rename = "balance.available")]
36    BalanceAvailable,
37    #[serde(rename = "billing_portal.configuration.created")]
38    BillingPortalConfigurationCreated,
39    #[serde(rename = "billing_portal.configuration.updated")]
40    BillingPortalConfigurationUpdated,
41    #[serde(rename = "capability.updated")]
42    CapabilityUpdated,
43    #[serde(rename = "cash_balance.funds_available")]
44    CashBalanceFundsAvailable,
45    #[serde(rename = "charge.captured")]
46    ChargeCaptured,
47    #[serde(rename = "charge.dispute.closed")]
48    ChargeDisputeClosed,
49    #[serde(rename = "charge.dispute.created")]
50    ChargeDisputeCreated,
51    #[serde(rename = "charge.dispute.funds_reinstated")]
52    ChargeDisputeFundsReinstated,
53    #[serde(rename = "charge.dispute.funds_withdrawn")]
54    ChargeDisputeFundsWithdrawn,
55    #[serde(rename = "charge.dispute.updated")]
56    ChargeDisputeUpdated,
57    #[serde(rename = "charge.expired")]
58    ChargeExpired,
59    #[serde(rename = "charge.failed")]
60    ChargeFailed,
61    #[serde(rename = "charge.pending")]
62    ChargePending,
63    #[serde(rename = "charge.refund.updated")]
64    ChargeRefundUpdated,
65    #[serde(rename = "charge.refunded")]
66    ChargeRefunded,
67    #[serde(rename = "charge.succeeded")]
68    ChargeSucceeded,
69    #[serde(rename = "charge.updated")]
70    ChargeUpdated,
71    #[serde(rename = "checkout.session.async_payment_failed")]
72    CheckoutSessionAsyncPaymentFailed,
73    #[serde(rename = "checkout.session.async_payment_succeeded")]
74    CheckoutSessionAsyncPaymentSucceeded,
75    #[serde(rename = "checkout.session.completed")]
76    CheckoutSessionCompleted,
77    #[serde(rename = "checkout.session.expired")]
78    CheckoutSessionExpired,
79    #[serde(rename = "coupon.created")]
80    CouponCreated,
81    #[serde(rename = "coupon.deleted")]
82    CouponDeleted,
83    #[serde(rename = "coupon.updated")]
84    CouponUpdated,
85    #[serde(rename = "credit_note.created")]
86    CreditNoteCreated,
87    #[serde(rename = "credit_note.updated")]
88    CreditNoteUpdated,
89    #[serde(rename = "credit_note.voided")]
90    CreditNoteVoided,
91    #[serde(rename = "customer.created")]
92    CustomerCreated,
93    #[serde(rename = "customer.deleted")]
94    CustomerDeleted,
95    #[serde(rename = "customer.discount.created")]
96    CustomerDiscountCreated,
97    #[serde(rename = "customer.discount.deleted")]
98    CustomerDiscountDeleted,
99    #[serde(rename = "customer.discount.updated")]
100    CustomerDiscountUpdated,
101    #[serde(rename = "customer.source.created")]
102    CustomerSourceCreated,
103    #[serde(rename = "customer.source.deleted")]
104    CustomerSourceDeleted,
105    #[serde(rename = "customer.source.expiring")]
106    CustomerSourceExpiring,
107    #[serde(rename = "customer.source.updated")]
108    CustomerSourceUpdated,
109    #[serde(rename = "customer.subscription.created")]
110    CustomerSubscriptionCreated,
111    #[serde(rename = "customer.subscription.deleted")]
112    CustomerSubscriptionDeleted,
113    #[serde(rename = "customer.subscription.paused")]
114    CustomerSubscriptionPaused,
115    #[serde(rename = "customer.subscription.pending_update_applied")]
116    CustomerSubscriptionPendingUpdateApplied,
117    #[serde(rename = "customer.subscription.pending_update_expired")]
118    CustomerSubscriptionPendingUpdateExpired,
119    #[serde(rename = "customer.subscription.resumed")]
120    CustomerSubscriptionResumed,
121    #[serde(rename = "customer.subscription.trial_will_end")]
122    CustomerSubscriptionTrialWillEnd,
123    #[serde(rename = "customer.subscription.updated")]
124    CustomerSubscriptionUpdated,
125    #[serde(rename = "customer.tax_id.created")]
126    CustomerTaxIdCreated,
127    #[serde(rename = "customer.tax_id.deleted")]
128    CustomerTaxIdDeleted,
129    #[serde(rename = "customer.tax_id.updated")]
130    CustomerTaxIdUpdated,
131    #[serde(rename = "customer.updated")]
132    CustomerUpdated,
133    #[serde(rename = "file.created")]
134    FileCreated,
135    #[serde(rename = "identity.verification_session.canceled")]
136    IdentityVerificationSessionCanceled,
137    #[serde(rename = "identity.verification_session.created")]
138    IdentityVerificationSessionCreated,
139    #[serde(rename = "identity.verification_session.processing")]
140    IdentityVerificationSessionProcessing,
141    #[serde(rename = "identity.verification_session.redacted")]
142    IdentityVerificationSessionRedacted,
143    #[serde(rename = "identity.verification_session.requires_input")]
144    IdentityVerificationSessionRequiresInput,
145    #[serde(rename = "identity.verification_session.verified")]
146    IdentityVerificationSessionVerified,
147    #[serde(rename = "invoice.created")]
148    InvoiceCreated,
149    #[serde(rename = "invoice.deleted")]
150    InvoiceDeleted,
151    #[serde(rename = "invoice.finalization_failed")]
152    InvoiceFinalizationFailed,
153    #[serde(rename = "invoice.finalized")]
154    InvoiceFinalized,
155    #[serde(rename = "invoice.marked_uncollectible")]
156    InvoiceMarkedUncollectible,
157    #[serde(rename = "invoice.paid")]
158    InvoicePaid,
159    #[serde(rename = "invoice.payment_action_required")]
160    InvoicePaymentActionRequired,
161    #[serde(rename = "invoice.payment_failed")]
162    InvoicePaymentFailed,
163    #[serde(rename = "invoice.payment_succeeded")]
164    InvoicePaymentSucceeded,
165    #[serde(rename = "invoice.sent")]
166    InvoiceSent,
167    #[serde(rename = "invoice.upcoming")]
168    InvoiceUpcoming,
169    #[serde(rename = "invoice.updated")]
170    InvoiceUpdated,
171    #[serde(rename = "invoice.voided")]
172    InvoiceVoided,
173    #[serde(rename = "invoiceitem.created")]
174    InvoiceItemCreated,
175    #[serde(rename = "invoiceitem.deleted")]
176    InvoiceItemDeleted,
177    #[serde(rename = "invoiceitem.updated")]
178    InvoiceItemUpdated,
179    #[serde(rename = "issuing_authorization.created")]
180    IssuingAuthorizationCreated,
181    #[serde(rename = "issuing_authorization.request")]
182    IssuingAuthorizationRequest,
183    #[serde(rename = "issuing_authorization.updated")]
184    IssuingAuthorizationUpdated,
185    #[serde(rename = "issuing_card.created")]
186    IssuingCardCreated,
187    #[serde(rename = "issuing_card.updated")]
188    IssuingCardUpdated,
189    #[serde(rename = "issuing_cardholder.created")]
190    IssuingCardholderCreated,
191    #[serde(rename = "issuing_cardholder.updated")]
192    IssuingCardholderUpdated,
193    #[serde(rename = "issuing_dispute.closed")]
194    IssuingDisputeClosed,
195    #[serde(rename = "issuing_dispute.created")]
196    IssuingDisputeCreated,
197    #[serde(rename = "issuing_dispute.funds_reinstated")]
198    IssuingDisputeFundsReinstated,
199    #[serde(rename = "issuing_dispute.submitted")]
200    IssuingDisputeSubmitted,
201    #[serde(rename = "issuing_dispute.updated")]
202    IssuingDisputeUpdated,
203    #[serde(rename = "issuing_transaction.created")]
204    IssuingTransactionCreated,
205    #[serde(rename = "issuing_transaction.updated")]
206    IssuingTransactionUpdated,
207    #[serde(rename = "mandate.updated")]
208    MandateUpdated,
209    #[serde(rename = "order.created")]
210    OrderCreated,
211    #[serde(rename = "order.payment_failed")]
212    OrderPaymentFailed,
213    #[serde(rename = "order.payment_succeeded")]
214    OrderPaymentSucceeded,
215    #[serde(rename = "order.updated")]
216    OrderUpdated,
217    #[serde(rename = "order_return.created")]
218    OrderReturnCreated,
219    #[serde(rename = "order_return.updated")]
220    OrderReturnUpdated,
221    #[serde(rename = "payment_intent.amount_capturable_updated")]
222    PaymentIntentAmountCapturableUpdated,
223    #[serde(rename = "payment_intent.canceled")]
224    PaymentIntentCanceled,
225    #[serde(rename = "payment_intent.created")]
226    PaymentIntentCreated,
227    #[serde(rename = "payment_intent.partially_funded")]
228    PaymentIntentPartiallyFunded,
229    #[serde(rename = "payment_intent.payment_failed")]
230    PaymentIntentPaymentFailed,
231    #[serde(rename = "payment_intent.processing")]
232    PaymentIntentProcessing,
233    #[serde(rename = "payment_intent.requires_action")]
234    PaymentIntentRequiresAction,
235    #[serde(rename = "payment_intent.requires_capture")]
236    PaymentIntentRequiresCapture,
237    #[serde(rename = "payment_intent.succeeded")]
238    PaymentIntentSucceeded,
239    #[serde(rename = "payment_link.created")]
240    PaymentLinkCreated,
241    #[serde(rename = "payment_link.updated")]
242    PaymentLinkUpdated,
243    #[serde(rename = "payment_method.attached")]
244    PaymentMethodAttached,
245    #[serde(rename = "payment_method.automatically_updated")]
246    PaymentMethodAutomaticallyUpdated,
247    #[serde(rename = "payment_method.detached")]
248    PaymentMethodDetached,
249    #[serde(rename = "payment_method.updated")]
250    PaymentMethodUpdated,
251    #[serde(rename = "payout.canceled")]
252    PayoutCanceled,
253    #[serde(rename = "payout.created")]
254    PayoutCreated,
255    #[serde(rename = "payout.failed")]
256    PayoutFailed,
257    #[serde(rename = "payout.paid")]
258    PayoutPaid,
259    #[serde(rename = "payout.updated")]
260    PayoutUpdated,
261    #[serde(rename = "person.created")]
262    PersonCreated,
263    #[serde(rename = "person.deleted")]
264    PersonDeleted,
265    #[serde(rename = "person.updated")]
266    PersonUpdated,
267    #[serde(rename = "plan.created")]
268    PlanCreated,
269    #[serde(rename = "plan.deleted")]
270    PlanDeleted,
271    #[serde(rename = "plan.updated")]
272    PlanUpdated,
273    #[serde(rename = "price.created")]
274    PriceCreated,
275    #[serde(rename = "price.deleted")]
276    PriceDeleted,
277    #[serde(rename = "price.updated")]
278    PriceUpdated,
279    #[serde(rename = "product.created")]
280    ProductCreated,
281    #[serde(rename = "product.deleted")]
282    ProductDeleted,
283    #[serde(rename = "product.updated")]
284    ProductUpdated,
285    #[serde(rename = "promotion_code.created")]
286    PromotionCodeCreated,
287    #[serde(rename = "promotion_code.updated")]
288    PromotionCodeUpdated,
289    #[serde(rename = "quote.accepted")]
290    QuoteAccepted,
291    #[serde(rename = "quote.canceled")]
292    QuoteCanceled,
293    #[serde(rename = "quote.created")]
294    QuoteCreated,
295    #[serde(rename = "quote.finalized")]
296    QuoteFinalized,
297    #[serde(rename = "radar.early_fraud_warning.created")]
298    RadarEarlyFraudWarningCreated,
299    #[serde(rename = "radar.early_fraud_warning.updated")]
300    RadarEarlyFraudWarningUpdated,
301    #[serde(rename = "recipient.created")]
302    RecipientCreated,
303    #[serde(rename = "recipient.deleted")]
304    RecipientDeleted,
305    #[serde(rename = "recipient.updated")]
306    RecipientUpdated,
307    #[serde(rename = "refund.created")]
308    RefundCreated,
309    #[serde(rename = "refund.updated")]
310    RefundUpdated,
311    #[serde(rename = "reporting.report_run.failed")]
312    ReportingReportRunFailed,
313    #[serde(rename = "reporting.report_run.succeeded")]
314    ReportingReportRunSucceeded,
315    #[serde(rename = "reporting.report_type.updated")]
316    ReportingReportTypeUpdated,
317    #[serde(rename = "review.closed")]
318    ReviewClosed,
319    #[serde(rename = "review.opened")]
320    ReviewOpened,
321    #[serde(rename = "setup_intent.canceled")]
322    SetupIntentCanceled,
323    #[serde(rename = "setup_intent.created")]
324    SetupIntentCreated,
325    #[serde(rename = "setup_intent.requires_action")]
326    SetupIntentRequiresAction,
327    #[serde(rename = "setup_intent.setup_failed")]
328    SetupIntentSetupFailed,
329    #[serde(rename = "setup_intent.succeeded")]
330    SetupIntentSucceeded,
331    #[serde(rename = "sigma.scheduled_query_run.created")]
332    SigmaScheduledQueryRunCreated,
333    #[serde(rename = "sku.created")]
334    SkuCreated,
335    #[serde(rename = "sku.deleted")]
336    SkuDeleted,
337    #[serde(rename = "sku.updated")]
338    SkuUpdated,
339    #[serde(rename = "source.canceled")]
340    SourceCanceled,
341    #[serde(rename = "source.chargeable")]
342    SourceChargeable,
343    #[serde(rename = "source.failed")]
344    SourceFailed,
345    #[serde(rename = "source.mandate_notification")]
346    SourceMandateNotification,
347    #[serde(rename = "source.refund_attributes_required")]
348    SourceRefundAttributesRequired,
349    #[serde(rename = "source.transaction.created")]
350    SourceTransactionCreated,
351    #[serde(rename = "source.transaction.updated")]
352    SourceTransactionUpdated,
353    #[serde(rename = "subscription_schedule.aborted")]
354    SubscriptionScheduleAborted,
355    #[serde(rename = "subscription_schedule.canceled")]
356    SubscriptionScheduleCanceled,
357    #[serde(rename = "subscription_schedule.completed")]
358    SubscriptionScheduleCompleted,
359    #[serde(rename = "subscription_schedule.created")]
360    SubscriptionScheduleCreated,
361    #[serde(rename = "subscription_schedule.expiring")]
362    SubscriptionScheduleExpiring,
363    #[serde(rename = "subscription_schedule.released")]
364    SubscriptionScheduleReleased,
365    #[serde(rename = "subscription_schedule.updated")]
366    SubscriptionScheduleUpdated,
367    #[serde(rename = "tax_rate.created")]
368    TaxRateCreated,
369    #[serde(rename = "tax_rate.updated")]
370    TaxRateUpdated,
371    #[serde(rename = "terminal.reader.action_failed")]
372    TerminalReaderActionFailed,
373    #[serde(rename = "terminal.reader.action_succeeded")]
374    TerminalReaderActionSucceeded,
375    #[serde(rename = "test_helpers.test_clock.advancing")]
376    TestHelpersTestClockAdvancing,
377    #[serde(rename = "test_helpers.test_clock.created")]
378    TestHelpersTestClockCreated,
379    #[serde(rename = "test_helpers.test_clock.deleted")]
380    TestHelpersTestClockDeleted,
381    #[serde(rename = "test_helpers.test_clock.internal_failure")]
382    TestHelpersTestClockInternalFailure,
383    #[serde(rename = "test_helpers.test_clock.ready")]
384    TestHelpersTestClockReady,
385    #[serde(rename = "topup.canceled")]
386    TopupCanceled,
387    #[serde(rename = "topup.created")]
388    TopupCreated,
389    #[serde(rename = "topup.failed")]
390    TopupFailed,
391    #[serde(rename = "topup.reversed")]
392    TopupReversed,
393    #[serde(rename = "topup.succeeded")]
394    TopupSucceeded,
395    #[serde(rename = "transfer.created")]
396    TransferCreated,
397    #[serde(rename = "transfer.failed")]
398    TransferFailed,
399    #[serde(rename = "transfer.paid")]
400    TransferPaid,
401    #[serde(rename = "transfer.reversed")]
402    TransferReversed,
403    #[serde(rename = "transfer.updated")]
404    TransferUpdated,
405    #[serde(other)]
406    #[default]
407    Unknown,
408}
409
410impl std::fmt::Display for EventType {
411    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412        self.serialize(f)
413    }
414}
415
416/// The resource representing a Stripe "NotificationEventData".
417///
418/// note: this is a manual override of the generated code;
419///       see notification_event_data.rs for the (broken) codegen
420#[derive(Clone, Debug, Deserialize, Serialize, Default)]
421pub struct NotificationEventData {
422    pub object: EventObject,
423    #[serde(skip_serializing_if = "Option::is_none")]
424    pub previous_attributes: Option<HashMap<String, Value>>,
425}
426
427#[derive(Clone, Debug, Deserialize, Serialize)]
428#[serde(tag = "object", rename_all = "snake_case")]
429pub enum EventObject {
430    Account(Account),
431    #[serde(rename = "capability")]
432    AccountCapabilities(AccountCapabilities),
433    Application(Application),
434    ApplicationFee(ApplicationFee),
435    #[serde(rename = "fee_refund")]
436    ApplicationFeeRefund(ApplicationFeeRefund),
437    Balance(Balance),
438    BankAccount(BankAccount),
439    #[serde(rename = "billing_portal.configuration")]
440    BillingPortalConfiguration(BillingPortalConfiguration),
441    Card(Card),
442    Charge(Charge),
443    #[serde(rename = "checkout.session")]
444    CheckoutSession(CheckoutSession),
445    Coupon(Coupon),
446    Customer(Customer),
447    Discount(Discount),
448    Dispute(Dispute),
449    File(File),
450    Invoice(Invoice),
451    #[serde(rename = "invoiceitem")]
452    InvoiceItem(InvoiceItem),
453    #[serde(rename = "issuing.authorization")]
454    IssuingAuthorization(IssuingAuthorization),
455    #[serde(rename = "issuing.card")]
456    IssuingCard(IssuingCard),
457    #[serde(rename = "issuing.cardholder")]
458    IssuingCardholder(IssuingCardholder),
459    #[serde(rename = "issuing.dispute")]
460    IssuingDispute(IssuingDispute),
461    #[serde(rename = "issuing.transaction")]
462    IssuingTransaction(IssuingTransaction),
463    Mandate(Mandate),
464    PaymentIntent(PaymentIntent),
465    PaymentLink(PaymentLink),
466    PaymentMethod(PaymentMethod),
467    Payout(Payout),
468    Person(Person),
469    Plan(Plan),
470    Price(Price),
471    Product(Product),
472    PromotionCode(PromotionCode),
473    Quote(Quote),
474    Refund(Refund),
475    Review(Review),
476    SetupIntent(SetupIntent),
477    Subscription(Subscription),
478    SubscriptionSchedule(SubscriptionSchedule),
479    TaxId(TaxId),
480    TaxRate(TaxRate),
481    #[serde(rename = "test_helpers.test_clock")]
482    TestHelpersTestClock(TestHelpersTestClock),
483    Topup(Topup),
484    Transfer(Transfer),
485}
486
487impl Default for EventObject {
488    fn default() -> Self {
489        EventObject::Account(Account::default())
490    }
491}
492
493#[cfg(feature = "webhook-events")]
494pub struct Webhook {
495    current_timestamp: i64,
496}
497
498#[cfg(feature = "webhook-events")]
499impl Webhook {
500    /// Construct an event from a webhook payload and signature.
501    ///
502    /// # Errors
503    ///
504    /// This function will return a WebhookError if:
505    ///  - the provided signature is invalid
506    ///  - the provided secret is invalid
507    ///  - the signature timestamp is older than 5 minutes
508    pub fn construct_event(payload: &str, sig: &str, secret: &str) -> Result<Event, WebhookError> {
509        Self { current_timestamp: Utc::now().timestamp() }.do_construct_event(payload, sig, secret)
510    }
511
512    /// Construct an event from a webhook payload and signature, verifying its signature
513    /// using the provided timestamp.
514    ///
515    /// This is helpful for replaying requests in tests and should be avoided otherwise
516    /// in production use.
517    ///
518    /// # Errors
519    ///
520    /// This function will return a WebhookError if:
521    /// - the provided signature is invalid
522    /// - the provided secret is invalid
523    /// - the signature timestamp is older than 5 minutes from the provided timestamp
524    pub fn construct_event_with_timestamp(
525        payload: &str,
526        sig: &str,
527        secret: &str,
528        timestamp: i64,
529    ) -> Result<Event, WebhookError> {
530        Self { current_timestamp: timestamp }.do_construct_event(payload, sig, secret)
531    }
532
533    fn do_construct_event(
534        self,
535        payload: &str,
536        sig: &str,
537        secret: &str,
538    ) -> Result<Event, WebhookError> {
539        // Get Stripe signature from header
540        let signature = Signature::parse(sig)?;
541        let signed_payload = format!("{}.{}", signature.t, payload);
542
543        // Compute HMAC with the SHA256 hash function, using endpoing secret as key
544        // and signed_payload string as the message.
545        let mut mac =
546            Hmac::<Sha256>::new_from_slice(secret.as_bytes()).map_err(|_| WebhookError::BadKey)?;
547        mac.update(signed_payload.as_bytes());
548
549        let sig = hex::decode(signature.v1).map_err(|_| WebhookError::BadSignature)?;
550
551        mac.verify_slice(sig.as_slice()).map_err(|_| WebhookError::BadSignature)?;
552
553        // Get current timestamp to compare to signature timestamp
554        if (self.current_timestamp - signature.t).abs() > 300 {
555            return Err(WebhookError::BadTimestamp(signature.t));
556        }
557
558        Ok(serde_json::from_str(payload)?)
559    }
560}
561
562#[cfg(feature = "webhook-events")]
563#[derive(Debug)]
564struct Signature<'r> {
565    t: i64,
566    v1: &'r str,
567}
568
569#[cfg(feature = "webhook-events")]
570impl<'r> Signature<'r> {
571    fn parse(raw: &'r str) -> Result<Signature<'r>, WebhookError> {
572        let headers: HashMap<&str, &str> = raw
573            .split(',')
574            .map(|header| {
575                let mut key_and_value = header.split('=');
576                let key = key_and_value.next();
577                let value = key_and_value.next();
578                (key, value)
579            })
580            .filter_map(|(key, value)| match (key, value) {
581                (Some(key), Some(value)) => Some((key, value)),
582                _ => None,
583            })
584            .collect();
585        let t = headers.get("t").ok_or(WebhookError::BadSignature)?;
586        let v1 = headers.get("v1").ok_or(WebhookError::BadSignature)?;
587        Ok(Signature { t: t.parse::<i64>().map_err(WebhookError::BadHeader)?, v1 })
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    #[cfg(feature = "webhook-events")]
594    #[test]
595    fn test_event_type_display() {
596        use super::EventType;
597
598        // Can catch issues like quotes being added to the string
599        assert_eq!(
600            EventType::CustomerSubscriptionCreated.to_string(),
601            "customer.subscription.created"
602        );
603    }
604
605    #[cfg(feature = "webhook-events")]
606    #[test]
607    fn test_signature_parse() {
608        use super::Signature;
609
610        let raw_signature =
611            "t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd";
612        let signature = Signature::parse(raw_signature).unwrap();
613        assert_eq!(signature.t, 1492774577);
614        assert_eq!(
615            signature.v1,
616            "5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd"
617        );
618
619        let raw_signature_with_test_mode = "t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd,v0=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39";
620        let signature = Signature::parse(raw_signature_with_test_mode).unwrap();
621        assert_eq!(signature.t, 1492774577);
622        assert_eq!(
623            signature.v1,
624            "5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd"
625        );
626    }
627
628    #[cfg(feature = "webhook-events")]
629    #[test]
630    fn test_webhook_construct_event() {
631        let payload = r#"{
632  "id": "evt_123",
633  "object": "event",
634  "account": "acct_123",
635  "api_version": "2017-05-25",
636  "created": 1533204620,
637  "data": {
638    "object": {
639      "id": "ii_123",
640      "object": "invoiceitem",
641      "amount": 1000,
642      "currency": "usd",
643      "customer": "cus_123",
644      "date": 1533204620,
645      "description": "Test Invoice Item",
646      "discountable": false,
647      "invoice": "in_123",
648      "livemode": false,
649      "metadata": {},
650      "period": {
651        "start": 1533204620,
652        "end": 1533204620
653      },
654      "proration": false,
655      "quantity": 3
656    }
657  },
658  "livemode": false,
659  "pending_webhooks": 1,
660  "request": {
661    "id": "req_123",
662    "idempotency_key": "idempotency-key-123"
663  },
664  "type": "invoiceitem.created"
665}
666"#;
667        let event_timestamp = 1533204620;
668        let secret = "webhook_secret".to_string();
669        let signature = format!("t={},v1=82216eca827bcb7b34b8055eb2d2d9e6bc13b9ac39ded14a61e69f70c565f53a,v0=63f3a72374a733066c4be69ed7f8e5ac85c22c9f0a6a612ab9a025a9e4ee7eef", event_timestamp);
670
671        let webhook = super::Webhook { current_timestamp: event_timestamp };
672
673        let event = webhook
674            .do_construct_event(payload, &signature, &secret)
675            .expect("Failed to construct event");
676
677        assert_eq!(event.type_, super::EventType::InvoiceItemCreated);
678        assert_eq!(event.id, "evt_123".parse::<crate::EventId>().unwrap());
679        assert_eq!(event.account, "acct_123".parse().ok());
680        assert_eq!(event.created, 1533204620);
681    }
682}