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        /// The client's Curve25519 public key (Montgomery form, binary)
174        #[serde_as(as = "Base64")]
175        pub public_key: [u8; 32],
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: [u8; 32],
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        CollectPayment,
847        Transactions,
848        Balances,
849        Transfer,
850    }
851}
852
853/// Ticket to use a service.
854///
855/// This must be issued by a trusted backend for use in the (untrusted) frontend.
856///
857/// It carries
858/// * an identifier of the service that is intended to be used,
859/// * an arbitrary ticket identifier for verifying the results later on and
860/// * any critical input data for the service.
861///
862/// The ticket data gets wrapped in a [JSON Web Token](https://jwt.io/) as claim `data`.
863/// The token must also contain an expiry in the claim `exp` and the key identifier in the `kid` header field.
864///
865/// Example issuance of an `Authenticated<Ticket<accounts::Service>>` for the accounts service:
866///
867/// ```
868/// # const HMAC_KEY: &[u8] = b"foo";
869/// # const HMAC_KEY_ID: &str = "foo";
870/// use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
871/// use serde::Serialize;
872/// use std::time::{SystemTime, UNIX_EPOCH};
873/// use uuid::Uuid;
874///
875/// let ticket_id = Uuid::new_v4();
876///
877/// #[derive(Serialize, Debug)]
878/// struct Ticket<T> {
879///     service: String,
880///     id: Uuid,
881///     data: T,
882/// }
883///
884/// #[derive(Serialize, Debug)]
885/// struct Claims<T> {
886///     data: Ticket<T>,
887///     exp: u64,
888/// }
889///
890/// let header = Header {
891///     alg: Algorithm::HS256,
892///     kid: Some(HMAC_KEY_ID.to_string()),
893///     ..Default::default()
894/// };
895///
896/// let authenticated_ticket = encode(
897///     &header,
898///     &Claims {
899///         data: Ticket {
900///             service: "Accounts".to_string(),
901///             id: ticket_id,
902///             data: (),
903///         },
904///         exp: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() + 600,
905///     },
906///     &EncodingKey::from_secret(HMAC_KEY),
907/// )?;
908/// # let auth: routex_api::Authenticated<routex_api::Ticket<routex_api::accounts::Service>> = authenticated_ticket.parse().unwrap();
909/// # auth.to_data();
910/// # auth.key_id().unwrap();
911/// # Ok::<(), Box<dyn std::error::Error>>(())
912#[derive(Serialize, Deserialize)]
913#[serde(rename_all = "camelCase")]
914#[serde(try_from = "DeserializedTicket<S::TicketData>")]
915pub struct Ticket<S: Service> {
916    pub service: ServiceId,
917    pub id: Uuid,
918    pub data: S::TicketData,
919}
920
921impl<S: Service> PartialEq for Ticket<S> {
922    fn eq(&self, other: &Self) -> bool {
923        let Self { service, id, data } = self;
924        service == &other.service && id == &other.id && data == &other.data
925    }
926}
927
928impl<S: Service> Eq for Ticket<S> {}
929
930impl<S: Service> fmt::Debug for Ticket<S> {
931    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
932        f.debug_struct("Ticket")
933            .field("service", &self.service)
934            .field("id", &self.id)
935            .field("data", &self.data)
936            .finish()
937    }
938}
939
940impl<S: Service> Clone for Ticket<S> {
941    fn clone(&self) -> Self {
942        Self {
943            service: self.service,
944            id: self.id,
945            data: self.data.clone(),
946        }
947    }
948}
949
950#[derive(Deserialize, Debug)]
951struct DeserializedTicket<T> {
952    service: ServiceId,
953    id: Uuid,
954    data: Option<T>,
955}
956
957impl<S: Service> TryFrom<DeserializedTicket<S::TicketData>> for Ticket<S> {
958    type Error = &'static str;
959
960    fn try_from(
961        DeserializedTicket { service, id, data }: DeserializedTicket<S::TicketData>,
962    ) -> Result<Self, Self::Error> {
963        let data = (&() as &dyn Any)
964            .downcast_ref::<S::TicketData>()
965            .cloned()
966            .or(data)
967            .ok_or("missing field `data`")?;
968
969        Ok(Self { service, id, data })
970    }
971}
972
973pub trait Service {
974    const ID: ServiceId;
975
976    type TicketData: Serialize
977        + for<'de> Deserialize<'de>
978        + PartialEq
979        + Eq
980        + Clone
981        + fmt::Debug
982        + Send
983        + Sync
984        + 'static;
985
986    type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
987
988    type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
989
990    /// Use the service
991    ///
992    /// Method: POST<br>
993    /// Request: `Self::Request`<br>
994    /// Response: [`OBResponse<Self>`](OBResponse)
995    fn path() -> Path {
996        Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
997    }
998
999    fn response_path() -> Path {
1000        Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
1001    }
1002
1003    fn confirmation_path() -> Path {
1004        Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
1005    }
1006}
1007
1008pub trait NonInteractiveService: Service {
1009    fn non_interactive_path() -> Path {
1010        Path::new([&Self::ID.to_string().to_kebab_case(), "non-interactive"])
1011    }
1012}
1013
1014#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1015#[serde(tag = "ticketStatus")]
1016pub enum TicketStatus<T> {
1017    Unfinished,
1018    Error(ErrorKind),
1019    Success(T),
1020}
1021
1022enum_with_display! {
1023    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1024    #[serde(tag = "error")]
1025    pub enum ErrorKind {
1026        UnexpectedError,
1027        Canceled,
1028        InvalidCredentials,
1029        ServiceBlocked,
1030        Unauthorized,
1031        AccessExceeded,
1032        PeriodOutOfBounds,
1033        UnsupportedProduct,
1034        PaymentFailed,
1035        UnexpectedValue,
1036        TicketError,
1037        ProviderError,
1038        NotFound,
1039    }
1040}
1041
1042#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1043#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1044#[serde(rename_all = "camelCase")]
1045pub struct Credentials {
1046    pub connection_id: ConnectionId,
1047    #[serde(skip_serializing_if = "Option::is_none")]
1048    #[cfg_attr(feature = "uniffi", uniffi(default))]
1049    pub user_id: Option<String>,
1050    #[serde(skip_serializing_if = "Option::is_none")]
1051    #[cfg_attr(feature = "uniffi", uniffi(default))]
1052    pub password: Option<String>,
1053    #[serde(skip_serializing_if = "Option::is_none")]
1054    #[cfg_attr(feature = "uniffi", uniffi(default))]
1055    pub connection_data: Option<ConnectionData>,
1056}
1057
1058#[derive(Serialize, Deserialize)]
1059#[serde(rename_all = "camelCase")]
1060pub struct ResponseData<S> {
1061    #[serde(bound = "S:")]
1062    pub context: InputContext<S>,
1063    pub response: String,
1064}
1065
1066impl<S> PartialEq for ResponseData<S> {
1067    fn eq(&self, other: &Self) -> bool {
1068        let Self { context, response } = self;
1069        context == &other.context && response == &other.response
1070    }
1071}
1072
1073impl<S> Eq for ResponseData<S> {}
1074
1075impl<S> Clone for ResponseData<S> {
1076    fn clone(&self) -> Self {
1077        Self {
1078            context: self.context.clone(),
1079            response: self.response.clone(),
1080        }
1081    }
1082}
1083
1084impl<S> fmt::Debug for ResponseData<S> {
1085    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1086        f.debug_struct("ResponseData")
1087            .field("context", &self.context)
1088            .field("response", &self.response)
1089            .finish()
1090    }
1091}
1092
1093#[derive(Serialize, Deserialize)]
1094#[serde(rename_all = "camelCase")]
1095pub struct ConfirmationData<S> {
1096    #[serde(bound = "S:")]
1097    pub context: ConfirmationContext<S>,
1098}
1099
1100impl<S> PartialEq for ConfirmationData<S> {
1101    fn eq(&self, other: &Self) -> bool {
1102        let Self { context } = self;
1103        context == &other.context
1104    }
1105}
1106
1107impl<S> Eq for ConfirmationData<S> {}
1108
1109impl<S> Clone for ConfirmationData<S> {
1110    fn clone(&self) -> Self {
1111        Self {
1112            context: self.context.clone(),
1113        }
1114    }
1115}
1116
1117impl<S> fmt::Debug for ConfirmationData<S> {
1118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1119        f.debug_struct("ConfirmationData")
1120            .field("context", &self.context)
1121            .finish()
1122    }
1123}
1124
1125#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1126#[serde(rename_all_fields = "camelCase")]
1127#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1128pub enum Error {
1129    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1130    UnexpectedError {
1131        #[serde(skip_serializing_if = "Option::is_none")]
1132        user_message: Option<String>,
1133    },
1134    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1135    Canceled {},
1136    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1137    InvalidCredentials {
1138        #[serde(skip_serializing_if = "Option::is_none")]
1139        user_message: Option<String>,
1140    },
1141    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1142    ServiceBlocked {
1143        #[serde(skip_serializing_if = "Option::is_none")]
1144        user_message: Option<String>,
1145        #[serde(skip_serializing_if = "Option::is_none")]
1146        code: Option<ServiceBlockedCode>,
1147    },
1148    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1149    Unauthorized {
1150        #[serde(skip_serializing_if = "Option::is_none")]
1151        user_message: Option<String>,
1152    },
1153    /// The bank rejected the request for exceeding the consent's permitted access
1154    /// frequency.
1155    ///
1156    /// This most commonly arises on the non-interactive refresh service, where a
1157    /// caller exceeds the bank's limit on requests made without a user in session.
1158    /// Provide a [`UserInSession`] to lift that limit.
1159    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1160    AccessExceeded {
1161        #[serde(skip_serializing_if = "Option::is_none")]
1162        user_message: Option<String>,
1163    },
1164    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1165    PeriodOutOfBounds {
1166        #[serde(skip_serializing_if = "Option::is_none")]
1167        user_message: Option<String>,
1168    },
1169    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1170    UnsupportedProduct {
1171        #[serde(skip_serializing_if = "Option::is_none")]
1172        reason: Option<UnsupportedProductReason>,
1173        #[serde(skip_serializing_if = "Option::is_none")]
1174        user_message: Option<String>,
1175    },
1176    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1177    PaymentFailed {
1178        #[serde(skip_serializing_if = "Option::is_none")]
1179        code: Option<PaymentErrorCode>,
1180        #[serde(skip_serializing_if = "Option::is_none")]
1181        user_message: Option<String>,
1182    },
1183    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1184    UnexpectedValue { error: String },
1185    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1186    TicketError {
1187        error: String,
1188        code: TicketErrorCode,
1189    },
1190    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1191    ProviderError {
1192        #[serde(skip_serializing_if = "Option::is_none")]
1193        code: Option<ProviderErrorCode>,
1194        #[serde(skip_serializing_if = "Option::is_none")]
1195        user_message: Option<String>,
1196    },
1197    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1198    InterruptError {},
1199}
1200
1201#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1202#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1203#[cfg_attr(not(feature = "server"), non_exhaustive)]
1204pub enum TicketErrorCode {
1205    /// Missing "yaxi-ticket" header
1206    Missing,
1207    /// Invalid ticket
1208    Invalid,
1209    /// Ticket token lacks "kid"
1210    MissingKey,
1211    /// Unknown key
1212    UnknownKey,
1213    /// Ticket does not match service
1214    Mismatch,
1215    /// Ticket is expired
1216    Expired,
1217    /// Ticket lifetime is too long
1218    InvalidLifetime,
1219    /// Expired key
1220    ExpiredKey,
1221    /// Environment mismatch between key and routex
1222    KeyEnvironmentMismatch,
1223}
1224
1225#[derive(Serialize, Deserialize, Clone, Debug)]
1226#[cfg_attr(not(feature = "server"), non_exhaustive)]
1227pub enum Filter<F> {
1228    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1229    Eq(F, Value),
1230    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1231    NotEq(F, Value),
1232    #[cfg(feature = "server")]
1233    Contains(F, Value),
1234    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1235    And(Box<Self>, Box<Self>),
1236    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1237    Or(Box<Self>, Box<Self>),
1238    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1239    Supports(SupportedService),
1240}
1241
1242#[derive(Serialize, Deserialize, Clone, Debug)]
1243#[cfg_attr(not(feature = "server"), non_exhaustive)]
1244#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1245pub enum SupportedService {
1246    CollectPayment,
1247}
1248
1249impl<F> Filter<F> {
1250    #[must_use]
1251    /// Add an additional filter.
1252    pub fn and(self, other: Filter<F>) -> Filter<F> {
1253        Filter::And(Box::new(self), Box::new(other))
1254    }
1255
1256    #[must_use]
1257    /// Add an alternative filter.
1258    pub fn or(self, other: Filter<F>) -> Filter<F> {
1259        Filter::Or(Box::new(self), Box::new(other))
1260    }
1261}
1262
1263pub struct Field<F, T> {
1264    field: F,
1265    phantom: PhantomData<T>,
1266}
1267
1268impl<F, T> Field<F, T>
1269where
1270    F: Copy,
1271    T: Serialize,
1272{
1273    /// Filter for equality with a given value.
1274    ///
1275    /// # Panics
1276    ///
1277    /// Panics if [`serde_json::to_value`] for the value fails.
1278    pub fn eq(&self, value: T) -> Filter<F> {
1279        Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1280    }
1281
1282    /// Filter for inequality with a given value.
1283    ///
1284    /// # Panics
1285    ///
1286    /// Panics if [`serde_json::to_value`] for the value fails.
1287    pub fn not_eq(&self, value: T) -> Filter<F> {
1288        Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1289    }
1290}
1291
1292pub trait GetValue {
1293    type Model;
1294
1295    fn get(&self, model: &Self::Model) -> Value;
1296}
1297
1298macro_rules! fields {
1299    {
1300        $model:ty, $enum_name:ident
1301        $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1302        $(
1303            server:
1304            $(($server_variant_name:ident, $server_field_name:ident))+
1305        )?
1306    } => {
1307        #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1308        #[non_exhaustive]
1309        #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1310        pub enum $enum_name {
1311            $($variant_name,)+
1312            $($(
1313                #[cfg(feature = "server")]
1314                $server_variant_name,
1315            )+)?
1316        }
1317
1318        impl $enum_name {
1319            $(
1320                pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1321                    field: $enum_name::$variant_name,
1322                    phantom: std::marker::PhantomData,
1323                };
1324            )+
1325        }
1326
1327        impl crate::GetValue for $enum_name {
1328            type Model = $model;
1329
1330            fn get(&self, model: &$model) -> serde_json::Value {
1331                match self {
1332                    $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1333                    $($(
1334                        #[cfg(feature = "server")]
1335                        $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1336                    )+)?
1337                }.expect("Serialization should work")
1338            }
1339        }
1340    }
1341}
1342
1343#[derive(Serialize, Deserialize)]
1344#[serde(rename_all = "camelCase")]
1345pub struct ServiceRequest<S: Service> {
1346    pub credentials: Credentials,
1347    #[serde(skip_serializing_if = "Option::is_none")]
1348    pub session: Option<Session>,
1349    #[serde(default, skip_serializing_if = "is_false")]
1350    pub recurring_consents: bool,
1351    #[serde(flatten, bound = "S:")]
1352    pub data: <S as Service>::RequestData,
1353}
1354
1355impl<S: Service> Clone for ServiceRequest<S> {
1356    fn clone(&self) -> Self {
1357        Self {
1358            credentials: self.credentials.clone(),
1359            session: self.session.clone(),
1360            recurring_consents: self.recurring_consents,
1361            data: self.data.clone(),
1362        }
1363    }
1364}
1365
1366#[derive(Serialize, Deserialize)]
1367#[serde(rename_all = "camelCase")]
1368pub struct NonInteractiveRequest<S: Service> {
1369    pub connection_data: ConnectionData,
1370    #[serde(skip_serializing_if = "Option::is_none")]
1371    pub session: Option<Session>,
1372    #[serde(default, skip_serializing_if = "Option::is_none")]
1373    pub user_in_session: Option<UserInSession>,
1374    #[serde(flatten, bound = "S:")]
1375    pub data: <S as Service>::RequestData,
1376}
1377
1378#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1379#[serde(rename_all = "camelCase")]
1380#[non_exhaustive]
1381#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1382pub struct Account {
1383    /// ISO 20022 IBAN2007Identifier.
1384    #[serde(skip_serializing_if = "Option::is_none")]
1385    #[cfg_attr(feature = "uniffi", uniffi(default))]
1386    pub iban: Option<String>,
1387
1388    /// Account number that is not an IBAN, e.g. ISO 20022 BBANIdentifier or primary account number (PAN) of a card account.
1389    #[serde(skip_serializing_if = "Option::is_none")]
1390    #[cfg_attr(feature = "uniffi", uniffi(default))]
1391    pub number: Option<String>,
1392
1393    /// ISO 20022 BICFIIdentifier.
1394    #[serde(skip_serializing_if = "Option::is_none")]
1395    #[cfg_attr(feature = "uniffi", uniffi(default))]
1396    pub bic: Option<String>,
1397
1398    /// National bank code.
1399    #[serde(skip_serializing_if = "Option::is_none")]
1400    #[cfg_attr(feature = "uniffi", uniffi(default))]
1401    pub bank_code: Option<String>,
1402
1403    /// ISO 4217 Alpha 3 currency code.
1404    #[serde(skip_serializing_if = "Option::is_none")]
1405    #[cfg_attr(feature = "uniffi", uniffi(default))]
1406    pub currency: Option<String>,
1407
1408    /// Name of account, assigned by ASPSP.
1409    #[serde(skip_serializing_if = "Option::is_none")]
1410    #[cfg_attr(feature = "uniffi", uniffi(default))]
1411    pub name: Option<String>,
1412
1413    /// Display name of account, assigned by PSU.
1414    #[serde(skip_serializing_if = "Option::is_none")]
1415    #[cfg_attr(feature = "uniffi", uniffi(default))]
1416    pub display_name: Option<String>,
1417
1418    /// Legal account owner.
1419    #[serde(skip_serializing_if = "Option::is_none")]
1420    #[cfg_attr(feature = "uniffi", uniffi(default))]
1421    pub owner_name: Option<String>,
1422
1423    /// Product name.
1424    #[serde(skip_serializing_if = "Option::is_none")]
1425    #[cfg_attr(feature = "uniffi", uniffi(default))]
1426    pub product_name: Option<String>,
1427
1428    /// Account status.
1429    #[serde(skip_serializing_if = "Option::is_none")]
1430    #[cfg_attr(feature = "uniffi", uniffi(default))]
1431    pub status: Option<AccountStatus>,
1432
1433    /// Account type.
1434    #[serde(rename = "type")]
1435    #[serde(skip_serializing_if = "Option::is_none")]
1436    #[cfg_attr(feature = "uniffi", uniffi(default))]
1437    pub type_: Option<AccountType>,
1438}
1439
1440pub mod accounts {
1441    use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1442    use serde::{Deserialize, Serialize};
1443
1444    #[derive(Clone, Debug)]
1445    pub struct Service {}
1446
1447    impl super::Service for Service {
1448        const ID: ServiceId = ServiceId::Accounts;
1449
1450        type TicketData = ();
1451
1452        type RequestData = RequestData;
1453
1454        type Output = Vec<Account>;
1455    }
1456
1457    impl super::NonInteractiveService for Service {}
1458
1459    fields! {
1460        routex_models::Account, AccountField
1461        (IBAN, Iban, iban, Option<String>)
1462        (NUMBER, Number, number, Option<String>)
1463        (BIC, Bic, bic, Option<String>)
1464        (BANK_CODE, BankCode, bank_code, Option<String>)
1465        (CURRENCY, Currency, currency, String)
1466        (NAME, Name, name, Option<String>)
1467        (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1468        (OWNER_NAME, OwnerName, owner_name, Option<String>)
1469        (PRODUCT_NAME, ProductName, product_name, Option<String>)
1470        (STATUS, Status, status, Option<AccountStatus>)
1471        (TYPE, Type, type_, Option<AccountType>)
1472        server:
1473        (Capabilities, capabilities)
1474    }
1475
1476    impl Account {
1477        #[must_use]
1478        /// Filter for possible support of YAXI Open Banking services.
1479        pub fn supports(service: SupportedService) -> Filter<AccountField> {
1480            Filter::Supports(service)
1481        }
1482    }
1483
1484    #[derive(Serialize, Deserialize, Clone)]
1485    #[serde(rename_all = "camelCase")]
1486    pub struct RequestData {
1487        pub fields: Vec<AccountField>,
1488        #[serde(skip_serializing_if = "Option::is_none")]
1489        pub filter: Option<Filter<AccountField>>,
1490    }
1491}
1492
1493#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1494#[serde(rename_all = "camelCase")]
1495#[serde(rename_all_fields = "camelCase")]
1496#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1497pub enum AccountIdentifier {
1498    /// ISO 20022 IBAN2007Identifier.
1499    Iban(String),
1500
1501    /// Account number that is not an IBAN, e.g. ISO 20022 BBANIdentifier.
1502    Number(String),
1503}
1504
1505#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1506#[serde(rename_all = "camelCase")]
1507#[serde(rename_all_fields = "camelCase")]
1508#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1509pub enum CreditorAccountIdentifier {
1510    /// ISO 20022 IBAN2007Identifier.
1511    Iban(String),
1512}
1513
1514#[serde_with::serde_as]
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 DebtorAccountIdentifier {
1520    /// ISO 20022 IBAN2007Identifier.
1521    #[serde(alias = "Iban")]
1522    Iban(String),
1523    #[cfg(feature = "server")]
1524    EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
1525}
1526
1527#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1528#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1529#[serde(rename_all = "camelCase")]
1530pub struct AccountReference {
1531    #[serde(flatten)]
1532    pub id: AccountIdentifier,
1533    #[serde(skip_serializing_if = "Option::is_none")]
1534    #[cfg_attr(feature = "uniffi", uniffi(default))]
1535    pub currency: Option<String>,
1536}
1537
1538#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1539#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1540#[serde(rename_all = "camelCase")]
1541pub struct DebtorAccountReference {
1542    #[serde(flatten)]
1543    pub id: DebtorAccountIdentifier,
1544    #[serde(skip_serializing_if = "Option::is_none")]
1545    #[cfg_attr(feature = "uniffi", uniffi(default))]
1546    pub currency: Option<String>,
1547}
1548
1549pub mod balances {
1550    pub use rust_decimal::Decimal;
1551    use serde::{Deserialize, Serialize};
1552
1553    use crate::{AccountReference, DateTime, ServiceId};
1554
1555    #[derive(Clone, Debug)]
1556    pub struct Service {}
1557
1558    impl super::Service for Service {
1559        const ID: ServiceId = ServiceId::Balances;
1560
1561        type TicketData = ();
1562
1563        type RequestData = RequestData;
1564
1565        type Output = Balances;
1566    }
1567
1568    impl super::NonInteractiveService for Service {}
1569
1570    #[derive(Serialize, Deserialize, Clone)]
1571    #[serde(rename_all = "camelCase")]
1572    pub struct RequestData {
1573        pub accounts: Vec<AccountReference>,
1574    }
1575
1576    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1577    #[serde(rename_all = "camelCase")]
1578    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1579    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1580    pub struct Balances {
1581        /// List of balances for accounts.
1582        pub balances: Vec<AccountBalances>,
1583
1584        /// Accounts that were requested but not found.
1585        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1586        #[cfg_attr(feature = "uniffi", uniffi(default))]
1587        pub missing_accounts: Vec<AccountReference>,
1588    }
1589
1590    #[allow(clippy::module_name_repetitions)]
1591    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1592    #[serde(rename_all = "camelCase")]
1593    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1594    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1595    pub struct AccountBalances {
1596        pub account: AccountReference,
1597
1598        /// List of balances (of different types) for the account.
1599        pub balances: Vec<Balance>,
1600    }
1601
1602    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1603    #[serde(rename_all = "camelCase")]
1604    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1605    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1606    pub struct Balance {
1607        pub amount: Decimal,
1608        pub currency: String,
1609        pub balance_type: BalanceType,
1610        #[serde(default, skip_serializing_if = "Option::is_none")]
1611        #[cfg_attr(feature = "uniffi", uniffi(default))]
1612        pub credit_limit_included: Option<bool>,
1613        #[serde(default, skip_serializing_if = "Option::is_none")]
1614        #[cfg_attr(feature = "uniffi", uniffi(default))]
1615        pub date_time: Option<DateTime>,
1616    }
1617
1618    #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1619    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1620    pub enum BalanceType {
1621        /// Balance from booked transactions.
1622        Booked,
1623
1624        /// Available balance from booked transactions.
1625        ///
1626        /// Depending on the bank, a credit line may or may not be included
1627        /// (see [`Balance::credit_limit_included`]). For the most current
1628        /// balance without credit, prefer [`Booked`](Self::Booked) and judge
1629        /// recency by [`Balance::date_time`].
1630        Available,
1631
1632        /// Expected balance from booked and pending transactions.
1633        Expected,
1634    }
1635}
1636
1637pub mod transactions {
1638    use chrono::NaiveDate;
1639    use isocountry::CountryCode;
1640    pub use routex_models::{Amount, Fee, TransactionStatus};
1641    use rust_decimal::Decimal;
1642    use serde::{Deserialize, Serialize};
1643    use url::Url;
1644
1645    use crate::{AccountReference, ServiceId};
1646
1647    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1648    #[serde(rename_all = "camelCase")]
1649    pub struct TicketData {
1650        pub account: AccountReference,
1651        pub range: Range,
1652        pub webhook: Option<Url>,
1653    }
1654
1655    #[derive(Clone, Debug)]
1656    pub struct Service {}
1657
1658    impl super::Service for Service {
1659        const ID: ServiceId = ServiceId::Transactions;
1660
1661        type TicketData = TicketData;
1662
1663        type RequestData = RequestData;
1664
1665        type Output = Option<Vec<Transaction>>;
1666    }
1667
1668    impl super::NonInteractiveService for Service {}
1669
1670    #[derive(Serialize, Deserialize, Clone)]
1671    #[serde(rename_all = "camelCase")]
1672    pub struct RequestData {}
1673
1674    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1675    #[serde(rename_all = "camelCase")]
1676    #[serde(rename_all_fields = "camelCase")]
1677    pub enum Range {
1678        Reference(String),
1679        #[serde(untagged)]
1680        Period {
1681            /// Earliest date (inclusive)
1682            from: NaiveDate,
1683            /// Latest date (inclusive)
1684            to: Option<NaiveDate>,
1685        },
1686    }
1687
1688    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1689    #[serde(rename_all = "camelCase")]
1690    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1691    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1692    pub struct Transaction {
1693        /// Identifier used for delta requests.
1694        #[serde(skip_serializing_if = "Option::is_none")]
1695        #[cfg_attr(feature = "uniffi", uniffi(default))]
1696        pub entry_reference: Option<String>,
1697
1698        #[serde(skip_serializing_if = "Option::is_none")]
1699        #[cfg_attr(feature = "uniffi", uniffi(default))]
1700        pub batch: Option<BatchData>,
1701
1702        /// Booking date (ASPSP's books).
1703        #[serde(skip_serializing_if = "Option::is_none")]
1704        #[cfg_attr(feature = "uniffi", uniffi(default))]
1705        pub booking_date: Option<NaiveDate>,
1706
1707        /// Value date. Expected / requested value date in case of pending entries.
1708        #[serde(skip_serializing_if = "Option::is_none")]
1709        #[cfg_attr(feature = "uniffi", uniffi(default))]
1710        pub value_date: Option<NaiveDate>,
1711
1712        /// Date of the actual transaction, e.g. a card payment.
1713        #[serde(skip_serializing_if = "Option::is_none")]
1714        #[cfg_attr(feature = "uniffi", uniffi(default))]
1715        pub transaction_date: Option<NaiveDate>,
1716
1717        /// Transaction status.
1718        pub status: TransactionStatus,
1719
1720        /// Unique reference assigned by the account servicer.
1721        #[serde(skip_serializing_if = "Option::is_none")]
1722        #[cfg_attr(feature = "uniffi", uniffi(default))]
1723        pub account_servicer_reference: Option<String>,
1724
1725        /// Unique identifier assigned by the sending party.
1726        #[serde(skip_serializing_if = "Option::is_none")]
1727        #[cfg_attr(feature = "uniffi", uniffi(default))]
1728        pub payment_id: Option<String>,
1729
1730        /// Unique identifier assigned by the first instructing agent.
1731        #[serde(skip_serializing_if = "Option::is_none")]
1732        #[cfg_attr(feature = "uniffi", uniffi(default))]
1733        pub transaction_id: Option<String>,
1734
1735        /// Unique end-to-end identifier assigned by the initiating party.
1736        #[serde(skip_serializing_if = "Option::is_none")]
1737        #[cfg_attr(feature = "uniffi", uniffi(default))]
1738        pub end_to_end_id: Option<String>,
1739
1740        /// Mandate identifier.
1741        #[serde(skip_serializing_if = "Option::is_none")]
1742        #[cfg_attr(feature = "uniffi", uniffi(default))]
1743        pub mandate_id: Option<String>,
1744
1745        /// SEPA creditor identifier.
1746        #[serde(skip_serializing_if = "Option::is_none")]
1747        #[cfg_attr(feature = "uniffi", uniffi(default))]
1748        pub creditor_id: Option<String>,
1749
1750        /// Transaction amount as billed to the account.
1751        pub amount: Amount,
1752
1753        /// Indicator for reversals.
1754        #[serde(default, skip_serializing_if = "super::is_false")]
1755        #[cfg_attr(feature = "uniffi", uniffi(default))]
1756        pub reversal: bool,
1757
1758        /// Original amount of the transaction.
1759        #[serde(skip_serializing_if = "Option::is_none")]
1760        #[cfg_attr(feature = "uniffi", uniffi(default))]
1761        pub original_amount: Option<Amount>,
1762
1763        /// Exchange rates.
1764        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1765        #[cfg_attr(feature = "uniffi", uniffi(default))]
1766        pub exchanges: Vec<ExchangeRate>,
1767
1768        /// Any fees related to the transaction.
1769        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1770        #[cfg_attr(feature = "uniffi", uniffi(default))]
1771        pub fees: Vec<Fee>,
1772
1773        /// Creditor data. In case of reversals this refers to the initial transaction.
1774        #[serde(skip_serializing_if = "Option::is_none")]
1775        #[cfg_attr(feature = "uniffi", uniffi(default))]
1776        pub creditor: Option<Party>,
1777
1778        /// Debtor data. In case of reversals this refers to the initial transaction.
1779        #[serde(skip_serializing_if = "Option::is_none")]
1780        #[cfg_attr(feature = "uniffi", uniffi(default))]
1781        pub debtor: Option<Party>,
1782
1783        /// Remittance (purpose).
1784        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1785        #[cfg_attr(feature = "uniffi", uniffi(default))]
1786        pub remittance_information: Vec<String>,
1787
1788        /// ISO 20022 ExternalPurpose1Code.
1789        #[serde(skip_serializing_if = "Option::is_none")]
1790        #[cfg_attr(feature = "uniffi", uniffi(default))]
1791        pub purpose_code: Option<String>,
1792
1793        /// Bank Transaction Codes.
1794        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1795        #[cfg_attr(feature = "uniffi", uniffi(default))]
1796        pub bank_transaction_codes: Vec<BankTransactionCode>,
1797
1798        /// Additional information attached to the transaction.
1799        ///
1800        /// 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.
1801        #[serde(skip_serializing_if = "Option::is_none")]
1802        #[cfg_attr(feature = "uniffi", uniffi(default))]
1803        pub additional_information: Option<String>,
1804    }
1805
1806    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1807    #[serde(rename_all = "camelCase")]
1808    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1809    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1810    pub struct BatchData {
1811        /// Number of transactions in the batch, if known.
1812        #[serde(skip_serializing_if = "Option::is_none")]
1813        #[cfg_attr(feature = "uniffi", uniffi(default))]
1814        pub number_of_transactions: Option<u32>,
1815
1816        /// Details of transactions in the batch.
1817        ///
1818        /// Note that this does not necessarily match a given number of transactions.
1819        /// It could be e.g. empty as no details are given or a single entry with common details on all transactions in the batch.
1820        pub transactions: Vec<BatchTransactionDetails>,
1821    }
1822
1823    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1824    #[serde(rename_all = "camelCase")]
1825    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1826    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1827    pub struct BatchTransactionDetails {
1828        /// Unique reference assigned by the account servicer.
1829        #[serde(skip_serializing_if = "Option::is_none")]
1830        #[cfg_attr(feature = "uniffi", uniffi(default))]
1831        pub account_servicer_reference: Option<String>,
1832
1833        /// Unique identifier assigned by the sending party.
1834        #[serde(skip_serializing_if = "Option::is_none")]
1835        #[cfg_attr(feature = "uniffi", uniffi(default))]
1836        pub payment_id: Option<String>,
1837
1838        /// Unique identifier assigned by the first instructing agent.
1839        #[serde(skip_serializing_if = "Option::is_none")]
1840        #[cfg_attr(feature = "uniffi", uniffi(default))]
1841        pub transaction_id: Option<String>,
1842
1843        /// Unique end-to-end identifier assigned by the initiating party.
1844        #[serde(skip_serializing_if = "Option::is_none")]
1845        #[cfg_attr(feature = "uniffi", uniffi(default))]
1846        pub end_to_end_id: Option<String>,
1847
1848        /// Mandate identifier.
1849        #[serde(skip_serializing_if = "Option::is_none")]
1850        #[cfg_attr(feature = "uniffi", uniffi(default))]
1851        pub mandate_id: Option<String>,
1852
1853        /// SEPA creditor identifier.
1854        #[serde(skip_serializing_if = "Option::is_none")]
1855        #[cfg_attr(feature = "uniffi", uniffi(default))]
1856        pub creditor_id: Option<String>,
1857
1858        /// Transaction amount as billed to the account.
1859        #[serde(skip_serializing_if = "Option::is_none")]
1860        #[cfg_attr(feature = "uniffi", uniffi(default))]
1861        pub amount: Option<Amount>,
1862
1863        /// Indicator for reversals.
1864        #[serde(default, skip_serializing_if = "super::is_false")]
1865        #[cfg_attr(feature = "uniffi", uniffi(default))]
1866        pub reversal: bool,
1867
1868        /// Original amount of the transaction.
1869        #[serde(skip_serializing_if = "Option::is_none")]
1870        #[cfg_attr(feature = "uniffi", uniffi(default))]
1871        pub original_amount: Option<Amount>,
1872
1873        /// Exchange rates.
1874        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1875        #[cfg_attr(feature = "uniffi", uniffi(default))]
1876        pub exchanges: Vec<ExchangeRate>,
1877
1878        /// Any fees related to the transaction.
1879        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1880        #[cfg_attr(feature = "uniffi", uniffi(default))]
1881        pub fees: Vec<Fee>,
1882
1883        /// Creditor data. In case of reversals this refers to the initial transaction.
1884        #[serde(skip_serializing_if = "Option::is_none")]
1885        #[cfg_attr(feature = "uniffi", uniffi(default))]
1886        pub creditor: Option<Party>,
1887
1888        /// Debtor data. In case of reversals this refers to the initial transaction.
1889        #[serde(skip_serializing_if = "Option::is_none")]
1890        #[cfg_attr(feature = "uniffi", uniffi(default))]
1891        pub debtor: Option<Party>,
1892
1893        /// Remittance (purpose).
1894        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1895        #[cfg_attr(feature = "uniffi", uniffi(default))]
1896        pub remittance_information: Vec<String>,
1897
1898        /// ISO 20022 ExternalPurpose1Code.
1899        #[serde(skip_serializing_if = "Option::is_none")]
1900        #[cfg_attr(feature = "uniffi", uniffi(default))]
1901        pub purpose_code: Option<String>,
1902
1903        /// Bank Transaction Codes.
1904        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1905        #[cfg_attr(feature = "uniffi", uniffi(default))]
1906        pub bank_transaction_codes: Vec<BankTransactionCode>,
1907
1908        /// Additional information attached to the transaction.
1909        ///
1910        /// 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.
1911        #[serde(skip_serializing_if = "Option::is_none")]
1912        #[cfg_attr(feature = "uniffi", uniffi(default))]
1913        pub additional_information: Option<String>,
1914    }
1915
1916    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1917    #[serde(rename_all = "camelCase")]
1918    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1919    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1920    pub struct ExchangeRate {
1921        /// ISO 4217 Alpha 3 currency code of the source currency that gets converted.
1922        pub source_currency: String,
1923        /// ISO 4217 Alpha 3 currency code of the target currency that
1924        /// the source currency gets converted into.
1925        #[serde(skip_serializing_if = "Option::is_none")]
1926        #[cfg_attr(feature = "uniffi", uniffi(default))]
1927        pub target_currency: Option<String>,
1928        /// ISO 4217 Alpha 3 currency code of the unit currency for the exchange rate.
1929        #[serde(skip_serializing_if = "Option::is_none")]
1930        #[cfg_attr(feature = "uniffi", uniffi(default))]
1931        pub unit_currency: Option<String>,
1932        pub exchange_rate: Decimal,
1933    }
1934
1935    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1936    #[serde(rename_all = "camelCase")]
1937    #[cfg_attr(not(feature = "server"), non_exhaustive)]
1938    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1939    pub struct Party {
1940        /// Creditor / debtor name.
1941        #[serde(skip_serializing_if = "Option::is_none")]
1942        #[cfg_attr(feature = "uniffi", uniffi(default))]
1943        pub name: Option<String>,
1944
1945        /// ISO 20022 IBAN2007Identifier for the creditor / debtor account.
1946        #[serde(skip_serializing_if = "Option::is_none")]
1947        #[cfg_attr(feature = "uniffi", uniffi(default))]
1948        pub iban: Option<String>,
1949
1950        /// ISO 20022 BICFIIdentifier for the creditor / debtor agent.
1951        #[serde(skip_serializing_if = "Option::is_none")]
1952        #[cfg_attr(feature = "uniffi", uniffi(default))]
1953        pub bic: Option<String>,
1954
1955        /// Ultimate creditor / debtor (name).
1956        #[serde(skip_serializing_if = "Option::is_none")]
1957        #[cfg_attr(feature = "uniffi", uniffi(default))]
1958        pub ultimate: Option<String>,
1959    }
1960
1961    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1962    #[serde(rename_all = "camelCase")]
1963    #[serde(rename_all_fields = "camelCase")]
1964    #[non_exhaustive]
1965    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1966    pub enum BankTransactionCode {
1967        /// ISO 20022 Bank Transaction Code.
1968        Iso {
1969            /// ISO 20022 ExternalBankTransactionDomain1Code.
1970            domain: String,
1971
1972            /// ISO 20022 ExternalBankTransactionFamily1Code.
1973            family: String,
1974
1975            /// ISO 20022 ExternalBankTransactionSubFamily1Code.
1976            sub_family: String,
1977        },
1978        /// SWIFT transaction code.
1979        Swift(String),
1980        /// BAI2 transaction code.
1981        Bai(String),
1982        /// National transaction code, e.g. German GVC.
1983        National { code: String, country: CountryCode },
1984        /// Unspecified transaction codes, possibly with an issuer information.
1985        Other {
1986            code: String,
1987            #[serde(skip_serializing_if = "Option::is_none")]
1988            issuer: Option<String>,
1989        },
1990    }
1991}
1992
1993#[allow(clippy::trivially_copy_pass_by_ref)]
1994fn is_false(val: &bool) -> bool {
1995    !*val
1996}
1997
1998#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1999#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2000#[non_exhaustive]
2001pub enum PaymentStatus {
2002    /// The payment was received and is getting processed.
2003    ///
2004    /// Especially without realtime bookings this is often the final status reported by the ASPSP.
2005    Accepted,
2006
2007    /// The payment was partially accepted, i.e. only some of the transactions of a bulk payment or the payment needs further authorization.
2008    PartiallyAccepted,
2009
2010    /// Settlement on the debtor's account has been completed.
2011    CompletedDebtor,
2012
2013    /// Settlement on the creditor's account has been completed.
2014    CompletedCreditor,
2015}
2016
2017pub mod collect_payment {
2018    pub use routex_models::Amount;
2019    use serde::{Deserialize, Serialize};
2020    use serde_with::base64::Base64;
2021
2022    use super::{
2023        CreditorAccountIdentifier, DebtorAccountReference, Path, PaymentStatus, ServiceId,
2024    };
2025
2026    /// Query ticket status, possibly polling pending confirmations
2027    ///
2028    /// Method: POST<br>
2029    /// Response: [`Option<TicketStatus<SuccessStatusData>>`](super::TicketStatus)
2030    pub fn status_path(ticket_id: &str) -> Path {
2031        Path::new(["collect-payment", "status", ticket_id])
2032    }
2033
2034    #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
2035    #[serde(rename_all = "camelCase")]
2036    #[non_exhaustive]
2037    pub struct SuccessStatusData {
2038        #[serde(skip_serializing_if = "Option::is_none")]
2039        pub payment_status: Option<PaymentStatus>,
2040    }
2041
2042    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2043    #[serde(rename_all = "camelCase")]
2044    pub struct TicketData {
2045        pub amount: Amount,
2046        pub creditor_account: CreditorAccountIdentifier,
2047        pub creditor_name: String,
2048        pub remittance: String,
2049        #[serde(skip_serializing_if = "Option::is_none")]
2050        pub instant: Option<bool>,
2051        #[serde(skip_serializing_if = "Option::is_none")]
2052        pub fields: Option<Vec<Field>>,
2053    }
2054
2055    #[derive(Clone, Debug)]
2056    pub struct Service {}
2057
2058    impl super::Service for Service {
2059        const ID: ServiceId = ServiceId::CollectPayment;
2060
2061        type TicketData = TicketData;
2062
2063        type RequestData = RequestData;
2064
2065        type Output = PaymentInitiation;
2066    }
2067
2068    #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
2069    #[serde(rename_all = "camelCase")]
2070    #[non_exhaustive]
2071    #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2072    pub enum Field {
2073        DebtorIban,
2074        DebtorName,
2075        EncryptedDebtorIban,
2076        EncryptedDebtorName,
2077    }
2078
2079    #[derive(Serialize, Deserialize, Clone)]
2080    #[serde(rename_all = "camelCase")]
2081    pub struct RequestData {
2082        #[serde(skip_serializing_if = "Option::is_none")]
2083        pub account: Option<DebtorAccountReference>,
2084    }
2085
2086    #[serde_with::serde_as]
2087    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2088    #[serde(rename_all = "camelCase")]
2089    #[non_exhaustive]
2090    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2091    pub struct PaymentInitiation {
2092        #[serde(skip_serializing_if = "Option::is_none")]
2093        #[cfg_attr(feature = "uniffi", uniffi(default))]
2094        pub status: Option<PaymentStatus>,
2095        #[serde(skip_serializing_if = "Option::is_none")]
2096        #[cfg_attr(feature = "uniffi", uniffi(default))]
2097        pub debtor_name: Option<String>,
2098        #[serde(skip_serializing_if = "Option::is_none")]
2099        #[cfg_attr(feature = "uniffi", uniffi(default))]
2100        pub debtor_iban: Option<String>,
2101        #[serde(skip_serializing_if = "Option::is_none")]
2102        #[cfg_attr(feature = "uniffi", uniffi(default))]
2103        #[serde_as(as = "Option<Base64>")]
2104        pub encrypted_debtor_name: Option<Vec<u8>>,
2105        #[serde(skip_serializing_if = "Option::is_none")]
2106        #[cfg_attr(feature = "uniffi", uniffi(default))]
2107        #[serde_as(as = "Option<Base64>")]
2108        pub encrypted_debtor_iban: Option<Vec<u8>>,
2109    }
2110}
2111
2112pub mod transfer {
2113    pub use routex_models::{ChargeBearer, CreditorAddress, ISODateTimeOrDate, PaymentProduct};
2114    use serde::{Deserialize, Serialize};
2115
2116    use super::{
2117        Amount, CreditorAccountIdentifier, DebtorAccountReference, PaymentStatus, ServiceId,
2118    };
2119
2120    #[derive(Clone, Debug)]
2121    pub struct Service {}
2122
2123    impl super::Service for Service {
2124        const ID: ServiceId = ServiceId::Transfer;
2125
2126        type TicketData = ();
2127
2128        type RequestData = RequestData;
2129
2130        type Output = Transfer;
2131    }
2132
2133    #[derive(Serialize, Deserialize, Clone)]
2134    #[serde(rename_all = "camelCase")]
2135    pub struct RequestData {
2136        pub product: PaymentProduct,
2137        #[serde(skip_serializing_if = "Option::is_none")]
2138        pub debtor_account: Option<DebtorAccountReference>,
2139        #[serde(skip_serializing_if = "Option::is_none")]
2140        pub debtor_name: Option<String>,
2141        #[serde(skip_serializing_if = "Option::is_none")]
2142        pub requested_execution_date: Option<ISODateTimeOrDate>,
2143        pub details: Vec<TransferDetails>,
2144    }
2145
2146    #[derive(Serialize, Deserialize, Clone)]
2147    #[serde(rename_all = "camelCase")]
2148    #[non_exhaustive]
2149    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2150    pub struct TransferDetails {
2151        #[serde(skip_serializing_if = "Option::is_none")]
2152        #[cfg_attr(feature = "uniffi", uniffi(default))]
2153        pub end_to_end_identification: Option<String>,
2154        pub amount: Amount,
2155        pub creditor_account: CreditorAccountIdentifier,
2156        #[serde(skip_serializing_if = "Option::is_none")]
2157        #[cfg_attr(feature = "uniffi", uniffi(default))]
2158        pub creditor_agent_bic: Option<String>,
2159        pub creditor_name: String,
2160        #[serde(skip_serializing_if = "Option::is_none")]
2161        #[cfg_attr(feature = "uniffi", uniffi(default))]
2162        pub creditor_address: Option<CreditorAddress>,
2163        #[serde(skip_serializing_if = "Option::is_none")]
2164        #[cfg_attr(feature = "uniffi", uniffi(default))]
2165        pub remittance: Option<String>,
2166        #[serde(skip_serializing_if = "Option::is_none")]
2167        #[cfg_attr(feature = "uniffi", uniffi(default))]
2168        pub charge_bearer: Option<ChargeBearer>,
2169    }
2170
2171    impl TransferDetails {
2172        pub fn new(
2173            amount: Amount,
2174            creditor_account: CreditorAccountIdentifier,
2175            creditor_name: impl Into<String>,
2176        ) -> Self {
2177            Self {
2178                end_to_end_identification: None,
2179                amount,
2180                creditor_account,
2181                creditor_agent_bic: None,
2182                creditor_name: creditor_name.into(),
2183                creditor_address: None,
2184                remittance: None,
2185                charge_bearer: None,
2186            }
2187        }
2188    }
2189
2190    #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2191    #[serde(rename_all = "camelCase")]
2192    #[non_exhaustive]
2193    #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2194    pub struct Transfer {
2195        #[serde(skip_serializing_if = "Option::is_none")]
2196        #[cfg_attr(feature = "uniffi", uniffi(default))]
2197        pub status: Option<PaymentStatus>,
2198    }
2199}
2200
2201#[cfg(test)]
2202mod tests {
2203    use std::fmt::Debug;
2204
2205    use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
2206    use serde::{Deserialize, Serialize};
2207    use serde_json::json;
2208    use uuid::Uuid;
2209
2210    use crate::{
2211        Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
2212        InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
2213    };
2214
2215    #[test]
2216    fn read_response() {
2217        let serialized = serde_json::to_string(
2218            &encode(
2219                &Header::new(Algorithm::HS256),
2220                &Claims {
2221                    data: "data",
2222                    exp: 2_540_808_000,
2223                },
2224                &EncodingKey::from_secret(b"does_not_matter"),
2225            )
2226            .unwrap(),
2227        )
2228        .unwrap();
2229
2230        let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
2231
2232        assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
2233    }
2234
2235    struct Service;
2236
2237    impl crate::Service for Service {
2238        const ID: ServiceId = ServiceId::Accounts;
2239
2240        type TicketData = ();
2241
2242        type RequestData = ();
2243
2244        type Output = ();
2245    }
2246
2247    #[allow(
2248        dead_code,
2249        unconditional_recursion,
2250        clippy::extra_unused_type_parameters
2251    )]
2252    fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
2253        test_bounds::<InputContext<Service>>();
2254        test_bounds::<ConfirmationContext<Service>>();
2255        test_bounds::<DialogInput<Service>>();
2256        test_bounds::<Dialog<Service>>();
2257        test_bounds::<Redirect<Service>>();
2258        test_bounds::<RedirectHandle<Service>>();
2259        test_bounds::<OBResult<()>>();
2260        test_bounds::<Session>();
2261
2262        test_bounds::<Authenticated<Ticket<Service>>>();
2263        test_bounds::<Ticket<Service>>();
2264        test_bounds::<ResponseData<Service>>();
2265        test_bounds::<ConfirmationData<Service>>();
2266    }
2267
2268    fn test_eq_<T: PartialEq + Debug>(o: &T) {
2269        assert!(o.eq(o));
2270    }
2271
2272    #[test]
2273    fn test_eq() {
2274        let ic = InputContext::<Service>::from(vec![]);
2275        let cc = ConfirmationContext::<Service>::from(vec![]);
2276
2277        test_eq_(&ic);
2278        test_eq_(&cc);
2279        test_eq_(&Redirect {
2280            url: "url:".parse().unwrap(),
2281            context: cc.clone(),
2282        });
2283        test_eq_(&RedirectHandle {
2284            handle: String::new(),
2285            context: cc.clone(),
2286        });
2287        test_eq_(&Ticket::<Service> {
2288            service: ServiceId::Accounts,
2289            id: Uuid::new_v4(),
2290            data: (),
2291        });
2292        test_eq_(&ResponseData {
2293            context: ic,
2294            response: String::new(),
2295        });
2296        test_eq_(&ConfirmationData { context: cc });
2297    }
2298
2299    #[test]
2300    fn no_ticket_data() {
2301        assert!(
2302            serde_json::from_value::<Ticket<Service>>(json!({
2303            "id": Uuid::new_v4(),
2304            "service": ServiceId::Accounts,
2305            }))
2306            .is_ok()
2307        );
2308    }
2309
2310    #[test]
2311    fn missing_ticket_data() {
2312        let err = serde_json::from_value::<Ticket<super::collect_payment::Service>>(json!({
2313            "id": Uuid::new_v4(),
2314            "service": ServiceId::CollectPayment,
2315        }))
2316        .unwrap_err();
2317
2318        assert_eq!(err.to_string(), "missing field `data`");
2319    }
2320
2321    #[test]
2322    fn user_in_session_wire() {
2323        use super::UserInSession;
2324
2325        let at = UserInSession::At("203.0.113.7".parse().unwrap());
2326        assert_eq!(
2327            serde_json::to_value(UserInSession::OnThisConnection).unwrap(),
2328            json!("connection")
2329        );
2330        assert_eq!(serde_json::to_value(&at).unwrap(), json!("203.0.113.7"));
2331
2332        assert_eq!(
2333            serde_json::from_value::<UserInSession>(json!("connection")).unwrap(),
2334            UserInSession::OnThisConnection,
2335        );
2336        assert_eq!(
2337            serde_json::from_value::<UserInSession>(json!("203.0.113.7")).unwrap(),
2338            at
2339        );
2340
2341        assert!(serde_json::from_value::<UserInSession>(json!("nonsense")).is_err());
2342    }
2343}