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