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