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#[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 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 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 #[serde_as(as = "Base64")]
147 pub public_key: [u8; 32],
148 }
149
150 #[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 #[serde_with::serde_as]
163 #[derive(Serialize, Deserialize, Clone, Debug)]
164 #[serde(rename_all = "camelCase")]
165 pub struct Response {
166 #[serde_as(as = "Base64")]
168 pub attestation_report: Vec<u8>,
169 pub vcek: String,
171 #[serde_as(as = "Base64")]
173 pub chacha_box: Vec<u8>,
174 pub system_version: PublishedVersionEntry,
177 }
178}
179
180pub mod traces {
181 use super::Path;
182
183 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 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 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 #[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 #[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 Types(Vec<ConnectionType>),
282 Countries(Vec<CountryCode>),
284 Name(String),
286 Bic(String),
288 BankCode(String),
290 Term(String),
292 #[cfg(feature = "server")]
293 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
294 }
295
296 #[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,
303 Sandboxes,
305 }
306
307 pub fn fetch_path(connection_id: &str) -> Path {
312 Path::new(["info", connection_id])
313 }
314
315 #[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 pub id: ConnectionId,
324
325 pub countries: Vec<CountryCode>,
327
328 pub display_name: String,
330
331 pub credentials: CredentialsModel,
333
334 #[serde(skip_serializing_if = "Option::is_none")]
336 #[cfg_attr(feature = "uniffi", uniffi(default))]
337 pub user_id: Option<String>,
338
339 #[serde(skip_serializing_if = "Option::is_none")]
341 #[cfg_attr(feature = "uniffi", uniffi(default))]
342 pub password: Option<String>,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
346 #[cfg_attr(feature = "uniffi", uniffi(default))]
347 pub advice: Option<String>,
348
349 pub logo_id: String,
351
352 #[allow(clippy::doc_markdown)]
353 #[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#[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 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 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 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 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 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 fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
566 where
567 T: for<'de> Deserialize<'de> + Clone;
568}
569
570#[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#[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#[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
719fn 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#[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
764fn 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#[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 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,
1159 Invalid,
1161 MissingKey,
1163 UnknownKey,
1165 Mismatch,
1167 Expired,
1169 InvalidLifetime,
1171 ExpiredKey,
1173 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 pub fn and(self, other: Filter<F>) -> Filter<F> {
1205 Filter::And(Box::new(self), Box::new(other))
1206 }
1207
1208 #[must_use]
1209 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 pub fn eq(&self, value: T) -> Filter<F> {
1231 Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1232 }
1233
1234 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 #[serde(skip_serializing_if = "Option::is_none")]
1335 #[cfg_attr(feature = "uniffi", uniffi(default))]
1336 pub iban: Option<String>,
1337
1338 #[serde(skip_serializing_if = "Option::is_none")]
1340 #[cfg_attr(feature = "uniffi", uniffi(default))]
1341 pub number: Option<String>,
1342
1343 #[serde(skip_serializing_if = "Option::is_none")]
1345 #[cfg_attr(feature = "uniffi", uniffi(default))]
1346 pub bic: Option<String>,
1347
1348 #[serde(skip_serializing_if = "Option::is_none")]
1350 #[cfg_attr(feature = "uniffi", uniffi(default))]
1351 pub bank_code: Option<String>,
1352
1353 #[serde(skip_serializing_if = "Option::is_none")]
1355 #[cfg_attr(feature = "uniffi", uniffi(default))]
1356 pub currency: Option<String>,
1357
1358 #[serde(skip_serializing_if = "Option::is_none")]
1360 #[cfg_attr(feature = "uniffi", uniffi(default))]
1361 pub name: Option<String>,
1362
1363 #[serde(skip_serializing_if = "Option::is_none")]
1365 #[cfg_attr(feature = "uniffi", uniffi(default))]
1366 pub display_name: Option<String>,
1367
1368 #[serde(skip_serializing_if = "Option::is_none")]
1370 #[cfg_attr(feature = "uniffi", uniffi(default))]
1371 pub owner_name: Option<String>,
1372
1373 #[serde(skip_serializing_if = "Option::is_none")]
1375 #[cfg_attr(feature = "uniffi", uniffi(default))]
1376 pub product_name: Option<String>,
1377
1378 #[serde(skip_serializing_if = "Option::is_none")]
1380 #[cfg_attr(feature = "uniffi", uniffi(default))]
1381 pub status: Option<AccountStatus>,
1382
1383 #[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 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 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 pub balances: Vec<AccountBalances>,
1497
1498 #[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 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 Booked,
1534
1535 Available,
1537
1538 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 from: NaiveDate,
1589 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 #[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 #[serde(skip_serializing_if = "Option::is_none")]
1610 #[cfg_attr(feature = "uniffi", uniffi(default))]
1611 pub booking_date: Option<NaiveDate>,
1612
1613 #[serde(skip_serializing_if = "Option::is_none")]
1615 #[cfg_attr(feature = "uniffi", uniffi(default))]
1616 pub value_date: Option<NaiveDate>,
1617
1618 #[serde(skip_serializing_if = "Option::is_none")]
1620 #[cfg_attr(feature = "uniffi", uniffi(default))]
1621 pub transaction_date: Option<NaiveDate>,
1622
1623 pub status: TransactionStatus,
1625
1626 #[serde(skip_serializing_if = "Option::is_none")]
1628 #[cfg_attr(feature = "uniffi", uniffi(default))]
1629 pub account_servicer_reference: Option<String>,
1630
1631 #[serde(skip_serializing_if = "Option::is_none")]
1633 #[cfg_attr(feature = "uniffi", uniffi(default))]
1634 pub payment_id: Option<String>,
1635
1636 #[serde(skip_serializing_if = "Option::is_none")]
1638 #[cfg_attr(feature = "uniffi", uniffi(default))]
1639 pub transaction_id: Option<String>,
1640
1641 #[serde(skip_serializing_if = "Option::is_none")]
1643 #[cfg_attr(feature = "uniffi", uniffi(default))]
1644 pub end_to_end_id: Option<String>,
1645
1646 #[serde(skip_serializing_if = "Option::is_none")]
1648 #[cfg_attr(feature = "uniffi", uniffi(default))]
1649 pub mandate_id: Option<String>,
1650
1651 #[serde(skip_serializing_if = "Option::is_none")]
1653 #[cfg_attr(feature = "uniffi", uniffi(default))]
1654 pub creditor_id: Option<String>,
1655
1656 pub amount: Amount,
1658
1659 #[serde(default, skip_serializing_if = "super::is_false")]
1661 #[cfg_attr(feature = "uniffi", uniffi(default))]
1662 pub reversal: bool,
1663
1664 #[serde(skip_serializing_if = "Option::is_none")]
1666 #[cfg_attr(feature = "uniffi", uniffi(default))]
1667 pub original_amount: Option<Amount>,
1668
1669 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1671 #[cfg_attr(feature = "uniffi", uniffi(default))]
1672 pub exchanges: Vec<ExchangeRate>,
1673
1674 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1676 #[cfg_attr(feature = "uniffi", uniffi(default))]
1677 pub fees: Vec<Fee>,
1678
1679 #[serde(skip_serializing_if = "Option::is_none")]
1681 #[cfg_attr(feature = "uniffi", uniffi(default))]
1682 pub creditor: Option<Party>,
1683
1684 #[serde(skip_serializing_if = "Option::is_none")]
1686 #[cfg_attr(feature = "uniffi", uniffi(default))]
1687 pub debtor: Option<Party>,
1688
1689 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1691 #[cfg_attr(feature = "uniffi", uniffi(default))]
1692 pub remittance_information: Vec<String>,
1693
1694 #[serde(skip_serializing_if = "Option::is_none")]
1696 #[cfg_attr(feature = "uniffi", uniffi(default))]
1697 pub purpose_code: Option<String>,
1698
1699 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1701 #[cfg_attr(feature = "uniffi", uniffi(default))]
1702 pub bank_transaction_codes: Vec<BankTransactionCode>,
1703
1704 #[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 #[serde(skip_serializing_if = "Option::is_none")]
1719 #[cfg_attr(feature = "uniffi", uniffi(default))]
1720 pub number_of_transactions: Option<u32>,
1721
1722 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 #[serde(skip_serializing_if = "Option::is_none")]
1736 #[cfg_attr(feature = "uniffi", uniffi(default))]
1737 pub account_servicer_reference: Option<String>,
1738
1739 #[serde(skip_serializing_if = "Option::is_none")]
1741 #[cfg_attr(feature = "uniffi", uniffi(default))]
1742 pub payment_id: Option<String>,
1743
1744 #[serde(skip_serializing_if = "Option::is_none")]
1746 #[cfg_attr(feature = "uniffi", uniffi(default))]
1747 pub transaction_id: Option<String>,
1748
1749 #[serde(skip_serializing_if = "Option::is_none")]
1751 #[cfg_attr(feature = "uniffi", uniffi(default))]
1752 pub end_to_end_id: Option<String>,
1753
1754 #[serde(skip_serializing_if = "Option::is_none")]
1756 #[cfg_attr(feature = "uniffi", uniffi(default))]
1757 pub mandate_id: Option<String>,
1758
1759 #[serde(skip_serializing_if = "Option::is_none")]
1761 #[cfg_attr(feature = "uniffi", uniffi(default))]
1762 pub creditor_id: Option<String>,
1763
1764 #[serde(skip_serializing_if = "Option::is_none")]
1766 #[cfg_attr(feature = "uniffi", uniffi(default))]
1767 pub amount: Option<Amount>,
1768
1769 #[serde(default, skip_serializing_if = "super::is_false")]
1771 #[cfg_attr(feature = "uniffi", uniffi(default))]
1772 pub reversal: bool,
1773
1774 #[serde(skip_serializing_if = "Option::is_none")]
1776 #[cfg_attr(feature = "uniffi", uniffi(default))]
1777 pub original_amount: Option<Amount>,
1778
1779 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1781 #[cfg_attr(feature = "uniffi", uniffi(default))]
1782 pub exchanges: Vec<ExchangeRate>,
1783
1784 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1786 #[cfg_attr(feature = "uniffi", uniffi(default))]
1787 pub fees: Vec<Fee>,
1788
1789 #[serde(skip_serializing_if = "Option::is_none")]
1791 #[cfg_attr(feature = "uniffi", uniffi(default))]
1792 pub creditor: Option<Party>,
1793
1794 #[serde(skip_serializing_if = "Option::is_none")]
1796 #[cfg_attr(feature = "uniffi", uniffi(default))]
1797 pub debtor: Option<Party>,
1798
1799 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1801 #[cfg_attr(feature = "uniffi", uniffi(default))]
1802 pub remittance_information: Vec<String>,
1803
1804 #[serde(skip_serializing_if = "Option::is_none")]
1806 #[cfg_attr(feature = "uniffi", uniffi(default))]
1807 pub purpose_code: Option<String>,
1808
1809 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1811 #[cfg_attr(feature = "uniffi", uniffi(default))]
1812 pub bank_transaction_codes: Vec<BankTransactionCode>,
1813
1814 #[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 pub source_currency: String,
1829 #[serde(skip_serializing_if = "Option::is_none")]
1832 #[cfg_attr(feature = "uniffi", uniffi(default))]
1833 pub target_currency: Option<String>,
1834 #[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 #[serde(skip_serializing_if = "Option::is_none")]
1848 #[cfg_attr(feature = "uniffi", uniffi(default))]
1849 pub name: Option<String>,
1850
1851 #[serde(skip_serializing_if = "Option::is_none")]
1853 #[cfg_attr(feature = "uniffi", uniffi(default))]
1854 pub iban: Option<String>,
1855
1856 #[serde(skip_serializing_if = "Option::is_none")]
1858 #[cfg_attr(feature = "uniffi", uniffi(default))]
1859 pub bic: Option<String>,
1860
1861 #[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 {
1875 domain: String,
1877
1878 family: String,
1880
1881 sub_family: String,
1883 },
1884 Swift(String),
1886 Bai(String),
1888 National { code: String, country: CountryCode },
1890 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 Accepted,
1912
1913 PartiallyAccepted,
1915
1916 CompletedDebtor,
1918
1919 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 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 #[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}