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::net::IpAddr;
23use std::str::FromStr;
24use url::Url;
25use uuid::Uuid;
26
27#[cfg(feature = "uniffi")]
28uniffi::setup_scaffolding!();
29
30#[cfg(feature = "uniffi")]
31uniffi::custom_type!(ConnectionData, Vec<u8>, {
32 try_lift: |val| Ok(val.into()),
33 lower: |obj| obj.into(),
34});
35
36#[cfg(feature = "uniffi")]
37uniffi::custom_type!(Session, Vec<u8>, {
38 try_lift: |val| Ok(val.into()),
39 lower: |obj| obj.into(),
40});
41
42#[cfg(feature = "uniffi")]
43uniffi::use_remote_type!(routex_models::CountryCode);
44
45#[cfg(feature = "uniffi")]
46uniffi::use_remote_type!(routex_models::Decimal);
47
48#[cfg(feature = "uniffi")]
49uniffi::custom_type!(NaiveDate, String, {
50 remote,
51 try_lift: |val| Ok(val.parse()?),
52 lower: |obj| obj.to_string(),
53});
54
55type DateTime = chrono::DateTime<Utc>;
56
57#[cfg(feature = "uniffi")]
58uniffi::custom_type!(DateTime, String, {
59 remote,
60 try_lift: |val| Ok(val.parse()?),
61 lower: |obj| obj.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
62});
63
64pub type Dialog<S> = routex_models::Dialog<ConfirmationContext<S>, InputContext<S>>;
65pub type DialogInput<S> = routex_models::DialogInput<ConfirmationContext<S>, InputContext<S>>;
66
67#[must_use]
74pub struct Path(Vec<String>);
75
76impl Path {
77 fn new<I>(segments: I) -> Self
78 where
79 I: IntoIterator,
80 I::Item: ToString,
81 {
82 Self(segments.into_iter().map(|s| s.to_string()).collect())
83 }
84
85 #[must_use]
86 pub fn to_url(&self, base: &Url) -> Url {
92 let mut url = base.clone();
93 url.path_segments_mut()
94 .expect("cannot be base")
95 .extend(&self.0);
96 url
97 }
98}
99
100impl fmt::Display for Path {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 for segment in &self.0 {
103 f.write_str("/")?;
104 f.write_str(segment)?;
105 }
106
107 Ok(())
108 }
109}
110
111pub mod headers {
112 use http::HeaderName;
113
114 pub static SESSION_ID: HeaderName = HeaderName::from_static("yaxi-session-id");
115
116 pub static TICKET: HeaderName = HeaderName::from_static("yaxi-ticket");
117
118 pub static TICKET_ID: HeaderName = HeaderName::from_static("yaxi-ticket-id");
119
120 pub static REDIRECT_URI: HeaderName = HeaderName::from_static("yaxi-redirect-uri");
121
122 pub static TRACE_ID: HeaderName = HeaderName::from_static("yaxi-trace-id");
123}
124
125#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
135#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
136pub enum UserInSession {
137 #[serde(rename = "connection")]
139 OnThisConnection,
140 #[serde(untagged)]
142 At(IpAddr),
143}
144
145#[cfg(feature = "uniffi")]
146uniffi::custom_type!(IpAddr, String, {
147 remote,
148 try_lift: |val| Ok(val.parse()?),
149 lower: |obj| obj.to_string(),
150});
151
152pub const CURRENT_MEDIA_TYPE: &str = "application/vnd.yaxi.v6";
153
154pub mod keys {
155 use super::Path;
156 use clerk_report::PublishedVersionEntry;
157 use serde::{Deserialize, Serialize};
158 use serde_with::base64::Base64;
159
160 pub fn settlement_path() -> Path {
166 Path::new(["key-settlement"])
167 }
168
169 #[serde_with::serde_as]
170 #[derive(Serialize, Deserialize, Clone, Debug)]
171 #[serde(rename_all = "camelCase")]
172 pub struct Request {
173 #[serde_as(as = "Base64")]
175 pub public_key: [u8; 32],
176 }
177
178 #[serde_with::serde_as]
180 #[derive(Serialize, Deserialize)]
181 #[serde(rename_all = "camelCase")]
182 pub struct SettlementBoxMessage {
183 #[serde_as(as = "Base64")]
184 pub public_key: [u8; 32],
185 #[serde_as(as = "Base64")]
186 pub session_id: [u8; 32],
187 }
188
189 #[serde_with::serde_as]
191 #[derive(Serialize, Deserialize, Clone, Debug)]
192 #[serde(rename_all = "camelCase")]
193 pub struct Response {
194 #[serde_as(as = "Base64")]
196 pub attestation_report: Vec<u8>,
197 pub vcek: String,
199 #[serde_as(as = "Base64")]
201 pub chacha_box: Vec<u8>,
202 pub system_version: PublishedVersionEntry,
205 #[serde(default = "default_vcpus")]
207 pub vcpus: usize,
208 }
209
210 fn default_vcpus() -> usize {
211 2
212 }
213}
214
215pub mod traces {
216 use super::Path;
217
218 pub fn path(trace_id: &str) -> Path {
223 Path::new(["traces", trace_id])
224 }
225}
226
227pub mod redirects {
228 use super::Path;
229 use serde::{Deserialize, Serialize};
230 use url::Url;
231
232 pub fn path() -> Path {
238 Path::new(["redirects"])
239 }
240
241 #[derive(Serialize, Deserialize, Clone, Debug)]
242 #[serde(rename_all = "camelCase")]
243 pub struct Request {
244 pub handle: String,
245 pub redirect_uri: String,
246 }
247
248 #[derive(Serialize, Deserialize, Clone, Debug)]
249 #[serde(rename_all = "camelCase")]
250 pub struct Response {
251 #[serde(serialize_with = "super::serialize_url")]
252 pub redirect_url: Url,
253 }
254}
255
256pub mod info {
257 use super::Path;
258 pub use isocountry::CountryCode;
259 use routex_models::{ConnectionId, CredentialsModel};
260 use serde::{Deserialize, Serialize};
261 #[cfg(feature = "server")]
262 use serde_with::base64::Base64;
263
264 pub fn search_path() -> Path {
270 Path::new(["search"])
271 }
272
273 #[derive(Serialize, Deserialize, Clone, Debug)]
274 #[serde(rename_all = "camelCase")]
275 #[non_exhaustive]
276 pub struct Request {
277 pub filters: Vec<SearchFilter>,
278 #[serde(default)]
279 pub iban_detection: bool,
280 #[serde(skip_serializing_if = "Option::is_none")]
281 pub limit: Option<usize>,
282 #[serde(default)]
283 #[serde(skip_serializing_if = "Vec::is_empty")]
284 pub details: Vec<Details>,
285 }
286
287 impl Request {
288 pub fn new(filters: impl IntoIterator<Item = SearchFilter>) -> Self {
289 Self {
290 filters: filters.into_iter().collect(),
291 iban_detection: false,
292 limit: None,
293 details: Vec::new(),
294 }
295 }
296 }
297
298 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
300 #[serde(rename_all = "camelCase")]
301 #[non_exhaustive]
302 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
303 pub enum Details {
304 Bics,
305 BankCodes,
306 }
307
308 #[serde_with::serde_as]
312 #[derive(Serialize, Deserialize, Clone, Debug)]
313 #[serde(rename_all = "camelCase")]
314 #[serde(rename_all_fields = "camelCase")]
315 pub enum SearchFilter {
316 Types(Vec<ConnectionType>),
318 Countries(Vec<CountryCode>),
320 Name(String),
322 Bic(String),
324 BankCode(String),
326 Term(String),
328 #[cfg(feature = "server")]
329 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
330 }
331
332 #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
334 #[non_exhaustive]
335 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
336 pub enum ConnectionType {
337 Production,
339 Sandboxes,
341 }
342
343 pub fn fetch_path(connection_id: &str) -> Path {
348 Path::new(["info", connection_id])
349 }
350
351 #[allow(clippy::module_name_repetitions)]
353 #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
354 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
355 #[serde(rename_all = "camelCase")]
356 #[cfg_attr(not(feature = "server"), non_exhaustive)]
357 pub struct ConnectionInfo {
358 pub id: ConnectionId,
360
361 pub countries: Vec<CountryCode>,
363
364 pub display_name: String,
366
367 pub credentials: CredentialsModel,
369
370 #[serde(skip_serializing_if = "Option::is_none")]
372 #[cfg_attr(feature = "uniffi", uniffi(default))]
373 pub user_id: Option<String>,
374
375 #[serde(skip_serializing_if = "Option::is_none")]
377 #[cfg_attr(feature = "uniffi", uniffi(default))]
378 pub password: Option<String>,
379
380 #[serde(skip_serializing_if = "Option::is_none")]
382 #[cfg_attr(feature = "uniffi", uniffi(default))]
383 pub advice: Option<String>,
384
385 pub logo_id: String,
387
388 #[allow(clippy::doc_markdown)]
389 #[serde(skip_serializing_if = "Option::is_none")]
393 #[cfg_attr(feature = "uniffi", uniffi(default))]
394 pub bics: Option<Vec<String>>,
395
396 #[serde(skip_serializing_if = "Option::is_none")]
400 #[cfg_attr(feature = "uniffi", uniffi(default))]
401 pub bank_codes: Option<Vec<String>>,
402
403 #[serde(default, skip_serializing_if = "Vec::is_empty")]
405 #[cfg_attr(feature = "uniffi", uniffi(default))]
406 pub labels: Vec<String>,
407 }
408}
409
410macro_rules! context {
411 ($name:ident) => {
412 #[serde_with::serde_as]
413 #[derive(Serialize, Deserialize)]
414 #[serde(transparent)]
415 pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
416
417 impl<S> PartialEq for $name<S> {
418 fn eq(&self, other: &Self) -> bool {
419 self.0 == other.0
420 }
421 }
422
423 impl<S> Eq for $name<S> {}
424
425 impl<S> Clone for $name<S> {
426 fn clone(&self) -> Self {
427 Self(self.0.clone(), self.1)
428 }
429 }
430
431 impl<S> fmt::Debug for $name<S> {
432 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
433 f.debug_tuple("$name")
434 .field(&self.0)
435 .field(&self.1)
436 .finish()
437 }
438 }
439
440 impl<S> From<Vec<u8>> for $name<S> {
441 fn from(value: Vec<u8>) -> Self {
442 Self(value, PhantomData)
443 }
444 }
445
446 impl<S> From<$name<S>> for Vec<u8> {
447 fn from(value: $name<S>) -> Self {
448 value.0
449 }
450 }
451
452 impl<S> AsRef<[u8]> for $name<S> {
453 fn as_ref(&self) -> &[u8] {
454 &self.0
455 }
456 }
457 };
458}
459
460context!(InputContext);
461
462context!(ConfirmationContext);
463
464#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
490#[serde(transparent)]
491pub struct Authenticated<T> {
492 jwt: String,
493 _phantom: PhantomData<T>,
494}
495
496impl<'de, T> Deserialize<'de> for Authenticated<T>
497where
498 T: for<'t> Deserialize<'t> + Clone,
499{
500 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
501 where
502 D: serde::Deserializer<'de>,
503 {
504 String::deserialize(deserializer)?
505 .parse()
506 .map_err(serde::de::Error::custom)
507 }
508}
509
510impl<T> Authenticated<T>
511where
512 T: Serialize,
513{
514 #[must_use]
515 pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
521 let header = Header {
522 kid: Some(key_id.into()),
523 ..Default::default()
524 };
525
526 let jwt = encode(
527 &header,
528 &Claims {
529 data,
530 exp: 2_540_808_000,
532 },
533 &EncodingKey::from_secret(key),
534 )
535 .expect("Encoding should work");
536
537 Self {
538 jwt,
539 _phantom: PhantomData,
540 }
541 }
542}
543
544impl<T> FromStr for Authenticated<T>
545where
546 T: for<'de> Deserialize<'de> + Clone,
547{
548 type Err = jsonwebtoken::errors::Error;
549
550 fn from_str(jwt: &str) -> Result<Self, Self::Err> {
551 decode::<T>(jwt)?;
553 Ok(Self {
554 jwt: jwt.to_string(),
555 _phantom: PhantomData,
556 })
557 }
558}
559
560impl<T> Authenticated<T>
561where
562 T: for<'de> Deserialize<'de> + Clone,
563{
564 #[must_use]
565 pub fn as_str(&self) -> &str {
566 &self.jwt
567 }
568
569 #[must_use]
570 pub fn key_id(&self) -> Option<String> {
576 decode_header(&self.jwt).unwrap().kid
577 }
578
579 #[cfg(feature = "client")]
580 #[must_use]
581 #[allow(clippy::missing_panics_doc)]
582 pub fn to_data(&self) -> T {
583 decode(&self.jwt).unwrap()
584 }
585
586 #[cfg(feature = "server")]
587 pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
593 decoder.decode::<T>(&self.jwt).map(|d| d.data)
594 }
595}
596
597#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
598#[serde(rename_all = "camelCase")]
599pub struct Claims<T> {
600 pub data: T,
601 pub exp: u64,
602}
603
604#[cfg(feature = "server")]
605pub trait Decoder {
606 type Error;
607
608 fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
614 where
615 T: for<'de> Deserialize<'de> + Clone;
616}
617
618#[derive(Serialize, Deserialize, Debug)]
623pub enum OBResponse<S: Service> {
624 #[serde(bound = "S:")]
625 Result(
626 Authenticated<OBResult<S::Output>>,
627 Option<Session>,
628 Option<ConnectionData>,
629 ),
630 #[serde(bound = "S:")]
631 Dialog(Dialog<S>),
632 #[serde(bound = "S:")]
633 Redirect(Redirect<S>),
634 #[serde(bound = "S:")]
635 RedirectHandle(RedirectHandle<S>),
636}
637
638impl<S: Service> Clone for OBResponse<S> {
639 fn clone(&self) -> Self {
640 match self {
641 OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
642 authenticated.clone(),
643 session.clone(),
644 connection_data.clone(),
645 ),
646 OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
647 OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
648 OBResponse::RedirectHandle(redirect_handle) => {
649 OBResponse::RedirectHandle(redirect_handle.clone())
650 }
651 }
652 }
653}
654
655#[derive(Serialize, Deserialize, Debug)]
656#[cfg_attr(not(feature = "server"), non_exhaustive)]
657#[serde(rename_all = "camelCase")]
658pub struct NonInteractiveResponse<S: Service> {
659 #[serde(bound = "S:")]
660 pub result: S::Output,
661 #[serde(skip_serializing_if = "Option::is_none")]
662 pub session: Option<Session>,
663 #[serde(skip_serializing_if = "Option::is_none")]
664 pub connection_data: Option<ConnectionData>,
665}
666
667#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
672#[serde(rename_all = "camelCase")]
673pub struct OBResult<T> {
674 pub data: T,
675 pub ticket_id: Uuid,
676 pub timestamp: DateTime,
677}
678
679#[serde_with::serde_as]
680#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
681pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
682
683impl From<Vec<u8>> for Session {
684 fn from(value: Vec<u8>) -> Self {
685 Session(value)
686 }
687}
688
689impl From<Session> for Vec<u8> {
690 fn from(session: Session) -> Self {
691 session.0
692 }
693}
694
695impl AsRef<[u8]> for Session {
696 fn as_ref(&self) -> &[u8] {
697 &self.0
698 }
699}
700
701#[serde_with::serde_as]
702#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
703#[serde(transparent)]
704pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
705
706impl From<Vec<u8>> for ConnectionData {
707 fn from(value: Vec<u8>) -> Self {
708 ConnectionData(value)
709 }
710}
711
712impl From<ConnectionData> for Vec<u8> {
713 fn from(connection_data: ConnectionData) -> Self {
714 connection_data.0
715 }
716}
717
718impl AsRef<[u8]> for ConnectionData {
719 fn as_ref(&self) -> &[u8] {
720 &self.0
721 }
722}
723
724#[derive(Serialize, Deserialize)]
732#[serde(rename_all = "camelCase")]
733pub struct Redirect<S> {
734 #[serde(serialize_with = "serialize_url")]
735 pub url: Url,
736 #[serde(bound = "S:")]
737 pub context: ConfirmationContext<S>,
738}
739
740impl<S> PartialEq for Redirect<S> {
741 fn eq(&self, other: &Self) -> bool {
742 let Self { url, context } = self;
743 url == &other.url && context == &other.context
744 }
745}
746
747impl<S> Eq for Redirect<S> {}
748
749impl<S> Clone for Redirect<S> {
750 fn clone(&self) -> Self {
751 Self {
752 url: self.url.clone(),
753 context: self.context.clone(),
754 }
755 }
756}
757
758impl<S> fmt::Debug for Redirect<S> {
759 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
760 f.debug_struct("Redirect")
761 .field("url", &self.url)
762 .field("context", &self.context)
763 .finish()
764 }
765}
766
767fn serialize_url<S: Serializer>(value: &Url, serializer: S) -> Result<S::Ok, S::Error> {
770 let string_repr = value.to_string().replace('+', "%20");
771 serializer.serialize_str(&string_repr)
772}
773
774#[derive(Serialize, Deserialize)]
778#[serde(rename_all = "camelCase")]
779pub struct RedirectHandle<S> {
780 pub handle: String,
781 #[serde(bound = "S:")]
782 pub context: ConfirmationContext<S>,
783}
784
785impl<S> PartialEq for RedirectHandle<S> {
786 fn eq(&self, other: &Self) -> bool {
787 let Self { handle, context } = self;
788 handle == &other.handle && context == &other.context
789 }
790}
791
792impl<S> Eq for RedirectHandle<S> {}
793
794impl<S> Clone for RedirectHandle<S> {
795 fn clone(&self) -> Self {
796 Self {
797 handle: self.handle.clone(),
798 context: self.context.clone(),
799 }
800 }
801}
802
803impl<S> fmt::Debug for RedirectHandle<S> {
804 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805 f.debug_struct("RedirectHandle")
806 .field("handle", &self.handle)
807 .field("context", &self.context)
808 .finish()
809 }
810}
811
812fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
814where
815 T: for<'de> Deserialize<'de> + Clone,
816{
817 insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
818}
819
820macro_rules! enum_with_display {
821 {
822 $(#[$meta:meta])*
823 pub enum $name:ident {
824 $($variant:ident,)+
825 }
826 } => {
827 $(#[$meta])*
828 pub enum $name {
829 $($variant,)+
830 }
831
832 impl fmt::Display for $name {
833 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834 f.write_str(match self {
835 $(Self::$variant => stringify!($variant),)+
836 })
837 }
838 }
839 }
840}
841
842enum_with_display! {
843 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
844 pub enum ServiceId {
845 Accounts,
846 CollectPayment,
847 Transactions,
848 Balances,
849 Transfer,
850 }
851}
852
853#[derive(Serialize, Deserialize)]
913#[serde(rename_all = "camelCase")]
914#[serde(try_from = "DeserializedTicket<S::TicketData>")]
915pub struct Ticket<S: Service> {
916 pub service: ServiceId,
917 pub id: Uuid,
918 pub data: S::TicketData,
919}
920
921impl<S: Service> PartialEq for Ticket<S> {
922 fn eq(&self, other: &Self) -> bool {
923 let Self { service, id, data } = self;
924 service == &other.service && id == &other.id && data == &other.data
925 }
926}
927
928impl<S: Service> Eq for Ticket<S> {}
929
930impl<S: Service> fmt::Debug for Ticket<S> {
931 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
932 f.debug_struct("Ticket")
933 .field("service", &self.service)
934 .field("id", &self.id)
935 .field("data", &self.data)
936 .finish()
937 }
938}
939
940impl<S: Service> Clone for Ticket<S> {
941 fn clone(&self) -> Self {
942 Self {
943 service: self.service,
944 id: self.id,
945 data: self.data.clone(),
946 }
947 }
948}
949
950#[derive(Deserialize, Debug)]
951struct DeserializedTicket<T> {
952 service: ServiceId,
953 id: Uuid,
954 data: Option<T>,
955}
956
957impl<S: Service> TryFrom<DeserializedTicket<S::TicketData>> for Ticket<S> {
958 type Error = &'static str;
959
960 fn try_from(
961 DeserializedTicket { service, id, data }: DeserializedTicket<S::TicketData>,
962 ) -> Result<Self, Self::Error> {
963 let data = (&() as &dyn Any)
964 .downcast_ref::<S::TicketData>()
965 .cloned()
966 .or(data)
967 .ok_or("missing field `data`")?;
968
969 Ok(Self { service, id, data })
970 }
971}
972
973pub trait Service {
974 const ID: ServiceId;
975
976 type TicketData: Serialize
977 + for<'de> Deserialize<'de>
978 + PartialEq
979 + Eq
980 + Clone
981 + fmt::Debug
982 + Send
983 + Sync
984 + 'static;
985
986 type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
987
988 type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
989
990 fn path() -> Path {
996 Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
997 }
998
999 fn response_path() -> Path {
1000 Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
1001 }
1002
1003 fn confirmation_path() -> Path {
1004 Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
1005 }
1006}
1007
1008pub trait NonInteractiveService: Service {
1009 fn non_interactive_path() -> Path {
1010 Path::new([&Self::ID.to_string().to_kebab_case(), "non-interactive"])
1011 }
1012}
1013
1014#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1015#[serde(tag = "ticketStatus")]
1016pub enum TicketStatus<T> {
1017 Unfinished,
1018 Error(ErrorKind),
1019 Success(T),
1020}
1021
1022enum_with_display! {
1023 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1024 #[serde(tag = "error")]
1025 pub enum ErrorKind {
1026 UnexpectedError,
1027 Canceled,
1028 InvalidCredentials,
1029 ServiceBlocked,
1030 Unauthorized,
1031 AccessExceeded,
1032 PeriodOutOfBounds,
1033 UnsupportedProduct,
1034 PaymentFailed,
1035 UnexpectedValue,
1036 TicketError,
1037 ProviderError,
1038 NotFound,
1039 }
1040}
1041
1042#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1043#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1044#[serde(rename_all = "camelCase")]
1045pub struct Credentials {
1046 pub connection_id: ConnectionId,
1047 #[serde(skip_serializing_if = "Option::is_none")]
1048 #[cfg_attr(feature = "uniffi", uniffi(default))]
1049 pub user_id: Option<String>,
1050 #[serde(skip_serializing_if = "Option::is_none")]
1051 #[cfg_attr(feature = "uniffi", uniffi(default))]
1052 pub password: Option<String>,
1053 #[serde(skip_serializing_if = "Option::is_none")]
1054 #[cfg_attr(feature = "uniffi", uniffi(default))]
1055 pub connection_data: Option<ConnectionData>,
1056}
1057
1058#[derive(Serialize, Deserialize)]
1059#[serde(rename_all = "camelCase")]
1060pub struct ResponseData<S> {
1061 #[serde(bound = "S:")]
1062 pub context: InputContext<S>,
1063 pub response: String,
1064}
1065
1066impl<S> PartialEq for ResponseData<S> {
1067 fn eq(&self, other: &Self) -> bool {
1068 let Self { context, response } = self;
1069 context == &other.context && response == &other.response
1070 }
1071}
1072
1073impl<S> Eq for ResponseData<S> {}
1074
1075impl<S> Clone for ResponseData<S> {
1076 fn clone(&self) -> Self {
1077 Self {
1078 context: self.context.clone(),
1079 response: self.response.clone(),
1080 }
1081 }
1082}
1083
1084impl<S> fmt::Debug for ResponseData<S> {
1085 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1086 f.debug_struct("ResponseData")
1087 .field("context", &self.context)
1088 .field("response", &self.response)
1089 .finish()
1090 }
1091}
1092
1093#[derive(Serialize, Deserialize)]
1094#[serde(rename_all = "camelCase")]
1095pub struct ConfirmationData<S> {
1096 #[serde(bound = "S:")]
1097 pub context: ConfirmationContext<S>,
1098}
1099
1100impl<S> PartialEq for ConfirmationData<S> {
1101 fn eq(&self, other: &Self) -> bool {
1102 let Self { context } = self;
1103 context == &other.context
1104 }
1105}
1106
1107impl<S> Eq for ConfirmationData<S> {}
1108
1109impl<S> Clone for ConfirmationData<S> {
1110 fn clone(&self) -> Self {
1111 Self {
1112 context: self.context.clone(),
1113 }
1114 }
1115}
1116
1117impl<S> fmt::Debug for ConfirmationData<S> {
1118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1119 f.debug_struct("ConfirmationData")
1120 .field("context", &self.context)
1121 .finish()
1122 }
1123}
1124
1125#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1126#[serde(rename_all_fields = "camelCase")]
1127#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1128pub enum Error {
1129 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1130 UnexpectedError {
1131 #[serde(skip_serializing_if = "Option::is_none")]
1132 user_message: Option<String>,
1133 },
1134 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1135 Canceled {},
1136 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1137 InvalidCredentials {
1138 #[serde(skip_serializing_if = "Option::is_none")]
1139 user_message: Option<String>,
1140 },
1141 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1142 ServiceBlocked {
1143 #[serde(skip_serializing_if = "Option::is_none")]
1144 user_message: Option<String>,
1145 #[serde(skip_serializing_if = "Option::is_none")]
1146 code: Option<ServiceBlockedCode>,
1147 },
1148 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1149 Unauthorized {
1150 #[serde(skip_serializing_if = "Option::is_none")]
1151 user_message: Option<String>,
1152 },
1153 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1160 AccessExceeded {
1161 #[serde(skip_serializing_if = "Option::is_none")]
1162 user_message: Option<String>,
1163 },
1164 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1165 PeriodOutOfBounds {
1166 #[serde(skip_serializing_if = "Option::is_none")]
1167 user_message: Option<String>,
1168 },
1169 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1170 UnsupportedProduct {
1171 #[serde(skip_serializing_if = "Option::is_none")]
1172 reason: Option<UnsupportedProductReason>,
1173 #[serde(skip_serializing_if = "Option::is_none")]
1174 user_message: Option<String>,
1175 },
1176 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1177 PaymentFailed {
1178 #[serde(skip_serializing_if = "Option::is_none")]
1179 code: Option<PaymentErrorCode>,
1180 #[serde(skip_serializing_if = "Option::is_none")]
1181 user_message: Option<String>,
1182 },
1183 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1184 UnexpectedValue { error: String },
1185 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1186 TicketError {
1187 error: String,
1188 code: TicketErrorCode,
1189 },
1190 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1191 ProviderError {
1192 #[serde(skip_serializing_if = "Option::is_none")]
1193 code: Option<ProviderErrorCode>,
1194 #[serde(skip_serializing_if = "Option::is_none")]
1195 user_message: Option<String>,
1196 },
1197 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1198 InterruptError {},
1199}
1200
1201#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1202#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1203#[cfg_attr(not(feature = "server"), non_exhaustive)]
1204pub enum TicketErrorCode {
1205 Missing,
1207 Invalid,
1209 MissingKey,
1211 UnknownKey,
1213 Mismatch,
1215 Expired,
1217 InvalidLifetime,
1219 ExpiredKey,
1221 KeyEnvironmentMismatch,
1223}
1224
1225#[derive(Serialize, Deserialize, Clone, Debug)]
1226#[cfg_attr(not(feature = "server"), non_exhaustive)]
1227pub enum Filter<F> {
1228 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1229 Eq(F, Value),
1230 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1231 NotEq(F, Value),
1232 #[cfg(feature = "server")]
1233 Contains(F, Value),
1234 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1235 And(Box<Self>, Box<Self>),
1236 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1237 Or(Box<Self>, Box<Self>),
1238 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1239 Supports(SupportedService),
1240}
1241
1242#[derive(Serialize, Deserialize, Clone, Debug)]
1243#[cfg_attr(not(feature = "server"), non_exhaustive)]
1244#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1245pub enum SupportedService {
1246 CollectPayment,
1247}
1248
1249impl<F> Filter<F> {
1250 #[must_use]
1251 pub fn and(self, other: Filter<F>) -> Filter<F> {
1253 Filter::And(Box::new(self), Box::new(other))
1254 }
1255
1256 #[must_use]
1257 pub fn or(self, other: Filter<F>) -> Filter<F> {
1259 Filter::Or(Box::new(self), Box::new(other))
1260 }
1261}
1262
1263pub struct Field<F, T> {
1264 field: F,
1265 phantom: PhantomData<T>,
1266}
1267
1268impl<F, T> Field<F, T>
1269where
1270 F: Copy,
1271 T: Serialize,
1272{
1273 pub fn eq(&self, value: T) -> Filter<F> {
1279 Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1280 }
1281
1282 pub fn not_eq(&self, value: T) -> Filter<F> {
1288 Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1289 }
1290}
1291
1292pub trait GetValue {
1293 type Model;
1294
1295 fn get(&self, model: &Self::Model) -> Value;
1296}
1297
1298macro_rules! fields {
1299 {
1300 $model:ty, $enum_name:ident
1301 $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1302 $(
1303 server:
1304 $(($server_variant_name:ident, $server_field_name:ident))+
1305 )?
1306 } => {
1307 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1308 #[non_exhaustive]
1309 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1310 pub enum $enum_name {
1311 $($variant_name,)+
1312 $($(
1313 #[cfg(feature = "server")]
1314 $server_variant_name,
1315 )+)?
1316 }
1317
1318 impl $enum_name {
1319 $(
1320 pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1321 field: $enum_name::$variant_name,
1322 phantom: std::marker::PhantomData,
1323 };
1324 )+
1325 }
1326
1327 impl crate::GetValue for $enum_name {
1328 type Model = $model;
1329
1330 fn get(&self, model: &$model) -> serde_json::Value {
1331 match self {
1332 $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1333 $($(
1334 #[cfg(feature = "server")]
1335 $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1336 )+)?
1337 }.expect("Serialization should work")
1338 }
1339 }
1340 }
1341}
1342
1343#[derive(Serialize, Deserialize)]
1344#[serde(rename_all = "camelCase")]
1345pub struct ServiceRequest<S: Service> {
1346 pub credentials: Credentials,
1347 #[serde(skip_serializing_if = "Option::is_none")]
1348 pub session: Option<Session>,
1349 #[serde(default, skip_serializing_if = "is_false")]
1350 pub recurring_consents: bool,
1351 #[serde(flatten, bound = "S:")]
1352 pub data: <S as Service>::RequestData,
1353}
1354
1355impl<S: Service> Clone for ServiceRequest<S> {
1356 fn clone(&self) -> Self {
1357 Self {
1358 credentials: self.credentials.clone(),
1359 session: self.session.clone(),
1360 recurring_consents: self.recurring_consents,
1361 data: self.data.clone(),
1362 }
1363 }
1364}
1365
1366#[derive(Serialize, Deserialize)]
1367#[serde(rename_all = "camelCase")]
1368pub struct NonInteractiveRequest<S: Service> {
1369 pub connection_data: ConnectionData,
1370 #[serde(skip_serializing_if = "Option::is_none")]
1371 pub session: Option<Session>,
1372 #[serde(default, skip_serializing_if = "Option::is_none")]
1373 pub user_in_session: Option<UserInSession>,
1374 #[serde(flatten, bound = "S:")]
1375 pub data: <S as Service>::RequestData,
1376}
1377
1378#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1379#[serde(rename_all = "camelCase")]
1380#[non_exhaustive]
1381#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1382pub struct Account {
1383 #[serde(skip_serializing_if = "Option::is_none")]
1385 #[cfg_attr(feature = "uniffi", uniffi(default))]
1386 pub iban: Option<String>,
1387
1388 #[serde(skip_serializing_if = "Option::is_none")]
1390 #[cfg_attr(feature = "uniffi", uniffi(default))]
1391 pub number: Option<String>,
1392
1393 #[serde(skip_serializing_if = "Option::is_none")]
1395 #[cfg_attr(feature = "uniffi", uniffi(default))]
1396 pub bic: Option<String>,
1397
1398 #[serde(skip_serializing_if = "Option::is_none")]
1400 #[cfg_attr(feature = "uniffi", uniffi(default))]
1401 pub bank_code: Option<String>,
1402
1403 #[serde(skip_serializing_if = "Option::is_none")]
1405 #[cfg_attr(feature = "uniffi", uniffi(default))]
1406 pub currency: Option<String>,
1407
1408 #[serde(skip_serializing_if = "Option::is_none")]
1410 #[cfg_attr(feature = "uniffi", uniffi(default))]
1411 pub name: Option<String>,
1412
1413 #[serde(skip_serializing_if = "Option::is_none")]
1415 #[cfg_attr(feature = "uniffi", uniffi(default))]
1416 pub display_name: Option<String>,
1417
1418 #[serde(skip_serializing_if = "Option::is_none")]
1420 #[cfg_attr(feature = "uniffi", uniffi(default))]
1421 pub owner_name: Option<String>,
1422
1423 #[serde(skip_serializing_if = "Option::is_none")]
1425 #[cfg_attr(feature = "uniffi", uniffi(default))]
1426 pub product_name: Option<String>,
1427
1428 #[serde(skip_serializing_if = "Option::is_none")]
1430 #[cfg_attr(feature = "uniffi", uniffi(default))]
1431 pub status: Option<AccountStatus>,
1432
1433 #[serde(rename = "type")]
1435 #[serde(skip_serializing_if = "Option::is_none")]
1436 #[cfg_attr(feature = "uniffi", uniffi(default))]
1437 pub type_: Option<AccountType>,
1438}
1439
1440pub mod accounts {
1441 use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1442 use serde::{Deserialize, Serialize};
1443
1444 #[derive(Clone, Debug)]
1445 pub struct Service {}
1446
1447 impl super::Service for Service {
1448 const ID: ServiceId = ServiceId::Accounts;
1449
1450 type TicketData = ();
1451
1452 type RequestData = RequestData;
1453
1454 type Output = Vec<Account>;
1455 }
1456
1457 impl super::NonInteractiveService for Service {}
1458
1459 fields! {
1460 routex_models::Account, AccountField
1461 (IBAN, Iban, iban, Option<String>)
1462 (NUMBER, Number, number, Option<String>)
1463 (BIC, Bic, bic, Option<String>)
1464 (BANK_CODE, BankCode, bank_code, Option<String>)
1465 (CURRENCY, Currency, currency, String)
1466 (NAME, Name, name, Option<String>)
1467 (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1468 (OWNER_NAME, OwnerName, owner_name, Option<String>)
1469 (PRODUCT_NAME, ProductName, product_name, Option<String>)
1470 (STATUS, Status, status, Option<AccountStatus>)
1471 (TYPE, Type, type_, Option<AccountType>)
1472 server:
1473 (Capabilities, capabilities)
1474 }
1475
1476 impl Account {
1477 #[must_use]
1478 pub fn supports(service: SupportedService) -> Filter<AccountField> {
1480 Filter::Supports(service)
1481 }
1482 }
1483
1484 #[derive(Serialize, Deserialize, Clone)]
1485 #[serde(rename_all = "camelCase")]
1486 pub struct RequestData {
1487 pub fields: Vec<AccountField>,
1488 #[serde(skip_serializing_if = "Option::is_none")]
1489 pub filter: Option<Filter<AccountField>>,
1490 }
1491}
1492
1493#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1494#[serde(rename_all = "camelCase")]
1495#[serde(rename_all_fields = "camelCase")]
1496#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1497pub enum AccountIdentifier {
1498 Iban(String),
1500
1501 Number(String),
1503}
1504
1505#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1506#[serde(rename_all = "camelCase")]
1507#[serde(rename_all_fields = "camelCase")]
1508#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1509pub enum CreditorAccountIdentifier {
1510 Iban(String),
1512}
1513
1514#[serde_with::serde_as]
1515#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1516#[serde(rename_all = "camelCase")]
1517#[serde(rename_all_fields = "camelCase")]
1518#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1519pub enum DebtorAccountIdentifier {
1520 #[serde(alias = "Iban")]
1522 Iban(String),
1523 #[cfg(feature = "server")]
1524 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
1525}
1526
1527#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1528#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1529#[serde(rename_all = "camelCase")]
1530pub struct AccountReference {
1531 #[serde(flatten)]
1532 pub id: AccountIdentifier,
1533 #[serde(skip_serializing_if = "Option::is_none")]
1534 #[cfg_attr(feature = "uniffi", uniffi(default))]
1535 pub currency: Option<String>,
1536}
1537
1538#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1539#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1540#[serde(rename_all = "camelCase")]
1541pub struct DebtorAccountReference {
1542 #[serde(flatten)]
1543 pub id: DebtorAccountIdentifier,
1544 #[serde(skip_serializing_if = "Option::is_none")]
1545 #[cfg_attr(feature = "uniffi", uniffi(default))]
1546 pub currency: Option<String>,
1547}
1548
1549pub mod balances {
1550 pub use rust_decimal::Decimal;
1551 use serde::{Deserialize, Serialize};
1552
1553 use crate::{AccountReference, DateTime, ServiceId};
1554
1555 #[derive(Clone, Debug)]
1556 pub struct Service {}
1557
1558 impl super::Service for Service {
1559 const ID: ServiceId = ServiceId::Balances;
1560
1561 type TicketData = ();
1562
1563 type RequestData = RequestData;
1564
1565 type Output = Balances;
1566 }
1567
1568 impl super::NonInteractiveService for Service {}
1569
1570 #[derive(Serialize, Deserialize, Clone)]
1571 #[serde(rename_all = "camelCase")]
1572 pub struct RequestData {
1573 pub accounts: Vec<AccountReference>,
1574 }
1575
1576 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1577 #[serde(rename_all = "camelCase")]
1578 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1579 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1580 pub struct Balances {
1581 pub balances: Vec<AccountBalances>,
1583
1584 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1586 #[cfg_attr(feature = "uniffi", uniffi(default))]
1587 pub missing_accounts: Vec<AccountReference>,
1588 }
1589
1590 #[allow(clippy::module_name_repetitions)]
1591 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1592 #[serde(rename_all = "camelCase")]
1593 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1594 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1595 pub struct AccountBalances {
1596 pub account: AccountReference,
1597
1598 pub balances: Vec<Balance>,
1600 }
1601
1602 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1603 #[serde(rename_all = "camelCase")]
1604 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1605 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1606 pub struct Balance {
1607 pub amount: Decimal,
1608 pub currency: String,
1609 pub balance_type: BalanceType,
1610 #[serde(default, skip_serializing_if = "Option::is_none")]
1611 #[cfg_attr(feature = "uniffi", uniffi(default))]
1612 pub credit_limit_included: Option<bool>,
1613 #[serde(default, skip_serializing_if = "Option::is_none")]
1614 #[cfg_attr(feature = "uniffi", uniffi(default))]
1615 pub date_time: Option<DateTime>,
1616 }
1617
1618 #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1619 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1620 pub enum BalanceType {
1621 Booked,
1623
1624 Available,
1631
1632 Expected,
1634 }
1635}
1636
1637pub mod transactions {
1638 use chrono::NaiveDate;
1639 use isocountry::CountryCode;
1640 pub use routex_models::{Amount, Fee, TransactionStatus};
1641 use rust_decimal::Decimal;
1642 use serde::{Deserialize, Serialize};
1643 use url::Url;
1644
1645 use crate::{AccountReference, ServiceId};
1646
1647 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1648 #[serde(rename_all = "camelCase")]
1649 pub struct TicketData {
1650 pub account: AccountReference,
1651 pub range: Range,
1652 pub webhook: Option<Url>,
1653 }
1654
1655 #[derive(Clone, Debug)]
1656 pub struct Service {}
1657
1658 impl super::Service for Service {
1659 const ID: ServiceId = ServiceId::Transactions;
1660
1661 type TicketData = TicketData;
1662
1663 type RequestData = RequestData;
1664
1665 type Output = Option<Vec<Transaction>>;
1666 }
1667
1668 impl super::NonInteractiveService for Service {}
1669
1670 #[derive(Serialize, Deserialize, Clone)]
1671 #[serde(rename_all = "camelCase")]
1672 pub struct RequestData {}
1673
1674 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1675 #[serde(rename_all = "camelCase")]
1676 #[serde(rename_all_fields = "camelCase")]
1677 pub enum Range {
1678 Reference(String),
1679 #[serde(untagged)]
1680 Period {
1681 from: NaiveDate,
1683 to: Option<NaiveDate>,
1685 },
1686 }
1687
1688 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1689 #[serde(rename_all = "camelCase")]
1690 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1691 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1692 pub struct Transaction {
1693 #[serde(skip_serializing_if = "Option::is_none")]
1695 #[cfg_attr(feature = "uniffi", uniffi(default))]
1696 pub entry_reference: Option<String>,
1697
1698 #[serde(skip_serializing_if = "Option::is_none")]
1699 #[cfg_attr(feature = "uniffi", uniffi(default))]
1700 pub batch: Option<BatchData>,
1701
1702 #[serde(skip_serializing_if = "Option::is_none")]
1704 #[cfg_attr(feature = "uniffi", uniffi(default))]
1705 pub booking_date: Option<NaiveDate>,
1706
1707 #[serde(skip_serializing_if = "Option::is_none")]
1709 #[cfg_attr(feature = "uniffi", uniffi(default))]
1710 pub value_date: Option<NaiveDate>,
1711
1712 #[serde(skip_serializing_if = "Option::is_none")]
1714 #[cfg_attr(feature = "uniffi", uniffi(default))]
1715 pub transaction_date: Option<NaiveDate>,
1716
1717 pub status: TransactionStatus,
1719
1720 #[serde(skip_serializing_if = "Option::is_none")]
1722 #[cfg_attr(feature = "uniffi", uniffi(default))]
1723 pub account_servicer_reference: Option<String>,
1724
1725 #[serde(skip_serializing_if = "Option::is_none")]
1727 #[cfg_attr(feature = "uniffi", uniffi(default))]
1728 pub payment_id: Option<String>,
1729
1730 #[serde(skip_serializing_if = "Option::is_none")]
1732 #[cfg_attr(feature = "uniffi", uniffi(default))]
1733 pub transaction_id: Option<String>,
1734
1735 #[serde(skip_serializing_if = "Option::is_none")]
1737 #[cfg_attr(feature = "uniffi", uniffi(default))]
1738 pub end_to_end_id: Option<String>,
1739
1740 #[serde(skip_serializing_if = "Option::is_none")]
1742 #[cfg_attr(feature = "uniffi", uniffi(default))]
1743 pub mandate_id: Option<String>,
1744
1745 #[serde(skip_serializing_if = "Option::is_none")]
1747 #[cfg_attr(feature = "uniffi", uniffi(default))]
1748 pub creditor_id: Option<String>,
1749
1750 pub amount: Amount,
1752
1753 #[serde(default, skip_serializing_if = "super::is_false")]
1755 #[cfg_attr(feature = "uniffi", uniffi(default))]
1756 pub reversal: bool,
1757
1758 #[serde(skip_serializing_if = "Option::is_none")]
1760 #[cfg_attr(feature = "uniffi", uniffi(default))]
1761 pub original_amount: Option<Amount>,
1762
1763 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1765 #[cfg_attr(feature = "uniffi", uniffi(default))]
1766 pub exchanges: Vec<ExchangeRate>,
1767
1768 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1770 #[cfg_attr(feature = "uniffi", uniffi(default))]
1771 pub fees: Vec<Fee>,
1772
1773 #[serde(skip_serializing_if = "Option::is_none")]
1775 #[cfg_attr(feature = "uniffi", uniffi(default))]
1776 pub creditor: Option<Party>,
1777
1778 #[serde(skip_serializing_if = "Option::is_none")]
1780 #[cfg_attr(feature = "uniffi", uniffi(default))]
1781 pub debtor: Option<Party>,
1782
1783 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1785 #[cfg_attr(feature = "uniffi", uniffi(default))]
1786 pub remittance_information: Vec<String>,
1787
1788 #[serde(skip_serializing_if = "Option::is_none")]
1790 #[cfg_attr(feature = "uniffi", uniffi(default))]
1791 pub purpose_code: Option<String>,
1792
1793 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1795 #[cfg_attr(feature = "uniffi", uniffi(default))]
1796 pub bank_transaction_codes: Vec<BankTransactionCode>,
1797
1798 #[serde(skip_serializing_if = "Option::is_none")]
1802 #[cfg_attr(feature = "uniffi", uniffi(default))]
1803 pub additional_information: Option<String>,
1804 }
1805
1806 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1807 #[serde(rename_all = "camelCase")]
1808 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1809 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1810 pub struct BatchData {
1811 #[serde(skip_serializing_if = "Option::is_none")]
1813 #[cfg_attr(feature = "uniffi", uniffi(default))]
1814 pub number_of_transactions: Option<u32>,
1815
1816 pub transactions: Vec<BatchTransactionDetails>,
1821 }
1822
1823 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1824 #[serde(rename_all = "camelCase")]
1825 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1826 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1827 pub struct BatchTransactionDetails {
1828 #[serde(skip_serializing_if = "Option::is_none")]
1830 #[cfg_attr(feature = "uniffi", uniffi(default))]
1831 pub account_servicer_reference: Option<String>,
1832
1833 #[serde(skip_serializing_if = "Option::is_none")]
1835 #[cfg_attr(feature = "uniffi", uniffi(default))]
1836 pub payment_id: Option<String>,
1837
1838 #[serde(skip_serializing_if = "Option::is_none")]
1840 #[cfg_attr(feature = "uniffi", uniffi(default))]
1841 pub transaction_id: Option<String>,
1842
1843 #[serde(skip_serializing_if = "Option::is_none")]
1845 #[cfg_attr(feature = "uniffi", uniffi(default))]
1846 pub end_to_end_id: Option<String>,
1847
1848 #[serde(skip_serializing_if = "Option::is_none")]
1850 #[cfg_attr(feature = "uniffi", uniffi(default))]
1851 pub mandate_id: Option<String>,
1852
1853 #[serde(skip_serializing_if = "Option::is_none")]
1855 #[cfg_attr(feature = "uniffi", uniffi(default))]
1856 pub creditor_id: Option<String>,
1857
1858 #[serde(skip_serializing_if = "Option::is_none")]
1860 #[cfg_attr(feature = "uniffi", uniffi(default))]
1861 pub amount: Option<Amount>,
1862
1863 #[serde(default, skip_serializing_if = "super::is_false")]
1865 #[cfg_attr(feature = "uniffi", uniffi(default))]
1866 pub reversal: bool,
1867
1868 #[serde(skip_serializing_if = "Option::is_none")]
1870 #[cfg_attr(feature = "uniffi", uniffi(default))]
1871 pub original_amount: Option<Amount>,
1872
1873 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1875 #[cfg_attr(feature = "uniffi", uniffi(default))]
1876 pub exchanges: Vec<ExchangeRate>,
1877
1878 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1880 #[cfg_attr(feature = "uniffi", uniffi(default))]
1881 pub fees: Vec<Fee>,
1882
1883 #[serde(skip_serializing_if = "Option::is_none")]
1885 #[cfg_attr(feature = "uniffi", uniffi(default))]
1886 pub creditor: Option<Party>,
1887
1888 #[serde(skip_serializing_if = "Option::is_none")]
1890 #[cfg_attr(feature = "uniffi", uniffi(default))]
1891 pub debtor: Option<Party>,
1892
1893 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1895 #[cfg_attr(feature = "uniffi", uniffi(default))]
1896 pub remittance_information: Vec<String>,
1897
1898 #[serde(skip_serializing_if = "Option::is_none")]
1900 #[cfg_attr(feature = "uniffi", uniffi(default))]
1901 pub purpose_code: Option<String>,
1902
1903 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1905 #[cfg_attr(feature = "uniffi", uniffi(default))]
1906 pub bank_transaction_codes: Vec<BankTransactionCode>,
1907
1908 #[serde(skip_serializing_if = "Option::is_none")]
1912 #[cfg_attr(feature = "uniffi", uniffi(default))]
1913 pub additional_information: Option<String>,
1914 }
1915
1916 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1917 #[serde(rename_all = "camelCase")]
1918 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1919 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1920 pub struct ExchangeRate {
1921 pub source_currency: String,
1923 #[serde(skip_serializing_if = "Option::is_none")]
1926 #[cfg_attr(feature = "uniffi", uniffi(default))]
1927 pub target_currency: Option<String>,
1928 #[serde(skip_serializing_if = "Option::is_none")]
1930 #[cfg_attr(feature = "uniffi", uniffi(default))]
1931 pub unit_currency: Option<String>,
1932 pub exchange_rate: Decimal,
1933 }
1934
1935 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1936 #[serde(rename_all = "camelCase")]
1937 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1938 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1939 pub struct Party {
1940 #[serde(skip_serializing_if = "Option::is_none")]
1942 #[cfg_attr(feature = "uniffi", uniffi(default))]
1943 pub name: Option<String>,
1944
1945 #[serde(skip_serializing_if = "Option::is_none")]
1947 #[cfg_attr(feature = "uniffi", uniffi(default))]
1948 pub iban: Option<String>,
1949
1950 #[serde(skip_serializing_if = "Option::is_none")]
1952 #[cfg_attr(feature = "uniffi", uniffi(default))]
1953 pub bic: Option<String>,
1954
1955 #[serde(skip_serializing_if = "Option::is_none")]
1957 #[cfg_attr(feature = "uniffi", uniffi(default))]
1958 pub ultimate: Option<String>,
1959 }
1960
1961 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1962 #[serde(rename_all = "camelCase")]
1963 #[serde(rename_all_fields = "camelCase")]
1964 #[non_exhaustive]
1965 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1966 pub enum BankTransactionCode {
1967 Iso {
1969 domain: String,
1971
1972 family: String,
1974
1975 sub_family: String,
1977 },
1978 Swift(String),
1980 Bai(String),
1982 National { code: String, country: CountryCode },
1984 Other {
1986 code: String,
1987 #[serde(skip_serializing_if = "Option::is_none")]
1988 issuer: Option<String>,
1989 },
1990 }
1991}
1992
1993#[allow(clippy::trivially_copy_pass_by_ref)]
1994fn is_false(val: &bool) -> bool {
1995 !*val
1996}
1997
1998#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1999#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2000#[non_exhaustive]
2001pub enum PaymentStatus {
2002 Accepted,
2006
2007 PartiallyAccepted,
2009
2010 CompletedDebtor,
2012
2013 CompletedCreditor,
2015}
2016
2017pub mod collect_payment {
2018 pub use routex_models::Amount;
2019 use serde::{Deserialize, Serialize};
2020 use serde_with::base64::Base64;
2021
2022 use super::{
2023 CreditorAccountIdentifier, DebtorAccountReference, Path, PaymentStatus, ServiceId,
2024 };
2025
2026 pub fn status_path(ticket_id: &str) -> Path {
2031 Path::new(["collect-payment", "status", ticket_id])
2032 }
2033
2034 #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
2035 #[serde(rename_all = "camelCase")]
2036 #[non_exhaustive]
2037 pub struct SuccessStatusData {
2038 #[serde(skip_serializing_if = "Option::is_none")]
2039 pub payment_status: Option<PaymentStatus>,
2040 }
2041
2042 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2043 #[serde(rename_all = "camelCase")]
2044 pub struct TicketData {
2045 pub amount: Amount,
2046 pub creditor_account: CreditorAccountIdentifier,
2047 pub creditor_name: String,
2048 pub remittance: String,
2049 #[serde(skip_serializing_if = "Option::is_none")]
2050 pub instant: Option<bool>,
2051 #[serde(skip_serializing_if = "Option::is_none")]
2052 pub fields: Option<Vec<Field>>,
2053 }
2054
2055 #[derive(Clone, Debug)]
2056 pub struct Service {}
2057
2058 impl super::Service for Service {
2059 const ID: ServiceId = ServiceId::CollectPayment;
2060
2061 type TicketData = TicketData;
2062
2063 type RequestData = RequestData;
2064
2065 type Output = PaymentInitiation;
2066 }
2067
2068 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
2069 #[serde(rename_all = "camelCase")]
2070 #[non_exhaustive]
2071 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2072 pub enum Field {
2073 DebtorIban,
2074 DebtorName,
2075 EncryptedDebtorIban,
2076 EncryptedDebtorName,
2077 }
2078
2079 #[derive(Serialize, Deserialize, Clone)]
2080 #[serde(rename_all = "camelCase")]
2081 pub struct RequestData {
2082 #[serde(skip_serializing_if = "Option::is_none")]
2083 pub account: Option<DebtorAccountReference>,
2084 }
2085
2086 #[serde_with::serde_as]
2087 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2088 #[serde(rename_all = "camelCase")]
2089 #[non_exhaustive]
2090 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2091 pub struct PaymentInitiation {
2092 #[serde(skip_serializing_if = "Option::is_none")]
2093 #[cfg_attr(feature = "uniffi", uniffi(default))]
2094 pub status: Option<PaymentStatus>,
2095 #[serde(skip_serializing_if = "Option::is_none")]
2096 #[cfg_attr(feature = "uniffi", uniffi(default))]
2097 pub debtor_name: Option<String>,
2098 #[serde(skip_serializing_if = "Option::is_none")]
2099 #[cfg_attr(feature = "uniffi", uniffi(default))]
2100 pub debtor_iban: Option<String>,
2101 #[serde(skip_serializing_if = "Option::is_none")]
2102 #[cfg_attr(feature = "uniffi", uniffi(default))]
2103 #[serde_as(as = "Option<Base64>")]
2104 pub encrypted_debtor_name: Option<Vec<u8>>,
2105 #[serde(skip_serializing_if = "Option::is_none")]
2106 #[cfg_attr(feature = "uniffi", uniffi(default))]
2107 #[serde_as(as = "Option<Base64>")]
2108 pub encrypted_debtor_iban: Option<Vec<u8>>,
2109 }
2110}
2111
2112pub mod transfer {
2113 pub use routex_models::{ChargeBearer, CreditorAddress, ISODateTimeOrDate, PaymentProduct};
2114 use serde::{Deserialize, Serialize};
2115
2116 use super::{
2117 Amount, CreditorAccountIdentifier, DebtorAccountReference, PaymentStatus, ServiceId,
2118 };
2119
2120 #[derive(Clone, Debug)]
2121 pub struct Service {}
2122
2123 impl super::Service for Service {
2124 const ID: ServiceId = ServiceId::Transfer;
2125
2126 type TicketData = ();
2127
2128 type RequestData = RequestData;
2129
2130 type Output = Transfer;
2131 }
2132
2133 #[derive(Serialize, Deserialize, Clone)]
2134 #[serde(rename_all = "camelCase")]
2135 pub struct RequestData {
2136 pub product: PaymentProduct,
2137 #[serde(skip_serializing_if = "Option::is_none")]
2138 pub debtor_account: Option<DebtorAccountReference>,
2139 #[serde(skip_serializing_if = "Option::is_none")]
2140 pub debtor_name: Option<String>,
2141 #[serde(skip_serializing_if = "Option::is_none")]
2142 pub requested_execution_date: Option<ISODateTimeOrDate>,
2143 pub details: Vec<TransferDetails>,
2144 }
2145
2146 #[derive(Serialize, Deserialize, Clone)]
2147 #[serde(rename_all = "camelCase")]
2148 #[non_exhaustive]
2149 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2150 pub struct TransferDetails {
2151 #[serde(skip_serializing_if = "Option::is_none")]
2152 #[cfg_attr(feature = "uniffi", uniffi(default))]
2153 pub end_to_end_identification: Option<String>,
2154 pub amount: Amount,
2155 pub creditor_account: CreditorAccountIdentifier,
2156 #[serde(skip_serializing_if = "Option::is_none")]
2157 #[cfg_attr(feature = "uniffi", uniffi(default))]
2158 pub creditor_agent_bic: Option<String>,
2159 pub creditor_name: String,
2160 #[serde(skip_serializing_if = "Option::is_none")]
2161 #[cfg_attr(feature = "uniffi", uniffi(default))]
2162 pub creditor_address: Option<CreditorAddress>,
2163 #[serde(skip_serializing_if = "Option::is_none")]
2164 #[cfg_attr(feature = "uniffi", uniffi(default))]
2165 pub remittance: Option<String>,
2166 #[serde(skip_serializing_if = "Option::is_none")]
2167 #[cfg_attr(feature = "uniffi", uniffi(default))]
2168 pub charge_bearer: Option<ChargeBearer>,
2169 }
2170
2171 impl TransferDetails {
2172 pub fn new(
2173 amount: Amount,
2174 creditor_account: CreditorAccountIdentifier,
2175 creditor_name: impl Into<String>,
2176 ) -> Self {
2177 Self {
2178 end_to_end_identification: None,
2179 amount,
2180 creditor_account,
2181 creditor_agent_bic: None,
2182 creditor_name: creditor_name.into(),
2183 creditor_address: None,
2184 remittance: None,
2185 charge_bearer: None,
2186 }
2187 }
2188 }
2189
2190 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2191 #[serde(rename_all = "camelCase")]
2192 #[non_exhaustive]
2193 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2194 pub struct Transfer {
2195 #[serde(skip_serializing_if = "Option::is_none")]
2196 #[cfg_attr(feature = "uniffi", uniffi(default))]
2197 pub status: Option<PaymentStatus>,
2198 }
2199}
2200
2201#[cfg(test)]
2202mod tests {
2203 use std::fmt::Debug;
2204
2205 use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
2206 use serde::{Deserialize, Serialize};
2207 use serde_json::json;
2208 use uuid::Uuid;
2209
2210 use crate::{
2211 Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
2212 InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
2213 };
2214
2215 #[test]
2216 fn read_response() {
2217 let serialized = serde_json::to_string(
2218 &encode(
2219 &Header::new(Algorithm::HS256),
2220 &Claims {
2221 data: "data",
2222 exp: 2_540_808_000,
2223 },
2224 &EncodingKey::from_secret(b"does_not_matter"),
2225 )
2226 .unwrap(),
2227 )
2228 .unwrap();
2229
2230 let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
2231
2232 assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
2233 }
2234
2235 struct Service;
2236
2237 impl crate::Service for Service {
2238 const ID: ServiceId = ServiceId::Accounts;
2239
2240 type TicketData = ();
2241
2242 type RequestData = ();
2243
2244 type Output = ();
2245 }
2246
2247 #[allow(
2248 dead_code,
2249 unconditional_recursion,
2250 clippy::extra_unused_type_parameters
2251 )]
2252 fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
2253 test_bounds::<InputContext<Service>>();
2254 test_bounds::<ConfirmationContext<Service>>();
2255 test_bounds::<DialogInput<Service>>();
2256 test_bounds::<Dialog<Service>>();
2257 test_bounds::<Redirect<Service>>();
2258 test_bounds::<RedirectHandle<Service>>();
2259 test_bounds::<OBResult<()>>();
2260 test_bounds::<Session>();
2261
2262 test_bounds::<Authenticated<Ticket<Service>>>();
2263 test_bounds::<Ticket<Service>>();
2264 test_bounds::<ResponseData<Service>>();
2265 test_bounds::<ConfirmationData<Service>>();
2266 }
2267
2268 fn test_eq_<T: PartialEq + Debug>(o: &T) {
2269 assert!(o.eq(o));
2270 }
2271
2272 #[test]
2273 fn test_eq() {
2274 let ic = InputContext::<Service>::from(vec![]);
2275 let cc = ConfirmationContext::<Service>::from(vec![]);
2276
2277 test_eq_(&ic);
2278 test_eq_(&cc);
2279 test_eq_(&Redirect {
2280 url: "url:".parse().unwrap(),
2281 context: cc.clone(),
2282 });
2283 test_eq_(&RedirectHandle {
2284 handle: String::new(),
2285 context: cc.clone(),
2286 });
2287 test_eq_(&Ticket::<Service> {
2288 service: ServiceId::Accounts,
2289 id: Uuid::new_v4(),
2290 data: (),
2291 });
2292 test_eq_(&ResponseData {
2293 context: ic,
2294 response: String::new(),
2295 });
2296 test_eq_(&ConfirmationData { context: cc });
2297 }
2298
2299 #[test]
2300 fn no_ticket_data() {
2301 assert!(
2302 serde_json::from_value::<Ticket<Service>>(json!({
2303 "id": Uuid::new_v4(),
2304 "service": ServiceId::Accounts,
2305 }))
2306 .is_ok()
2307 );
2308 }
2309
2310 #[test]
2311 fn missing_ticket_data() {
2312 let err = serde_json::from_value::<Ticket<super::collect_payment::Service>>(json!({
2313 "id": Uuid::new_v4(),
2314 "service": ServiceId::CollectPayment,
2315 }))
2316 .unwrap_err();
2317
2318 assert_eq!(err.to_string(), "missing field `data`");
2319 }
2320
2321 #[test]
2322 fn user_in_session_wire() {
2323 use super::UserInSession;
2324
2325 let at = UserInSession::At("203.0.113.7".parse().unwrap());
2326 assert_eq!(
2327 serde_json::to_value(UserInSession::OnThisConnection).unwrap(),
2328 json!("connection")
2329 );
2330 assert_eq!(serde_json::to_value(&at).unwrap(), json!("203.0.113.7"));
2331
2332 assert_eq!(
2333 serde_json::from_value::<UserInSession>(json!("connection")).unwrap(),
2334 UserInSession::OnThisConnection,
2335 );
2336 assert_eq!(
2337 serde_json::from_value::<UserInSession>(json!("203.0.113.7")).unwrap(),
2338 at
2339 );
2340
2341 assert!(serde_json::from_value::<UserInSession>(json!("nonsense")).is_err());
2342 }
2343}