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 #[serde(default = "default_vcpus")]
179 pub vcpus: usize,
180 }
181
182 fn default_vcpus() -> usize {
183 2
184 }
185}
186
187pub mod traces {
188 use super::Path;
189
190 pub fn path(trace_id: &str) -> Path {
195 Path::new(["traces", trace_id])
196 }
197}
198
199pub mod redirects {
200 use super::Path;
201 use serde::{Deserialize, Serialize};
202 use url::Url;
203
204 pub fn path() -> Path {
210 Path::new(["redirects"])
211 }
212
213 #[derive(Serialize, Deserialize, Clone, Debug)]
214 #[serde(rename_all = "camelCase")]
215 pub struct Request {
216 pub handle: String,
217 pub redirect_uri: String,
218 }
219
220 #[derive(Serialize, Deserialize, Clone, Debug)]
221 #[serde(rename_all = "camelCase")]
222 pub struct Response {
223 #[serde(serialize_with = "super::serialize_url")]
224 pub redirect_url: Url,
225 }
226}
227
228pub mod info {
229 use super::Path;
230 pub use isocountry::CountryCode;
231 use routex_models::{ConnectionId, CredentialsModel};
232 use serde::{Deserialize, Serialize};
233 #[cfg(feature = "server")]
234 use serde_with::base64::Base64;
235
236 pub fn search_path() -> Path {
242 Path::new(["search"])
243 }
244
245 #[derive(Serialize, Deserialize, Clone, Debug)]
246 #[serde(rename_all = "camelCase")]
247 #[non_exhaustive]
248 pub struct Request {
249 pub filters: Vec<SearchFilter>,
250 #[serde(default)]
251 pub iban_detection: bool,
252 #[serde(skip_serializing_if = "Option::is_none")]
253 pub limit: Option<usize>,
254 #[serde(default)]
255 #[serde(skip_serializing_if = "Vec::is_empty")]
256 pub details: Vec<Details>,
257 }
258
259 impl Request {
260 pub fn new(filters: impl IntoIterator<Item = SearchFilter>) -> Self {
261 Self {
262 filters: filters.into_iter().collect(),
263 iban_detection: false,
264 limit: None,
265 details: Vec::new(),
266 }
267 }
268 }
269
270 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
272 #[serde(rename_all = "camelCase")]
273 #[non_exhaustive]
274 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
275 pub enum Details {
276 Bics,
277 }
278
279 #[serde_with::serde_as]
283 #[derive(Serialize, Deserialize, Clone, Debug)]
284 #[serde(rename_all = "camelCase")]
285 #[serde(rename_all_fields = "camelCase")]
286 pub enum SearchFilter {
287 Types(Vec<ConnectionType>),
289 Countries(Vec<CountryCode>),
291 Name(String),
293 Bic(String),
295 BankCode(String),
297 Term(String),
299 #[cfg(feature = "server")]
300 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
301 }
302
303 #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
305 #[non_exhaustive]
306 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
307 pub enum ConnectionType {
308 Production,
310 Sandboxes,
312 }
313
314 pub fn fetch_path(connection_id: &str) -> Path {
319 Path::new(["info", connection_id])
320 }
321
322 #[allow(clippy::module_name_repetitions)]
324 #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
325 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
326 #[serde(rename_all = "camelCase")]
327 #[cfg_attr(not(feature = "server"), non_exhaustive)]
328 pub struct ConnectionInfo {
329 pub id: ConnectionId,
331
332 pub countries: Vec<CountryCode>,
334
335 pub display_name: String,
337
338 pub credentials: CredentialsModel,
340
341 #[serde(skip_serializing_if = "Option::is_none")]
343 #[cfg_attr(feature = "uniffi", uniffi(default))]
344 pub user_id: Option<String>,
345
346 #[serde(skip_serializing_if = "Option::is_none")]
348 #[cfg_attr(feature = "uniffi", uniffi(default))]
349 pub password: Option<String>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
353 #[cfg_attr(feature = "uniffi", uniffi(default))]
354 pub advice: Option<String>,
355
356 pub logo_id: String,
358
359 #[allow(clippy::doc_markdown)]
360 #[serde(skip_serializing_if = "Option::is_none")]
364 #[cfg_attr(feature = "uniffi", uniffi(default))]
365 pub bics: Option<Vec<String>>,
366 }
367}
368
369macro_rules! context {
370 ($name:ident) => {
371 #[serde_with::serde_as]
372 #[derive(Serialize, Deserialize)]
373 #[serde(transparent)]
374 pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
375
376 impl<S> PartialEq for $name<S> {
377 fn eq(&self, other: &Self) -> bool {
378 self.0 == other.0
379 }
380 }
381
382 impl<S> Eq for $name<S> {}
383
384 impl<S> Clone for $name<S> {
385 fn clone(&self) -> Self {
386 Self(self.0.clone(), self.1)
387 }
388 }
389
390 impl<S> fmt::Debug for $name<S> {
391 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
392 f.debug_tuple("$name")
393 .field(&self.0)
394 .field(&self.1)
395 .finish()
396 }
397 }
398
399 impl<S> From<Vec<u8>> for $name<S> {
400 fn from(value: Vec<u8>) -> Self {
401 Self(value, PhantomData)
402 }
403 }
404
405 impl<S> From<$name<S>> for Vec<u8> {
406 fn from(value: $name<S>) -> Self {
407 value.0
408 }
409 }
410
411 impl<S> AsRef<[u8]> for $name<S> {
412 fn as_ref(&self) -> &[u8] {
413 &self.0
414 }
415 }
416 };
417}
418
419context!(InputContext);
420
421context!(ConfirmationContext);
422
423#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
449#[serde(transparent)]
450pub struct Authenticated<T> {
451 jwt: String,
452 _phantom: PhantomData<T>,
453}
454
455impl<'de, T> Deserialize<'de> for Authenticated<T>
456where
457 T: for<'t> Deserialize<'t> + Clone,
458{
459 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
460 where
461 D: serde::Deserializer<'de>,
462 {
463 String::deserialize(deserializer)?
464 .parse()
465 .map_err(serde::de::Error::custom)
466 }
467}
468
469impl<T> Authenticated<T>
470where
471 T: Serialize,
472{
473 #[must_use]
474 pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
480 let header = Header {
481 kid: Some(key_id.into()),
482 ..Default::default()
483 };
484
485 let jwt = encode(
486 &header,
487 &Claims {
488 data,
489 exp: 2_540_808_000,
491 },
492 &EncodingKey::from_secret(key),
493 )
494 .expect("Encoding should work");
495
496 Self {
497 jwt,
498 _phantom: PhantomData,
499 }
500 }
501}
502
503impl<T> FromStr for Authenticated<T>
504where
505 T: for<'de> Deserialize<'de> + Clone,
506{
507 type Err = jsonwebtoken::errors::Error;
508
509 fn from_str(jwt: &str) -> Result<Self, Self::Err> {
510 decode::<T>(jwt)?;
512 Ok(Self {
513 jwt: jwt.to_string(),
514 _phantom: PhantomData,
515 })
516 }
517}
518
519impl<T> Authenticated<T>
520where
521 T: for<'de> Deserialize<'de> + Clone,
522{
523 #[must_use]
524 pub fn as_str(&self) -> &str {
525 &self.jwt
526 }
527
528 #[must_use]
529 pub fn key_id(&self) -> Option<String> {
535 decode_header(&self.jwt).unwrap().kid
536 }
537
538 #[cfg(feature = "client")]
539 #[must_use]
540 #[allow(clippy::missing_panics_doc)]
541 pub fn to_data(&self) -> T {
542 decode(&self.jwt).unwrap()
543 }
544
545 #[cfg(feature = "server")]
546 pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
552 decoder.decode::<T>(&self.jwt).map(|d| d.data)
553 }
554}
555
556#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
557#[serde(rename_all = "camelCase")]
558pub struct Claims<T> {
559 pub data: T,
560 pub exp: u64,
561}
562
563#[cfg(feature = "server")]
564pub trait Decoder {
565 type Error;
566
567 fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
573 where
574 T: for<'de> Deserialize<'de> + Clone;
575}
576
577#[derive(Serialize, Deserialize, Debug)]
582pub enum OBResponse<S: Service> {
583 #[serde(bound = "S:")]
584 Result(
585 Authenticated<OBResult<S::Output>>,
586 Option<Session>,
587 Option<ConnectionData>,
588 ),
589 #[serde(bound = "S:")]
590 Dialog(Dialog<S>),
591 #[serde(bound = "S:")]
592 Redirect(Redirect<S>),
593 #[serde(bound = "S:")]
594 RedirectHandle(RedirectHandle<S>),
595}
596
597impl<S: Service> Clone for OBResponse<S> {
598 fn clone(&self) -> Self {
599 match self {
600 OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
601 authenticated.clone(),
602 session.clone(),
603 connection_data.clone(),
604 ),
605 OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
606 OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
607 OBResponse::RedirectHandle(redirect_handle) => {
608 OBResponse::RedirectHandle(redirect_handle.clone())
609 }
610 }
611 }
612}
613
614#[derive(Serialize, Deserialize, Debug)]
615#[cfg_attr(not(feature = "server"), non_exhaustive)]
616#[serde(rename_all = "camelCase")]
617pub struct NonInteractiveResponse<S: Service> {
618 #[serde(bound = "S:")]
619 pub result: S::Output,
620 #[serde(skip_serializing_if = "Option::is_none")]
621 pub session: Option<Session>,
622 #[serde(skip_serializing_if = "Option::is_none")]
623 pub connection_data: Option<ConnectionData>,
624}
625
626#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
631#[serde(rename_all = "camelCase")]
632pub struct OBResult<T> {
633 pub data: T,
634 pub ticket_id: Uuid,
635 pub timestamp: DateTime,
636}
637
638#[serde_with::serde_as]
639#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
640pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
641
642impl From<Vec<u8>> for Session {
643 fn from(value: Vec<u8>) -> Self {
644 Session(value)
645 }
646}
647
648impl From<Session> for Vec<u8> {
649 fn from(session: Session) -> Self {
650 session.0
651 }
652}
653
654impl AsRef<[u8]> for Session {
655 fn as_ref(&self) -> &[u8] {
656 &self.0
657 }
658}
659
660#[serde_with::serde_as]
661#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
662#[serde(transparent)]
663pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
664
665impl From<Vec<u8>> for ConnectionData {
666 fn from(value: Vec<u8>) -> Self {
667 ConnectionData(value)
668 }
669}
670
671impl From<ConnectionData> for Vec<u8> {
672 fn from(connection_data: ConnectionData) -> Self {
673 connection_data.0
674 }
675}
676
677impl AsRef<[u8]> for ConnectionData {
678 fn as_ref(&self) -> &[u8] {
679 &self.0
680 }
681}
682
683#[derive(Serialize, Deserialize)]
691#[serde(rename_all = "camelCase")]
692pub struct Redirect<S> {
693 #[serde(serialize_with = "serialize_url")]
694 pub url: Url,
695 #[serde(bound = "S:")]
696 pub context: ConfirmationContext<S>,
697}
698
699impl<S> PartialEq for Redirect<S> {
700 fn eq(&self, other: &Self) -> bool {
701 let Self { url, context } = self;
702 url == &other.url && context == &other.context
703 }
704}
705
706impl<S> Eq for Redirect<S> {}
707
708impl<S> Clone for Redirect<S> {
709 fn clone(&self) -> Self {
710 Self {
711 url: self.url.clone(),
712 context: self.context.clone(),
713 }
714 }
715}
716
717impl<S> fmt::Debug for Redirect<S> {
718 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
719 f.debug_struct("Redirect")
720 .field("url", &self.url)
721 .field("context", &self.context)
722 .finish()
723 }
724}
725
726fn serialize_url<S: Serializer>(value: &Url, serializer: S) -> Result<S::Ok, S::Error> {
729 let string_repr = value.to_string().replace('+', "%20");
730 serializer.serialize_str(&string_repr)
731}
732
733#[derive(Serialize, Deserialize)]
737#[serde(rename_all = "camelCase")]
738pub struct RedirectHandle<S> {
739 pub handle: String,
740 #[serde(bound = "S:")]
741 pub context: ConfirmationContext<S>,
742}
743
744impl<S> PartialEq for RedirectHandle<S> {
745 fn eq(&self, other: &Self) -> bool {
746 let Self { handle, context } = self;
747 handle == &other.handle && context == &other.context
748 }
749}
750
751impl<S> Eq for RedirectHandle<S> {}
752
753impl<S> Clone for RedirectHandle<S> {
754 fn clone(&self) -> Self {
755 Self {
756 handle: self.handle.clone(),
757 context: self.context.clone(),
758 }
759 }
760}
761
762impl<S> fmt::Debug for RedirectHandle<S> {
763 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
764 f.debug_struct("RedirectHandle")
765 .field("handle", &self.handle)
766 .field("context", &self.context)
767 .finish()
768 }
769}
770
771fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
773where
774 T: for<'de> Deserialize<'de> + Clone,
775{
776 insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
777}
778
779macro_rules! enum_with_display {
780 {
781 $(#[$meta:meta])*
782 pub enum $name:ident {
783 $($variant:ident,)+
784 }
785 } => {
786 $(#[$meta])*
787 pub enum $name {
788 $($variant,)+
789 }
790
791 impl fmt::Display for $name {
792 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
793 f.write_str(match self {
794 $(Self::$variant => stringify!($variant),)+
795 })
796 }
797 }
798 }
799}
800
801enum_with_display! {
802 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
803 pub enum ServiceId {
804 Accounts,
805 CollectPayment,
806 Transactions,
807 Balances,
808 Transfer,
809 }
810}
811
812#[derive(Serialize, Deserialize)]
872#[serde(rename_all = "camelCase")]
873#[serde(try_from = "DeserializedTicket<S::TicketData>")]
874pub struct Ticket<S: Service> {
875 pub service: ServiceId,
876 pub id: Uuid,
877 pub data: S::TicketData,
878}
879
880impl<S: Service> PartialEq for Ticket<S> {
881 fn eq(&self, other: &Self) -> bool {
882 let Self { service, id, data } = self;
883 service == &other.service && id == &other.id && data == &other.data
884 }
885}
886
887impl<S: Service> Eq for Ticket<S> {}
888
889impl<S: Service> fmt::Debug for Ticket<S> {
890 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
891 f.debug_struct("Ticket")
892 .field("service", &self.service)
893 .field("id", &self.id)
894 .field("data", &self.data)
895 .finish()
896 }
897}
898
899impl<S: Service> Clone for Ticket<S> {
900 fn clone(&self) -> Self {
901 Self {
902 service: self.service,
903 id: self.id,
904 data: self.data.clone(),
905 }
906 }
907}
908
909#[derive(Deserialize, Debug)]
910struct DeserializedTicket<T> {
911 service: ServiceId,
912 id: Uuid,
913 data: Option<T>,
914}
915
916impl<S: Service> TryFrom<DeserializedTicket<S::TicketData>> for Ticket<S> {
917 type Error = &'static str;
918
919 fn try_from(
920 DeserializedTicket { service, id, data }: DeserializedTicket<S::TicketData>,
921 ) -> Result<Self, Self::Error> {
922 let data = (&() as &dyn Any)
923 .downcast_ref::<S::TicketData>()
924 .cloned()
925 .or(data)
926 .ok_or("missing field `data`")?;
927
928 Ok(Self { service, id, data })
929 }
930}
931
932pub trait Service {
933 const ID: ServiceId;
934
935 type TicketData: Serialize
936 + for<'de> Deserialize<'de>
937 + PartialEq
938 + Eq
939 + Clone
940 + fmt::Debug
941 + Send
942 + Sync
943 + 'static;
944
945 type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
946
947 type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
948
949 fn path() -> Path {
955 Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
956 }
957
958 fn response_path() -> Path {
959 Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
960 }
961
962 fn confirmation_path() -> Path {
963 Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
964 }
965}
966
967pub trait NonInteractiveService: Service {
968 fn non_interactive_path() -> Path {
969 Path::new([&Self::ID.to_string().to_kebab_case(), "non-interactive"])
970 }
971}
972
973#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
974#[serde(tag = "ticketStatus")]
975pub enum TicketStatus<T> {
976 Unfinished,
977 Error(ErrorKind),
978 Success(T),
979}
980
981enum_with_display! {
982 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
983 #[serde(tag = "error")]
984 pub enum ErrorKind {
985 UnexpectedError,
986 Canceled,
987 InvalidCredentials,
988 ServiceBlocked,
989 Unauthorized,
990 ConsentExpired,
991 AccessExceeded,
992 PeriodOutOfBounds,
993 UnsupportedProduct,
994 PaymentFailed,
995 UnexpectedValue,
996 TicketError,
997 ProviderError,
998 NotFound,
999 }
1000}
1001
1002#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1003#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1004#[serde(rename_all = "camelCase")]
1005pub struct Credentials {
1006 pub connection_id: ConnectionId,
1007 #[serde(skip_serializing_if = "Option::is_none")]
1008 #[cfg_attr(feature = "uniffi", uniffi(default))]
1009 pub user_id: Option<String>,
1010 #[serde(skip_serializing_if = "Option::is_none")]
1011 #[cfg_attr(feature = "uniffi", uniffi(default))]
1012 pub password: Option<String>,
1013 #[serde(skip_serializing_if = "Option::is_none")]
1014 #[cfg_attr(feature = "uniffi", uniffi(default))]
1015 pub connection_data: Option<ConnectionData>,
1016}
1017
1018#[derive(Serialize, Deserialize)]
1019#[serde(rename_all = "camelCase")]
1020pub struct ResponseData<S> {
1021 #[serde(bound = "S:")]
1022 pub context: InputContext<S>,
1023 pub response: String,
1024}
1025
1026impl<S> PartialEq for ResponseData<S> {
1027 fn eq(&self, other: &Self) -> bool {
1028 let Self { context, response } = self;
1029 context == &other.context && response == &other.response
1030 }
1031}
1032
1033impl<S> Eq for ResponseData<S> {}
1034
1035impl<S> Clone for ResponseData<S> {
1036 fn clone(&self) -> Self {
1037 Self {
1038 context: self.context.clone(),
1039 response: self.response.clone(),
1040 }
1041 }
1042}
1043
1044impl<S> fmt::Debug for ResponseData<S> {
1045 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1046 f.debug_struct("ResponseData")
1047 .field("context", &self.context)
1048 .field("response", &self.response)
1049 .finish()
1050 }
1051}
1052
1053#[derive(Serialize, Deserialize)]
1054#[serde(rename_all = "camelCase")]
1055pub struct ConfirmationData<S> {
1056 #[serde(bound = "S:")]
1057 pub context: ConfirmationContext<S>,
1058}
1059
1060impl<S> PartialEq for ConfirmationData<S> {
1061 fn eq(&self, other: &Self) -> bool {
1062 let Self { context } = self;
1063 context == &other.context
1064 }
1065}
1066
1067impl<S> Eq for ConfirmationData<S> {}
1068
1069impl<S> Clone for ConfirmationData<S> {
1070 fn clone(&self) -> Self {
1071 Self {
1072 context: self.context.clone(),
1073 }
1074 }
1075}
1076
1077impl<S> fmt::Debug for ConfirmationData<S> {
1078 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1079 f.debug_struct("ConfirmationData")
1080 .field("context", &self.context)
1081 .finish()
1082 }
1083}
1084
1085#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1086#[serde(rename_all_fields = "camelCase")]
1087#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1088pub enum Error {
1089 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1090 UnexpectedError {
1091 #[serde(skip_serializing_if = "Option::is_none")]
1092 user_message: Option<String>,
1093 },
1094 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1095 Canceled {},
1096 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1097 InvalidCredentials {
1098 #[serde(skip_serializing_if = "Option::is_none")]
1099 user_message: Option<String>,
1100 },
1101 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1102 ServiceBlocked {
1103 #[serde(skip_serializing_if = "Option::is_none")]
1104 user_message: Option<String>,
1105 #[serde(skip_serializing_if = "Option::is_none")]
1106 code: Option<ServiceBlockedCode>,
1107 },
1108 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1109 Unauthorized {
1110 #[serde(skip_serializing_if = "Option::is_none")]
1111 user_message: Option<String>,
1112 },
1113 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1114 ConsentExpired {
1115 #[serde(skip_serializing_if = "Option::is_none")]
1116 user_message: Option<String>,
1117 },
1118 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1119 AccessExceeded {
1120 #[serde(skip_serializing_if = "Option::is_none")]
1121 user_message: Option<String>,
1122 },
1123 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1124 PeriodOutOfBounds {
1125 #[serde(skip_serializing_if = "Option::is_none")]
1126 user_message: Option<String>,
1127 },
1128 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1129 UnsupportedProduct {
1130 #[serde(skip_serializing_if = "Option::is_none")]
1131 reason: Option<UnsupportedProductReason>,
1132 #[serde(skip_serializing_if = "Option::is_none")]
1133 user_message: Option<String>,
1134 },
1135 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1136 PaymentFailed {
1137 #[serde(skip_serializing_if = "Option::is_none")]
1138 code: Option<PaymentErrorCode>,
1139 #[serde(skip_serializing_if = "Option::is_none")]
1140 user_message: Option<String>,
1141 },
1142 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1143 UnexpectedValue { error: String },
1144 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1145 TicketError {
1146 error: String,
1147 code: TicketErrorCode,
1148 },
1149 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1150 ProviderError {
1151 #[serde(skip_serializing_if = "Option::is_none")]
1152 code: Option<ProviderErrorCode>,
1153 #[serde(skip_serializing_if = "Option::is_none")]
1154 user_message: Option<String>,
1155 },
1156 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1157 InterruptError {},
1158}
1159
1160#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1161#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1162#[cfg_attr(not(feature = "server"), non_exhaustive)]
1163pub enum TicketErrorCode {
1164 Missing,
1166 Invalid,
1168 MissingKey,
1170 UnknownKey,
1172 Mismatch,
1174 Expired,
1176 InvalidLifetime,
1178 ExpiredKey,
1180 KeyEnvironmentMismatch,
1182}
1183
1184#[derive(Serialize, Deserialize, Clone, Debug)]
1185#[cfg_attr(not(feature = "server"), non_exhaustive)]
1186pub enum Filter<F> {
1187 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1188 Eq(F, Value),
1189 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1190 NotEq(F, Value),
1191 #[cfg(feature = "server")]
1192 Contains(F, Value),
1193 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1194 And(Box<Self>, Box<Self>),
1195 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1196 Or(Box<Self>, Box<Self>),
1197 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1198 Supports(SupportedService),
1199}
1200
1201#[derive(Serialize, Deserialize, Clone, Debug)]
1202#[cfg_attr(not(feature = "server"), non_exhaustive)]
1203#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1204pub enum SupportedService {
1205 CollectPayment,
1206}
1207
1208impl<F> Filter<F> {
1209 #[must_use]
1210 pub fn and(self, other: Filter<F>) -> Filter<F> {
1212 Filter::And(Box::new(self), Box::new(other))
1213 }
1214
1215 #[must_use]
1216 pub fn or(self, other: Filter<F>) -> Filter<F> {
1218 Filter::Or(Box::new(self), Box::new(other))
1219 }
1220}
1221
1222pub struct Field<F, T> {
1223 field: F,
1224 phantom: PhantomData<T>,
1225}
1226
1227impl<F, T> Field<F, T>
1228where
1229 F: Copy,
1230 T: Serialize,
1231{
1232 pub fn eq(&self, value: T) -> Filter<F> {
1238 Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1239 }
1240
1241 pub fn not_eq(&self, value: T) -> Filter<F> {
1247 Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1248 }
1249}
1250
1251pub trait GetValue {
1252 type Model;
1253
1254 fn get(&self, model: &Self::Model) -> Value;
1255}
1256
1257macro_rules! fields {
1258 {
1259 $model:ty, $enum_name:ident
1260 $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1261 $(
1262 server:
1263 $(($server_variant_name:ident, $server_field_name:ident))+
1264 )?
1265 } => {
1266 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1267 #[non_exhaustive]
1268 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1269 pub enum $enum_name {
1270 $($variant_name,)+
1271 $($(
1272 #[cfg(feature = "server")]
1273 $server_variant_name,
1274 )+)?
1275 }
1276
1277 impl $enum_name {
1278 $(
1279 pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1280 field: $enum_name::$variant_name,
1281 phantom: std::marker::PhantomData,
1282 };
1283 )+
1284 }
1285
1286 impl crate::GetValue for $enum_name {
1287 type Model = $model;
1288
1289 fn get(&self, model: &$model) -> serde_json::Value {
1290 match self {
1291 $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1292 $($(
1293 #[cfg(feature = "server")]
1294 $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1295 )+)?
1296 }.expect("Serialization should work")
1297 }
1298 }
1299 }
1300}
1301
1302#[derive(Serialize, Deserialize)]
1303#[serde(rename_all = "camelCase")]
1304pub struct ServiceRequest<S: Service> {
1305 pub credentials: Credentials,
1306 #[serde(skip_serializing_if = "Option::is_none")]
1307 pub session: Option<Session>,
1308 #[serde(default, skip_serializing_if = "is_false")]
1309 pub recurring_consents: bool,
1310 #[serde(flatten, bound = "S:")]
1311 pub data: <S as Service>::RequestData,
1312}
1313
1314impl<S: Service> Clone for ServiceRequest<S> {
1315 fn clone(&self) -> Self {
1316 Self {
1317 credentials: self.credentials.clone(),
1318 session: self.session.clone(),
1319 recurring_consents: self.recurring_consents,
1320 data: self.data.clone(),
1321 }
1322 }
1323}
1324
1325#[derive(Serialize, Deserialize)]
1326#[serde(rename_all = "camelCase")]
1327pub struct NonInteractiveRequest<S: Service> {
1328 pub connection_data: ConnectionData,
1329 #[serde(skip_serializing_if = "Option::is_none")]
1330 pub session: Option<Session>,
1331 #[serde(flatten, bound = "S:")]
1332 pub data: <S as Service>::RequestData,
1333}
1334
1335#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1336#[serde(rename_all = "camelCase")]
1337#[non_exhaustive]
1338#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1339pub struct Account {
1340 #[serde(skip_serializing_if = "Option::is_none")]
1342 #[cfg_attr(feature = "uniffi", uniffi(default))]
1343 pub iban: Option<String>,
1344
1345 #[serde(skip_serializing_if = "Option::is_none")]
1347 #[cfg_attr(feature = "uniffi", uniffi(default))]
1348 pub number: Option<String>,
1349
1350 #[serde(skip_serializing_if = "Option::is_none")]
1352 #[cfg_attr(feature = "uniffi", uniffi(default))]
1353 pub bic: Option<String>,
1354
1355 #[serde(skip_serializing_if = "Option::is_none")]
1357 #[cfg_attr(feature = "uniffi", uniffi(default))]
1358 pub bank_code: Option<String>,
1359
1360 #[serde(skip_serializing_if = "Option::is_none")]
1362 #[cfg_attr(feature = "uniffi", uniffi(default))]
1363 pub currency: Option<String>,
1364
1365 #[serde(skip_serializing_if = "Option::is_none")]
1367 #[cfg_attr(feature = "uniffi", uniffi(default))]
1368 pub name: Option<String>,
1369
1370 #[serde(skip_serializing_if = "Option::is_none")]
1372 #[cfg_attr(feature = "uniffi", uniffi(default))]
1373 pub display_name: Option<String>,
1374
1375 #[serde(skip_serializing_if = "Option::is_none")]
1377 #[cfg_attr(feature = "uniffi", uniffi(default))]
1378 pub owner_name: Option<String>,
1379
1380 #[serde(skip_serializing_if = "Option::is_none")]
1382 #[cfg_attr(feature = "uniffi", uniffi(default))]
1383 pub product_name: Option<String>,
1384
1385 #[serde(skip_serializing_if = "Option::is_none")]
1387 #[cfg_attr(feature = "uniffi", uniffi(default))]
1388 pub status: Option<AccountStatus>,
1389
1390 #[serde(rename = "type")]
1392 #[serde(skip_serializing_if = "Option::is_none")]
1393 #[cfg_attr(feature = "uniffi", uniffi(default))]
1394 pub type_: Option<AccountType>,
1395}
1396
1397pub mod accounts {
1398 use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1399 use serde::{Deserialize, Serialize};
1400
1401 #[derive(Clone, Debug)]
1402 pub struct Service {}
1403
1404 impl super::Service for Service {
1405 const ID: ServiceId = ServiceId::Accounts;
1406
1407 type TicketData = ();
1408
1409 type RequestData = RequestData;
1410
1411 type Output = Vec<Account>;
1412 }
1413
1414 impl super::NonInteractiveService for Service {}
1415
1416 fields! {
1417 routex_models::Account, AccountField
1418 (IBAN, Iban, iban, Option<String>)
1419 (NUMBER, Number, number, Option<String>)
1420 (BIC, Bic, bic, Option<String>)
1421 (BANK_CODE, BankCode, bank_code, Option<String>)
1422 (CURRENCY, Currency, currency, String)
1423 (NAME, Name, name, Option<String>)
1424 (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1425 (OWNER_NAME, OwnerName, owner_name, Option<String>)
1426 (PRODUCT_NAME, ProductName, product_name, Option<String>)
1427 (STATUS, Status, status, Option<AccountStatus>)
1428 (TYPE, Type, type_, Option<AccountType>)
1429 server:
1430 (Capabilities, capabilities)
1431 }
1432
1433 impl Account {
1434 #[must_use]
1435 pub fn supports(service: SupportedService) -> Filter<AccountField> {
1437 Filter::Supports(service)
1438 }
1439 }
1440
1441 #[derive(Serialize, Deserialize, Clone)]
1442 #[serde(rename_all = "camelCase")]
1443 pub struct RequestData {
1444 pub fields: Vec<AccountField>,
1445 #[serde(skip_serializing_if = "Option::is_none")]
1446 pub filter: Option<Filter<AccountField>>,
1447 }
1448}
1449
1450#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1451#[serde(rename_all = "camelCase")]
1452#[serde(rename_all_fields = "camelCase")]
1453#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1454pub enum AccountIdentifier {
1455 Iban(String),
1457}
1458
1459#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1460#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1461#[serde(rename_all = "camelCase")]
1462pub struct AccountReference {
1463 #[serde(flatten)]
1464 pub id: AccountIdentifier,
1465 #[serde(skip_serializing_if = "Option::is_none")]
1466 #[cfg_attr(feature = "uniffi", uniffi(default))]
1467 pub currency: Option<String>,
1468}
1469
1470pub mod balances {
1471 pub use rust_decimal::Decimal;
1472 use serde::{Deserialize, Serialize};
1473
1474 use crate::{AccountReference, ServiceId};
1475
1476 #[derive(Clone, Debug)]
1477 pub struct Service {}
1478
1479 impl super::Service for Service {
1480 const ID: ServiceId = ServiceId::Balances;
1481
1482 type TicketData = ();
1483
1484 type RequestData = RequestData;
1485
1486 type Output = Balances;
1487 }
1488
1489 impl super::NonInteractiveService for Service {}
1490
1491 #[derive(Serialize, Deserialize, Clone)]
1492 #[serde(rename_all = "camelCase")]
1493 pub struct RequestData {
1494 pub accounts: Vec<AccountReference>,
1495 }
1496
1497 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1498 #[serde(rename_all = "camelCase")]
1499 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1500 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1501 pub struct Balances {
1502 pub balances: Vec<AccountBalances>,
1504
1505 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1507 #[cfg_attr(feature = "uniffi", uniffi(default))]
1508 pub missing_accounts: Vec<AccountReference>,
1509 }
1510
1511 #[allow(clippy::module_name_repetitions)]
1512 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1513 #[serde(rename_all = "camelCase")]
1514 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1515 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1516 pub struct AccountBalances {
1517 pub account: AccountReference,
1518
1519 pub balances: Vec<Balance>,
1521 }
1522
1523 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1524 #[serde(rename_all = "camelCase")]
1525 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1526 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1527 pub struct Balance {
1528 pub amount: Decimal,
1529 pub currency: String,
1530 pub balance_type: BalanceType,
1531 #[serde(default, skip_serializing_if = "Option::is_none")]
1532 #[cfg_attr(feature = "uniffi", uniffi(default))]
1533 pub credit_limit_included: Option<bool>,
1534 }
1535
1536 #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1537 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1538 pub enum BalanceType {
1539 Booked,
1541
1542 Available,
1544
1545 Expected,
1547 }
1548}
1549
1550pub mod transactions {
1551 use chrono::NaiveDate;
1552 use isocountry::CountryCode;
1553 pub use routex_models::{Amount, Fee, TransactionStatus};
1554 use rust_decimal::Decimal;
1555 use serde::{Deserialize, Serialize};
1556 use url::Url;
1557
1558 use crate::{AccountReference, ServiceId};
1559
1560 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1561 #[serde(rename_all = "camelCase")]
1562 pub struct TicketData {
1563 pub account: AccountReference,
1564 pub range: Range,
1565 pub webhook: Option<Url>,
1566 }
1567
1568 #[derive(Clone, Debug)]
1569 pub struct Service {}
1570
1571 impl super::Service for Service {
1572 const ID: ServiceId = ServiceId::Transactions;
1573
1574 type TicketData = TicketData;
1575
1576 type RequestData = RequestData;
1577
1578 type Output = Option<Vec<Transaction>>;
1579 }
1580
1581 impl super::NonInteractiveService for Service {}
1582
1583 #[derive(Serialize, Deserialize, Clone)]
1584 #[serde(rename_all = "camelCase")]
1585 pub struct RequestData {}
1586
1587 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1588 #[serde(rename_all = "camelCase")]
1589 #[serde(rename_all_fields = "camelCase")]
1590 pub enum Range {
1591 Reference(String),
1592 #[serde(untagged)]
1593 Period {
1594 from: NaiveDate,
1596 to: Option<NaiveDate>,
1598 },
1599 }
1600
1601 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1602 #[serde(rename_all = "camelCase")]
1603 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1604 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1605 pub struct Transaction {
1606 #[serde(skip_serializing_if = "Option::is_none")]
1608 #[cfg_attr(feature = "uniffi", uniffi(default))]
1609 pub entry_reference: Option<String>,
1610
1611 #[serde(skip_serializing_if = "Option::is_none")]
1612 #[cfg_attr(feature = "uniffi", uniffi(default))]
1613 pub batch: Option<BatchData>,
1614
1615 #[serde(skip_serializing_if = "Option::is_none")]
1617 #[cfg_attr(feature = "uniffi", uniffi(default))]
1618 pub booking_date: Option<NaiveDate>,
1619
1620 #[serde(skip_serializing_if = "Option::is_none")]
1622 #[cfg_attr(feature = "uniffi", uniffi(default))]
1623 pub value_date: Option<NaiveDate>,
1624
1625 #[serde(skip_serializing_if = "Option::is_none")]
1627 #[cfg_attr(feature = "uniffi", uniffi(default))]
1628 pub transaction_date: Option<NaiveDate>,
1629
1630 pub status: TransactionStatus,
1632
1633 #[serde(skip_serializing_if = "Option::is_none")]
1635 #[cfg_attr(feature = "uniffi", uniffi(default))]
1636 pub account_servicer_reference: Option<String>,
1637
1638 #[serde(skip_serializing_if = "Option::is_none")]
1640 #[cfg_attr(feature = "uniffi", uniffi(default))]
1641 pub payment_id: Option<String>,
1642
1643 #[serde(skip_serializing_if = "Option::is_none")]
1645 #[cfg_attr(feature = "uniffi", uniffi(default))]
1646 pub transaction_id: Option<String>,
1647
1648 #[serde(skip_serializing_if = "Option::is_none")]
1650 #[cfg_attr(feature = "uniffi", uniffi(default))]
1651 pub end_to_end_id: Option<String>,
1652
1653 #[serde(skip_serializing_if = "Option::is_none")]
1655 #[cfg_attr(feature = "uniffi", uniffi(default))]
1656 pub mandate_id: Option<String>,
1657
1658 #[serde(skip_serializing_if = "Option::is_none")]
1660 #[cfg_attr(feature = "uniffi", uniffi(default))]
1661 pub creditor_id: Option<String>,
1662
1663 pub amount: Amount,
1665
1666 #[serde(default, skip_serializing_if = "super::is_false")]
1668 #[cfg_attr(feature = "uniffi", uniffi(default))]
1669 pub reversal: bool,
1670
1671 #[serde(skip_serializing_if = "Option::is_none")]
1673 #[cfg_attr(feature = "uniffi", uniffi(default))]
1674 pub original_amount: Option<Amount>,
1675
1676 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1678 #[cfg_attr(feature = "uniffi", uniffi(default))]
1679 pub exchanges: Vec<ExchangeRate>,
1680
1681 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1683 #[cfg_attr(feature = "uniffi", uniffi(default))]
1684 pub fees: Vec<Fee>,
1685
1686 #[serde(skip_serializing_if = "Option::is_none")]
1688 #[cfg_attr(feature = "uniffi", uniffi(default))]
1689 pub creditor: Option<Party>,
1690
1691 #[serde(skip_serializing_if = "Option::is_none")]
1693 #[cfg_attr(feature = "uniffi", uniffi(default))]
1694 pub debtor: Option<Party>,
1695
1696 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1698 #[cfg_attr(feature = "uniffi", uniffi(default))]
1699 pub remittance_information: Vec<String>,
1700
1701 #[serde(skip_serializing_if = "Option::is_none")]
1703 #[cfg_attr(feature = "uniffi", uniffi(default))]
1704 pub purpose_code: Option<String>,
1705
1706 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1708 #[cfg_attr(feature = "uniffi", uniffi(default))]
1709 pub bank_transaction_codes: Vec<BankTransactionCode>,
1710
1711 #[serde(skip_serializing_if = "Option::is_none")]
1715 #[cfg_attr(feature = "uniffi", uniffi(default))]
1716 pub additional_information: Option<String>,
1717 }
1718
1719 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1720 #[serde(rename_all = "camelCase")]
1721 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1722 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1723 pub struct BatchData {
1724 #[serde(skip_serializing_if = "Option::is_none")]
1726 #[cfg_attr(feature = "uniffi", uniffi(default))]
1727 pub number_of_transactions: Option<u32>,
1728
1729 pub transactions: Vec<BatchTransactionDetails>,
1734 }
1735
1736 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1737 #[serde(rename_all = "camelCase")]
1738 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1739 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1740 pub struct BatchTransactionDetails {
1741 #[serde(skip_serializing_if = "Option::is_none")]
1743 #[cfg_attr(feature = "uniffi", uniffi(default))]
1744 pub account_servicer_reference: Option<String>,
1745
1746 #[serde(skip_serializing_if = "Option::is_none")]
1748 #[cfg_attr(feature = "uniffi", uniffi(default))]
1749 pub payment_id: Option<String>,
1750
1751 #[serde(skip_serializing_if = "Option::is_none")]
1753 #[cfg_attr(feature = "uniffi", uniffi(default))]
1754 pub transaction_id: Option<String>,
1755
1756 #[serde(skip_serializing_if = "Option::is_none")]
1758 #[cfg_attr(feature = "uniffi", uniffi(default))]
1759 pub end_to_end_id: Option<String>,
1760
1761 #[serde(skip_serializing_if = "Option::is_none")]
1763 #[cfg_attr(feature = "uniffi", uniffi(default))]
1764 pub mandate_id: Option<String>,
1765
1766 #[serde(skip_serializing_if = "Option::is_none")]
1768 #[cfg_attr(feature = "uniffi", uniffi(default))]
1769 pub creditor_id: Option<String>,
1770
1771 #[serde(skip_serializing_if = "Option::is_none")]
1773 #[cfg_attr(feature = "uniffi", uniffi(default))]
1774 pub amount: Option<Amount>,
1775
1776 #[serde(default, skip_serializing_if = "super::is_false")]
1778 #[cfg_attr(feature = "uniffi", uniffi(default))]
1779 pub reversal: bool,
1780
1781 #[serde(skip_serializing_if = "Option::is_none")]
1783 #[cfg_attr(feature = "uniffi", uniffi(default))]
1784 pub original_amount: Option<Amount>,
1785
1786 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1788 #[cfg_attr(feature = "uniffi", uniffi(default))]
1789 pub exchanges: Vec<ExchangeRate>,
1790
1791 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1793 #[cfg_attr(feature = "uniffi", uniffi(default))]
1794 pub fees: Vec<Fee>,
1795
1796 #[serde(skip_serializing_if = "Option::is_none")]
1798 #[cfg_attr(feature = "uniffi", uniffi(default))]
1799 pub creditor: Option<Party>,
1800
1801 #[serde(skip_serializing_if = "Option::is_none")]
1803 #[cfg_attr(feature = "uniffi", uniffi(default))]
1804 pub debtor: Option<Party>,
1805
1806 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1808 #[cfg_attr(feature = "uniffi", uniffi(default))]
1809 pub remittance_information: Vec<String>,
1810
1811 #[serde(skip_serializing_if = "Option::is_none")]
1813 #[cfg_attr(feature = "uniffi", uniffi(default))]
1814 pub purpose_code: Option<String>,
1815
1816 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1818 #[cfg_attr(feature = "uniffi", uniffi(default))]
1819 pub bank_transaction_codes: Vec<BankTransactionCode>,
1820
1821 #[serde(skip_serializing_if = "Option::is_none")]
1825 #[cfg_attr(feature = "uniffi", uniffi(default))]
1826 pub additional_information: Option<String>,
1827 }
1828
1829 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1830 #[serde(rename_all = "camelCase")]
1831 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1832 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1833 pub struct ExchangeRate {
1834 pub source_currency: String,
1836 #[serde(skip_serializing_if = "Option::is_none")]
1839 #[cfg_attr(feature = "uniffi", uniffi(default))]
1840 pub target_currency: Option<String>,
1841 #[serde(skip_serializing_if = "Option::is_none")]
1843 #[cfg_attr(feature = "uniffi", uniffi(default))]
1844 pub unit_currency: Option<String>,
1845 pub exchange_rate: Decimal,
1846 }
1847
1848 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1849 #[serde(rename_all = "camelCase")]
1850 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1851 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1852 pub struct Party {
1853 #[serde(skip_serializing_if = "Option::is_none")]
1855 #[cfg_attr(feature = "uniffi", uniffi(default))]
1856 pub name: Option<String>,
1857
1858 #[serde(skip_serializing_if = "Option::is_none")]
1860 #[cfg_attr(feature = "uniffi", uniffi(default))]
1861 pub iban: Option<String>,
1862
1863 #[serde(skip_serializing_if = "Option::is_none")]
1865 #[cfg_attr(feature = "uniffi", uniffi(default))]
1866 pub bic: Option<String>,
1867
1868 #[serde(skip_serializing_if = "Option::is_none")]
1870 #[cfg_attr(feature = "uniffi", uniffi(default))]
1871 pub ultimate: Option<String>,
1872 }
1873
1874 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1875 #[serde(rename_all = "camelCase")]
1876 #[serde(rename_all_fields = "camelCase")]
1877 #[non_exhaustive]
1878 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1879 pub enum BankTransactionCode {
1880 Iso {
1882 domain: String,
1884
1885 family: String,
1887
1888 sub_family: String,
1890 },
1891 Swift(String),
1893 Bai(String),
1895 National { code: String, country: CountryCode },
1897 Other {
1899 code: String,
1900 #[serde(skip_serializing_if = "Option::is_none")]
1901 issuer: Option<String>,
1902 },
1903 }
1904}
1905
1906#[allow(clippy::trivially_copy_pass_by_ref)]
1907fn is_false(val: &bool) -> bool {
1908 !*val
1909}
1910
1911#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1912#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1913#[non_exhaustive]
1914pub enum PaymentStatus {
1915 Accepted,
1919
1920 PartiallyAccepted,
1922
1923 CompletedDebtor,
1925
1926 CompletedCreditor,
1928}
1929
1930pub mod collect_payment {
1931 pub use routex_models::Amount;
1932 use serde::{Deserialize, Serialize};
1933 use serde_with::base64::Base64;
1934
1935 #[cfg(not(feature = "server"))]
1936 pub use super::{
1937 AccountIdentifier as DebtorAccountIdentifier, AccountReference as DebtorAccountReference,
1938 };
1939 use super::{AccountIdentifier, Path, PaymentStatus, ServiceId};
1940
1941 pub fn status_path(ticket_id: &str) -> Path {
1946 Path::new(["collect-payment", "status", ticket_id])
1947 }
1948
1949 #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
1950 #[serde(rename_all = "camelCase")]
1951 #[non_exhaustive]
1952 pub struct SuccessStatusData {
1953 #[serde(skip_serializing_if = "Option::is_none")]
1954 pub payment_status: Option<PaymentStatus>,
1955 }
1956
1957 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1958 #[serde(rename_all = "camelCase")]
1959 pub struct TicketData {
1960 pub amount: Amount,
1961 pub creditor_account: AccountIdentifier,
1962 pub creditor_name: String,
1963 pub remittance: String,
1964 #[serde(skip_serializing_if = "Option::is_none")]
1965 pub instant: Option<bool>,
1966 #[serde(skip_serializing_if = "Option::is_none")]
1967 pub fields: Option<Vec<Field>>,
1968 }
1969
1970 #[derive(Clone, Debug)]
1971 pub struct Service {}
1972
1973 impl super::Service for Service {
1974 const ID: ServiceId = ServiceId::CollectPayment;
1975
1976 type TicketData = TicketData;
1977
1978 type RequestData = RequestData;
1979
1980 type Output = PaymentInitiation;
1981 }
1982
1983 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1984 #[serde(rename_all = "camelCase")]
1985 #[non_exhaustive]
1986 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1987 pub enum Field {
1988 DebtorIban,
1989 DebtorName,
1990 EncryptedDebtorIban,
1991 }
1992
1993 #[cfg(feature = "server")]
1994 #[serde_with::serde_as]
1995 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1996 #[serde(rename_all = "camelCase")]
1997 #[serde(rename_all_fields = "camelCase")]
1998 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1999 pub enum DebtorAccountIdentifier {
2000 #[serde(alias = "Iban")]
2002 Iban(String),
2003 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
2004 }
2005
2006 #[cfg(feature = "server")]
2007 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2008 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2009 #[serde(rename_all = "camelCase")]
2010 pub struct DebtorAccountReference {
2011 #[serde(flatten)]
2012 pub id: DebtorAccountIdentifier,
2013 #[serde(skip_serializing_if = "Option::is_none")]
2014 #[cfg_attr(feature = "uniffi", uniffi(default))]
2015 pub currency: Option<String>,
2016 }
2017
2018 #[derive(Serialize, Deserialize, Clone)]
2019 #[serde(rename_all = "camelCase")]
2020 pub struct RequestData {
2021 #[serde(skip_serializing_if = "Option::is_none")]
2022 pub account: Option<DebtorAccountReference>,
2023 }
2024
2025 #[serde_with::serde_as]
2026 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2027 #[serde(rename_all = "camelCase")]
2028 #[non_exhaustive]
2029 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2030 pub struct PaymentInitiation {
2031 #[serde(skip_serializing_if = "Option::is_none")]
2032 #[cfg_attr(feature = "uniffi", uniffi(default))]
2033 pub status: Option<PaymentStatus>,
2034 #[serde(skip_serializing_if = "Option::is_none")]
2035 #[cfg_attr(feature = "uniffi", uniffi(default))]
2036 pub debtor_name: Option<String>,
2037 #[serde(skip_serializing_if = "Option::is_none")]
2038 #[cfg_attr(feature = "uniffi", uniffi(default))]
2039 pub debtor_iban: Option<String>,
2040 #[serde(skip_serializing_if = "Option::is_none")]
2041 #[cfg_attr(feature = "uniffi", uniffi(default))]
2042 #[serde_as(as = "Option<Base64>")]
2043 pub encrypted_debtor_iban: Option<Vec<u8>>,
2044 }
2045}
2046
2047pub mod transfer {
2048 pub use routex_models::{ChargeBearer, CreditorAddress, ISODateTimeOrDate, PaymentProduct};
2049 use serde::{Deserialize, Serialize};
2050
2051 use super::{AccountIdentifier, AccountReference, Amount, PaymentStatus, ServiceId};
2052
2053 #[derive(Clone, Debug)]
2054 pub struct Service {}
2055
2056 impl super::Service for Service {
2057 const ID: ServiceId = ServiceId::Transfer;
2058
2059 type TicketData = ();
2060
2061 type RequestData = RequestData;
2062
2063 type Output = Transfer;
2064 }
2065
2066 #[derive(Serialize, Deserialize, Clone)]
2067 #[serde(rename_all = "camelCase")]
2068 pub struct RequestData {
2069 pub product: PaymentProduct,
2070 #[serde(skip_serializing_if = "Option::is_none")]
2071 pub debtor_account: Option<AccountReference>,
2072 #[serde(skip_serializing_if = "Option::is_none")]
2073 pub debtor_name: Option<String>,
2074 #[serde(skip_serializing_if = "Option::is_none")]
2075 pub requested_execution_date: Option<ISODateTimeOrDate>,
2076 pub details: Vec<TransferDetails>,
2077 }
2078
2079 #[derive(Serialize, Deserialize, Clone)]
2080 #[serde(rename_all = "camelCase")]
2081 #[non_exhaustive]
2082 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2083 pub struct TransferDetails {
2084 #[serde(skip_serializing_if = "Option::is_none")]
2085 #[cfg_attr(feature = "uniffi", uniffi(default))]
2086 pub end_to_end_identification: Option<String>,
2087 pub amount: Amount,
2088 pub creditor_account: AccountIdentifier,
2089 #[serde(skip_serializing_if = "Option::is_none")]
2090 #[cfg_attr(feature = "uniffi", uniffi(default))]
2091 pub creditor_agent_bic: Option<String>,
2092 pub creditor_name: String,
2093 #[serde(skip_serializing_if = "Option::is_none")]
2094 #[cfg_attr(feature = "uniffi", uniffi(default))]
2095 pub creditor_address: Option<CreditorAddress>,
2096 #[serde(skip_serializing_if = "Option::is_none")]
2097 #[cfg_attr(feature = "uniffi", uniffi(default))]
2098 pub remittance: Option<String>,
2099 #[serde(skip_serializing_if = "Option::is_none")]
2100 #[cfg_attr(feature = "uniffi", uniffi(default))]
2101 pub charge_bearer: Option<ChargeBearer>,
2102 }
2103
2104 impl TransferDetails {
2105 pub fn new(
2106 amount: Amount,
2107 creditor_account: AccountIdentifier,
2108 creditor_name: impl Into<String>,
2109 ) -> Self {
2110 Self {
2111 end_to_end_identification: None,
2112 amount,
2113 creditor_account,
2114 creditor_agent_bic: None,
2115 creditor_name: creditor_name.into(),
2116 creditor_address: None,
2117 remittance: None,
2118 charge_bearer: None,
2119 }
2120 }
2121 }
2122
2123 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2124 #[serde(rename_all = "camelCase")]
2125 #[non_exhaustive]
2126 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2127 pub struct Transfer {
2128 #[serde(skip_serializing_if = "Option::is_none")]
2129 #[cfg_attr(feature = "uniffi", uniffi(default))]
2130 pub status: Option<PaymentStatus>,
2131 }
2132}
2133
2134#[cfg(test)]
2135mod tests {
2136 use std::fmt::Debug;
2137
2138 use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
2139 use serde::{Deserialize, Serialize};
2140 use serde_json::json;
2141 use uuid::Uuid;
2142
2143 use crate::{
2144 Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
2145 InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
2146 };
2147
2148 #[test]
2149 fn read_response() {
2150 let serialized = serde_json::to_string(
2151 &encode(
2152 &Header::new(Algorithm::HS256),
2153 &Claims {
2154 data: "data",
2155 exp: 2_540_808_000,
2156 },
2157 &EncodingKey::from_secret(b"does_not_matter"),
2158 )
2159 .unwrap(),
2160 )
2161 .unwrap();
2162
2163 let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
2164
2165 assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
2166 }
2167
2168 struct Service;
2169
2170 impl crate::Service for Service {
2171 const ID: ServiceId = ServiceId::Accounts;
2172
2173 type TicketData = ();
2174
2175 type RequestData = ();
2176
2177 type Output = ();
2178 }
2179
2180 #[allow(
2181 dead_code,
2182 unconditional_recursion,
2183 clippy::extra_unused_type_parameters
2184 )]
2185 fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
2186 test_bounds::<InputContext<Service>>();
2187 test_bounds::<ConfirmationContext<Service>>();
2188 test_bounds::<DialogInput<Service>>();
2189 test_bounds::<Dialog<Service>>();
2190 test_bounds::<Redirect<Service>>();
2191 test_bounds::<RedirectHandle<Service>>();
2192 test_bounds::<OBResult<()>>();
2193 test_bounds::<Session>();
2194
2195 test_bounds::<Authenticated<Ticket<Service>>>();
2196 test_bounds::<Ticket<Service>>();
2197 test_bounds::<ResponseData<Service>>();
2198 test_bounds::<ConfirmationData<Service>>();
2199 }
2200
2201 fn test_eq_<T: PartialEq + Debug>(o: &T) {
2202 assert!(o.eq(o));
2203 }
2204
2205 #[test]
2206 fn test_eq() {
2207 let ic = InputContext::<Service>::from(vec![]);
2208 let cc = ConfirmationContext::<Service>::from(vec![]);
2209
2210 test_eq_(&ic);
2211 test_eq_(&cc);
2212 test_eq_(&Redirect {
2213 url: "url:".parse().unwrap(),
2214 context: cc.clone(),
2215 });
2216 test_eq_(&RedirectHandle {
2217 handle: String::new(),
2218 context: cc.clone(),
2219 });
2220 test_eq_(&Ticket::<Service> {
2221 service: ServiceId::Accounts,
2222 id: Uuid::new_v4(),
2223 data: (),
2224 });
2225 test_eq_(&ResponseData {
2226 context: ic,
2227 response: String::new(),
2228 });
2229 test_eq_(&ConfirmationData { context: cc });
2230 }
2231
2232 #[test]
2233 fn no_ticket_data() {
2234 assert!(
2235 serde_json::from_value::<Ticket<Service>>(json!({
2236 "id": Uuid::new_v4(),
2237 "service": ServiceId::Accounts,
2238 }))
2239 .is_ok()
2240 );
2241 }
2242
2243 #[test]
2244 fn missing_ticket_data() {
2245 let err = serde_json::from_value::<Ticket<super::collect_payment::Service>>(json!({
2246 "id": Uuid::new_v4(),
2247 "service": ServiceId::CollectPayment,
2248 }))
2249 .unwrap_err();
2250
2251 assert_eq!(err.to_string(), "missing field `data`");
2252 }
2253}