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