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