sumup 0.5.14

Rust SDK for the SumUp API.
Documentation
//! Verify and parse event notifications sent by SumUp.
//!
//! Events let your integration react to changes in SumUp without polling the
//! API. Use them to update orders in your own system when a checkout is paid,
//! failed, or expired, trigger fulfillment or accounting workflows, reconcile
//! asynchronous payment state, and keep local records in sync with SumUp.
//!
//! Event receivers should read the HTTP request body as raw bytes and pass it
//! together with the `X-SumUp-Webhook-Signature` header to
//! [`crate::Client::parse_event_notification`]. The SDK verifies the signature
//! and timestamp before deserializing the payload.
//!
//! ```no_run
//! # use sumup::{Client, Secret};
//! # use sumup::events::{EventNotification, SIGNATURE_HEADER};
//! # use axum::{body::Bytes, http::HeaderMap};
//! # fn header<'a>(headers: &'a HeaderMap, name: &str) -> &'a str {
//! #     headers.get(name).and_then(|value| value.to_str().ok()).unwrap()
//! # }
//! # fn example(headers: HeaderMap, body: Bytes, secret: Secret) -> Result<(), sumup::events::EventError> {
//! let client = Client::default();
//! let event = client.parse_event_notification(
//!     secret.secret(),
//!     body.as_ref(),
//!     header(&headers, SIGNATURE_HEADER),
//! )?;
//!
//! match event {
//!     EventNotification::MemberUpdated(event) => {
//!         println!("member updated: {}", event.object.id);
//!     }
//!     EventNotification::Unknown(event) => {
//!         println!("unknown event type: {}", event.event_type);
//!     }
//!     _ => {}
//! }
//! # Ok(())
//! # }
//! ```
//!
//! Parsed notifications include a compact reference to the affected API
//! resource. Known event types expose [`FetchObject::fetch_object`] so your
//! integration can load the latest checkout or member state before continuing
//! domain-specific processing. Event delivery can be retried, so handlers should
//! be idempotent and safe to run more than once.

pub use crate::events_handler::{
    EventCallbackError, EventError, EventHandlerRegistrationError, EventHandlingError,
    EventsHandler, IntoEventHandlerResult, SIGNATURE_HEADER, SIGNATURE_VERSION, verify_signature,
};

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub(crate) struct RawEvent {
    id: String,
    #[serde(rename = "type")]
    event_type: String,
    created_at: crate::datetime::DateTime,
    object: EventObject,
}

impl RawEvent {
    pub(crate) fn event_type(&self) -> &str {
        &self.event_type
    }
}

/// Reference to the API resource affected by an event.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct EventObject {
    /// Resource identifier from the event payload.
    pub id: String,
    /// Resource type from the event payload.
    #[serde(rename = "type")]
    pub object_type: String,
    /// API URL that can be used to fetch the resource.
    pub url: String,
}

/// Trait implemented by typed events that can fetch their referenced API
/// resource.
pub trait FetchObject {
    /// SDK type returned when the referenced resource is fetched.
    type Object;

    /// Fetches the latest representation of the referenced API resource.
    fn fetch_object(
        &self,
    ) -> impl std::future::Future<Output = crate::error::SdkResult<Self::Object>> + '_;
}

pub(crate) mod private {
    pub trait Sealed {}
}

/// Static metadata for an SDK-generated typed event notification.
///
/// This trait is sealed and cannot be implemented outside this SDK. Events that
/// are not recognized by this SDK version are passed to the unhandled-event
/// callback as [`EventNotification::Unknown`].
///
/// ```compile_fail
/// use sumup::events::EventSpec;
///
/// struct CustomEvent;
///
/// impl EventSpec for CustomEvent {
///     const EVENT_TYPE: &'static str = "custom.created";
///     type FetchedObject = sumup::readers::Reader;
/// }
/// ```
pub trait EventSpec: private::Sealed {
    /// Event type string from the notification payload.
    const EVENT_TYPE: &'static str;

    /// SDK type returned when the referenced resource is fetched.
    type FetchedObject: serde::de::DeserializeOwned;
}

/// Event notification with static event metadata.
#[derive(Debug, Clone)]
pub struct Event<E: EventSpec> {
    /// Event identifier from the notification.
    pub id: String,
    /// Time when the event was created.
    pub created_at: crate::datetime::DateTime,
    /// Referenced API resource.
    pub object: EventObject,
    client: crate::Client,
    spec: std::marker::PhantomData<E>,
}

impl<E: EventSpec> Event<E> {
    pub(crate) fn from_raw(client: crate::Client, event: RawEvent) -> Self {
        Self {
            id: event.id,
            created_at: event.created_at,
            object: event.object,
            client,
            spec: std::marker::PhantomData,
        }
    }

    /// Returns the event type.
    pub fn event_type(&self) -> &'static str {
        E::EVENT_TYPE
    }
}

impl<E: EventSpec> FetchObject for Event<E> {
    type Object = E::FetchedObject;

    fn fetch_object(
        &self,
    ) -> impl std::future::Future<Output = crate::error::SdkResult<Self::Object>> + '_ {
        crate::events_handler::fetch_object(&self.client, &self.object.url)
    }
}

/// Event notification for an event type that does not yet have a dedicated SDK
/// variant.
#[derive(Debug, Clone)]
pub struct UnknownEvent {
    /// Event identifier from the notification.
    pub id: String,
    /// Event type string from the notification.
    pub event_type: String,
    /// Time when the event was created.
    pub created_at: crate::datetime::DateTime,
    /// Referenced API resource.
    pub object: EventObject,
    client: crate::Client,
}

impl UnknownEvent {
    pub(crate) fn from_raw(client: crate::Client, event: RawEvent) -> Self {
        Self {
            id: event.id,
            event_type: event.event_type,
            created_at: event.created_at,
            object: event.object,
            client,
        }
    }
}

impl FetchObject for UnknownEvent {
    type Object = serde_json::Value;

    fn fetch_object(
        &self,
    ) -> impl std::future::Future<Output = crate::error::SdkResult<Self::Object>> + '_ {
        crate::events_handler::fetch_object(&self.client, &self.object.url)
    }
}