routex_api/
lib.rs

1#[cfg(feature = "uniffi")]
2use chrono::NaiveDate;
3use chrono::Utc;
4use heck::ToKebabCase;
5#[cfg(feature = "uniffi")]
6use isocountry::CountryCode;
7use jsonwebtoken::dangerous::insecure_decode;
8use jsonwebtoken::{EncodingKey, Header, decode_header, encode};
9pub use routex_models::{
10    AccountStatus, AccountType, Amount, ConnectionId, CredentialsModel, DialogContext,
11    DialogOption, Image, InputType, PaymentErrorCode, ProviderErrorCode, SecrecyLevel,
12    ServiceBlockedCode, UnsupportedProductReason,
13};
14#[cfg(feature = "uniffi")]
15use rust_decimal::Decimal;
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18use serde_with::base64::Base64;
19use std::fmt;
20use std::marker::PhantomData;
21use std::str::FromStr;
22use url::Url;
23
24#[cfg(feature = "uniffi")]
25uniffi::setup_scaffolding!();
26
27#[cfg(feature = "uniffi")]
28uniffi::custom_type!(ConnectionData, Vec<u8>, {
29    try_lift: |val| Ok(val.into()),
30    lower: |obj| obj.into(),
31});
32
33#[cfg(feature = "uniffi")]
34uniffi::custom_type!(Session, Vec<u8>, {
35    try_lift: |val| Ok(val.into()),
36    lower: |obj| obj.into(),
37});
38
39#[cfg(feature = "uniffi")]
40uniffi::custom_type!(CountryCode, String, {
41    remote,
42    try_lift: |val| Ok(Self::for_alpha2(&val)?),
43    lower: |obj| obj.alpha2().to_string(),
44});
45
46#[cfg(feature = "uniffi")]
47uniffi::use_remote_type!(routex_models::Decimal);
48
49#[cfg(feature = "uniffi")]
50uniffi::custom_type!(NaiveDate, String, {
51    remote,
52    try_lift: |val| Ok(val.parse()?),
53    lower: |obj| obj.to_string(),
54});
55
56type DateTime = chrono::DateTime<Utc>;
57
58#[cfg(feature = "uniffi")]
59uniffi::custom_type!(DateTime, String, {
60    remote,
61    try_lift: |val| Ok(val.parse()?),
62    lower: |obj| obj.to_string(),
63});
64
65pub type Dialog<S> = routex_models::Dialog<ConfirmationContext<S>, InputContext<S>>;
66pub type DialogInput<S> = routex_models::DialogInput<ConfirmationContext<S>, InputContext<S>>;
67
68/// Representation of an API path
69///
70/// Use [`to_url`](Path::to_url) to build requests on clients,
71/// [`to_string`](ToString::to_string) to get for service routes.
72/// Note that the [`Display`](fmt::Display) implementation does NOT escape
73/// the segments, so that e.g. axum / matchit captures can be used.
74pub struct Path(Vec<String>);
75
76impl Path {
77    fn new<I>(segments: I) -> Self
78    where
79        I: IntoIterator,
80        I::Item: ToString,
81    {
82        Self(segments.into_iter().map(|s| s.to_string()).collect())
83    }
84
85    #[must_use]
86    /// Build a URL from a base and the path.
87    ///
88    /// # Panics
89    ///
90    /// Panics if the given URL is cannot-be-a-base.
91    pub fn to_url(&self, base: &Url) -> Url {
92        let mut url = base.clone();
93        url.path_segments_mut()
94            .expect("cannot be base")
95            .extend(&self.0);
96        url
97    }
98}
99
100impl fmt::Display for Path {
101    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102        for segment in &self.0 {
103            f.write_str("/")?;
104            f.write_str(segment)?;
105        }
106
107        Ok(())
108    }
109}
110
111pub mod headers {
112    use http::HeaderName;
113
114    pub static SESSION_ID: HeaderName = HeaderName::from_static("yaxi-session-id");
115
116    pub static TICKET: HeaderName = HeaderName::from_static("yaxi-ticket");
117
118    pub static TICKET_ID: HeaderName = HeaderName::from_static("yaxi-ticket-id");
119
120    pub static REDIRECT_URI: HeaderName = HeaderName::from_static("yaxi-redirect-uri");
121
122    pub static TRACE_ID: HeaderName = HeaderName::from_static("yaxi-trace-id");
123}
124
125pub const CURRENT_MEDIA_TYPE: &str = "application/vnd.yaxi.v5";
126
127pub mod keys {
128    use super::Path;
129    use clerk_report::PublishedVersionEntry;
130    use serde::{Deserialize, Serialize};
131    use serde_with::base64::Base64;
132
133    /// Run a TEE-attested key settlement
134    ///
135    /// Method: POST<br>
136    /// Request: [`Request`]<br>
137    /// Response: [`Response`]
138    #[must_use]
139    pub fn settlement_path() -> Path {
140        Path::new(["key-settlement"])
141    }
142
143    #[serde_with::serde_as]
144    #[derive(Serialize, Deserialize, Clone, Debug)]
145    #[serde(rename_all = "camelCase")]
146    pub struct Request {
147        /// The client's Curve25519 public key (Montgomery form, binary)
148        #[serde_as(as = "Base64")]
149        pub public_key: [u8; 32],
150    }
151
152    /// The contents of a key settlement's sealed box
153    #[serde_with::serde_as]
154    #[derive(Serialize, Deserialize)]
155    #[serde(rename_all = "camelCase")]
156    pub struct SettlementBoxMessage {
157        #[serde_as(as = "Base64")]
158        pub public_key: [u8; 32],
159        #[serde_as(as = "Base64")]
160        pub session_id: [u8; 32],
161    }
162
163    /// Response to a request for a TEE-attested key settlement.
164    #[serde_with::serde_as]
165    #[derive(Serialize, Deserialize, Clone, Debug)]
166    #[serde(rename_all = "camelCase")]
167    pub struct Response {
168        /// TEE attestation report which also authenticates `chacha_box`
169        #[serde_as(as = "Base64")]
170        pub attestation_report: Vec<u8>,
171        /// Versioned Chip Endorsement Key, AMD SEV Key and AMD Root Key as chain of PEM-encoded certificates
172        pub vcek: String,
173        /// Sealed box containing [`SettlementBoxMessage`]
174        #[serde_as(as = "Base64")]
175        pub chacha_box: Vec<u8>,
176        /// The TEE's version, signed by YAXI. The contained measurement needs to match the
177        /// attestation report's measurement.
178        pub system_version: PublishedVersionEntry,
179    }
180}
181
182pub mod traces {
183    use super::Path;
184
185    /// Retrieve trace data
186    ///
187    /// Method: GET<br>
188    /// Response: Trace data string
189    #[must_use]
190    pub fn path(trace_id: &str) -> Path {
191        Path::new(["traces", trace_id])
192    }
193}
194
195pub mod redirects {
196    use super::Path;
197    use serde::{Deserialize, Serialize};
198    use url::Url;
199
200    /// Register redirects
201    ///
202    /// Method: POST<br>
203    /// Request: [`Request`]<br>
204    /// Response: [`Response`]
205    #[must_use]
206    pub fn path() -> Path {
207        Path::new(["redirects"])
208    }
209
210    #[derive(Serialize, Deserialize, Clone, Debug)]
211    #[serde(rename_all = "camelCase")]
212    pub struct Request {
213        pub handle: String,
214        #[serde(alias = "redirect_uri")]
215        pub redirect_uri: String,
216    }
217
218    #[derive(Serialize, Deserialize, Clone, Debug)]
219    #[serde(rename_all = "camelCase")]
220    pub struct Response {
221        pub redirect_url: Url,
222    }
223}
224
225pub mod info {
226    use super::Path;
227    pub use isocountry::CountryCode;
228    use routex_models::{ConnectionId, CredentialsModel};
229    use serde::{Deserialize, Serialize};
230
231    /// Search for connections (service provider integrations)
232    ///
233    /// Method: GET<br>
234    /// Request: [`Request`]<br>
235    /// Response: [`Vec<[ConnectionInfo]>`](ConnectionInfo)
236    #[must_use]
237    pub fn search_path() -> Path {
238        Path::new(["search"])
239    }
240
241    #[derive(Serialize, Deserialize, Clone, Debug)]
242    #[serde(rename_all = "camelCase")]
243    pub struct Request {
244        pub filters: Vec<SearchFilter>,
245        #[serde(alias = "iban_detection")]
246        pub iban_detection: bool,
247        #[serde(skip_serializing_if = "Option::is_none")]
248        pub limit: Option<usize>,
249    }
250
251    /// Filters for the connection lookup
252    ///
253    /// String filters look for the given value anywhere in the related field, case-insensitive.
254    #[derive(Serialize, Deserialize, Clone, Debug)]
255    #[serde(rename_all = "camelCase")]
256    #[serde(rename_all_fields = "camelCase")]
257    pub enum SearchFilter {
258        /// List of [`ConnectionType`]s to consider.
259        #[serde(alias = "Types")]
260        Types(Vec<ConnectionType>),
261        /// List of [`CountryCode`]s to consider.
262        #[serde(alias = "Countries")]
263        Countries(Vec<CountryCode>),
264        /// String filter for the provider / product name or any alias.
265        #[serde(alias = "Name")]
266        Name(String),
267        /// String filter for the BIC.
268        #[serde(alias = "Bic")]
269        Bic(String),
270        /// String filter for the (national) bank code.
271        #[serde(alias = "BankCode")]
272        BankCode(String),
273        /// String filter for any of those fields.
274        #[serde(alias = "Term")]
275        Term(String),
276    }
277
278    /// Type of connections to consider when searching
279    #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
280    #[non_exhaustive]
281    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
282    pub enum ConnectionType {
283        /// Production connections.
284        Production,
285        /// Sandboxes connections, especially test systems provided by third-parties.
286        Sandboxes,
287    }
288
289    /// Query connection information
290    ///
291    /// Method: GET<br>
292    /// Response: [`ConnectionInfo`]
293    #[must_use]
294    pub fn fetch_path(connection_id: &str) -> Path {
295        Path::new(["info", connection_id])
296    }
297
298    /// Connection meta data
299    #[allow(clippy::module_name_repetitions)]
300    #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
301    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
302    #[serde(rename_all = "camelCase")]
303    #[cfg_attr(not(feature = "server"), non_exhaustive)]
304    pub struct ConnectionInfo {
305        /// Unique identifier.
306        pub id: ConnectionId,
307
308        /// ISO 3166-1 ALPHA-2 country codes.
309        pub countries: Vec<CountryCode>,
310
311        /// Display name.
312        pub display_name: String,
313
314        /// Credentials model.
315        pub credentials: CredentialsModel,
316
317        /// Human-friendly label for the user identifier if relevant.
318        #[serde(skip_serializing_if = "Option::is_none")]
319        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
320        pub user_id: Option<String>,
321
322        /// Advice for the credentials to be displayed.
323        #[serde(skip_serializing_if = "Option::is_none")]
324        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
325        pub advice: Option<String>,
326
327        /// Logo identifier.
328        pub logo_id: String,
329    }
330}
331
332macro_rules! context {
333    ($name:ident) => {
334        #[serde_with::serde_as]
335        #[derive(Serialize, Deserialize)]
336        #[serde(transparent)]
337        pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
338
339        impl<S> PartialEq for $name<S> {
340            fn eq(&self, other: &Self) -> bool {
341                self.0 == other.0
342            }
343        }
344
345        impl<S> Eq for $name<S> {}
346
347        impl<S> Clone for $name<S> {
348            fn clone(&self) -> Self {
349                Self(self.0.clone(), self.1)
350            }
351        }
352
353        impl<S> fmt::Debug for $name<S> {
354            fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
355                f.debug_tuple("$name")
356                    .field(&self.0)
357                    .field(&self.1)
358                    .finish()
359            }
360        }
361
362        impl<S> From<Vec<u8>> for $name<S> {
363            fn from(value: Vec<u8>) -> Self {
364                Self(value, PhantomData)
365            }
366        }
367
368        impl<S> From<$name<S>> for Vec<u8> {
369            fn from(value: $name<S>) -> Self {
370                value.0
371            }
372        }
373
374        impl<S> AsRef<[u8]> for $name<S> {
375            fn as_ref(&self) -> &[u8] {
376                &self.0
377            }
378        }
379    };
380}
381
382context!(InputContext);
383
384context!(ConfirmationContext);
385
386/// Data authenticated with an HMAC
387///
388/// Use [`to_data()`](Authenticated::to_data) to access the data locally.
389///
390/// Serialization is supported for transfer to a remote system as [JSON Web Token](https://jwt.io/).
391/// The remote system can verify and read the data from the `data` claim:
392/// ```
393/// # let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjpudWxsLCJleHAiOjIwMDAwMDAwMDB9.mWSPWTs-N4npIQpnybpFHbRiLW4D8xS36mX3CmRlRCM";
394/// # type T = ();
395/// # let hmac_key = b"a";
396/// use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
397/// use serde::Deserialize;
398///
399/// #[derive(Deserialize, Clone, Debug)]
400/// struct Claims {
401///     data: T,
402///     exp: u64,
403/// }
404///
405/// let data = decode::<Claims>(
406///     &jwt,
407///     &DecodingKey::from_secret(hmac_key),
408///     &Validation::new(Algorithm::HS256),
409/// )?.claims.data;
410/// # Ok::<(), Box<dyn std::error::Error>>(())
411#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
412#[serde(transparent)]
413pub struct Authenticated<T> {
414    jwt: String,
415    _phantom: PhantomData<T>,
416}
417
418impl<'de, T> Deserialize<'de> for Authenticated<T>
419where
420    T: for<'t> Deserialize<'t> + Clone,
421{
422    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
423    where
424        D: serde::Deserializer<'de>,
425    {
426        String::deserialize(deserializer)?
427            .parse()
428            .map_err(serde::de::Error::custom)
429    }
430}
431
432impl<T> Authenticated<T>
433where
434    T: Serialize,
435{
436    #[must_use]
437    /// Initialize authenticated data.
438    ///
439    /// # Panics
440    ///
441    /// Panics if [`jsonwebtoken::encode`] fails.
442    pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
443        let header = Header {
444            kid: Some(key_id.into()),
445            ..Default::default()
446        };
447
448        let jwt = encode(
449            &header,
450            &Claims {
451                data,
452                // 2050-07-07 14:00 CEST
453                exp: 2_540_808_000,
454            },
455            &EncodingKey::from_secret(key),
456        )
457        .expect("Encoding should work");
458
459        Self {
460            jwt,
461            _phantom: PhantomData,
462        }
463    }
464}
465
466impl<T> FromStr for Authenticated<T>
467where
468    T: for<'de> Deserialize<'de> + Clone,
469{
470    type Err = jsonwebtoken::errors::Error;
471
472    fn from_str(jwt: &str) -> Result<Self, Self::Err> {
473        // Syntax check
474        decode::<T>(jwt)?;
475        Ok(Self {
476            jwt: jwt.to_string(),
477            _phantom: PhantomData,
478        })
479    }
480}
481
482impl<T> Authenticated<T>
483where
484    T: for<'de> Deserialize<'de> + Clone,
485{
486    #[must_use]
487    pub fn as_str(&self) -> &str {
488        &self.jwt
489    }
490
491    #[must_use]
492    /// Get the key identifier from the token header.
493    ///
494    /// # Panics
495    ///
496    /// Panics if the token header cannot get decoded. This is unexpected as decoding is tested in [`FromStr`].
497    pub fn key_id(&self) -> Option<String> {
498        decode_header(&self.jwt).unwrap().kid
499    }
500
501    #[cfg(feature = "client")]
502    #[must_use]
503    #[allow(clippy::missing_panics_doc)]
504    pub fn to_data(&self) -> T {
505        decode(&self.jwt).unwrap()
506    }
507
508    #[cfg(feature = "server")]
509    /// Return contained data, applying a decoder.
510    ///
511    /// # Errors
512    ///
513    /// Forwards errors returned by [`Decoder::decode`].
514    pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
515        decoder.decode::<T>(&self.jwt).map(|d| d.data)
516    }
517}
518
519#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
520#[serde(rename_all = "camelCase")]
521pub struct Claims<T> {
522    pub data: T,
523    pub exp: u64,
524}
525
526#[cfg(feature = "server")]
527pub trait Decoder {
528    type Error;
529
530    /// Verifies `mac` for `data`.
531    ///
532    /// # Errors
533    ///
534    /// Returns `Self::Error` if verification fails.
535    fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
536    where
537        T: for<'de> Deserialize<'de> + Clone;
538}
539
540/// Response from YAXI Open Banking services.
541///
542/// The response either carries an [authenticated](Authenticated) [result](OBResult)
543/// or an interrupt (i.e. a dialog or redirect for the user).
544#[derive(Serialize, Deserialize, Debug)]
545pub enum OBResponse<S: Service> {
546    #[serde(bound = "S:")]
547    Result(
548        Authenticated<OBResult<S::Output>>,
549        Option<Session>,
550        Option<ConnectionData>,
551    ),
552    #[serde(bound = "S:")]
553    Dialog(Dialog<S>),
554    #[serde(bound = "S:")]
555    Redirect(Redirect<S>),
556    #[serde(bound = "S:")]
557    RedirectHandle(RedirectHandle<S>),
558}
559
560impl<S: Service> Clone for OBResponse<S> {
561    fn clone(&self) -> Self {
562        match self {
563            OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
564                authenticated.clone(),
565                session.clone(),
566                connection_data.clone(),
567            ),
568            OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
569            OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
570            OBResponse::RedirectHandle(redirect_handle) => {
571                OBResponse::RedirectHandle(redirect_handle.clone())
572            }
573        }
574    }
575}
576
577/// A value returned by YAXI Open Banking services.
578///
579/// Besides the value itself, it contains a timestamp and a ticket identifier
580/// (bound to known input parameters and service type).
581#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
582#[serde(rename_all = "camelCase")]
583pub struct OBResult<T> {
584    pub data: T,
585    pub ticket_id: String,
586    pub timestamp: DateTime,
587}
588
589#[serde_with::serde_as]
590#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
591pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
592
593impl From<Vec<u8>> for Session {
594    fn from(value: Vec<u8>) -> Self {
595        Session(value)
596    }
597}
598
599impl From<Session> for Vec<u8> {
600    fn from(session: Session) -> Self {
601        session.0
602    }
603}
604
605impl AsRef<[u8]> for Session {
606    fn as_ref(&self) -> &[u8] {
607        &self.0
608    }
609}
610
611#[serde_with::serde_as]
612#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
613#[serde(transparent)]
614pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
615
616impl From<Vec<u8>> for ConnectionData {
617    fn from(value: Vec<u8>) -> Self {
618        ConnectionData(value)
619    }
620}
621
622impl From<ConnectionData> for Vec<u8> {
623    fn from(connection_data: ConnectionData) -> Self {
624        connection_data.0
625    }
626}
627
628impl AsRef<[u8]> for ConnectionData {
629    fn as_ref(&self) -> &[u8] {
630        &self.0
631    }
632}
633
634/// User redirect.
635///
636/// The user is meant to get sent to the [`url`](Self::url) and the [`context`](Self::context) can
637/// be used for continuing the process at the service that issued the redirect object afterward.
638///
639/// A web application needs to direct the user agent to the returned URL.
640/// A desktop or mobile application could either open it in a browser or inside an element like a WebView.
641#[derive(Serialize, Deserialize)]
642#[serde(rename_all = "camelCase")]
643pub struct Redirect<S> {
644    pub url: Url,
645    #[serde(bound = "S:")]
646    pub context: ConfirmationContext<S>,
647}
648
649impl<S> PartialEq for Redirect<S> {
650    fn eq(&self, other: &Self) -> bool {
651        let Self { url, context } = self;
652        url == &other.url && context == &other.context
653    }
654}
655
656impl<S> Eq for Redirect<S> {}
657
658impl<S> Clone for Redirect<S> {
659    fn clone(&self) -> Self {
660        Self {
661            url: self.url.clone(),
662            context: self.context.clone(),
663        }
664    }
665}
666
667impl<S> fmt::Debug for Redirect<S> {
668    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669        f.debug_struct("Redirect")
670            .field("url", &self.url)
671            .field("context", &self.context)
672            .finish()
673    }
674}
675
676/// Incomplete user redirect.
677///
678/// A final redirect URI needs to get registered, using the handle, to receive the URL to send the user to.
679#[derive(Serialize, Deserialize)]
680#[serde(rename_all = "camelCase")]
681pub struct RedirectHandle<S> {
682    pub handle: String,
683    #[serde(bound = "S:")]
684    pub context: ConfirmationContext<S>,
685}
686
687impl<S> PartialEq for RedirectHandle<S> {
688    fn eq(&self, other: &Self) -> bool {
689        let Self { handle, context } = self;
690        handle == &other.handle && context == &other.context
691    }
692}
693
694impl<S> Eq for RedirectHandle<S> {}
695
696impl<S> Clone for RedirectHandle<S> {
697    fn clone(&self) -> Self {
698        Self {
699            handle: self.handle.clone(),
700            context: self.context.clone(),
701        }
702    }
703}
704
705impl<S> fmt::Debug for RedirectHandle<S> {
706    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
707        f.debug_struct("RedirectHandle")
708            .field("handle", &self.handle)
709            .field("context", &self.context)
710            .finish()
711    }
712}
713
714/// Decode a JWT without any validation or verification.
715fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
716where
717    T: for<'de> Deserialize<'de> + Clone,
718{
719    insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
720}
721
722#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
723pub enum ServiceId {
724    Accounts,
725    CollectPayment,
726    Transactions,
727    Balances,
728}
729
730impl fmt::Display for ServiceId {
731    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732        self.serialize(f)
733    }
734}
735
736/// Ticket to use a service.
737///
738/// This must be issued by a trusted backend for use in the (untrusted) frontend.
739///
740/// It carries
741/// * an identifier of the service that is intended to be used,
742/// * an arbitrary ticket identifier for verifying the results later on and
743/// * any critical input data for the service.
744///
745/// The ticket data gets wrapped in a [JSON Web Token](https://jwt.io/) as claim `data`.
746/// The token must also contain an expiry in the claim `exp` and the key identifier in the `kid` header field.
747///
748/// Example issuance of an `Authenticated<Ticket<accounts::Service>>` for the accounts service:
749///
750/// ```
751/// # const HMAC_KEY: &[u8] = b"foo";
752/// # const HMAC_KEY_ID: &str = "foo";
753/// use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
754/// use serde::Serialize;
755/// use std::time::{SystemTime, UNIX_EPOCH};
756/// use uuid::Uuid;
757///
758/// let ticket_id = Uuid::new_v4().to_string();
759///
760/// #[derive(Serialize, Debug)]
761/// struct Ticket<T> {
762///     service: String,
763///     id: String,
764///     data: T,
765/// }
766///
767/// #[derive(Serialize, Debug)]
768/// struct Claims<T> {
769///     data: Ticket<T>,
770///     exp: u64,
771/// }
772///
773/// let header = Header {
774///     alg: Algorithm::HS256,
775///     kid: Some(HMAC_KEY_ID.to_string()),
776///     ..Default::default()
777/// };
778///
779/// let authenticated_ticket = encode(
780///     &header,
781///     &Claims {
782///         data: Ticket {
783///             service: "Accounts".to_string(),
784///             id: ticket_id,
785///             data: (),
786///         },
787///         exp: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() + 600,
788///     },
789///     &EncodingKey::from_secret(HMAC_KEY),
790/// )?;
791/// # let auth: routex_api::Authenticated<routex_api::Ticket<routex_api::accounts::Service>> = authenticated_ticket.parse().unwrap();
792/// # auth.to_data();
793/// # auth.key_id().unwrap();
794/// # Ok::<(), Box<dyn std::error::Error>>(())
795#[derive(Serialize, Deserialize)]
796#[serde(rename_all = "camelCase")]
797pub struct Ticket<S: Service> {
798    pub service: ServiceId,
799    pub id: String,
800    pub data: S::TicketData,
801}
802
803impl<S: Service> PartialEq for Ticket<S> {
804    fn eq(&self, other: &Self) -> bool {
805        let Self { service, id, data } = self;
806        service == &other.service && id == &other.id && data == &other.data
807    }
808}
809
810impl<S: Service> Eq for Ticket<S> {}
811
812impl<S: Service> fmt::Debug for Ticket<S> {
813    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
814        f.debug_struct("Ticket")
815            .field("service", &self.service)
816            .field("id", &self.id)
817            .field("data", &self.data)
818            .finish()
819    }
820}
821
822impl<S: Service> Clone for Ticket<S> {
823    fn clone(&self) -> Self {
824        Self {
825            service: self.service,
826            id: self.id.clone(),
827            data: self.data.clone(),
828        }
829    }
830}
831
832pub trait Service {
833    const ID: ServiceId;
834
835    type TicketData: Serialize
836        + for<'de> Deserialize<'de>
837        + PartialEq
838        + Eq
839        + Clone
840        + fmt::Debug
841        + Send
842        + Sync;
843
844    type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
845
846    type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
847
848    /// Use the service
849    ///
850    /// Method: POST<br>
851    /// Request: `Self::Request`<br>
852    /// Response: [`OBResponse<Self>`](OBResponse)
853    #[must_use]
854    fn path() -> Path {
855        Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
856    }
857
858    #[must_use]
859    fn response_path() -> Path {
860        Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
861    }
862
863    #[must_use]
864    fn confirmation_path() -> Path {
865        Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
866    }
867}
868
869#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
870#[serde(tag = "ticketStatus")]
871pub enum TicketStatus<T> {
872    Unfinished,
873    Error(ErrorKind),
874    Success(T),
875}
876
877#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
878#[serde(tag = "error")]
879pub enum ErrorKind {
880    UnexpectedError,
881    Canceled,
882    InvalidCredentials,
883    ServiceBlocked,
884    Unauthorized,
885    ConsentExpired,
886    AccessExceeded,
887    PeriodOutOfBounds,
888    UnsupportedProduct,
889    PaymentFailed,
890    UnexpectedValue,
891    TicketError,
892    ProviderError,
893    NotFound,
894}
895
896impl From<&Error> for ErrorKind {
897    fn from(err: &Error) -> Self {
898        match err {
899            Error::UnexpectedError { .. } => Self::UnexpectedError,
900            Error::Canceled { .. } => Self::Canceled,
901            Error::InvalidCredentials { .. } => Self::InvalidCredentials,
902            Error::ServiceBlocked { .. } => Self::ServiceBlocked,
903            Error::Unauthorized { .. } => Self::Unauthorized,
904            Error::ConsentExpired { .. } => Self::ConsentExpired,
905            Error::AccessExceeded { .. } => Self::AccessExceeded,
906            Error::PeriodOutOfBounds { .. } => Self::PeriodOutOfBounds,
907            Error::UnsupportedProduct { .. } => Self::UnsupportedProduct,
908            Error::PaymentFailed { .. } => Self::PaymentFailed,
909            Error::UnexpectedValue { .. } => Self::UnexpectedValue,
910            Error::TicketError { .. } => Self::TicketError,
911            Error::ProviderError { .. } => Self::ProviderError,
912        }
913    }
914}
915
916#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
917#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
918#[serde(rename_all = "camelCase")]
919pub struct Credentials {
920    #[serde(alias = "connection_id")]
921    pub connection_id: ConnectionId,
922    #[serde(skip_serializing_if = "Option::is_none")]
923    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
924    #[serde(alias = "user_id")]
925    pub user_id: Option<String>,
926    #[serde(skip_serializing_if = "Option::is_none")]
927    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
928    pub password: Option<String>,
929    #[serde(skip_serializing_if = "Option::is_none")]
930    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
931    #[serde(alias = "connection_data")]
932    pub connection_data: Option<ConnectionData>,
933}
934
935#[derive(Serialize, Deserialize)]
936#[serde(rename_all = "camelCase")]
937pub struct ResponseData<S> {
938    #[serde(bound = "S:")]
939    pub context: InputContext<S>,
940    pub response: String,
941}
942
943impl<S> PartialEq for ResponseData<S> {
944    fn eq(&self, other: &Self) -> bool {
945        let Self { context, response } = self;
946        context == &other.context && response == &other.response
947    }
948}
949
950impl<S> Eq for ResponseData<S> {}
951
952impl<S> Clone for ResponseData<S> {
953    fn clone(&self) -> Self {
954        Self {
955            context: self.context.clone(),
956            response: self.response.clone(),
957        }
958    }
959}
960
961impl<S> fmt::Debug for ResponseData<S> {
962    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963        f.debug_struct("ResponseData")
964            .field("context", &self.context)
965            .field("response", &self.response)
966            .finish()
967    }
968}
969
970#[derive(Serialize, Deserialize)]
971#[serde(rename_all = "camelCase")]
972pub struct ConfirmationData<S> {
973    #[serde(bound = "S:")]
974    pub context: ConfirmationContext<S>,
975}
976
977impl<S> PartialEq for ConfirmationData<S> {
978    fn eq(&self, other: &Self) -> bool {
979        let Self { context } = self;
980        context == &other.context
981    }
982}
983
984impl<S> Eq for ConfirmationData<S> {}
985
986impl<S> Clone for ConfirmationData<S> {
987    fn clone(&self) -> Self {
988        Self {
989            context: self.context.clone(),
990        }
991    }
992}
993
994impl<S> fmt::Debug for ConfirmationData<S> {
995    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
996        f.debug_struct("ConfirmationData")
997            .field("context", &self.context)
998            .finish()
999    }
1000}
1001
1002#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1003#[serde(rename_all_fields = "camelCase")]
1004#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1005pub enum Error {
1006    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1007    UnexpectedError {
1008        #[serde(skip_serializing_if = "Option::is_none")]
1009        user_message: Option<String>,
1010    },
1011    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1012    Canceled {},
1013    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1014    InvalidCredentials {
1015        #[serde(skip_serializing_if = "Option::is_none")]
1016        user_message: Option<String>,
1017    },
1018    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1019    ServiceBlocked {
1020        #[serde(skip_serializing_if = "Option::is_none")]
1021        user_message: Option<String>,
1022        #[serde(skip_serializing_if = "Option::is_none")]
1023        code: Option<ServiceBlockedCode>,
1024    },
1025    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1026    Unauthorized {
1027        #[serde(skip_serializing_if = "Option::is_none")]
1028        user_message: Option<String>,
1029    },
1030    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1031    ConsentExpired {
1032        #[serde(skip_serializing_if = "Option::is_none")]
1033        user_message: Option<String>,
1034    },
1035    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1036    AccessExceeded {
1037        #[serde(skip_serializing_if = "Option::is_none")]
1038        user_message: Option<String>,
1039    },
1040    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1041    PeriodOutOfBounds {
1042        #[serde(skip_serializing_if = "Option::is_none")]
1043        user_message: Option<String>,
1044    },
1045    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1046    UnsupportedProduct {
1047        #[serde(skip_serializing_if = "Option::is_none")]
1048        reason: Option<UnsupportedProductReason>,
1049        #[serde(skip_serializing_if = "Option::is_none")]
1050        user_message: Option<String>,
1051    },
1052    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1053    PaymentFailed {
1054        #[serde(skip_serializing_if = "Option::is_none")]
1055        code: Option<PaymentErrorCode>,
1056        #[serde(skip_serializing_if = "Option::is_none")]
1057        user_message: Option<String>,
1058    },
1059    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1060    UnexpectedValue { error: String },
1061    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1062    TicketError {
1063        error: String,
1064        code: TicketErrorCode,
1065    },
1066    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1067    ProviderError {
1068        #[serde(skip_serializing_if = "Option::is_none")]
1069        code: Option<ProviderErrorCode>,
1070        #[serde(skip_serializing_if = "Option::is_none")]
1071        user_message: Option<String>,
1072    },
1073}
1074
1075#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1076#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1077#[cfg_attr(not(feature = "server"), non_exhaustive)]
1078pub enum TicketErrorCode {
1079    /// Missing "yaxi-ticket" header
1080    Missing,
1081    /// Invalid ticket
1082    Invalid,
1083    /// Ticket token lacks "kid"
1084    MissingKey,
1085    /// Unknown key
1086    UnknownKey,
1087    /// Ticket does not match service
1088    Mismatch,
1089    /// Ticket is expired
1090    Expired,
1091    /// Ticket lifetime is too long
1092    InvalidLifetime,
1093    /// Expired key
1094    ExpiredKey,
1095    /// Environment mismatch between key and routex
1096    KeyEnvironmentMismatch,
1097}
1098
1099#[derive(Serialize, Deserialize, Clone, Debug)]
1100#[cfg_attr(not(feature = "server"), non_exhaustive)]
1101pub enum Filter<F> {
1102    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1103    Eq(F, Value),
1104    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1105    NotEq(F, Value),
1106    #[cfg(feature = "server")]
1107    Contains(F, Value),
1108    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1109    And(Box<Self>, Box<Self>),
1110    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1111    Or(Box<Self>, Box<Self>),
1112    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1113    Supports(SupportedService),
1114}
1115
1116#[derive(Serialize, Deserialize, Clone, Debug)]
1117#[cfg_attr(not(feature = "server"), non_exhaustive)]
1118#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1119pub enum SupportedService {
1120    CollectPayment,
1121}
1122
1123impl<F> Filter<F> {
1124    #[must_use]
1125    /// Add an additional filter.
1126    pub fn and(self, other: Filter<F>) -> Filter<F> {
1127        Filter::And(Box::new(self), Box::new(other))
1128    }
1129
1130    #[must_use]
1131    /// Add an alternative filter.
1132    pub fn or(self, other: Filter<F>) -> Filter<F> {
1133        Filter::Or(Box::new(self), Box::new(other))
1134    }
1135}
1136
1137pub struct Field<F, T> {
1138    field: F,
1139    phantom: PhantomData<T>,
1140}
1141
1142impl<F, T> Field<F, T>
1143where
1144    F: Copy,
1145    T: Serialize,
1146{
1147    /// Filter for equality with a given value.
1148    ///
1149    /// # Panics
1150    ///
1151    /// Panics if [`serde_json::to_value`] for the value fails.
1152    pub fn eq(&self, value: T) -> Filter<F> {
1153        Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1154    }
1155
1156    /// Filter for inequality with a given value.
1157    ///
1158    /// # Panics
1159    ///
1160    /// Panics if [`serde_json::to_value`] for the value fails.
1161    pub fn not_eq(&self, value: T) -> Filter<F> {
1162        Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1163    }
1164}
1165
1166pub trait GetValue {
1167    type Model;
1168
1169    fn get(&self, model: &Self::Model) -> Value;
1170}
1171
1172macro_rules! fields {
1173    {
1174        $model:ty, $enum_name:ident
1175        $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1176        $(
1177            server:
1178            $(($server_variant_name:ident, $server_field_name:ident))+
1179        )?
1180    } => {
1181        #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1182        #[non_exhaustive]
1183        #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1184        pub enum $enum_name {
1185            $($variant_name,)+
1186            $($(
1187                #[cfg(feature = "server")]
1188                $server_variant_name,
1189            )+)?
1190        }
1191
1192        impl $enum_name {
1193            $(
1194                pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1195                    field: $enum_name::$variant_name,
1196                    phantom: std::marker::PhantomData,
1197                };
1198            )+
1199        }
1200
1201        impl crate::GetValue for $enum_name {
1202            type Model = $model;
1203
1204            fn get(&self, model: &$model) -> serde_json::Value {
1205                match self {
1206                    $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1207                    $($(
1208                        #[cfg(feature = "server")]
1209                        $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1210                    )+)?
1211                }.expect("Serialization should work")
1212            }
1213        }
1214    }
1215}
1216
1217#[derive(Serialize, Deserialize)]
1218#[serde(rename_all = "camelCase")]
1219pub struct ServiceRequest<S: Service> {
1220    pub credentials: Credentials,
1221    pub session: Option<Session>,
1222    #[serde(default, skip_serializing_if = "is_false")]
1223    pub recurring_consents: bool,
1224    #[serde(flatten, bound = "S:")]
1225    pub data: <S as Service>::RequestData,
1226}
1227
1228impl<S: Service> Clone for ServiceRequest<S> {
1229    fn clone(&self) -> Self {
1230        Self {
1231            credentials: self.credentials.clone(),
1232            session: self.session.clone(),
1233            recurring_consents: self.recurring_consents,
1234            data: self.data.clone(),
1235        }
1236    }
1237}
1238
1239#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1240#[serde(rename_all = "camelCase")]
1241#[non_exhaustive]
1242#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1243pub struct Account {
1244    /// ISO 20022 IBAN2007Identifier.
1245    #[serde(skip_serializing_if = "Option::is_none")]
1246    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1247    pub iban: Option<String>,
1248
1249    /// Account number that is not an IBAN, e.g. ISO 20022 BBANIdentifier or primary account number (PAN) of a card account.
1250    #[serde(skip_serializing_if = "Option::is_none")]
1251    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1252    pub number: Option<String>,
1253
1254    /// ISO 20022 BICFIIdentifier.
1255    #[serde(skip_serializing_if = "Option::is_none")]
1256    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1257    pub bic: Option<String>,
1258
1259    /// National bank code.
1260    #[serde(skip_serializing_if = "Option::is_none")]
1261    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1262    pub bank_code: Option<String>,
1263
1264    /// ISO 4217 Alpha 3 currency code.
1265    #[serde(skip_serializing_if = "Option::is_none")]
1266    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1267    pub currency: Option<String>,
1268
1269    /// Name of account, assigned by ASPSP.
1270    #[serde(skip_serializing_if = "Option::is_none")]
1271    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1272    pub name: Option<String>,
1273
1274    /// Display name of account, assigned by PSU.
1275    #[serde(skip_serializing_if = "Option::is_none")]
1276    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1277    pub display_name: Option<String>,
1278
1279    /// Legal account owner.
1280    #[serde(skip_serializing_if = "Option::is_none")]
1281    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1282    pub owner_name: Option<String>,
1283
1284    /// Product name.
1285    #[serde(skip_serializing_if = "Option::is_none")]
1286    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1287    pub product_name: Option<String>,
1288
1289    /// Account status.
1290    #[serde(skip_serializing_if = "Option::is_none")]
1291    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1292    pub status: Option<AccountStatus>,
1293
1294    /// Account type.
1295    #[serde(rename = "type")]
1296    #[serde(skip_serializing_if = "Option::is_none")]
1297    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1298    pub type_: Option<AccountType>,
1299}
1300
1301pub mod accounts {
1302    use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1303    use serde::{Deserialize, Serialize};
1304
1305    #[derive(Clone, Debug)]
1306    pub struct Service {}
1307
1308    impl super::Service for Service {
1309        const ID: ServiceId = ServiceId::Accounts;
1310
1311        type TicketData = ();
1312
1313        type RequestData = RequestData;
1314
1315        type Output = Vec<Account>;
1316    }
1317
1318    fields! {
1319        routex_models::Account, AccountField
1320        (IBAN, Iban, iban, Option<String>)
1321        (NUMBER, Number, number, Option<String>)
1322        (BIC, Bic, bic, Option<String>)
1323        (BANK_CODE, BankCode, bank_code, Option<String>)
1324        (CURRENCY, Currency, currency, String)
1325        (NAME, Name, name, Option<String>)
1326        (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1327        (OWNER_NAME, OwnerName, owner_name, Option<String>)
1328        (PRODUCT_NAME, ProductName, product_name, Option<String>)
1329        (STATUS, Status, status, Option<AccountStatus>)
1330        (TYPE, Type, type_, Option<AccountType>)
1331        server:
1332        (Capabilities, capabilities)
1333    }
1334
1335    impl Account {
1336        #[must_use]
1337        /// Filter for possible support of YAXI Open Banking services.
1338        pub fn supports(service: SupportedService) -> Filter<AccountField> {
1339            Filter::Supports(service)
1340        }
1341    }
1342
1343    #[derive(Serialize, Deserialize, Clone)]
1344    #[serde(rename_all = "camelCase")]
1345    pub struct RequestData {
1346        pub fields: Vec<AccountField>,
1347        pub filter: Option<Filter<AccountField>>,
1348    }
1349}
1350
1351#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1352#[serde(rename_all = "camelCase")]
1353#[serde(rename_all_fields = "camelCase")]
1354#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1355pub enum AccountIdentifier {
1356    /// ISO 20022 IBAN2007Identifier.
1357    #[serde(alias = "Iban")]
1358    Iban(String),
1359}
1360
1361#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1362#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1363#[serde(rename_all = "camelCase")]
1364pub struct AccountReference {
1365    #[serde(flatten)]
1366    pub id: AccountIdentifier,
1367    #[serde(skip_serializing_if = "Option::is_none")]
1368    #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1369    pub currency: Option<String>,
1370}
1371
1372pub mod balances {
1373    pub use rust_decimal::Decimal;
1374    use serde::{Deserialize, Serialize};
1375
1376    use crate::{AccountReference, ServiceId};
1377
1378    #[derive(Clone, Debug)]
1379    pub struct Service {}
1380
1381    impl super::Service for Service {
1382        const ID: ServiceId = ServiceId::Balances;
1383
1384        type TicketData = ();
1385
1386        type RequestData = RequestData;
1387
1388        type Output = Balances;
1389    }
1390
1391    #[derive(Serialize, Deserialize, Clone)]
1392    #[serde(rename_all = "camelCase")]
1393    pub struct RequestData {
1394        pub accounts: Vec<AccountReference>,
1395    }
1396
1397    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1398    #[serde(rename_all = "camelCase")]
1399    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1400    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1401    pub struct Balances {
1402        /// List of balances for accounts.
1403        pub balances: Vec<AccountBalances>,
1404
1405        /// Accounts that were requested in [`Request::accounts`] but not found.
1406        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1407        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1408        pub missing_accounts: Vec<AccountReference>,
1409    }
1410
1411    #[allow(clippy::module_name_repetitions)]
1412    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1413    #[serde(rename_all = "camelCase")]
1414    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1415    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1416    pub struct AccountBalances {
1417        pub account: AccountReference,
1418
1419        /// List of balances (of different types) for the account.
1420        pub balances: Vec<Balance>,
1421    }
1422
1423    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1424    #[serde(rename_all = "camelCase")]
1425    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1426    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1427    pub struct Balance {
1428        pub amount: Decimal,
1429        pub currency: String,
1430        pub balance_type: BalanceType,
1431    }
1432
1433    #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1434    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1435    pub enum BalanceType {
1436        /// Balance from booked transactions.
1437        Booked,
1438
1439        /// Balance from booked transactions and pending debits.
1440        Available,
1441
1442        /// Expected balance from booked and pending transactions.
1443        Expected,
1444    }
1445}
1446
1447pub mod transactions {
1448    use chrono::NaiveDate;
1449    use isocountry::CountryCode;
1450    pub use routex_models::{Amount, Fee, TransactionStatus};
1451    use rust_decimal::Decimal;
1452    use serde::{Deserialize, Serialize};
1453    use url::Url;
1454
1455    use crate::{AccountReference, ServiceId};
1456
1457    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1458    #[serde(rename_all = "camelCase")]
1459    pub struct TicketData {
1460        pub account: AccountReference,
1461        pub range: Range,
1462        pub webhook: Option<Url>,
1463    }
1464
1465    #[derive(Clone, Debug)]
1466    pub struct Service {}
1467
1468    impl super::Service for Service {
1469        const ID: ServiceId = ServiceId::Transactions;
1470
1471        type TicketData = TicketData;
1472
1473        type RequestData = RequestData;
1474
1475        type Output = Option<Vec<Transaction>>;
1476    }
1477
1478    #[derive(Serialize, Deserialize, Clone)]
1479    #[serde(rename_all = "camelCase")]
1480    pub struct RequestData {}
1481
1482    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1483    #[serde(rename_all = "camelCase")]
1484    #[serde(rename_all_fields = "camelCase")]
1485    pub enum Range {
1486        Reference(String),
1487        #[serde(untagged)]
1488        Period {
1489            /// Earliest date (inclusive)
1490            from: NaiveDate,
1491            /// Latest date (inclusive)
1492            to: Option<NaiveDate>,
1493        },
1494    }
1495
1496    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1497    #[serde(rename_all = "camelCase")]
1498    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1499    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1500    pub struct Transaction {
1501        /// Identifier used for delta requests.
1502        #[serde(skip_serializing_if = "Option::is_none")]
1503        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1504        pub entry_reference: Option<String>,
1505
1506        #[serde(skip_serializing_if = "Option::is_none")]
1507        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1508        pub batch: Option<BatchData>,
1509
1510        /// Booking date (ASPSP's books).
1511        #[serde(skip_serializing_if = "Option::is_none")]
1512        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1513        pub booking_date: Option<NaiveDate>,
1514
1515        /// Value date. Expected / requested value date in case of pending entries.
1516        #[serde(skip_serializing_if = "Option::is_none")]
1517        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1518        pub value_date: Option<NaiveDate>,
1519
1520        /// Date of the actual transaction, e.g. a card payment.
1521        #[serde(skip_serializing_if = "Option::is_none")]
1522        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1523        pub transaction_date: Option<NaiveDate>,
1524
1525        /// Transaction status.
1526        pub status: TransactionStatus,
1527
1528        /// Unique reference assigned by the account servicer.
1529        #[serde(skip_serializing_if = "Option::is_none")]
1530        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1531        pub account_servicer_reference: Option<String>,
1532
1533        /// Unique identifier assigned by the sending party.
1534        #[serde(skip_serializing_if = "Option::is_none")]
1535        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1536        pub payment_id: Option<String>,
1537
1538        /// Unique identifier assigned by the first instructing agent.
1539        #[serde(skip_serializing_if = "Option::is_none")]
1540        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1541        pub transaction_id: Option<String>,
1542
1543        /// Unique end-to-end identifier assigned by the initiating party.
1544        #[serde(skip_serializing_if = "Option::is_none")]
1545        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1546        pub end_to_end_id: Option<String>,
1547
1548        /// Mandate identifier.
1549        #[serde(skip_serializing_if = "Option::is_none")]
1550        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1551        pub mandate_id: Option<String>,
1552
1553        /// SEPA creditor identifier.
1554        #[serde(skip_serializing_if = "Option::is_none")]
1555        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1556        pub creditor_id: Option<String>,
1557
1558        /// Transaction amount as billed to the account.
1559        pub amount: Amount,
1560
1561        /// Indicator for reversals.
1562        #[serde(default, skip_serializing_if = "super::is_false")]
1563        #[cfg_attr(feature = "uniffi", uniffi(default = false))]
1564        pub reversal: bool,
1565
1566        /// Original amount of the transaction.
1567        #[serde(skip_serializing_if = "Option::is_none")]
1568        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1569        pub original_amount: Option<Amount>,
1570
1571        /// Exchange rates.
1572        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1573        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1574        pub exchanges: Vec<ExchangeRate>,
1575
1576        /// Any fees related to the transaction.
1577        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1578        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1579        pub fees: Vec<Fee>,
1580
1581        /// Creditor data. In case of reversals this refers to the initial transaction.
1582        #[serde(skip_serializing_if = "Option::is_none")]
1583        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1584        pub creditor: Option<Party>,
1585
1586        /// Debtor data. In case of reversals this refers to the initial transaction.
1587        #[serde(skip_serializing_if = "Option::is_none")]
1588        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1589        pub debtor: Option<Party>,
1590
1591        /// Remittance (purpose).
1592        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1593        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1594        pub remittance_information: Vec<String>,
1595
1596        /// ISO 20022 ExternalPurpose1Code.
1597        #[serde(skip_serializing_if = "Option::is_none")]
1598        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1599        pub purpose_code: Option<String>,
1600
1601        /// Bank Transaction Codes.
1602        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1603        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1604        pub bank_transaction_codes: Vec<BankTransactionCode>,
1605
1606        /// Additional information attached to the transaction.
1607        ///
1608        /// This might be a proprietary, localized, human-readable long text corresponding to some machine-readable bank transaction code that is not directly provided by the bank.
1609        #[serde(skip_serializing_if = "Option::is_none")]
1610        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1611        pub additional_information: Option<String>,
1612    }
1613
1614    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1615    #[serde(rename_all = "camelCase")]
1616    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1617    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1618    pub struct BatchData {
1619        /// Number of transactions in the batch, if known.
1620        #[serde(skip_serializing_if = "Option::is_none")]
1621        pub number_of_transactions: Option<u32>,
1622
1623        /// Details of transactions in the batch.
1624        ///
1625        /// Note that this does not necessarily match a given number of transactions.
1626        /// It could be e.g. empty as no details are given or a single entry with common details on all transactions in the batch.
1627        pub transactions: Vec<BatchTransactionDetails>,
1628    }
1629
1630    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1631    #[serde(rename_all = "camelCase")]
1632    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1633    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1634    pub struct BatchTransactionDetails {
1635        /// Unique reference assigned by the account servicer.
1636        #[serde(skip_serializing_if = "Option::is_none")]
1637        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1638        pub account_servicer_reference: Option<String>,
1639
1640        /// Unique identifier assigned by the sending party.
1641        #[serde(skip_serializing_if = "Option::is_none")]
1642        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1643        pub payment_id: Option<String>,
1644
1645        /// Unique identifier assigned by the first instructing agent.
1646        #[serde(skip_serializing_if = "Option::is_none")]
1647        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1648        pub transaction_id: Option<String>,
1649
1650        /// Unique end-to-end identifier assigned by the initiating party.
1651        #[serde(skip_serializing_if = "Option::is_none")]
1652        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1653        pub end_to_end_id: Option<String>,
1654
1655        /// Mandate identifier.
1656        #[serde(skip_serializing_if = "Option::is_none")]
1657        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1658        pub mandate_id: Option<String>,
1659
1660        /// SEPA creditor identifier.
1661        #[serde(skip_serializing_if = "Option::is_none")]
1662        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1663        pub creditor_id: Option<String>,
1664
1665        /// Transaction amount as billed to the account.
1666        #[serde(skip_serializing_if = "Option::is_none")]
1667        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1668        pub amount: Option<Amount>,
1669
1670        /// Indicator for reversals.
1671        #[serde(default, skip_serializing_if = "super::is_false")]
1672        #[cfg_attr(feature = "uniffi", uniffi(default = false))]
1673        pub reversal: bool,
1674
1675        /// Original amount of the transaction.
1676        #[serde(skip_serializing_if = "Option::is_none")]
1677        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1678        pub original_amount: Option<Amount>,
1679
1680        /// Exchange rates.
1681        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1682        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1683        pub exchanges: Vec<ExchangeRate>,
1684
1685        /// Any fees related to the transaction.
1686        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1687        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1688        pub fees: Vec<Fee>,
1689
1690        /// Creditor data. In case of reversals this refers to the initial transaction.
1691        #[serde(skip_serializing_if = "Option::is_none")]
1692        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1693        pub creditor: Option<Party>,
1694
1695        /// Debtor data. In case of reversals this refers to the initial transaction.
1696        #[serde(skip_serializing_if = "Option::is_none")]
1697        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1698        pub debtor: Option<Party>,
1699
1700        /// Remittance (purpose).
1701        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1702        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1703        pub remittance_information: Vec<String>,
1704
1705        /// ISO 20022 ExternalPurpose1Code.
1706        #[serde(skip_serializing_if = "Option::is_none")]
1707        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1708        pub purpose_code: Option<String>,
1709
1710        /// Bank Transaction Codes.
1711        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1712        #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1713        pub bank_transaction_codes: Vec<BankTransactionCode>,
1714
1715        /// Additional information attached to the transaction.
1716        ///
1717        /// This might be a proprietary, localized, human-readable long text corresponding to some machine-readable bank transaction code that is not directly provided by the bank.
1718        #[serde(skip_serializing_if = "Option::is_none")]
1719        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1720        pub additional_information: Option<String>,
1721    }
1722
1723    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1724    #[serde(rename_all = "camelCase")]
1725    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1726    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1727    pub struct ExchangeRate {
1728        /// ISO 4217 Alpha 3 currency code of the source currency that gets converted.
1729        pub source_currency: String,
1730        /// ISO 4217 Alpha 3 currency code of the target currency that
1731        /// the source currency gets converted into.
1732        #[serde(skip_serializing_if = "Option::is_none")]
1733        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1734        pub target_currency: Option<String>,
1735        /// ISO 4217 Alpha 3 currency code of the unit currency for the exchange rate.
1736        #[serde(skip_serializing_if = "Option::is_none")]
1737        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1738        pub unit_currency: Option<String>,
1739        pub exchange_rate: Decimal,
1740    }
1741
1742    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1743    #[serde(rename_all = "camelCase")]
1744    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1745    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1746    pub struct Party {
1747        /// Creditor / debtor name.
1748        #[serde(skip_serializing_if = "Option::is_none")]
1749        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1750        pub name: Option<String>,
1751
1752        /// ISO 20022 IBAN2007Identifier for the creditor / debtor account.
1753        #[serde(skip_serializing_if = "Option::is_none")]
1754        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1755        pub iban: Option<String>,
1756
1757        /// ISO 20022 BICFIIdentifier for the creditor / debtor agent.
1758        #[serde(skip_serializing_if = "Option::is_none")]
1759        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1760        pub bic: Option<String>,
1761
1762        /// Ultimate creditor / debtor (name).
1763        #[serde(skip_serializing_if = "Option::is_none")]
1764        #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1765        pub ultimate: Option<String>,
1766    }
1767
1768    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1769    #[serde(rename_all = "camelCase")]
1770    #[serde(rename_all_fields = "camelCase")]
1771    #[non_exhaustive]
1772    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1773    pub enum BankTransactionCode {
1774        /// ISO 20022 Bank Transaction Code.
1775        Iso {
1776            /// ISO 20022 ExternalBankTransactionDomain1Code.
1777            domain: String,
1778
1779            /// ISO 20022 ExternalBankTransactionFamily1Code.
1780            family: String,
1781
1782            /// ISO 20022 ExternalBankTransactionSubFamily1Code.
1783            sub_family: String,
1784        },
1785        /// SWIFT transaction code.
1786        Swift(String),
1787        /// BAI2 transaction code.
1788        Bai(String),
1789        /// National transaction code, e.g. German GVC.
1790        National { code: String, country: CountryCode },
1791        /// Unspecified transaction codes, possibly with an issuer information.
1792        Other {
1793            code: String,
1794            #[serde(skip_serializing_if = "Option::is_none")]
1795            issuer: Option<String>,
1796        },
1797    }
1798}
1799
1800#[allow(clippy::trivially_copy_pass_by_ref)]
1801fn is_false(val: &bool) -> bool {
1802    !*val
1803}
1804
1805pub mod collect_payment {
1806    pub use routex_models::Amount;
1807    use serde::{Deserialize, Serialize};
1808
1809    use super::{AccountIdentifier, AccountReference, Path, ServiceId};
1810
1811    /// Query ticket status, possibly polling pending confirmations
1812    ///
1813    /// Method: POST<br>
1814    /// Response: [`Option<TicketStatus<SuccessStatusData>>`](super::TicketStatus)
1815    #[must_use]
1816    pub fn status_path(ticket_id: &str) -> Path {
1817        Path::new(["collect-payment", "status", ticket_id])
1818    }
1819
1820    #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
1821    #[serde(rename_all = "camelCase")]
1822    #[non_exhaustive]
1823    pub struct SuccessStatusData {}
1824
1825    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1826    #[serde(rename_all = "camelCase")]
1827    pub struct TicketData {
1828        pub amount: Amount,
1829        pub creditor_account: AccountIdentifier,
1830        pub creditor_name: String,
1831        pub remittance: String,
1832        #[serde(skip_serializing_if = "Option::is_none")]
1833        pub instant: Option<bool>,
1834        #[serde(skip_serializing_if = "Option::is_none")]
1835        pub fields: Option<Vec<Field>>,
1836    }
1837
1838    #[derive(Clone, Debug)]
1839    pub struct Service {}
1840
1841    impl super::Service for Service {
1842        const ID: ServiceId = ServiceId::CollectPayment;
1843
1844        type TicketData = TicketData;
1845
1846        type RequestData = RequestData;
1847
1848        type Output = PaymentInitiation;
1849    }
1850
1851    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1852    #[serde(rename_all = "camelCase")]
1853    #[non_exhaustive]
1854    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1855    pub enum Field {
1856        DebtorIban,
1857        DebtorName,
1858    }
1859
1860    #[derive(Serialize, Deserialize, Clone)]
1861    #[serde(rename_all = "camelCase")]
1862    pub struct RequestData {
1863        pub account: Option<AccountReference>,
1864    }
1865
1866    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1867    #[serde(rename_all = "camelCase")]
1868    #[non_exhaustive]
1869    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1870    pub struct PaymentInitiation {
1871        #[serde(skip_serializing_if = "Option::is_none")]
1872        pub debtor_name: Option<String>,
1873        #[serde(skip_serializing_if = "Option::is_none")]
1874        pub debtor_iban: Option<String>,
1875    }
1876}
1877
1878#[cfg(test)]
1879mod tests {
1880    use std::fmt::Debug;
1881
1882    use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
1883    use serde::{Deserialize, Serialize};
1884
1885    use crate::{
1886        Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
1887        InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
1888    };
1889
1890    #[test]
1891    fn read_response() {
1892        let serialized = serde_json::to_string(
1893            &encode(
1894                &Header::new(Algorithm::HS256),
1895                &Claims {
1896                    data: "data",
1897                    exp: 2_540_808_000,
1898                },
1899                &EncodingKey::from_secret(b"does_not_matter"),
1900            )
1901            .unwrap(),
1902        )
1903        .unwrap();
1904
1905        let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
1906
1907        assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
1908    }
1909
1910    struct Service;
1911
1912    impl crate::Service for Service {
1913        const ID: ServiceId = ServiceId::Accounts;
1914
1915        type TicketData = ();
1916
1917        type RequestData = ();
1918
1919        type Output = ();
1920    }
1921
1922    #[allow(
1923        dead_code,
1924        unconditional_recursion,
1925        clippy::extra_unused_type_parameters
1926    )]
1927    fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
1928        test_bounds::<InputContext<Service>>();
1929        test_bounds::<ConfirmationContext<Service>>();
1930        test_bounds::<DialogInput<Service>>();
1931        test_bounds::<Dialog<Service>>();
1932        test_bounds::<Redirect<Service>>();
1933        test_bounds::<RedirectHandle<Service>>();
1934        test_bounds::<OBResult<()>>();
1935        test_bounds::<Session>();
1936
1937        test_bounds::<Authenticated<Ticket<Service>>>();
1938        test_bounds::<Ticket<Service>>();
1939        test_bounds::<ResponseData<Service>>();
1940        test_bounds::<ConfirmationData<Service>>();
1941    }
1942
1943    fn test_eq_<T: PartialEq + Debug>(o: &T) {
1944        assert!(o.eq(o));
1945    }
1946
1947    #[test]
1948    fn test_eq() {
1949        let ic = InputContext::<Service>::from(vec![]);
1950        let cc = ConfirmationContext::<Service>::from(vec![]);
1951
1952        test_eq_(&ic);
1953        test_eq_(&cc);
1954        test_eq_(&Redirect {
1955            url: "url:".parse().unwrap(),
1956            context: cc.clone(),
1957        });
1958        test_eq_(&RedirectHandle {
1959            handle: String::new(),
1960            context: cc.clone(),
1961        });
1962        test_eq_(&Ticket::<Service> {
1963            service: ServiceId::Accounts,
1964            id: String::new(),
1965            data: (),
1966        });
1967        test_eq_(&ResponseData {
1968            context: ic,
1969            response: String::new(),
1970        });
1971        test_eq_(&ConfirmationData { context: cc });
1972    }
1973}