Skip to main content

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, Serializer};
17use serde_json::Value;
18use serde_with::base64::Base64;
19use std::any::Any;
20use std::fmt;
21use std::marker::PhantomData;
22use std::net::IpAddr;
23use std::str::FromStr;
24use url::Url;
25use uuid::Uuid;
26
27#[cfg(feature = "uniffi")]
28uniffi::setup_scaffolding!();
29
30#[cfg(feature = "uniffi")]
31uniffi::custom_type!(ConnectionData, Vec<u8>, {
32    try_lift: |val| Ok(val.into()),
33    lower: |obj| obj.into(),
34});
35
36#[cfg(feature = "uniffi")]
37uniffi::custom_type!(Session, Vec<u8>, {
38    try_lift: |val| Ok(val.into()),
39    lower: |obj| obj.into(),
40});
41
42#[cfg(feature = "uniffi")]
43uniffi::use_remote_type!(routex_models::CountryCode);
44
45#[cfg(feature = "uniffi")]
46uniffi::use_remote_type!(routex_models::Decimal);
47
48#[cfg(feature = "uniffi")]
49uniffi::custom_type!(NaiveDate, String, {
50    remote,
51    try_lift: |val| Ok(val.parse()?),
52    lower: |obj| obj.to_string(),
53});
54
55type DateTime = chrono::DateTime<Utc>;
56
57#[cfg(feature = "uniffi")]
58uniffi::custom_type!(DateTime, String, {
59    remote,
60    try_lift: |val| Ok(val.parse()?),
61    lower: |obj| obj.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
62});
63
64pub type Dialog<S> = routex_models::Dialog<ConfirmationContext<S>, InputContext<S>>;
65pub type DialogInput<S> = routex_models::DialogInput<ConfirmationContext<S>, InputContext<S>>;
66
67/// Representation of an API path
68///
69/// Use [`to_url`](Path::to_url) to build requests on clients,
70/// [`to_string`](ToString::to_string) to get for service routes.
71/// Note that the [`Display`](fmt::Display) implementation does NOT escape
72/// the segments, so that e.g. axum / matchit captures can be used.
73#[must_use]
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
125/// Indicates that a user is in session for a non-interactive service call.
126///
127/// When a user is in session, YAXI forwards the user's IP address to their bank. That
128/// address is either this connection's own source IP ([`UserInSession::OnThisConnection`])
129/// or an IP address the caller provides ([`UserInSession::At`]).
130///
131/// Without a user in session, banks limit how many requests a caller may make and
132/// reject excess requests with an [`Error::AccessExceeded`] error. A user in session
133/// lifts that limit.
134#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
135#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
136pub enum UserInSession {
137    /// The user's IP is this connection's own source IP.
138    #[serde(rename = "connection")]
139    OnThisConnection,
140    /// The user is at the given IP address.
141    #[serde(untagged)]
142    At(IpAddr),
143}
144
145#[cfg(feature = "uniffi")]
146uniffi::custom_type!(IpAddr, String, {
147    remote,
148    try_lift: |val| Ok(val.parse()?),
149    lower: |obj| obj.to_string(),
150});
151
152pub const CURRENT_MEDIA_TYPE: &str = "application/vnd.yaxi.v6";
153
154pub mod keys {
155    use super::Path;
156    use clerk_report::PublishedVersionEntry;
157    use serde::{Deserialize, Serialize};
158    use serde_with::base64::Base64;
159
160    /// Run a TEE-attested key settlement
161    ///
162    /// Method: POST<br>
163    /// Request: [`Request`]<br>
164    /// Response: [`Response`]
165    pub fn settlement_path() -> Path {
166        Path::new(["key-settlement"])
167    }
168
169    #[serde_with::serde_as]
170    #[derive(Serialize, Deserialize, Clone, Debug)]
171    #[serde(rename_all = "camelCase")]
172    pub struct Request {
173        #[serde_as(as = "Base64")]
174        pub public_key: Vec<u8>,
175        pub version: Option<u8>,
176    }
177
178    /// The contents of a key settlement's sealed box
179    #[serde_with::serde_as]
180    #[derive(Serialize, Deserialize)]
181    #[serde(rename_all = "camelCase")]
182    pub struct SettlementBoxMessage {
183        #[serde_as(as = "Base64")]
184        pub public_key: Vec<u8>,
185        #[serde_as(as = "Base64")]
186        pub session_id: [u8; 32],
187    }
188
189    /// Response to a request for a TEE-attested key settlement.
190    #[serde_with::serde_as]
191    #[derive(Serialize, Deserialize, Clone, Debug)]
192    #[serde(rename_all = "camelCase")]
193    pub struct Response {
194        /// TEE attestation report which also authenticates `chacha_box`
195        #[serde_as(as = "Base64")]
196        pub attestation_report: Vec<u8>,
197        /// Versioned Chip Endorsement Key, AMD SEV Key and AMD Root Key as chain of PEM-encoded certificates
198        pub vcek: String,
199        /// Sealed box containing [`SettlementBoxMessage`]
200        #[serde_as(as = "Base64")]
201        pub chacha_box: Vec<u8>,
202        /// The TEE's version, signed by YAXI. The contained measurement needs to match the
203        /// attestation report's measurement.
204        pub system_version: PublishedVersionEntry,
205        /// Number of vCPUs of the issuing system.
206        #[serde(default = "default_vcpus")]
207        pub vcpus: usize,
208    }
209
210    fn default_vcpus() -> usize {
211        2
212    }
213}
214
215pub mod traces {
216    use super::Path;
217
218    /// Retrieve trace data
219    ///
220    /// Method: GET<br>
221    /// Response: Trace data string
222    pub fn path(trace_id: &str) -> Path {
223        Path::new(["traces", trace_id])
224    }
225}
226
227pub mod redirects {
228    use super::Path;
229    use serde::{Deserialize, Serialize};
230    use url::Url;
231
232    /// Register redirects
233    ///
234    /// Method: POST<br>
235    /// Request: [`Request`]<br>
236    /// Response: [`Response`]
237    pub fn path() -> Path {
238        Path::new(["redirects"])
239    }
240
241    #[derive(Serialize, Deserialize, Clone, Debug)]
242    #[serde(rename_all = "camelCase")]
243    pub struct Request {
244        pub handle: String,
245        pub redirect_uri: String,
246    }
247
248    #[derive(Serialize, Deserialize, Clone, Debug)]
249    #[serde(rename_all = "camelCase")]
250    pub struct Response {
251        #[serde(serialize_with = "super::serialize_url")]
252        pub redirect_url: Url,
253    }
254}
255
256pub mod info {
257    use super::Path;
258    pub use isocountry::CountryCode;
259    use routex_models::{ConnectionId, CredentialsModel};
260    use serde::{Deserialize, Serialize};
261    #[cfg(feature = "server")]
262    use serde_with::base64::Base64;
263
264    /// Search for connections (service provider integrations)
265    ///
266    /// Method: GET<br>
267    /// Request: [`Request`]<br>
268    /// Response: [`Vec<[ConnectionInfo]>`](ConnectionInfo)
269    pub fn search_path() -> Path {
270        Path::new(["search"])
271    }
272
273    #[derive(Serialize, Deserialize, Clone, Debug)]
274    #[serde(rename_all = "camelCase")]
275    #[non_exhaustive]
276    pub struct Request {
277        pub filters: Vec<SearchFilter>,
278        #[serde(default)]
279        pub iban_detection: bool,
280        #[serde(skip_serializing_if = "Option::is_none")]
281        pub limit: Option<usize>,
282        #[serde(default)]
283        #[serde(skip_serializing_if = "Vec::is_empty")]
284        pub details: Vec<Details>,
285    }
286
287    impl Request {
288        pub fn new(filters: impl IntoIterator<Item = SearchFilter>) -> Self {
289            Self {
290                filters: filters.into_iter().collect(),
291                iban_detection: false,
292                limit: None,
293                details: Vec::new(),
294            }
295        }
296    }
297
298    /// Details to contain in search results.
299    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
300    #[serde(rename_all = "camelCase")]
301    #[non_exhaustive]
302    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
303    pub enum Details {
304        Bics,
305        BankCodes,
306    }
307
308    /// Filters for the connection lookup
309    ///
310    /// String filters look for the given value anywhere in the related field, case-insensitive.
311    #[serde_with::serde_as]
312    #[derive(Serialize, Deserialize, Clone, Debug)]
313    #[serde(rename_all = "camelCase")]
314    #[serde(rename_all_fields = "camelCase")]
315    pub enum SearchFilter {
316        /// List of [`ConnectionType`]s to consider.
317        Types(Vec<ConnectionType>),
318        /// List of [`CountryCode`]s to consider.
319        Countries(Vec<CountryCode>),
320        /// String filter for the provider / product name or any alias.
321        Name(String),
322        /// String filter for the BIC.
323        Bic(String),
324        /// String filter for the (national) bank code.
325        BankCode(String),
326        /// String filter for any of those fields.
327        Term(String),
328        #[cfg(feature = "server")]
329        EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
330    }
331
332    /// Type of connections to consider when searching
333    #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
334    #[non_exhaustive]
335    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
336    pub enum ConnectionType {
337        /// Production connections.
338        Production,
339        /// Sandboxes connections, especially test systems provided by third-parties.
340        Sandboxes,
341    }
342
343    /// Query connection information
344    ///
345    /// Method: GET<br>
346    /// Response: [`ConnectionInfo`]
347    pub fn fetch_path(connection_id: &str) -> Path {
348        Path::new(["info", connection_id])
349    }
350
351    /// Connection meta data
352    #[allow(clippy::module_name_repetitions)]
353    #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
354    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
355    #[serde(rename_all = "camelCase")]
356    #[cfg_attr(not(feature = "server"), non_exhaustive)]
357    pub struct ConnectionInfo {
358        /// Unique identifier.
359        pub id: ConnectionId,
360
361        /// ISO 3166-1 ALPHA-2 country codes.
362        pub countries: Vec<CountryCode>,
363
364        /// Display name.
365        pub display_name: String,
366
367        /// Credentials model.
368        pub credentials: CredentialsModel,
369
370        /// Human-friendly label for the user identifier if relevant.
371        #[serde(skip_serializing_if = "Option::is_none")]
372        #[cfg_attr(feature = "uniffi", uniffi(default))]
373        pub user_id: Option<String>,
374
375        /// Human-friendly label for the PIN / password if relevant.
376        #[serde(skip_serializing_if = "Option::is_none")]
377        #[cfg_attr(feature = "uniffi", uniffi(default))]
378        pub password: Option<String>,
379
380        /// Advice for the credentials to be displayed.
381        #[serde(skip_serializing_if = "Option::is_none")]
382        #[cfg_attr(feature = "uniffi", uniffi(default))]
383        pub advice: Option<String>,
384
385        /// Logo identifier.
386        pub logo_id: String,
387
388        #[allow(clippy::doc_markdown)]
389        /// ISO 20022 BICFIIdentifiers.
390        ///
391        /// Note that this is only included in search results if requested.
392        #[serde(skip_serializing_if = "Option::is_none")]
393        #[cfg_attr(feature = "uniffi", uniffi(default))]
394        pub bics: Option<Vec<String>>,
395
396        /// National bank codes (as used in IBANs).
397        ///
398        /// Note that this is only included in search results if requested.
399        #[serde(skip_serializing_if = "Option::is_none")]
400        #[cfg_attr(feature = "uniffi", uniffi(default))]
401        pub bank_codes: Option<Vec<String>>,
402
403        /// Labels categorizing the connection (e.g. "beta", "fints").
404        #[serde(default, skip_serializing_if = "Vec::is_empty")]
405        #[cfg_attr(feature = "uniffi", uniffi(default))]
406        pub labels: Vec<String>,
407    }
408}
409
410macro_rules! context {
411    ($name:ident) => {
412        #[serde_with::serde_as]
413        #[derive(Serialize, Deserialize)]
414        #[serde(transparent)]
415        pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
416
417        impl<S> PartialEq for $name<S> {
418            fn eq(&self, other: &Self) -> bool {
419                self.0 == other.0
420            }
421        }
422
423        impl<S> Eq for $name<S> {}
424
425        impl<S> Clone for $name<S> {
426            fn clone(&self) -> Self {
427                Self(self.0.clone(), self.1)
428            }
429        }
430
431        impl<S> fmt::Debug for $name<S> {
432            fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
433                f.debug_tuple("$name")
434                    .field(&self.0)
435                    .field(&self.1)
436                    .finish()
437            }
438        }
439
440        impl<S> From<Vec<u8>> for $name<S> {
441            fn from(value: Vec<u8>) -> Self {
442                Self(value, PhantomData)
443            }
444        }
445
446        impl<S> From<$name<S>> for Vec<u8> {
447            fn from(value: $name<S>) -> Self {
448                value.0
449            }
450        }
451
452        impl<S> AsRef<[u8]> for $name<S> {
453            fn as_ref(&self) -> &[u8] {
454                &self.0
455            }
456        }
457    };
458}
459
460context!(InputContext);
461
462context!(ConfirmationContext);
463
464/// Data authenticated with an HMAC
465///
466/// Use [`to_data()`](Authenticated::to_data) to access the data locally.
467///
468/// Serialization is supported for transfer to a remote system as [JSON Web Token](https://jwt.io/).
469/// The remote system can verify and read the data from the `data` claim:
470/// ```
471/// # let jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJkYXRhIjpudWxsLCJleHAiOjIwMDAwMDAwMDB9.mWSPWTs-N4npIQpnybpFHbRiLW4D8xS36mX3CmRlRCM";
472/// # type T = ();
473/// # let hmac_key = b"a";
474/// use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
475/// use serde::Deserialize;
476///
477/// #[derive(Deserialize, Clone, Debug)]
478/// struct Claims {
479///     data: T,
480///     exp: u64,
481/// }
482///
483/// let data = decode::<Claims>(
484///     &jwt,
485///     &DecodingKey::from_secret(hmac_key),
486///     &Validation::new(Algorithm::HS256),
487/// )?.claims.data;
488/// # Ok::<(), Box<dyn std::error::Error>>(())
489#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
490#[serde(transparent)]
491pub struct Authenticated<T> {
492    jwt: String,
493    _phantom: PhantomData<T>,
494}
495
496impl<'de, T> Deserialize<'de> for Authenticated<T>
497where
498    T: for<'t> Deserialize<'t> + Clone,
499{
500    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
501    where
502        D: serde::Deserializer<'de>,
503    {
504        String::deserialize(deserializer)?
505            .parse()
506            .map_err(serde::de::Error::custom)
507    }
508}
509
510impl<T> Authenticated<T>
511where
512    T: Serialize,
513{
514    #[must_use]
515    /// Initialize authenticated data.
516    ///
517    /// # Panics
518    ///
519    /// Panics if [`jsonwebtoken::encode`] fails.
520    pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
521        let header = Header {
522            kid: Some(key_id.into()),
523            ..Default::default()
524        };
525
526        let jwt = encode(
527            &header,
528            &Claims {
529                data,
530                // 2050-07-07 14:00 CEST
531                exp: 2_540_808_000,
532            },
533            &EncodingKey::from_secret(key),
534        )
535        .expect("Encoding should work");
536
537        Self {
538            jwt,
539            _phantom: PhantomData,
540        }
541    }
542}
543
544impl<T> FromStr for Authenticated<T>
545where
546    T: for<'de> Deserialize<'de> + Clone,
547{
548    type Err = jsonwebtoken::errors::Error;
549
550    fn from_str(jwt: &str) -> Result<Self, Self::Err> {
551        // Syntax check
552        decode::<T>(jwt)?;
553        Ok(Self {
554            jwt: jwt.to_string(),
555            _phantom: PhantomData,
556        })
557    }
558}
559
560impl<T> Authenticated<T>
561where
562    T: for<'de> Deserialize<'de> + Clone,
563{
564    #[must_use]
565    pub fn as_str(&self) -> &str {
566        &self.jwt
567    }
568
569    #[must_use]
570    /// Get the key identifier from the token header.
571    ///
572    /// # Panics
573    ///
574    /// Panics if the token header cannot get decoded. This is unexpected as decoding is tested in [`FromStr`].
575    pub fn key_id(&self) -> Option<String> {
576        decode_header(&self.jwt).unwrap().kid
577    }
578
579    #[cfg(feature = "client")]
580    #[must_use]
581    #[allow(clippy::missing_panics_doc)]
582    pub fn to_data(&self) -> T {
583        decode(&self.jwt).unwrap()
584    }
585
586    #[cfg(feature = "server")]
587    /// Return contained data, applying a decoder.
588    ///
589    /// # Errors
590    ///
591    /// Forwards errors returned by [`Decoder::decode`].
592    pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
593        decoder.decode::<T>(&self.jwt).map(|d| d.data)
594    }
595}
596
597#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
598#[serde(rename_all = "camelCase")]
599pub struct Claims<T> {
600    pub data: T,
601    pub exp: u64,
602}
603
604#[cfg(feature = "server")]
605pub trait Decoder {
606    type Error;
607
608    /// Verifies `mac` for `data`.
609    ///
610    /// # Errors
611    ///
612    /// Returns `Self::Error` if verification fails.
613    fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
614    where
615        T: for<'de> Deserialize<'de> + Clone;
616}
617
618/// Response from YAXI Open Banking services.
619///
620/// The response either carries an [authenticated](Authenticated) [result](OBResult)
621/// or an interrupt (i.e. a dialog or redirect for the user).
622#[derive(Serialize, Deserialize, Debug)]
623pub enum OBResponse<S: Service> {
624    #[serde(bound = "S:")]
625    Result(
626        Authenticated<OBResult<S::Output>>,
627        Option<Session>,
628        Option<ConnectionData>,
629    ),
630    #[serde(bound = "S:")]
631    Dialog(Dialog<S>),
632    #[serde(bound = "S:")]
633    Redirect(Redirect<S>),
634    #[serde(bound = "S:")]
635    RedirectHandle(RedirectHandle<S>),
636}
637
638impl<S: Service> Clone for OBResponse<S> {
639    fn clone(&self) -> Self {
640        match self {
641            OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
642                authenticated.clone(),
643                session.clone(),
644                connection_data.clone(),
645            ),
646            OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
647            OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
648            OBResponse::RedirectHandle(redirect_handle) => {
649                OBResponse::RedirectHandle(redirect_handle.clone())
650            }
651        }
652    }
653}
654
655#[derive(Serialize, Deserialize, Debug)]
656#[cfg_attr(not(feature = "server"), non_exhaustive)]
657#[serde(rename_all = "camelCase")]
658pub struct NonInteractiveResponse<S: Service> {
659    #[serde(bound = "S:")]
660    pub result: S::Output,
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub session: Option<Session>,
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub connection_data: Option<ConnectionData>,
665}
666
667/// A value returned by YAXI Open Banking services.
668///
669/// Besides the value itself, it contains a timestamp and a ticket identifier
670/// (bound to known input parameters and service type).
671#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
672#[serde(rename_all = "camelCase")]
673pub struct OBResult<T> {
674    pub data: T,
675    pub ticket_id: Uuid,
676    pub timestamp: DateTime,
677}
678
679#[serde_with::serde_as]
680#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
681pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
682
683impl From<Vec<u8>> for Session {
684    fn from(value: Vec<u8>) -> Self {
685        Session(value)
686    }
687}
688
689impl From<Session> for Vec<u8> {
690    fn from(session: Session) -> Self {
691        session.0
692    }
693}
694
695impl AsRef<[u8]> for Session {
696    fn as_ref(&self) -> &[u8] {
697        &self.0
698    }
699}
700
701#[serde_with::serde_as]
702#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
703#[serde(transparent)]
704pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
705
706impl From<Vec<u8>> for ConnectionData {
707    fn from(value: Vec<u8>) -> Self {
708        ConnectionData(value)
709    }
710}
711
712impl From<ConnectionData> for Vec<u8> {
713    fn from(connection_data: ConnectionData) -> Self {
714        connection_data.0
715    }
716}
717
718impl AsRef<[u8]> for ConnectionData {
719    fn as_ref(&self) -> &[u8] {
720        &self.0
721    }
722}
723
724/// User redirect.
725///
726/// The user is meant to get sent to the [`url`](Self::url) and the [`context`](Self::context) can
727/// be used for continuing the process at the service that issued the redirect object afterward.
728///
729/// A web application needs to direct the user agent to the returned URL.
730/// A desktop or mobile application could either open it in a browser or inside an element like a WebView.
731#[derive(Serialize, Deserialize)]
732#[serde(rename_all = "camelCase")]
733pub struct Redirect<S> {
734    #[serde(serialize_with = "serialize_url")]
735    pub url: Url,
736    #[serde(bound = "S:")]
737    pub context: ConfirmationContext<S>,
738}
739
740impl<S> PartialEq for Redirect<S> {
741    fn eq(&self, other: &Self) -> bool {
742        let Self { url, context } = self;
743        url == &other.url && context == &other.context
744    }
745}
746
747impl<S> Eq for Redirect<S> {}
748
749impl<S> Clone for Redirect<S> {
750    fn clone(&self) -> Self {
751        Self {
752            url: self.url.clone(),
753            context: self.context.clone(),
754        }
755    }
756}
757
758impl<S> fmt::Debug for Redirect<S> {
759    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760        f.debug_struct("Redirect")
761            .field("url", &self.url)
762            .field("context", &self.context)
763            .finish()
764    }
765}
766
767// This is a hack to work around Revolut not accepting plus-encoded URLs
768// See https://github.com/servo/rust-url/issues/927
769fn serialize_url<S: Serializer>(value: &Url, serializer: S) -> Result<S::Ok, S::Error> {
770    let string_repr = value.to_string().replace('+', "%20");
771    serializer.serialize_str(&string_repr)
772}
773
774/// Incomplete user redirect.
775///
776/// A final redirect URI needs to get registered, using the handle, to receive the URL to send the user to.
777#[derive(Serialize, Deserialize)]
778#[serde(rename_all = "camelCase")]
779pub struct RedirectHandle<S> {
780    pub handle: String,
781    #[serde(bound = "S:")]
782    pub context: ConfirmationContext<S>,
783}
784
785impl<S> PartialEq for RedirectHandle<S> {
786    fn eq(&self, other: &Self) -> bool {
787        let Self { handle, context } = self;
788        handle == &other.handle && context == &other.context
789    }
790}
791
792impl<S> Eq for RedirectHandle<S> {}
793
794impl<S> Clone for RedirectHandle<S> {
795    fn clone(&self) -> Self {
796        Self {
797            handle: self.handle.clone(),
798            context: self.context.clone(),
799        }
800    }
801}
802
803impl<S> fmt::Debug for RedirectHandle<S> {
804    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805        f.debug_struct("RedirectHandle")
806            .field("handle", &self.handle)
807            .field("context", &self.context)
808            .finish()
809    }
810}
811
812/// Decode a JWT without any validation or verification.
813fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
814where
815    T: for<'de> Deserialize<'de> + Clone,
816{
817    insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
818}
819
820macro_rules! enum_with_display {
821    {
822        $(#[$meta:meta])*
823        pub enum $name:ident {
824            $($variant:ident,)+
825        }
826    } => {
827        $(#[$meta])*
828        pub enum $name {
829            $($variant,)+
830        }
831
832        impl fmt::Display for $name {
833            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834                f.write_str(match self {
835                    $(Self::$variant => stringify!($variant),)+
836                })
837            }
838        }
839    }
840}
841
842enum_with_display! {
843    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
844    pub enum ServiceId {
845        Accounts,
846        Balances,
847        Transactions,
848        StandingOrders,
849        CollectPayment,
850        Transfer,
851    }
852}
853
854/// Ticket to use a service.
855///
856/// This must be issued by a trusted backend for use in the (untrusted) frontend.
857///
858/// It carries
859/// * an identifier of the service that is intended to be used,
860/// * an arbitrary ticket identifier for verifying the results later on and
861/// * any critical input data for the service.
862///
863/// The ticket data gets wrapped in a [JSON Web Token](https://jwt.io/) as claim `data`.
864/// The token must also contain an expiry in the claim `exp` and the key identifier in the `kid` header field.
865///
866/// Example issuance of an `Authenticated<Ticket<accounts::Service>>` for the accounts service:
867///
868/// ```
869/// # const HMAC_KEY: &[u8] = b"foo";
870/// # const HMAC_KEY_ID: &str = "foo";
871/// use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
872/// use serde::Serialize;
873/// use std::time::{SystemTime, UNIX_EPOCH};
874/// use uuid::Uuid;
875///
876/// let ticket_id = Uuid::new_v4();
877///
878/// #[derive(Serialize, Debug)]
879/// struct Ticket<T> {
880///     service: String,
881///     id: Uuid,
882///     data: T,
883/// }
884///
885/// #[derive(Serialize, Debug)]
886/// struct Claims<T> {
887///     data: Ticket<T>,
888///     exp: u64,
889/// }
890///
891/// let header = Header {
892///     alg: Algorithm::HS256,
893///     kid: Some(HMAC_KEY_ID.to_string()),
894///     ..Default::default()
895/// };
896///
897/// let authenticated_ticket = encode(
898///     &header,
899///     &Claims {
900///         data: Ticket {
901///             service: "Accounts".to_string(),
902///             id: ticket_id,
903///             data: (),
904///         },
905///         exp: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() + 600,
906///     },
907///     &EncodingKey::from_secret(HMAC_KEY),
908/// )?;
909/// # let auth: routex_api::Authenticated<routex_api::Ticket<routex_api::accounts::Service>> = authenticated_ticket.parse().unwrap();
910/// # auth.to_data();
911/// # auth.key_id().unwrap();
912/// # Ok::<(), Box<dyn std::error::Error>>(())
913#[derive(Serialize, Deserialize)]
914#[serde(rename_all = "camelCase")]
915#[serde(try_from = "DeserializedTicket<S::TicketData>")]
916pub struct Ticket<S: Service> {
917    pub service: ServiceId,
918    pub id: Uuid,
919    pub data: S::TicketData,
920}
921
922impl<S: Service> PartialEq for Ticket<S> {
923    fn eq(&self, other: &Self) -> bool {
924        let Self { service, id, data } = self;
925        service == &other.service && id == &other.id && data == &other.data
926    }
927}
928
929impl<S: Service> Eq for Ticket<S> {}
930
931impl<S: Service> fmt::Debug for Ticket<S> {
932    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
933        f.debug_struct("Ticket")
934            .field("service", &self.service)
935            .field("id", &self.id)
936            .field("data", &self.data)
937            .finish()
938    }
939}
940
941impl<S: Service> Clone for Ticket<S> {
942    fn clone(&self) -> Self {
943        Self {
944            service: self.service,
945            id: self.id,
946            data: self.data.clone(),
947        }
948    }
949}
950
951#[derive(Deserialize, Debug)]
952struct DeserializedTicket<T> {
953    service: ServiceId,
954    id: Uuid,
955    data: Option<T>,
956}
957
958impl<S: Service> TryFrom<DeserializedTicket<S::TicketData>> for Ticket<S> {
959    type Error = &'static str;
960
961    fn try_from(
962        DeserializedTicket { service, id, data }: DeserializedTicket<S::TicketData>,
963    ) -> Result<Self, Self::Error> {
964        let data = (&() as &dyn Any)
965            .downcast_ref::<S::TicketData>()
966            .cloned()
967            .or(data)
968            .ok_or("missing field `data`")?;
969
970        Ok(Self { service, id, data })
971    }
972}
973
974pub trait Service {
975    const ID: ServiceId;
976
977    type TicketData: Serialize
978        + for<'de> Deserialize<'de>
979        + PartialEq
980        + Eq
981        + Clone
982        + fmt::Debug
983        + Send
984        + Sync
985        + 'static;
986
987    type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
988
989    type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
990
991    /// Use the service
992    ///
993    /// Method: POST<br>
994    /// Request: `Self::Request`<br>
995    /// Response: [`OBResponse<Self>`](OBResponse)
996    fn path() -> Path {
997        Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
998    }
999
1000    fn response_path() -> Path {
1001        Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
1002    }
1003
1004    fn confirmation_path() -> Path {
1005        Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
1006    }
1007}
1008
1009pub trait NonInteractiveService: Service {
1010    fn non_interactive_path() -> Path {
1011        Path::new([&Self::ID.to_string().to_kebab_case(), "non-interactive"])
1012    }
1013}
1014
1015#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1016#[serde(tag = "ticketStatus")]
1017pub enum TicketStatus<T> {
1018    Unfinished,
1019    Error(ErrorKind),
1020    Success(T),
1021}
1022
1023enum_with_display! {
1024    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1025    #[serde(tag = "error")]
1026    pub enum ErrorKind {
1027        UnexpectedError,
1028        Canceled,
1029        InvalidCredentials,
1030        ServiceBlocked,
1031        Unauthorized,
1032        AccessExceeded,
1033        PeriodOutOfBounds,
1034        UnsupportedProduct,
1035        PaymentFailed,
1036        UnexpectedValue,
1037        TicketError,
1038        ProviderError,
1039        NotFound,
1040    }
1041}
1042
1043#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1044#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1045#[serde(rename_all = "camelCase")]
1046pub struct Credentials {
1047    pub connection_id: ConnectionId,
1048    #[serde(skip_serializing_if = "Option::is_none")]
1049    #[cfg_attr(feature = "uniffi", uniffi(default))]
1050    pub user_id: Option<String>,
1051    #[serde(skip_serializing_if = "Option::is_none")]
1052    #[cfg_attr(feature = "uniffi", uniffi(default))]
1053    pub password: Option<String>,
1054    #[serde(skip_serializing_if = "Option::is_none")]
1055    #[cfg_attr(feature = "uniffi", uniffi(default))]
1056    pub connection_data: Option<ConnectionData>,
1057}
1058
1059#[derive(Serialize, Deserialize)]
1060#[serde(rename_all = "camelCase")]
1061pub struct ResponseData<S> {
1062    #[serde(bound = "S:")]
1063    pub context: InputContext<S>,
1064    pub response: String,
1065}
1066
1067impl<S> PartialEq for ResponseData<S> {
1068    fn eq(&self, other: &Self) -> bool {
1069        let Self { context, response } = self;
1070        context == &other.context && response == &other.response
1071    }
1072}
1073
1074impl<S> Eq for ResponseData<S> {}
1075
1076impl<S> Clone for ResponseData<S> {
1077    fn clone(&self) -> Self {
1078        Self {
1079            context: self.context.clone(),
1080            response: self.response.clone(),
1081        }
1082    }
1083}
1084
1085impl<S> fmt::Debug for ResponseData<S> {
1086    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1087        f.debug_struct("ResponseData")
1088            .field("context", &self.context)
1089            .field("response", &self.response)
1090            .finish()
1091    }
1092}
1093
1094#[derive(Serialize, Deserialize)]
1095#[serde(rename_all = "camelCase")]
1096pub struct ConfirmationData<S> {
1097    #[serde(bound = "S:")]
1098    pub context: ConfirmationContext<S>,
1099}
1100
1101impl<S> PartialEq for ConfirmationData<S> {
1102    fn eq(&self, other: &Self) -> bool {
1103        let Self { context } = self;
1104        context == &other.context
1105    }
1106}
1107
1108impl<S> Eq for ConfirmationData<S> {}
1109
1110impl<S> Clone for ConfirmationData<S> {
1111    fn clone(&self) -> Self {
1112        Self {
1113            context: self.context.clone(),
1114        }
1115    }
1116}
1117
1118impl<S> fmt::Debug for ConfirmationData<S> {
1119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1120        f.debug_struct("ConfirmationData")
1121            .field("context", &self.context)
1122            .finish()
1123    }
1124}
1125
1126#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1127#[serde(rename_all_fields = "camelCase")]
1128#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1129pub enum Error {
1130    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1131    UnexpectedError {
1132        #[serde(skip_serializing_if = "Option::is_none")]
1133        user_message: Option<String>,
1134    },
1135    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1136    Canceled {},
1137    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1138    InvalidCredentials {
1139        #[serde(skip_serializing_if = "Option::is_none")]
1140        user_message: Option<String>,
1141    },
1142    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1143    ServiceBlocked {
1144        #[serde(skip_serializing_if = "Option::is_none")]
1145        user_message: Option<String>,
1146        #[serde(skip_serializing_if = "Option::is_none")]
1147        code: Option<ServiceBlockedCode>,
1148    },
1149    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1150    Unauthorized {
1151        #[serde(skip_serializing_if = "Option::is_none")]
1152        user_message: Option<String>,
1153    },
1154    /// The bank rejected the request for exceeding the consent's permitted access
1155    /// frequency.
1156    ///
1157    /// This most commonly arises on the non-interactive refresh service, where a
1158    /// caller exceeds the bank's limit on requests made without a user in session.
1159    /// Provide a [`UserInSession`] to lift that limit.
1160    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1161    AccessExceeded {
1162        #[serde(skip_serializing_if = "Option::is_none")]
1163        user_message: Option<String>,
1164    },
1165    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1166    PeriodOutOfBounds {
1167        #[serde(skip_serializing_if = "Option::is_none")]
1168        user_message: Option<String>,
1169    },
1170    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1171    UnsupportedProduct {
1172        #[serde(skip_serializing_if = "Option::is_none")]
1173        reason: Option<UnsupportedProductReason>,
1174        #[serde(skip_serializing_if = "Option::is_none")]
1175        user_message: Option<String>,
1176    },
1177    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1178    PaymentFailed {
1179        #[serde(skip_serializing_if = "Option::is_none")]
1180        code: Option<PaymentErrorCode>,
1181        #[serde(skip_serializing_if = "Option::is_none")]
1182        user_message: Option<String>,
1183    },
1184    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1185    UnexpectedValue { error: String },
1186    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1187    TicketError {
1188        error: String,
1189        code: TicketErrorCode,
1190    },
1191    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1192    ProviderError {
1193        #[serde(skip_serializing_if = "Option::is_none")]
1194        code: Option<ProviderErrorCode>,
1195        #[serde(skip_serializing_if = "Option::is_none")]
1196        user_message: Option<String>,
1197    },
1198    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1199    InterruptError {},
1200}
1201
1202#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1203#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1204#[cfg_attr(not(feature = "server"), non_exhaustive)]
1205pub enum TicketErrorCode {
1206    /// Missing "yaxi-ticket" header
1207    Missing,
1208    /// Invalid ticket
1209    Invalid,
1210    /// Ticket token lacks "kid"
1211    MissingKey,
1212    /// Unknown key
1213    UnknownKey,
1214    /// Ticket does not match service
1215    Mismatch,
1216    /// Ticket is expired
1217    Expired,
1218    /// Ticket lifetime is too long
1219    InvalidLifetime,
1220    /// Expired key
1221    ExpiredKey,
1222    /// Environment mismatch between key and routex
1223    KeyEnvironmentMismatch,
1224}
1225
1226#[derive(Serialize, Deserialize, Clone, Debug)]
1227#[cfg_attr(not(feature = "server"), non_exhaustive)]
1228pub enum Filter<F> {
1229    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1230    Eq(F, Value),
1231    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1232    NotEq(F, Value),
1233    #[cfg(feature = "server")]
1234    Contains(F, Value),
1235    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1236    And(Box<Self>, Box<Self>),
1237    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1238    Or(Box<Self>, Box<Self>),
1239    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1240    Supports(SupportedService),
1241}
1242
1243#[derive(Serialize, Deserialize, Clone, Debug)]
1244#[cfg_attr(not(feature = "server"), non_exhaustive)]
1245#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1246pub enum SupportedService {
1247    CollectPayment,
1248}
1249
1250impl<F> Filter<F> {
1251    #[must_use]
1252    /// Add an additional filter.
1253    pub fn and(self, other: Filter<F>) -> Filter<F> {
1254        Filter::And(Box::new(self), Box::new(other))
1255    }
1256
1257    #[must_use]
1258    /// Add an alternative filter.
1259    pub fn or(self, other: Filter<F>) -> Filter<F> {
1260        Filter::Or(Box::new(self), Box::new(other))
1261    }
1262}
1263
1264pub struct Field<F, T> {
1265    field: F,
1266    phantom: PhantomData<T>,
1267}
1268
1269impl<F, T> Field<F, T>
1270where
1271    F: Copy,
1272    T: Serialize,
1273{
1274    /// Filter for equality with a given value.
1275    ///
1276    /// # Panics
1277    ///
1278    /// Panics if [`serde_json::to_value`] for the value fails.
1279    pub fn eq(&self, value: T) -> Filter<F> {
1280        Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1281    }
1282
1283    /// Filter for inequality with a given value.
1284    ///
1285    /// # Panics
1286    ///
1287    /// Panics if [`serde_json::to_value`] for the value fails.
1288    pub fn not_eq(&self, value: T) -> Filter<F> {
1289        Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1290    }
1291}
1292
1293pub trait GetValue {
1294    type Model;
1295
1296    fn get(&self, model: &Self::Model) -> Value;
1297}
1298
1299macro_rules! fields {
1300    {
1301        $model:ty, $enum_name:ident
1302        $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1303        $(
1304            server:
1305            $(($server_variant_name:ident, $server_field_name:ident))+
1306        )?
1307    } => {
1308        #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1309        #[non_exhaustive]
1310        #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1311        pub enum $enum_name {
1312            $($variant_name,)+
1313            $($(
1314                #[cfg(feature = "server")]
1315                $server_variant_name,
1316            )+)?
1317        }
1318
1319        impl $enum_name {
1320            $(
1321                pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1322                    field: $enum_name::$variant_name,
1323                    phantom: std::marker::PhantomData,
1324                };
1325            )+
1326        }
1327
1328        impl crate::GetValue for $enum_name {
1329            type Model = $model;
1330
1331            fn get(&self, model: &$model) -> serde_json::Value {
1332                match self {
1333                    $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1334                    $($(
1335                        #[cfg(feature = "server")]
1336                        $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1337                    )+)?
1338                }.expect("Serialization should work")
1339            }
1340        }
1341    }
1342}
1343
1344#[derive(Serialize, Deserialize)]
1345#[serde(rename_all = "camelCase")]
1346pub struct ServiceRequest<S: Service> {
1347    pub credentials: Credentials,
1348    #[serde(skip_serializing_if = "Option::is_none")]
1349    pub session: Option<Session>,
1350    #[serde(default, skip_serializing_if = "is_false")]
1351    pub recurring_consents: bool,
1352    #[serde(flatten, bound = "S:")]
1353    pub data: <S as Service>::RequestData,
1354}
1355
1356impl<S: Service> Clone for ServiceRequest<S> {
1357    fn clone(&self) -> Self {
1358        Self {
1359            credentials: self.credentials.clone(),
1360            session: self.session.clone(),
1361            recurring_consents: self.recurring_consents,
1362            data: self.data.clone(),
1363        }
1364    }
1365}
1366
1367#[derive(Serialize, Deserialize)]
1368#[serde(rename_all = "camelCase")]
1369pub struct NonInteractiveRequest<S: Service> {
1370    pub connection_data: ConnectionData,
1371    #[serde(skip_serializing_if = "Option::is_none")]
1372    pub session: Option<Session>,
1373    #[serde(default, skip_serializing_if = "Option::is_none")]
1374    pub user_in_session: Option<UserInSession>,
1375    #[serde(flatten, bound = "S:")]
1376    pub data: <S as Service>::RequestData,
1377}
1378
1379#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1380#[serde(rename_all = "camelCase")]
1381#[non_exhaustive]
1382#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1383pub struct Account {
1384    /// ISO 20022 IBAN2007Identifier.
1385    #[serde(skip_serializing_if = "Option::is_none")]
1386    #[cfg_attr(feature = "uniffi", uniffi(default))]
1387    pub iban: Option<String>,
1388
1389    /// Account number that is not an IBAN, e.g. ISO 20022 BBANIdentifier or primary account number (PAN) of a card account.
1390    #[serde(skip_serializing_if = "Option::is_none")]
1391    #[cfg_attr(feature = "uniffi", uniffi(default))]
1392    pub number: Option<String>,
1393
1394    /// Masked payment card number
1395    #[serde(skip_serializing_if = "Option::is_none")]
1396    #[cfg_attr(feature = "uniffi", uniffi(default))]
1397    pub masked_pan: Option<String>,
1398
1399    /// ISO 20022 BICFIIdentifier.
1400    #[serde(skip_serializing_if = "Option::is_none")]
1401    #[cfg_attr(feature = "uniffi", uniffi(default))]
1402    pub bic: Option<String>,
1403
1404    /// National bank code.
1405    #[serde(skip_serializing_if = "Option::is_none")]
1406    #[cfg_attr(feature = "uniffi", uniffi(default))]
1407    pub bank_code: Option<String>,
1408
1409    /// ISO 4217 Alpha 3 currency code.
1410    #[serde(skip_serializing_if = "Option::is_none")]
1411    #[cfg_attr(feature = "uniffi", uniffi(default))]
1412    pub currency: Option<String>,
1413
1414    /// Name of account, assigned by ASPSP.
1415    #[serde(skip_serializing_if = "Option::is_none")]
1416    #[cfg_attr(feature = "uniffi", uniffi(default))]
1417    pub name: Option<String>,
1418
1419    /// Display name of account, assigned by PSU.
1420    #[serde(skip_serializing_if = "Option::is_none")]
1421    #[cfg_attr(feature = "uniffi", uniffi(default))]
1422    pub display_name: Option<String>,
1423
1424    /// Legal account owner.
1425    #[serde(skip_serializing_if = "Option::is_none")]
1426    #[cfg_attr(feature = "uniffi", uniffi(default))]
1427    pub owner_name: Option<String>,
1428
1429    /// Product name.
1430    #[serde(skip_serializing_if = "Option::is_none")]
1431    #[cfg_attr(feature = "uniffi", uniffi(default))]
1432    pub product_name: Option<String>,
1433
1434    /// Account status.
1435    #[serde(skip_serializing_if = "Option::is_none")]
1436    #[cfg_attr(feature = "uniffi", uniffi(default))]
1437    pub status: Option<AccountStatus>,
1438
1439    /// Account type.
1440    #[serde(rename = "type")]
1441    #[serde(skip_serializing_if = "Option::is_none")]
1442    #[cfg_attr(feature = "uniffi", uniffi(default))]
1443    pub type_: Option<AccountType>,
1444}
1445
1446pub mod accounts {
1447    use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1448    use serde::{Deserialize, Serialize};
1449
1450    #[derive(Clone, Debug)]
1451    pub struct Service {}
1452
1453    impl super::Service for Service {
1454        const ID: ServiceId = ServiceId::Accounts;
1455
1456        type TicketData = ();
1457
1458        type RequestData = RequestData;
1459
1460        type Output = Vec<Account>;
1461    }
1462
1463    impl super::NonInteractiveService for Service {}
1464
1465    fields! {
1466        routex_models::Account, AccountField
1467        (IBAN, Iban, iban, Option<String>)
1468        (NUMBER, Number, number, Option<String>)
1469        (MASKED_PAN, MaskedPan, masked_pan, Option<String>)
1470        (BIC, Bic, bic, Option<String>)
1471        (BANK_CODE, BankCode, bank_code, Option<String>)
1472        (CURRENCY, Currency, currency, String)
1473        (NAME, Name, name, Option<String>)
1474        (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1475        (OWNER_NAME, OwnerName, owner_name, Option<String>)
1476        (PRODUCT_NAME, ProductName, product_name, Option<String>)
1477        (STATUS, Status, status, Option<AccountStatus>)
1478        (TYPE, Type, type_, Option<AccountType>)
1479        server:
1480        (Capabilities, capabilities)
1481    }
1482
1483    impl Account {
1484        #[must_use]
1485        /// Filter for possible support of YAXI Open Banking services.
1486        pub fn supports(service: SupportedService) -> Filter<AccountField> {
1487            Filter::Supports(service)
1488        }
1489    }
1490
1491    #[derive(Serialize, Deserialize, Clone)]
1492    #[serde(rename_all = "camelCase")]
1493    pub struct RequestData {
1494        pub fields: Vec<AccountField>,
1495        #[serde(skip_serializing_if = "Option::is_none")]
1496        pub filter: Option<Filter<AccountField>>,
1497    }
1498}
1499
1500#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1501#[serde(rename_all = "camelCase")]
1502#[serde(rename_all_fields = "camelCase")]
1503#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1504pub enum AccountIdentifier {
1505    /// ISO 20022 IBAN2007Identifier.
1506    Iban(String),
1507
1508    /// Account number that is not an IBAN, e.g. ISO 20022 BBANIdentifier.
1509    Number(String),
1510
1511    /// Masked payment card number.
1512    MaskedPan(String),
1513}
1514
1515#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1516#[serde(rename_all = "camelCase")]
1517#[serde(rename_all_fields = "camelCase")]
1518#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1519pub enum CreditorAccountIdentifier {
1520    /// ISO 20022 IBAN2007Identifier.
1521    Iban(String),
1522}
1523
1524#[serde_with::serde_as]
1525#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1526#[serde(rename_all = "camelCase")]
1527#[serde(rename_all_fields = "camelCase")]
1528#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1529pub enum DebtorAccountIdentifier {
1530    /// ISO 20022 IBAN2007Identifier.
1531    #[serde(alias = "Iban")]
1532    Iban(String),
1533    #[cfg(feature = "server")]
1534    EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
1535}
1536
1537#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1538#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1539#[serde(rename_all = "camelCase")]
1540pub struct AccountReference {
1541    #[serde(flatten)]
1542    pub id: AccountIdentifier,
1543    #[serde(skip_serializing_if = "Option::is_none")]
1544    #[cfg_attr(feature = "uniffi", uniffi(default))]
1545    pub currency: Option<String>,
1546}
1547
1548#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1549#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1550#[serde(rename_all = "camelCase")]
1551pub struct DebtorAccountReference {
1552    #[serde(flatten)]
1553    pub id: DebtorAccountIdentifier,
1554    #[serde(skip_serializing_if = "Option::is_none")]
1555    #[cfg_attr(feature = "uniffi", uniffi(default))]
1556    pub currency: Option<String>,
1557}
1558
1559pub mod balances {
1560    pub use rust_decimal::Decimal;
1561    use serde::{Deserialize, Serialize};
1562
1563    use crate::{AccountReference, DateTime, ServiceId};
1564
1565    #[derive(Clone, Debug)]
1566    pub struct Service {}
1567
1568    impl super::Service for Service {
1569        const ID: ServiceId = ServiceId::Balances;
1570
1571        type TicketData = ();
1572
1573        type RequestData = RequestData;
1574
1575        type Output = Balances;
1576    }
1577
1578    impl super::NonInteractiveService for Service {}
1579
1580    #[derive(Serialize, Deserialize, Clone)]
1581    #[serde(rename_all = "camelCase")]
1582    pub struct RequestData {
1583        pub accounts: Vec<AccountReference>,
1584    }
1585
1586    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1587    #[serde(rename_all = "camelCase")]
1588    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1589    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1590    pub struct Balances {
1591        /// List of balances for accounts.
1592        pub balances: Vec<AccountBalances>,
1593
1594        /// Accounts that were requested but not found.
1595        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1596        #[cfg_attr(feature = "uniffi", uniffi(default))]
1597        pub missing_accounts: Vec<AccountReference>,
1598    }
1599
1600    #[allow(clippy::module_name_repetitions)]
1601    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1602    #[serde(rename_all = "camelCase")]
1603    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1604    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1605    pub struct AccountBalances {
1606        pub account: AccountReference,
1607
1608        /// List of balances (of different types) for the account.
1609        pub balances: Vec<Balance>,
1610    }
1611
1612    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1613    #[serde(rename_all = "camelCase")]
1614    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1615    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1616    pub struct Balance {
1617        pub amount: Decimal,
1618        pub currency: String,
1619        pub balance_type: BalanceType,
1620        #[serde(default, skip_serializing_if = "Option::is_none")]
1621        #[cfg_attr(feature = "uniffi", uniffi(default))]
1622        pub credit_limit_included: Option<bool>,
1623        #[serde(default, skip_serializing_if = "Option::is_none")]
1624        #[cfg_attr(feature = "uniffi", uniffi(default))]
1625        pub date_time: Option<DateTime>,
1626    }
1627
1628    #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1629    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1630    pub enum BalanceType {
1631        /// Balance from booked transactions.
1632        Booked,
1633
1634        /// Available balance from booked transactions.
1635        ///
1636        /// Depending on the bank, a credit line may or may not be included
1637        /// (see [`Balance::credit_limit_included`]). For the most current
1638        /// balance without credit, prefer [`Booked`](Self::Booked) and judge
1639        /// recency by [`Balance::date_time`].
1640        Available,
1641
1642        /// Expected balance from booked and pending transactions.
1643        Expected,
1644    }
1645}
1646
1647pub mod transactions {
1648    use chrono::NaiveDate;
1649    use isocountry::CountryCode;
1650    pub use routex_models::{Amount, Fee, TransactionStatus};
1651    use rust_decimal::Decimal;
1652    use serde::{Deserialize, Serialize};
1653    use url::Url;
1654
1655    use crate::{AccountReference, ServiceId};
1656
1657    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1658    #[serde(rename_all = "camelCase")]
1659    pub struct TicketData {
1660        pub account: AccountReference,
1661        pub range: Range,
1662        pub webhook: Option<Url>,
1663    }
1664
1665    #[derive(Clone, Debug)]
1666    pub struct Service {}
1667
1668    impl super::Service for Service {
1669        const ID: ServiceId = ServiceId::Transactions;
1670
1671        type TicketData = TicketData;
1672
1673        type RequestData = RequestData;
1674
1675        type Output = Option<Vec<Transaction>>;
1676    }
1677
1678    impl super::NonInteractiveService for Service {}
1679
1680    #[derive(Serialize, Deserialize, Clone)]
1681    #[serde(rename_all = "camelCase")]
1682    pub struct RequestData {}
1683
1684    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1685    #[serde(rename_all = "camelCase")]
1686    #[serde(rename_all_fields = "camelCase")]
1687    pub enum Range {
1688        Reference(String),
1689        #[serde(untagged)]
1690        Period {
1691            /// Earliest date (inclusive)
1692            from: NaiveDate,
1693            /// Latest date (inclusive)
1694            to: Option<NaiveDate>,
1695        },
1696    }
1697
1698    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1699    #[serde(rename_all = "camelCase")]
1700    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1701    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1702    pub struct Transaction {
1703        /// Identifier used for delta requests.
1704        #[serde(skip_serializing_if = "Option::is_none")]
1705        #[cfg_attr(feature = "uniffi", uniffi(default))]
1706        pub entry_reference: Option<String>,
1707
1708        #[serde(skip_serializing_if = "Option::is_none")]
1709        #[cfg_attr(feature = "uniffi", uniffi(default))]
1710        pub batch: Option<BatchData>,
1711
1712        /// Booking date (ASPSP's books).
1713        #[serde(skip_serializing_if = "Option::is_none")]
1714        #[cfg_attr(feature = "uniffi", uniffi(default))]
1715        pub booking_date: Option<NaiveDate>,
1716
1717        /// Value date. Expected / requested value date in case of pending entries.
1718        #[serde(skip_serializing_if = "Option::is_none")]
1719        #[cfg_attr(feature = "uniffi", uniffi(default))]
1720        pub value_date: Option<NaiveDate>,
1721
1722        /// Date of the actual transaction, e.g. a card payment.
1723        #[serde(skip_serializing_if = "Option::is_none")]
1724        #[cfg_attr(feature = "uniffi", uniffi(default))]
1725        pub transaction_date: Option<NaiveDate>,
1726
1727        /// Transaction status.
1728        pub status: TransactionStatus,
1729
1730        /// Unique reference assigned by the account servicer.
1731        #[serde(skip_serializing_if = "Option::is_none")]
1732        #[cfg_attr(feature = "uniffi", uniffi(default))]
1733        pub account_servicer_reference: Option<String>,
1734
1735        /// Unique identifier assigned by the sending party.
1736        #[serde(skip_serializing_if = "Option::is_none")]
1737        #[cfg_attr(feature = "uniffi", uniffi(default))]
1738        pub payment_id: Option<String>,
1739
1740        /// Unique identifier assigned by the first instructing agent.
1741        #[serde(skip_serializing_if = "Option::is_none")]
1742        #[cfg_attr(feature = "uniffi", uniffi(default))]
1743        pub transaction_id: Option<String>,
1744
1745        /// Unique end-to-end identifier assigned by the initiating party.
1746        #[serde(skip_serializing_if = "Option::is_none")]
1747        #[cfg_attr(feature = "uniffi", uniffi(default))]
1748        pub end_to_end_id: Option<String>,
1749
1750        /// Mandate identifier.
1751        #[serde(skip_serializing_if = "Option::is_none")]
1752        #[cfg_attr(feature = "uniffi", uniffi(default))]
1753        pub mandate_id: Option<String>,
1754
1755        /// SEPA creditor identifier.
1756        #[serde(skip_serializing_if = "Option::is_none")]
1757        #[cfg_attr(feature = "uniffi", uniffi(default))]
1758        pub creditor_id: Option<String>,
1759
1760        /// Transaction amount as billed to the account.
1761        pub amount: Amount,
1762
1763        /// Indicator for reversals.
1764        #[serde(default, skip_serializing_if = "super::is_false")]
1765        #[cfg_attr(feature = "uniffi", uniffi(default))]
1766        pub reversal: bool,
1767
1768        /// Original amount of the transaction.
1769        #[serde(skip_serializing_if = "Option::is_none")]
1770        #[cfg_attr(feature = "uniffi", uniffi(default))]
1771        pub original_amount: Option<Amount>,
1772
1773        /// Exchange rates.
1774        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1775        #[cfg_attr(feature = "uniffi", uniffi(default))]
1776        pub exchanges: Vec<ExchangeRate>,
1777
1778        /// Any fees related to the transaction.
1779        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1780        #[cfg_attr(feature = "uniffi", uniffi(default))]
1781        pub fees: Vec<Fee>,
1782
1783        /// Creditor data. In case of reversals this refers to the initial transaction.
1784        #[serde(skip_serializing_if = "Option::is_none")]
1785        #[cfg_attr(feature = "uniffi", uniffi(default))]
1786        pub creditor: Option<Party>,
1787
1788        /// Debtor data. In case of reversals this refers to the initial transaction.
1789        #[serde(skip_serializing_if = "Option::is_none")]
1790        #[cfg_attr(feature = "uniffi", uniffi(default))]
1791        pub debtor: Option<Party>,
1792
1793        /// Remittance (purpose).
1794        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1795        #[cfg_attr(feature = "uniffi", uniffi(default))]
1796        pub remittance_information: Vec<String>,
1797
1798        /// ISO 20022 ExternalPurpose1Code.
1799        #[serde(skip_serializing_if = "Option::is_none")]
1800        #[cfg_attr(feature = "uniffi", uniffi(default))]
1801        pub purpose_code: Option<String>,
1802
1803        /// Bank Transaction Codes.
1804        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1805        #[cfg_attr(feature = "uniffi", uniffi(default))]
1806        pub bank_transaction_codes: Vec<BankTransactionCode>,
1807
1808        /// Additional information attached to the transaction.
1809        #[serde(skip_serializing_if = "Option::is_none")]
1810        #[cfg_attr(feature = "uniffi", uniffi(default))]
1811        pub additional_information: Option<String>,
1812    }
1813
1814    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1815    #[serde(rename_all = "camelCase")]
1816    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1817    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1818    pub struct BatchData {
1819        /// Number of transactions in the batch, if known.
1820        #[serde(skip_serializing_if = "Option::is_none")]
1821        #[cfg_attr(feature = "uniffi", uniffi(default))]
1822        pub number_of_transactions: Option<u32>,
1823
1824        /// Details of transactions in the batch.
1825        ///
1826        /// Note that this does not necessarily match a given number of transactions.
1827        /// It could be e.g. empty as no details are given or a single entry with common details on all transactions in the batch.
1828        pub transactions: Vec<BatchTransactionDetails>,
1829    }
1830
1831    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1832    #[serde(rename_all = "camelCase")]
1833    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1834    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1835    pub struct BatchTransactionDetails {
1836        /// Unique reference assigned by the account servicer.
1837        #[serde(skip_serializing_if = "Option::is_none")]
1838        #[cfg_attr(feature = "uniffi", uniffi(default))]
1839        pub account_servicer_reference: Option<String>,
1840
1841        /// Unique identifier assigned by the sending party.
1842        #[serde(skip_serializing_if = "Option::is_none")]
1843        #[cfg_attr(feature = "uniffi", uniffi(default))]
1844        pub payment_id: Option<String>,
1845
1846        /// Unique identifier assigned by the first instructing agent.
1847        #[serde(skip_serializing_if = "Option::is_none")]
1848        #[cfg_attr(feature = "uniffi", uniffi(default))]
1849        pub transaction_id: Option<String>,
1850
1851        /// Unique end-to-end identifier assigned by the initiating party.
1852        #[serde(skip_serializing_if = "Option::is_none")]
1853        #[cfg_attr(feature = "uniffi", uniffi(default))]
1854        pub end_to_end_id: Option<String>,
1855
1856        /// Mandate identifier.
1857        #[serde(skip_serializing_if = "Option::is_none")]
1858        #[cfg_attr(feature = "uniffi", uniffi(default))]
1859        pub mandate_id: Option<String>,
1860
1861        /// SEPA creditor identifier.
1862        #[serde(skip_serializing_if = "Option::is_none")]
1863        #[cfg_attr(feature = "uniffi", uniffi(default))]
1864        pub creditor_id: Option<String>,
1865
1866        /// Transaction amount as billed to the account.
1867        #[serde(skip_serializing_if = "Option::is_none")]
1868        #[cfg_attr(feature = "uniffi", uniffi(default))]
1869        pub amount: Option<Amount>,
1870
1871        /// Indicator for reversals.
1872        #[serde(default, skip_serializing_if = "super::is_false")]
1873        #[cfg_attr(feature = "uniffi", uniffi(default))]
1874        pub reversal: bool,
1875
1876        /// Original amount of the transaction.
1877        #[serde(skip_serializing_if = "Option::is_none")]
1878        #[cfg_attr(feature = "uniffi", uniffi(default))]
1879        pub original_amount: Option<Amount>,
1880
1881        /// Exchange rates.
1882        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1883        #[cfg_attr(feature = "uniffi", uniffi(default))]
1884        pub exchanges: Vec<ExchangeRate>,
1885
1886        /// Any fees related to the transaction.
1887        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1888        #[cfg_attr(feature = "uniffi", uniffi(default))]
1889        pub fees: Vec<Fee>,
1890
1891        /// Creditor data. In case of reversals this refers to the initial transaction.
1892        #[serde(skip_serializing_if = "Option::is_none")]
1893        #[cfg_attr(feature = "uniffi", uniffi(default))]
1894        pub creditor: Option<Party>,
1895
1896        /// Debtor data. In case of reversals this refers to the initial transaction.
1897        #[serde(skip_serializing_if = "Option::is_none")]
1898        #[cfg_attr(feature = "uniffi", uniffi(default))]
1899        pub debtor: Option<Party>,
1900
1901        /// Remittance (purpose).
1902        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1903        #[cfg_attr(feature = "uniffi", uniffi(default))]
1904        pub remittance_information: Vec<String>,
1905
1906        /// ISO 20022 ExternalPurpose1Code.
1907        #[serde(skip_serializing_if = "Option::is_none")]
1908        #[cfg_attr(feature = "uniffi", uniffi(default))]
1909        pub purpose_code: Option<String>,
1910
1911        /// Bank Transaction Codes.
1912        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1913        #[cfg_attr(feature = "uniffi", uniffi(default))]
1914        pub bank_transaction_codes: Vec<BankTransactionCode>,
1915
1916        /// Additional information attached to the transaction.
1917        #[serde(skip_serializing_if = "Option::is_none")]
1918        #[cfg_attr(feature = "uniffi", uniffi(default))]
1919        pub additional_information: Option<String>,
1920    }
1921
1922    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1923    #[serde(rename_all = "camelCase")]
1924    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1925    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1926    pub struct ExchangeRate {
1927        /// ISO 4217 Alpha 3 currency code of the source currency that gets converted.
1928        pub source_currency: String,
1929        /// ISO 4217 Alpha 3 currency code of the target currency that
1930        /// the source currency gets converted into.
1931        #[serde(skip_serializing_if = "Option::is_none")]
1932        #[cfg_attr(feature = "uniffi", uniffi(default))]
1933        pub target_currency: Option<String>,
1934        /// ISO 4217 Alpha 3 currency code of the unit currency for the exchange rate.
1935        #[serde(skip_serializing_if = "Option::is_none")]
1936        #[cfg_attr(feature = "uniffi", uniffi(default))]
1937        pub unit_currency: Option<String>,
1938        pub exchange_rate: Decimal,
1939    }
1940
1941    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1942    #[serde(rename_all = "camelCase")]
1943    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1944    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1945    pub struct Party {
1946        /// Creditor / debtor name.
1947        #[serde(skip_serializing_if = "Option::is_none")]
1948        #[cfg_attr(feature = "uniffi", uniffi(default))]
1949        pub name: Option<String>,
1950
1951        /// ISO 20022 IBAN2007Identifier for the creditor / debtor account.
1952        #[serde(skip_serializing_if = "Option::is_none")]
1953        #[cfg_attr(feature = "uniffi", uniffi(default))]
1954        pub iban: Option<String>,
1955
1956        /// ISO 20022 BICFIIdentifier for the creditor / debtor agent.
1957        #[serde(skip_serializing_if = "Option::is_none")]
1958        #[cfg_attr(feature = "uniffi", uniffi(default))]
1959        pub bic: Option<String>,
1960
1961        /// Ultimate creditor / debtor (name).
1962        #[serde(skip_serializing_if = "Option::is_none")]
1963        #[cfg_attr(feature = "uniffi", uniffi(default))]
1964        pub ultimate: Option<String>,
1965    }
1966
1967    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1968    #[serde(rename_all = "camelCase")]
1969    #[serde(rename_all_fields = "camelCase")]
1970    #[non_exhaustive]
1971    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1972    pub enum BankTransactionCode {
1973        /// ISO 20022 Bank Transaction Code.
1974        Iso {
1975            /// ISO 20022 ExternalBankTransactionDomain1Code.
1976            domain: String,
1977
1978            /// ISO 20022 ExternalBankTransactionFamily1Code.
1979            family: String,
1980
1981            /// ISO 20022 ExternalBankTransactionSubFamily1Code.
1982            sub_family: String,
1983        },
1984        /// SWIFT transaction code.
1985        Swift(String),
1986        /// BAI2 transaction code.
1987        Bai(String),
1988        /// National transaction code, e.g. German GVC.
1989        National { code: String, country: CountryCode },
1990        /// Unspecified transaction codes, possibly with an issuer information.
1991        Other {
1992            code: String,
1993            #[serde(skip_serializing_if = "Option::is_none")]
1994            issuer: Option<String>,
1995        },
1996    }
1997}
1998
1999pub mod standing_orders {
2000    use chrono::NaiveDate;
2001    pub use routex_models::{Amount, ExecutionRule, Frequency, Month};
2002    use serde::{Deserialize, Serialize};
2003
2004    pub use crate::transactions::Party;
2005    use crate::{AccountReference, ServiceId};
2006
2007    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2008    #[serde(rename_all = "camelCase")]
2009    pub struct TicketData {
2010        pub account: AccountReference,
2011    }
2012
2013    #[derive(Clone, Debug)]
2014    pub struct Service {}
2015
2016    impl super::Service for Service {
2017        const ID: ServiceId = ServiceId::StandingOrders;
2018
2019        type TicketData = TicketData;
2020
2021        type RequestData = RequestData;
2022
2023        type Output = Vec<StandingOrder>;
2024    }
2025
2026    #[derive(Serialize, Deserialize, Clone)]
2027    #[serde(rename_all = "camelCase")]
2028    pub struct RequestData {}
2029
2030    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
2031    #[serde(rename_all = "camelCase")]
2032    #[cfg_attr(not(feature = "server"), non_exhaustive)]
2033    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2034    pub struct StandingOrder {
2035        /// Date of the first execution.
2036        pub start_date: NaiveDate,
2037
2038        /// Date of the last execution.
2039        #[serde(skip_serializing_if = "Option::is_none")]
2040        #[cfg_attr(feature = "uniffi", uniffi(default))]
2041        pub end_date: Option<NaiveDate>,
2042
2043        /// Rule to follow if the execution falls on a weekend or bank holiday.
2044        #[serde(skip_serializing_if = "Option::is_none")]
2045        #[cfg_attr(feature = "uniffi", uniffi(default))]
2046        pub execution_rule: Option<ExecutionRule>,
2047
2048        /// Execution frequency.
2049        #[serde(skip_serializing_if = "Option::is_none")]
2050        #[cfg_attr(feature = "uniffi", uniffi(default))]
2051        pub frequency: Option<Frequency>,
2052
2053        /// Day of month on which the order shall get executed.
2054        #[serde(skip_serializing_if = "Option::is_none")]
2055        #[cfg_attr(feature = "uniffi", uniffi(default))]
2056        pub day: Option<u8>,
2057
2058        /// Amount limit for fund skimming, i.e. funds exceeding this value get transferred
2059        /// instead of a fixed amount.
2060        #[serde(skip_serializing_if = "Option::is_none")]
2061        #[cfg_attr(feature = "uniffi", uniffi(default))]
2062        pub skimming_limit: Option<Amount>,
2063
2064        /// Unique end-to-end identifier assigned by the initiating party.
2065        #[serde(skip_serializing_if = "Option::is_none")]
2066        #[cfg_attr(feature = "uniffi", uniffi(default))]
2067        pub end_to_end_id: Option<String>,
2068
2069        /// Amount of each execution.
2070        pub amount: Amount,
2071
2072        /// Creditor data.
2073        #[serde(skip_serializing_if = "Option::is_none")]
2074        #[cfg_attr(feature = "uniffi", uniffi(default))]
2075        pub creditor: Option<Party>,
2076
2077        /// Debtor data.
2078        #[serde(skip_serializing_if = "Option::is_none")]
2079        #[cfg_attr(feature = "uniffi", uniffi(default))]
2080        pub debtor: Option<Party>,
2081
2082        /// Remittance (purpose).
2083        #[serde(default, skip_serializing_if = "Vec::is_empty")]
2084        #[cfg_attr(feature = "uniffi", uniffi(default))]
2085        pub remittance_information: Vec<String>,
2086
2087        /// ISO 20022 ExternalPurpose1Code.
2088        #[serde(skip_serializing_if = "Option::is_none")]
2089        #[cfg_attr(feature = "uniffi", uniffi(default))]
2090        pub purpose_code: Option<String>,
2091
2092        /// Additional information attached to the standing order.
2093        #[serde(skip_serializing_if = "Option::is_none")]
2094        #[cfg_attr(feature = "uniffi", uniffi(default))]
2095        pub additional_information: Option<String>,
2096    }
2097}
2098
2099#[allow(clippy::trivially_copy_pass_by_ref)]
2100fn is_false(val: &bool) -> bool {
2101    !*val
2102}
2103
2104#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
2105#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2106#[non_exhaustive]
2107pub enum PaymentStatus {
2108    /// The payment was received and is getting processed.
2109    ///
2110    /// Especially without realtime bookings this is often the final status reported by the ASPSP.
2111    Accepted,
2112
2113    /// The payment was partially accepted, i.e. only some of the transactions of a bulk payment or the payment needs further authorization.
2114    PartiallyAccepted,
2115
2116    /// Settlement on the debtor's account has been completed.
2117    CompletedDebtor,
2118
2119    /// Settlement on the creditor's account has been completed.
2120    CompletedCreditor,
2121}
2122
2123pub mod collect_payment {
2124    pub use routex_models::Amount;
2125    use serde::{Deserialize, Serialize};
2126    use serde_with::base64::Base64;
2127
2128    use super::{
2129        CreditorAccountIdentifier, DebtorAccountReference, Path, PaymentStatus, ServiceId,
2130    };
2131
2132    /// Query ticket status, possibly polling pending confirmations
2133    ///
2134    /// Method: POST<br>
2135    /// Response: [`Option<TicketStatus<SuccessStatusData>>`](super::TicketStatus)
2136    pub fn status_path(ticket_id: &str) -> Path {
2137        Path::new(["collect-payment", "status", ticket_id])
2138    }
2139
2140    #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
2141    #[serde(rename_all = "camelCase")]
2142    #[non_exhaustive]
2143    pub struct SuccessStatusData {
2144        #[serde(skip_serializing_if = "Option::is_none")]
2145        pub payment_status: Option<PaymentStatus>,
2146    }
2147
2148    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2149    #[serde(rename_all = "camelCase")]
2150    pub struct TicketData {
2151        pub amount: Amount,
2152        pub creditor_account: CreditorAccountIdentifier,
2153        pub creditor_name: String,
2154        pub remittance: String,
2155        #[serde(skip_serializing_if = "Option::is_none")]
2156        pub instant: Option<bool>,
2157        #[serde(skip_serializing_if = "Option::is_none")]
2158        pub fields: Option<Vec<Field>>,
2159    }
2160
2161    #[derive(Clone, Debug)]
2162    pub struct Service {}
2163
2164    impl super::Service for Service {
2165        const ID: ServiceId = ServiceId::CollectPayment;
2166
2167        type TicketData = TicketData;
2168
2169        type RequestData = RequestData;
2170
2171        type Output = PaymentInitiation;
2172    }
2173
2174    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
2175    #[serde(rename_all = "camelCase")]
2176    #[non_exhaustive]
2177    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2178    pub enum Field {
2179        DebtorIban,
2180        DebtorName,
2181        EncryptedDebtorIban,
2182        EncryptedDebtorName,
2183    }
2184
2185    #[derive(Serialize, Deserialize, Clone)]
2186    #[serde(rename_all = "camelCase")]
2187    pub struct RequestData {
2188        #[serde(skip_serializing_if = "Option::is_none")]
2189        pub account: Option<DebtorAccountReference>,
2190    }
2191
2192    #[serde_with::serde_as]
2193    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2194    #[serde(rename_all = "camelCase")]
2195    #[non_exhaustive]
2196    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2197    pub struct PaymentInitiation {
2198        #[serde(skip_serializing_if = "Option::is_none")]
2199        #[cfg_attr(feature = "uniffi", uniffi(default))]
2200        pub status: Option<PaymentStatus>,
2201        #[serde(skip_serializing_if = "Option::is_none")]
2202        #[cfg_attr(feature = "uniffi", uniffi(default))]
2203        pub debtor_name: Option<String>,
2204        #[serde(skip_serializing_if = "Option::is_none")]
2205        #[cfg_attr(feature = "uniffi", uniffi(default))]
2206        pub debtor_iban: Option<String>,
2207        #[serde(skip_serializing_if = "Option::is_none")]
2208        #[cfg_attr(feature = "uniffi", uniffi(default))]
2209        #[serde_as(as = "Option<Base64>")]
2210        pub encrypted_debtor_name: Option<Vec<u8>>,
2211        #[serde(skip_serializing_if = "Option::is_none")]
2212        #[cfg_attr(feature = "uniffi", uniffi(default))]
2213        #[serde_as(as = "Option<Base64>")]
2214        pub encrypted_debtor_iban: Option<Vec<u8>>,
2215    }
2216}
2217
2218pub mod transfer {
2219    pub use routex_models::{ChargeBearer, CreditorAddress, ISODateTimeOrDate, PaymentProduct};
2220    use serde::{Deserialize, Serialize};
2221
2222    use super::{
2223        Amount, CreditorAccountIdentifier, DebtorAccountReference, PaymentStatus, ServiceId,
2224    };
2225
2226    #[derive(Clone, Debug)]
2227    pub struct Service {}
2228
2229    impl super::Service for Service {
2230        const ID: ServiceId = ServiceId::Transfer;
2231
2232        type TicketData = ();
2233
2234        type RequestData = RequestData;
2235
2236        type Output = Transfer;
2237    }
2238
2239    #[derive(Serialize, Deserialize, Clone)]
2240    #[serde(rename_all = "camelCase")]
2241    pub struct RequestData {
2242        pub product: PaymentProduct,
2243        #[serde(skip_serializing_if = "Option::is_none")]
2244        pub debtor_account: Option<DebtorAccountReference>,
2245        #[serde(skip_serializing_if = "Option::is_none")]
2246        pub debtor_name: Option<String>,
2247        #[serde(skip_serializing_if = "Option::is_none")]
2248        pub requested_execution_date: Option<ISODateTimeOrDate>,
2249        pub details: Vec<TransferDetails>,
2250    }
2251
2252    #[derive(Serialize, Deserialize, Clone)]
2253    #[serde(rename_all = "camelCase")]
2254    #[non_exhaustive]
2255    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2256    pub struct TransferDetails {
2257        #[serde(skip_serializing_if = "Option::is_none")]
2258        #[cfg_attr(feature = "uniffi", uniffi(default))]
2259        pub end_to_end_identification: Option<String>,
2260        pub amount: Amount,
2261        pub creditor_account: CreditorAccountIdentifier,
2262        #[serde(skip_serializing_if = "Option::is_none")]
2263        #[cfg_attr(feature = "uniffi", uniffi(default))]
2264        pub creditor_agent_bic: Option<String>,
2265        pub creditor_name: String,
2266        #[serde(skip_serializing_if = "Option::is_none")]
2267        #[cfg_attr(feature = "uniffi", uniffi(default))]
2268        pub creditor_address: Option<CreditorAddress>,
2269        #[serde(skip_serializing_if = "Option::is_none")]
2270        #[cfg_attr(feature = "uniffi", uniffi(default))]
2271        pub remittance: Option<String>,
2272        #[serde(skip_serializing_if = "Option::is_none")]
2273        #[cfg_attr(feature = "uniffi", uniffi(default))]
2274        pub charge_bearer: Option<ChargeBearer>,
2275    }
2276
2277    impl TransferDetails {
2278        pub fn new(
2279            amount: Amount,
2280            creditor_account: CreditorAccountIdentifier,
2281            creditor_name: impl Into<String>,
2282        ) -> Self {
2283            Self {
2284                end_to_end_identification: None,
2285                amount,
2286                creditor_account,
2287                creditor_agent_bic: None,
2288                creditor_name: creditor_name.into(),
2289                creditor_address: None,
2290                remittance: None,
2291                charge_bearer: None,
2292            }
2293        }
2294    }
2295
2296    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2297    #[serde(rename_all = "camelCase")]
2298    #[non_exhaustive]
2299    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2300    pub struct Transfer {
2301        #[serde(skip_serializing_if = "Option::is_none")]
2302        #[cfg_attr(feature = "uniffi", uniffi(default))]
2303        pub status: Option<PaymentStatus>,
2304    }
2305}
2306
2307#[cfg(test)]
2308mod tests {
2309    use std::fmt::Debug;
2310
2311    use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
2312    use serde::{Deserialize, Serialize};
2313    use serde_json::json;
2314    use uuid::Uuid;
2315
2316    use crate::{
2317        Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
2318        InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
2319    };
2320
2321    #[test]
2322    fn read_response() {
2323        let serialized = serde_json::to_string(
2324            &encode(
2325                &Header::new(Algorithm::HS256),
2326                &Claims {
2327                    data: "data",
2328                    exp: 2_540_808_000,
2329                },
2330                &EncodingKey::from_secret(b"does_not_matter"),
2331            )
2332            .unwrap(),
2333        )
2334        .unwrap();
2335
2336        let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
2337
2338        assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
2339    }
2340
2341    struct Service;
2342
2343    impl crate::Service for Service {
2344        const ID: ServiceId = ServiceId::Accounts;
2345
2346        type TicketData = ();
2347
2348        type RequestData = ();
2349
2350        type Output = ();
2351    }
2352
2353    #[allow(
2354        dead_code,
2355        unconditional_recursion,
2356        clippy::extra_unused_type_parameters
2357    )]
2358    fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
2359        test_bounds::<InputContext<Service>>();
2360        test_bounds::<ConfirmationContext<Service>>();
2361        test_bounds::<DialogInput<Service>>();
2362        test_bounds::<Dialog<Service>>();
2363        test_bounds::<Redirect<Service>>();
2364        test_bounds::<RedirectHandle<Service>>();
2365        test_bounds::<OBResult<()>>();
2366        test_bounds::<Session>();
2367
2368        test_bounds::<Authenticated<Ticket<Service>>>();
2369        test_bounds::<Ticket<Service>>();
2370        test_bounds::<ResponseData<Service>>();
2371        test_bounds::<ConfirmationData<Service>>();
2372    }
2373
2374    fn test_eq_<T: PartialEq + Debug>(o: &T) {
2375        assert!(o.eq(o));
2376    }
2377
2378    #[test]
2379    fn test_eq() {
2380        let ic = InputContext::<Service>::from(vec![]);
2381        let cc = ConfirmationContext::<Service>::from(vec![]);
2382
2383        test_eq_(&ic);
2384        test_eq_(&cc);
2385        test_eq_(&Redirect {
2386            url: "url:".parse().unwrap(),
2387            context: cc.clone(),
2388        });
2389        test_eq_(&RedirectHandle {
2390            handle: String::new(),
2391            context: cc.clone(),
2392        });
2393        test_eq_(&Ticket::<Service> {
2394            service: ServiceId::Accounts,
2395            id: Uuid::new_v4(),
2396            data: (),
2397        });
2398        test_eq_(&ResponseData {
2399            context: ic,
2400            response: String::new(),
2401        });
2402        test_eq_(&ConfirmationData { context: cc });
2403    }
2404
2405    #[test]
2406    fn no_ticket_data() {
2407        assert!(
2408            serde_json::from_value::<Ticket<Service>>(json!({
2409            "id": Uuid::new_v4(),
2410            "service": ServiceId::Accounts,
2411            }))
2412            .is_ok()
2413        );
2414    }
2415
2416    #[test]
2417    fn missing_ticket_data() {
2418        let err = serde_json::from_value::<Ticket<super::collect_payment::Service>>(json!({
2419            "id": Uuid::new_v4(),
2420            "service": ServiceId::CollectPayment,
2421        }))
2422        .unwrap_err();
2423
2424        assert_eq!(err.to_string(), "missing field `data`");
2425    }
2426
2427    #[test]
2428    fn user_in_session_wire() {
2429        use super::UserInSession;
2430
2431        let at = UserInSession::At("203.0.113.7".parse().unwrap());
2432        assert_eq!(
2433            serde_json::to_value(UserInSession::OnThisConnection).unwrap(),
2434            json!("connection")
2435        );
2436        assert_eq!(serde_json::to_value(&at).unwrap(), json!("203.0.113.7"));
2437
2438        assert_eq!(
2439            serde_json::from_value::<UserInSession>(json!("connection")).unwrap(),
2440            UserInSession::OnThisConnection,
2441        );
2442        assert_eq!(
2443            serde_json::from_value::<UserInSession>(json!("203.0.113.7")).unwrap(),
2444            at
2445        );
2446
2447        assert!(serde_json::from_value::<UserInSession>(json!("nonsense")).is_err());
2448    }
2449}