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}
404
405macro_rules! context {
406 ($name:ident) => {
407 #[serde_with::serde_as]
408 #[derive(Serialize, Deserialize)]
409 #[serde(transparent)]
410 pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
411
412 impl<S> PartialEq for $name<S> {
413 fn eq(&self, other: &Self) -> bool {
414 self.0 == other.0
415 }
416 }
417
418 impl<S> Eq for $name<S> {}
419
420 impl<S> Clone for $name<S> {
421 fn clone(&self) -> Self {
422 Self(self.0.clone(), self.1)
423 }
424 }
425
426 impl<S> fmt::Debug for $name<S> {
427 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
428 f.debug_tuple("$name")
429 .field(&self.0)
430 .field(&self.1)
431 .finish()
432 }
433 }
434
435 impl<S> From<Vec<u8>> for $name<S> {
436 fn from(value: Vec<u8>) -> Self {
437 Self(value, PhantomData)
438 }
439 }
440
441 impl<S> From<$name<S>> for Vec<u8> {
442 fn from(value: $name<S>) -> Self {
443 value.0
444 }
445 }
446
447 impl<S> AsRef<[u8]> for $name<S> {
448 fn as_ref(&self) -> &[u8] {
449 &self.0
450 }
451 }
452 };
453}
454
455context!(InputContext);
456
457context!(ConfirmationContext);
458
459#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
485#[serde(transparent)]
486pub struct Authenticated<T> {
487 jwt: String,
488 _phantom: PhantomData<T>,
489}
490
491impl<'de, T> Deserialize<'de> for Authenticated<T>
492where
493 T: for<'t> Deserialize<'t> + Clone,
494{
495 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
496 where
497 D: serde::Deserializer<'de>,
498 {
499 String::deserialize(deserializer)?
500 .parse()
501 .map_err(serde::de::Error::custom)
502 }
503}
504
505impl<T> Authenticated<T>
506where
507 T: Serialize,
508{
509 #[must_use]
510 pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
516 let header = Header {
517 kid: Some(key_id.into()),
518 ..Default::default()
519 };
520
521 let jwt = encode(
522 &header,
523 &Claims {
524 data,
525 exp: 2_540_808_000,
527 },
528 &EncodingKey::from_secret(key),
529 )
530 .expect("Encoding should work");
531
532 Self {
533 jwt,
534 _phantom: PhantomData,
535 }
536 }
537}
538
539impl<T> FromStr for Authenticated<T>
540where
541 T: for<'de> Deserialize<'de> + Clone,
542{
543 type Err = jsonwebtoken::errors::Error;
544
545 fn from_str(jwt: &str) -> Result<Self, Self::Err> {
546 decode::<T>(jwt)?;
548 Ok(Self {
549 jwt: jwt.to_string(),
550 _phantom: PhantomData,
551 })
552 }
553}
554
555impl<T> Authenticated<T>
556where
557 T: for<'de> Deserialize<'de> + Clone,
558{
559 #[must_use]
560 pub fn as_str(&self) -> &str {
561 &self.jwt
562 }
563
564 #[must_use]
565 pub fn key_id(&self) -> Option<String> {
571 decode_header(&self.jwt).unwrap().kid
572 }
573
574 #[cfg(feature = "client")]
575 #[must_use]
576 #[allow(clippy::missing_panics_doc)]
577 pub fn to_data(&self) -> T {
578 decode(&self.jwt).unwrap()
579 }
580
581 #[cfg(feature = "server")]
582 pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
588 decoder.decode::<T>(&self.jwt).map(|d| d.data)
589 }
590}
591
592#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
593#[serde(rename_all = "camelCase")]
594pub struct Claims<T> {
595 pub data: T,
596 pub exp: u64,
597}
598
599#[cfg(feature = "server")]
600pub trait Decoder {
601 type Error;
602
603 fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
609 where
610 T: for<'de> Deserialize<'de> + Clone;
611}
612
613#[derive(Serialize, Deserialize, Debug)]
618pub enum OBResponse<S: Service> {
619 #[serde(bound = "S:")]
620 Result(
621 Authenticated<OBResult<S::Output>>,
622 Option<Session>,
623 Option<ConnectionData>,
624 ),
625 #[serde(bound = "S:")]
626 Dialog(Dialog<S>),
627 #[serde(bound = "S:")]
628 Redirect(Redirect<S>),
629 #[serde(bound = "S:")]
630 RedirectHandle(RedirectHandle<S>),
631}
632
633impl<S: Service> Clone for OBResponse<S> {
634 fn clone(&self) -> Self {
635 match self {
636 OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
637 authenticated.clone(),
638 session.clone(),
639 connection_data.clone(),
640 ),
641 OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
642 OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
643 OBResponse::RedirectHandle(redirect_handle) => {
644 OBResponse::RedirectHandle(redirect_handle.clone())
645 }
646 }
647 }
648}
649
650#[derive(Serialize, Deserialize, Debug)]
651#[cfg_attr(not(feature = "server"), non_exhaustive)]
652#[serde(rename_all = "camelCase")]
653pub struct NonInteractiveResponse<S: Service> {
654 #[serde(bound = "S:")]
655 pub result: S::Output,
656 #[serde(skip_serializing_if = "Option::is_none")]
657 pub session: Option<Session>,
658 #[serde(skip_serializing_if = "Option::is_none")]
659 pub connection_data: Option<ConnectionData>,
660}
661
662#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
667#[serde(rename_all = "camelCase")]
668pub struct OBResult<T> {
669 pub data: T,
670 pub ticket_id: Uuid,
671 pub timestamp: DateTime,
672}
673
674#[serde_with::serde_as]
675#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
676pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
677
678impl From<Vec<u8>> for Session {
679 fn from(value: Vec<u8>) -> Self {
680 Session(value)
681 }
682}
683
684impl From<Session> for Vec<u8> {
685 fn from(session: Session) -> Self {
686 session.0
687 }
688}
689
690impl AsRef<[u8]> for Session {
691 fn as_ref(&self) -> &[u8] {
692 &self.0
693 }
694}
695
696#[serde_with::serde_as]
697#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
698#[serde(transparent)]
699pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
700
701impl From<Vec<u8>> for ConnectionData {
702 fn from(value: Vec<u8>) -> Self {
703 ConnectionData(value)
704 }
705}
706
707impl From<ConnectionData> for Vec<u8> {
708 fn from(connection_data: ConnectionData) -> Self {
709 connection_data.0
710 }
711}
712
713impl AsRef<[u8]> for ConnectionData {
714 fn as_ref(&self) -> &[u8] {
715 &self.0
716 }
717}
718
719#[derive(Serialize, Deserialize)]
727#[serde(rename_all = "camelCase")]
728pub struct Redirect<S> {
729 #[serde(serialize_with = "serialize_url")]
730 pub url: Url,
731 #[serde(bound = "S:")]
732 pub context: ConfirmationContext<S>,
733}
734
735impl<S> PartialEq for Redirect<S> {
736 fn eq(&self, other: &Self) -> bool {
737 let Self { url, context } = self;
738 url == &other.url && context == &other.context
739 }
740}
741
742impl<S> Eq for Redirect<S> {}
743
744impl<S> Clone for Redirect<S> {
745 fn clone(&self) -> Self {
746 Self {
747 url: self.url.clone(),
748 context: self.context.clone(),
749 }
750 }
751}
752
753impl<S> fmt::Debug for Redirect<S> {
754 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
755 f.debug_struct("Redirect")
756 .field("url", &self.url)
757 .field("context", &self.context)
758 .finish()
759 }
760}
761
762fn serialize_url<S: Serializer>(value: &Url, serializer: S) -> Result<S::Ok, S::Error> {
765 let string_repr = value.to_string().replace('+', "%20");
766 serializer.serialize_str(&string_repr)
767}
768
769#[derive(Serialize, Deserialize)]
773#[serde(rename_all = "camelCase")]
774pub struct RedirectHandle<S> {
775 pub handle: String,
776 #[serde(bound = "S:")]
777 pub context: ConfirmationContext<S>,
778}
779
780impl<S> PartialEq for RedirectHandle<S> {
781 fn eq(&self, other: &Self) -> bool {
782 let Self { handle, context } = self;
783 handle == &other.handle && context == &other.context
784 }
785}
786
787impl<S> Eq for RedirectHandle<S> {}
788
789impl<S> Clone for RedirectHandle<S> {
790 fn clone(&self) -> Self {
791 Self {
792 handle: self.handle.clone(),
793 context: self.context.clone(),
794 }
795 }
796}
797
798impl<S> fmt::Debug for RedirectHandle<S> {
799 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800 f.debug_struct("RedirectHandle")
801 .field("handle", &self.handle)
802 .field("context", &self.context)
803 .finish()
804 }
805}
806
807fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
809where
810 T: for<'de> Deserialize<'de> + Clone,
811{
812 insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
813}
814
815macro_rules! enum_with_display {
816 {
817 $(#[$meta:meta])*
818 pub enum $name:ident {
819 $($variant:ident,)+
820 }
821 } => {
822 $(#[$meta])*
823 pub enum $name {
824 $($variant,)+
825 }
826
827 impl fmt::Display for $name {
828 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
829 f.write_str(match self {
830 $(Self::$variant => stringify!($variant),)+
831 })
832 }
833 }
834 }
835}
836
837enum_with_display! {
838 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
839 pub enum ServiceId {
840 Accounts,
841 CollectPayment,
842 Transactions,
843 Balances,
844 Transfer,
845 }
846}
847
848#[derive(Serialize, Deserialize)]
908#[serde(rename_all = "camelCase")]
909#[serde(try_from = "DeserializedTicket<S::TicketData>")]
910pub struct Ticket<S: Service> {
911 pub service: ServiceId,
912 pub id: Uuid,
913 pub data: S::TicketData,
914}
915
916impl<S: Service> PartialEq for Ticket<S> {
917 fn eq(&self, other: &Self) -> bool {
918 let Self { service, id, data } = self;
919 service == &other.service && id == &other.id && data == &other.data
920 }
921}
922
923impl<S: Service> Eq for Ticket<S> {}
924
925impl<S: Service> fmt::Debug for Ticket<S> {
926 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
927 f.debug_struct("Ticket")
928 .field("service", &self.service)
929 .field("id", &self.id)
930 .field("data", &self.data)
931 .finish()
932 }
933}
934
935impl<S: Service> Clone for Ticket<S> {
936 fn clone(&self) -> Self {
937 Self {
938 service: self.service,
939 id: self.id,
940 data: self.data.clone(),
941 }
942 }
943}
944
945#[derive(Deserialize, Debug)]
946struct DeserializedTicket<T> {
947 service: ServiceId,
948 id: Uuid,
949 data: Option<T>,
950}
951
952impl<S: Service> TryFrom<DeserializedTicket<S::TicketData>> for Ticket<S> {
953 type Error = &'static str;
954
955 fn try_from(
956 DeserializedTicket { service, id, data }: DeserializedTicket<S::TicketData>,
957 ) -> Result<Self, Self::Error> {
958 let data = (&() as &dyn Any)
959 .downcast_ref::<S::TicketData>()
960 .cloned()
961 .or(data)
962 .ok_or("missing field `data`")?;
963
964 Ok(Self { service, id, data })
965 }
966}
967
968pub trait Service {
969 const ID: ServiceId;
970
971 type TicketData: Serialize
972 + for<'de> Deserialize<'de>
973 + PartialEq
974 + Eq
975 + Clone
976 + fmt::Debug
977 + Send
978 + Sync
979 + 'static;
980
981 type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
982
983 type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
984
985 fn path() -> Path {
991 Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
992 }
993
994 fn response_path() -> Path {
995 Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
996 }
997
998 fn confirmation_path() -> Path {
999 Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
1000 }
1001}
1002
1003pub trait NonInteractiveService: Service {
1004 fn non_interactive_path() -> Path {
1005 Path::new([&Self::ID.to_string().to_kebab_case(), "non-interactive"])
1006 }
1007}
1008
1009#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1010#[serde(tag = "ticketStatus")]
1011pub enum TicketStatus<T> {
1012 Unfinished,
1013 Error(ErrorKind),
1014 Success(T),
1015}
1016
1017enum_with_display! {
1018 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1019 #[serde(tag = "error")]
1020 pub enum ErrorKind {
1021 UnexpectedError,
1022 Canceled,
1023 InvalidCredentials,
1024 ServiceBlocked,
1025 Unauthorized,
1026 AccessExceeded,
1027 PeriodOutOfBounds,
1028 UnsupportedProduct,
1029 PaymentFailed,
1030 UnexpectedValue,
1031 TicketError,
1032 ProviderError,
1033 NotFound,
1034 }
1035}
1036
1037#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1038#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1039#[serde(rename_all = "camelCase")]
1040pub struct Credentials {
1041 pub connection_id: ConnectionId,
1042 #[serde(skip_serializing_if = "Option::is_none")]
1043 #[cfg_attr(feature = "uniffi", uniffi(default))]
1044 pub user_id: Option<String>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1046 #[cfg_attr(feature = "uniffi", uniffi(default))]
1047 pub password: Option<String>,
1048 #[serde(skip_serializing_if = "Option::is_none")]
1049 #[cfg_attr(feature = "uniffi", uniffi(default))]
1050 pub connection_data: Option<ConnectionData>,
1051}
1052
1053#[derive(Serialize, Deserialize)]
1054#[serde(rename_all = "camelCase")]
1055pub struct ResponseData<S> {
1056 #[serde(bound = "S:")]
1057 pub context: InputContext<S>,
1058 pub response: String,
1059}
1060
1061impl<S> PartialEq for ResponseData<S> {
1062 fn eq(&self, other: &Self) -> bool {
1063 let Self { context, response } = self;
1064 context == &other.context && response == &other.response
1065 }
1066}
1067
1068impl<S> Eq for ResponseData<S> {}
1069
1070impl<S> Clone for ResponseData<S> {
1071 fn clone(&self) -> Self {
1072 Self {
1073 context: self.context.clone(),
1074 response: self.response.clone(),
1075 }
1076 }
1077}
1078
1079impl<S> fmt::Debug for ResponseData<S> {
1080 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1081 f.debug_struct("ResponseData")
1082 .field("context", &self.context)
1083 .field("response", &self.response)
1084 .finish()
1085 }
1086}
1087
1088#[derive(Serialize, Deserialize)]
1089#[serde(rename_all = "camelCase")]
1090pub struct ConfirmationData<S> {
1091 #[serde(bound = "S:")]
1092 pub context: ConfirmationContext<S>,
1093}
1094
1095impl<S> PartialEq for ConfirmationData<S> {
1096 fn eq(&self, other: &Self) -> bool {
1097 let Self { context } = self;
1098 context == &other.context
1099 }
1100}
1101
1102impl<S> Eq for ConfirmationData<S> {}
1103
1104impl<S> Clone for ConfirmationData<S> {
1105 fn clone(&self) -> Self {
1106 Self {
1107 context: self.context.clone(),
1108 }
1109 }
1110}
1111
1112impl<S> fmt::Debug for ConfirmationData<S> {
1113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1114 f.debug_struct("ConfirmationData")
1115 .field("context", &self.context)
1116 .finish()
1117 }
1118}
1119
1120#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1121#[serde(rename_all_fields = "camelCase")]
1122#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1123pub enum Error {
1124 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1125 UnexpectedError {
1126 #[serde(skip_serializing_if = "Option::is_none")]
1127 user_message: Option<String>,
1128 },
1129 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1130 Canceled {},
1131 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1132 InvalidCredentials {
1133 #[serde(skip_serializing_if = "Option::is_none")]
1134 user_message: Option<String>,
1135 },
1136 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1137 ServiceBlocked {
1138 #[serde(skip_serializing_if = "Option::is_none")]
1139 user_message: Option<String>,
1140 #[serde(skip_serializing_if = "Option::is_none")]
1141 code: Option<ServiceBlockedCode>,
1142 },
1143 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1144 Unauthorized {
1145 #[serde(skip_serializing_if = "Option::is_none")]
1146 user_message: Option<String>,
1147 },
1148 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1155 AccessExceeded {
1156 #[serde(skip_serializing_if = "Option::is_none")]
1157 user_message: Option<String>,
1158 },
1159 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1160 PeriodOutOfBounds {
1161 #[serde(skip_serializing_if = "Option::is_none")]
1162 user_message: Option<String>,
1163 },
1164 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1165 UnsupportedProduct {
1166 #[serde(skip_serializing_if = "Option::is_none")]
1167 reason: Option<UnsupportedProductReason>,
1168 #[serde(skip_serializing_if = "Option::is_none")]
1169 user_message: Option<String>,
1170 },
1171 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1172 PaymentFailed {
1173 #[serde(skip_serializing_if = "Option::is_none")]
1174 code: Option<PaymentErrorCode>,
1175 #[serde(skip_serializing_if = "Option::is_none")]
1176 user_message: Option<String>,
1177 },
1178 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1179 UnexpectedValue { error: String },
1180 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1181 TicketError {
1182 error: String,
1183 code: TicketErrorCode,
1184 },
1185 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1186 ProviderError {
1187 #[serde(skip_serializing_if = "Option::is_none")]
1188 code: Option<ProviderErrorCode>,
1189 #[serde(skip_serializing_if = "Option::is_none")]
1190 user_message: Option<String>,
1191 },
1192 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1193 InterruptError {},
1194}
1195
1196#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1197#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1198#[cfg_attr(not(feature = "server"), non_exhaustive)]
1199pub enum TicketErrorCode {
1200 Missing,
1202 Invalid,
1204 MissingKey,
1206 UnknownKey,
1208 Mismatch,
1210 Expired,
1212 InvalidLifetime,
1214 ExpiredKey,
1216 KeyEnvironmentMismatch,
1218}
1219
1220#[derive(Serialize, Deserialize, Clone, Debug)]
1221#[cfg_attr(not(feature = "server"), non_exhaustive)]
1222pub enum Filter<F> {
1223 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1224 Eq(F, Value),
1225 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1226 NotEq(F, Value),
1227 #[cfg(feature = "server")]
1228 Contains(F, Value),
1229 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1230 And(Box<Self>, Box<Self>),
1231 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1232 Or(Box<Self>, Box<Self>),
1233 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1234 Supports(SupportedService),
1235}
1236
1237#[derive(Serialize, Deserialize, Clone, Debug)]
1238#[cfg_attr(not(feature = "server"), non_exhaustive)]
1239#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1240pub enum SupportedService {
1241 CollectPayment,
1242}
1243
1244impl<F> Filter<F> {
1245 #[must_use]
1246 pub fn and(self, other: Filter<F>) -> Filter<F> {
1248 Filter::And(Box::new(self), Box::new(other))
1249 }
1250
1251 #[must_use]
1252 pub fn or(self, other: Filter<F>) -> Filter<F> {
1254 Filter::Or(Box::new(self), Box::new(other))
1255 }
1256}
1257
1258pub struct Field<F, T> {
1259 field: F,
1260 phantom: PhantomData<T>,
1261}
1262
1263impl<F, T> Field<F, T>
1264where
1265 F: Copy,
1266 T: Serialize,
1267{
1268 pub fn eq(&self, value: T) -> Filter<F> {
1274 Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1275 }
1276
1277 pub fn not_eq(&self, value: T) -> Filter<F> {
1283 Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1284 }
1285}
1286
1287pub trait GetValue {
1288 type Model;
1289
1290 fn get(&self, model: &Self::Model) -> Value;
1291}
1292
1293macro_rules! fields {
1294 {
1295 $model:ty, $enum_name:ident
1296 $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1297 $(
1298 server:
1299 $(($server_variant_name:ident, $server_field_name:ident))+
1300 )?
1301 } => {
1302 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1303 #[non_exhaustive]
1304 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1305 pub enum $enum_name {
1306 $($variant_name,)+
1307 $($(
1308 #[cfg(feature = "server")]
1309 $server_variant_name,
1310 )+)?
1311 }
1312
1313 impl $enum_name {
1314 $(
1315 pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1316 field: $enum_name::$variant_name,
1317 phantom: std::marker::PhantomData,
1318 };
1319 )+
1320 }
1321
1322 impl crate::GetValue for $enum_name {
1323 type Model = $model;
1324
1325 fn get(&self, model: &$model) -> serde_json::Value {
1326 match self {
1327 $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1328 $($(
1329 #[cfg(feature = "server")]
1330 $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1331 )+)?
1332 }.expect("Serialization should work")
1333 }
1334 }
1335 }
1336}
1337
1338#[derive(Serialize, Deserialize)]
1339#[serde(rename_all = "camelCase")]
1340pub struct ServiceRequest<S: Service> {
1341 pub credentials: Credentials,
1342 #[serde(skip_serializing_if = "Option::is_none")]
1343 pub session: Option<Session>,
1344 #[serde(default, skip_serializing_if = "is_false")]
1345 pub recurring_consents: bool,
1346 #[serde(flatten, bound = "S:")]
1347 pub data: <S as Service>::RequestData,
1348}
1349
1350impl<S: Service> Clone for ServiceRequest<S> {
1351 fn clone(&self) -> Self {
1352 Self {
1353 credentials: self.credentials.clone(),
1354 session: self.session.clone(),
1355 recurring_consents: self.recurring_consents,
1356 data: self.data.clone(),
1357 }
1358 }
1359}
1360
1361#[derive(Serialize, Deserialize)]
1362#[serde(rename_all = "camelCase")]
1363pub struct NonInteractiveRequest<S: Service> {
1364 pub connection_data: ConnectionData,
1365 #[serde(skip_serializing_if = "Option::is_none")]
1366 pub session: Option<Session>,
1367 #[serde(default, skip_serializing_if = "Option::is_none")]
1368 pub user_in_session: Option<UserInSession>,
1369 #[serde(flatten, bound = "S:")]
1370 pub data: <S as Service>::RequestData,
1371}
1372
1373#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1374#[serde(rename_all = "camelCase")]
1375#[non_exhaustive]
1376#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1377pub struct Account {
1378 #[serde(skip_serializing_if = "Option::is_none")]
1380 #[cfg_attr(feature = "uniffi", uniffi(default))]
1381 pub iban: Option<String>,
1382
1383 #[serde(skip_serializing_if = "Option::is_none")]
1385 #[cfg_attr(feature = "uniffi", uniffi(default))]
1386 pub number: Option<String>,
1387
1388 #[serde(skip_serializing_if = "Option::is_none")]
1390 #[cfg_attr(feature = "uniffi", uniffi(default))]
1391 pub bic: Option<String>,
1392
1393 #[serde(skip_serializing_if = "Option::is_none")]
1395 #[cfg_attr(feature = "uniffi", uniffi(default))]
1396 pub bank_code: Option<String>,
1397
1398 #[serde(skip_serializing_if = "Option::is_none")]
1400 #[cfg_attr(feature = "uniffi", uniffi(default))]
1401 pub currency: Option<String>,
1402
1403 #[serde(skip_serializing_if = "Option::is_none")]
1405 #[cfg_attr(feature = "uniffi", uniffi(default))]
1406 pub name: Option<String>,
1407
1408 #[serde(skip_serializing_if = "Option::is_none")]
1410 #[cfg_attr(feature = "uniffi", uniffi(default))]
1411 pub display_name: Option<String>,
1412
1413 #[serde(skip_serializing_if = "Option::is_none")]
1415 #[cfg_attr(feature = "uniffi", uniffi(default))]
1416 pub owner_name: Option<String>,
1417
1418 #[serde(skip_serializing_if = "Option::is_none")]
1420 #[cfg_attr(feature = "uniffi", uniffi(default))]
1421 pub product_name: Option<String>,
1422
1423 #[serde(skip_serializing_if = "Option::is_none")]
1425 #[cfg_attr(feature = "uniffi", uniffi(default))]
1426 pub status: Option<AccountStatus>,
1427
1428 #[serde(rename = "type")]
1430 #[serde(skip_serializing_if = "Option::is_none")]
1431 #[cfg_attr(feature = "uniffi", uniffi(default))]
1432 pub type_: Option<AccountType>,
1433}
1434
1435pub mod accounts {
1436 use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1437 use serde::{Deserialize, Serialize};
1438
1439 #[derive(Clone, Debug)]
1440 pub struct Service {}
1441
1442 impl super::Service for Service {
1443 const ID: ServiceId = ServiceId::Accounts;
1444
1445 type TicketData = ();
1446
1447 type RequestData = RequestData;
1448
1449 type Output = Vec<Account>;
1450 }
1451
1452 impl super::NonInteractiveService for Service {}
1453
1454 fields! {
1455 routex_models::Account, AccountField
1456 (IBAN, Iban, iban, Option<String>)
1457 (NUMBER, Number, number, Option<String>)
1458 (BIC, Bic, bic, Option<String>)
1459 (BANK_CODE, BankCode, bank_code, Option<String>)
1460 (CURRENCY, Currency, currency, String)
1461 (NAME, Name, name, Option<String>)
1462 (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1463 (OWNER_NAME, OwnerName, owner_name, Option<String>)
1464 (PRODUCT_NAME, ProductName, product_name, Option<String>)
1465 (STATUS, Status, status, Option<AccountStatus>)
1466 (TYPE, Type, type_, Option<AccountType>)
1467 server:
1468 (Capabilities, capabilities)
1469 }
1470
1471 impl Account {
1472 #[must_use]
1473 pub fn supports(service: SupportedService) -> Filter<AccountField> {
1475 Filter::Supports(service)
1476 }
1477 }
1478
1479 #[derive(Serialize, Deserialize, Clone)]
1480 #[serde(rename_all = "camelCase")]
1481 pub struct RequestData {
1482 pub fields: Vec<AccountField>,
1483 #[serde(skip_serializing_if = "Option::is_none")]
1484 pub filter: Option<Filter<AccountField>>,
1485 }
1486}
1487
1488#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1489#[serde(rename_all = "camelCase")]
1490#[serde(rename_all_fields = "camelCase")]
1491#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1492pub enum AccountIdentifier {
1493 Iban(String),
1495
1496 Number(String),
1498}
1499
1500#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1501#[serde(rename_all = "camelCase")]
1502#[serde(rename_all_fields = "camelCase")]
1503#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1504pub enum CreditorAccountIdentifier {
1505 Iban(String),
1507}
1508
1509#[serde_with::serde_as]
1510#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1511#[serde(rename_all = "camelCase")]
1512#[serde(rename_all_fields = "camelCase")]
1513#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1514pub enum DebtorAccountIdentifier {
1515 #[serde(alias = "Iban")]
1517 Iban(String),
1518 #[cfg(feature = "server")]
1519 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
1520}
1521
1522#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1523#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1524#[serde(rename_all = "camelCase")]
1525pub struct AccountReference {
1526 #[serde(flatten)]
1527 pub id: AccountIdentifier,
1528 #[serde(skip_serializing_if = "Option::is_none")]
1529 #[cfg_attr(feature = "uniffi", uniffi(default))]
1530 pub currency: Option<String>,
1531}
1532
1533#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1534#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1535#[serde(rename_all = "camelCase")]
1536pub struct DebtorAccountReference {
1537 #[serde(flatten)]
1538 pub id: DebtorAccountIdentifier,
1539 #[serde(skip_serializing_if = "Option::is_none")]
1540 #[cfg_attr(feature = "uniffi", uniffi(default))]
1541 pub currency: Option<String>,
1542}
1543
1544pub mod balances {
1545 pub use rust_decimal::Decimal;
1546 use serde::{Deserialize, Serialize};
1547
1548 use crate::{AccountReference, DateTime, ServiceId};
1549
1550 #[derive(Clone, Debug)]
1551 pub struct Service {}
1552
1553 impl super::Service for Service {
1554 const ID: ServiceId = ServiceId::Balances;
1555
1556 type TicketData = ();
1557
1558 type RequestData = RequestData;
1559
1560 type Output = Balances;
1561 }
1562
1563 impl super::NonInteractiveService for Service {}
1564
1565 #[derive(Serialize, Deserialize, Clone)]
1566 #[serde(rename_all = "camelCase")]
1567 pub struct RequestData {
1568 pub accounts: Vec<AccountReference>,
1569 }
1570
1571 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1572 #[serde(rename_all = "camelCase")]
1573 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1574 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1575 pub struct Balances {
1576 pub balances: Vec<AccountBalances>,
1578
1579 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1581 #[cfg_attr(feature = "uniffi", uniffi(default))]
1582 pub missing_accounts: Vec<AccountReference>,
1583 }
1584
1585 #[allow(clippy::module_name_repetitions)]
1586 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1587 #[serde(rename_all = "camelCase")]
1588 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1589 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1590 pub struct AccountBalances {
1591 pub account: AccountReference,
1592
1593 pub balances: Vec<Balance>,
1595 }
1596
1597 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1598 #[serde(rename_all = "camelCase")]
1599 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1600 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1601 pub struct Balance {
1602 pub amount: Decimal,
1603 pub currency: String,
1604 pub balance_type: BalanceType,
1605 #[serde(default, skip_serializing_if = "Option::is_none")]
1606 #[cfg_attr(feature = "uniffi", uniffi(default))]
1607 pub credit_limit_included: Option<bool>,
1608 #[serde(default, skip_serializing_if = "Option::is_none")]
1609 #[cfg_attr(feature = "uniffi", uniffi(default))]
1610 pub date_time: Option<DateTime>,
1611 }
1612
1613 #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1614 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1615 pub enum BalanceType {
1616 Booked,
1618
1619 Available,
1626
1627 Expected,
1629 }
1630}
1631
1632pub mod transactions {
1633 use chrono::NaiveDate;
1634 use isocountry::CountryCode;
1635 pub use routex_models::{Amount, Fee, TransactionStatus};
1636 use rust_decimal::Decimal;
1637 use serde::{Deserialize, Serialize};
1638 use url::Url;
1639
1640 use crate::{AccountReference, ServiceId};
1641
1642 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1643 #[serde(rename_all = "camelCase")]
1644 pub struct TicketData {
1645 pub account: AccountReference,
1646 pub range: Range,
1647 pub webhook: Option<Url>,
1648 }
1649
1650 #[derive(Clone, Debug)]
1651 pub struct Service {}
1652
1653 impl super::Service for Service {
1654 const ID: ServiceId = ServiceId::Transactions;
1655
1656 type TicketData = TicketData;
1657
1658 type RequestData = RequestData;
1659
1660 type Output = Option<Vec<Transaction>>;
1661 }
1662
1663 impl super::NonInteractiveService for Service {}
1664
1665 #[derive(Serialize, Deserialize, Clone)]
1666 #[serde(rename_all = "camelCase")]
1667 pub struct RequestData {}
1668
1669 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1670 #[serde(rename_all = "camelCase")]
1671 #[serde(rename_all_fields = "camelCase")]
1672 pub enum Range {
1673 Reference(String),
1674 #[serde(untagged)]
1675 Period {
1676 from: NaiveDate,
1678 to: Option<NaiveDate>,
1680 },
1681 }
1682
1683 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1684 #[serde(rename_all = "camelCase")]
1685 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1686 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1687 pub struct Transaction {
1688 #[serde(skip_serializing_if = "Option::is_none")]
1690 #[cfg_attr(feature = "uniffi", uniffi(default))]
1691 pub entry_reference: Option<String>,
1692
1693 #[serde(skip_serializing_if = "Option::is_none")]
1694 #[cfg_attr(feature = "uniffi", uniffi(default))]
1695 pub batch: Option<BatchData>,
1696
1697 #[serde(skip_serializing_if = "Option::is_none")]
1699 #[cfg_attr(feature = "uniffi", uniffi(default))]
1700 pub booking_date: Option<NaiveDate>,
1701
1702 #[serde(skip_serializing_if = "Option::is_none")]
1704 #[cfg_attr(feature = "uniffi", uniffi(default))]
1705 pub value_date: Option<NaiveDate>,
1706
1707 #[serde(skip_serializing_if = "Option::is_none")]
1709 #[cfg_attr(feature = "uniffi", uniffi(default))]
1710 pub transaction_date: Option<NaiveDate>,
1711
1712 pub status: TransactionStatus,
1714
1715 #[serde(skip_serializing_if = "Option::is_none")]
1717 #[cfg_attr(feature = "uniffi", uniffi(default))]
1718 pub account_servicer_reference: Option<String>,
1719
1720 #[serde(skip_serializing_if = "Option::is_none")]
1722 #[cfg_attr(feature = "uniffi", uniffi(default))]
1723 pub payment_id: Option<String>,
1724
1725 #[serde(skip_serializing_if = "Option::is_none")]
1727 #[cfg_attr(feature = "uniffi", uniffi(default))]
1728 pub transaction_id: Option<String>,
1729
1730 #[serde(skip_serializing_if = "Option::is_none")]
1732 #[cfg_attr(feature = "uniffi", uniffi(default))]
1733 pub end_to_end_id: Option<String>,
1734
1735 #[serde(skip_serializing_if = "Option::is_none")]
1737 #[cfg_attr(feature = "uniffi", uniffi(default))]
1738 pub mandate_id: Option<String>,
1739
1740 #[serde(skip_serializing_if = "Option::is_none")]
1742 #[cfg_attr(feature = "uniffi", uniffi(default))]
1743 pub creditor_id: Option<String>,
1744
1745 pub amount: Amount,
1747
1748 #[serde(default, skip_serializing_if = "super::is_false")]
1750 #[cfg_attr(feature = "uniffi", uniffi(default))]
1751 pub reversal: bool,
1752
1753 #[serde(skip_serializing_if = "Option::is_none")]
1755 #[cfg_attr(feature = "uniffi", uniffi(default))]
1756 pub original_amount: Option<Amount>,
1757
1758 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1760 #[cfg_attr(feature = "uniffi", uniffi(default))]
1761 pub exchanges: Vec<ExchangeRate>,
1762
1763 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1765 #[cfg_attr(feature = "uniffi", uniffi(default))]
1766 pub fees: Vec<Fee>,
1767
1768 #[serde(skip_serializing_if = "Option::is_none")]
1770 #[cfg_attr(feature = "uniffi", uniffi(default))]
1771 pub creditor: Option<Party>,
1772
1773 #[serde(skip_serializing_if = "Option::is_none")]
1775 #[cfg_attr(feature = "uniffi", uniffi(default))]
1776 pub debtor: Option<Party>,
1777
1778 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1780 #[cfg_attr(feature = "uniffi", uniffi(default))]
1781 pub remittance_information: Vec<String>,
1782
1783 #[serde(skip_serializing_if = "Option::is_none")]
1785 #[cfg_attr(feature = "uniffi", uniffi(default))]
1786 pub purpose_code: Option<String>,
1787
1788 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1790 #[cfg_attr(feature = "uniffi", uniffi(default))]
1791 pub bank_transaction_codes: Vec<BankTransactionCode>,
1792
1793 #[serde(skip_serializing_if = "Option::is_none")]
1797 #[cfg_attr(feature = "uniffi", uniffi(default))]
1798 pub additional_information: Option<String>,
1799 }
1800
1801 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1802 #[serde(rename_all = "camelCase")]
1803 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1804 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1805 pub struct BatchData {
1806 #[serde(skip_serializing_if = "Option::is_none")]
1808 #[cfg_attr(feature = "uniffi", uniffi(default))]
1809 pub number_of_transactions: Option<u32>,
1810
1811 pub transactions: Vec<BatchTransactionDetails>,
1816 }
1817
1818 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1819 #[serde(rename_all = "camelCase")]
1820 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1821 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1822 pub struct BatchTransactionDetails {
1823 #[serde(skip_serializing_if = "Option::is_none")]
1825 #[cfg_attr(feature = "uniffi", uniffi(default))]
1826 pub account_servicer_reference: Option<String>,
1827
1828 #[serde(skip_serializing_if = "Option::is_none")]
1830 #[cfg_attr(feature = "uniffi", uniffi(default))]
1831 pub payment_id: Option<String>,
1832
1833 #[serde(skip_serializing_if = "Option::is_none")]
1835 #[cfg_attr(feature = "uniffi", uniffi(default))]
1836 pub transaction_id: Option<String>,
1837
1838 #[serde(skip_serializing_if = "Option::is_none")]
1840 #[cfg_attr(feature = "uniffi", uniffi(default))]
1841 pub end_to_end_id: Option<String>,
1842
1843 #[serde(skip_serializing_if = "Option::is_none")]
1845 #[cfg_attr(feature = "uniffi", uniffi(default))]
1846 pub mandate_id: Option<String>,
1847
1848 #[serde(skip_serializing_if = "Option::is_none")]
1850 #[cfg_attr(feature = "uniffi", uniffi(default))]
1851 pub creditor_id: Option<String>,
1852
1853 #[serde(skip_serializing_if = "Option::is_none")]
1855 #[cfg_attr(feature = "uniffi", uniffi(default))]
1856 pub amount: Option<Amount>,
1857
1858 #[serde(default, skip_serializing_if = "super::is_false")]
1860 #[cfg_attr(feature = "uniffi", uniffi(default))]
1861 pub reversal: bool,
1862
1863 #[serde(skip_serializing_if = "Option::is_none")]
1865 #[cfg_attr(feature = "uniffi", uniffi(default))]
1866 pub original_amount: Option<Amount>,
1867
1868 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1870 #[cfg_attr(feature = "uniffi", uniffi(default))]
1871 pub exchanges: Vec<ExchangeRate>,
1872
1873 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1875 #[cfg_attr(feature = "uniffi", uniffi(default))]
1876 pub fees: Vec<Fee>,
1877
1878 #[serde(skip_serializing_if = "Option::is_none")]
1880 #[cfg_attr(feature = "uniffi", uniffi(default))]
1881 pub creditor: Option<Party>,
1882
1883 #[serde(skip_serializing_if = "Option::is_none")]
1885 #[cfg_attr(feature = "uniffi", uniffi(default))]
1886 pub debtor: Option<Party>,
1887
1888 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1890 #[cfg_attr(feature = "uniffi", uniffi(default))]
1891 pub remittance_information: Vec<String>,
1892
1893 #[serde(skip_serializing_if = "Option::is_none")]
1895 #[cfg_attr(feature = "uniffi", uniffi(default))]
1896 pub purpose_code: Option<String>,
1897
1898 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1900 #[cfg_attr(feature = "uniffi", uniffi(default))]
1901 pub bank_transaction_codes: Vec<BankTransactionCode>,
1902
1903 #[serde(skip_serializing_if = "Option::is_none")]
1907 #[cfg_attr(feature = "uniffi", uniffi(default))]
1908 pub additional_information: Option<String>,
1909 }
1910
1911 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1912 #[serde(rename_all = "camelCase")]
1913 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1914 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1915 pub struct ExchangeRate {
1916 pub source_currency: String,
1918 #[serde(skip_serializing_if = "Option::is_none")]
1921 #[cfg_attr(feature = "uniffi", uniffi(default))]
1922 pub target_currency: Option<String>,
1923 #[serde(skip_serializing_if = "Option::is_none")]
1925 #[cfg_attr(feature = "uniffi", uniffi(default))]
1926 pub unit_currency: Option<String>,
1927 pub exchange_rate: Decimal,
1928 }
1929
1930 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1931 #[serde(rename_all = "camelCase")]
1932 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1933 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1934 pub struct Party {
1935 #[serde(skip_serializing_if = "Option::is_none")]
1937 #[cfg_attr(feature = "uniffi", uniffi(default))]
1938 pub name: Option<String>,
1939
1940 #[serde(skip_serializing_if = "Option::is_none")]
1942 #[cfg_attr(feature = "uniffi", uniffi(default))]
1943 pub iban: Option<String>,
1944
1945 #[serde(skip_serializing_if = "Option::is_none")]
1947 #[cfg_attr(feature = "uniffi", uniffi(default))]
1948 pub bic: Option<String>,
1949
1950 #[serde(skip_serializing_if = "Option::is_none")]
1952 #[cfg_attr(feature = "uniffi", uniffi(default))]
1953 pub ultimate: Option<String>,
1954 }
1955
1956 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1957 #[serde(rename_all = "camelCase")]
1958 #[serde(rename_all_fields = "camelCase")]
1959 #[non_exhaustive]
1960 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1961 pub enum BankTransactionCode {
1962 Iso {
1964 domain: String,
1966
1967 family: String,
1969
1970 sub_family: String,
1972 },
1973 Swift(String),
1975 Bai(String),
1977 National { code: String, country: CountryCode },
1979 Other {
1981 code: String,
1982 #[serde(skip_serializing_if = "Option::is_none")]
1983 issuer: Option<String>,
1984 },
1985 }
1986}
1987
1988#[allow(clippy::trivially_copy_pass_by_ref)]
1989fn is_false(val: &bool) -> bool {
1990 !*val
1991}
1992
1993#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1994#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1995#[non_exhaustive]
1996pub enum PaymentStatus {
1997 Accepted,
2001
2002 PartiallyAccepted,
2004
2005 CompletedDebtor,
2007
2008 CompletedCreditor,
2010}
2011
2012pub mod collect_payment {
2013 pub use routex_models::Amount;
2014 use serde::{Deserialize, Serialize};
2015 use serde_with::base64::Base64;
2016
2017 use super::{
2018 CreditorAccountIdentifier, DebtorAccountReference, Path, PaymentStatus, ServiceId,
2019 };
2020
2021 pub fn status_path(ticket_id: &str) -> Path {
2026 Path::new(["collect-payment", "status", ticket_id])
2027 }
2028
2029 #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
2030 #[serde(rename_all = "camelCase")]
2031 #[non_exhaustive]
2032 pub struct SuccessStatusData {
2033 #[serde(skip_serializing_if = "Option::is_none")]
2034 pub payment_status: Option<PaymentStatus>,
2035 }
2036
2037 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
2038 #[serde(rename_all = "camelCase")]
2039 pub struct TicketData {
2040 pub amount: Amount,
2041 pub creditor_account: CreditorAccountIdentifier,
2042 pub creditor_name: String,
2043 pub remittance: String,
2044 #[serde(skip_serializing_if = "Option::is_none")]
2045 pub instant: Option<bool>,
2046 #[serde(skip_serializing_if = "Option::is_none")]
2047 pub fields: Option<Vec<Field>>,
2048 }
2049
2050 #[derive(Clone, Debug)]
2051 pub struct Service {}
2052
2053 impl super::Service for Service {
2054 const ID: ServiceId = ServiceId::CollectPayment;
2055
2056 type TicketData = TicketData;
2057
2058 type RequestData = RequestData;
2059
2060 type Output = PaymentInitiation;
2061 }
2062
2063 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
2064 #[serde(rename_all = "camelCase")]
2065 #[non_exhaustive]
2066 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2067 pub enum Field {
2068 DebtorIban,
2069 DebtorName,
2070 EncryptedDebtorIban,
2071 EncryptedDebtorName,
2072 }
2073
2074 #[derive(Serialize, Deserialize, Clone)]
2075 #[serde(rename_all = "camelCase")]
2076 pub struct RequestData {
2077 #[serde(skip_serializing_if = "Option::is_none")]
2078 pub account: Option<DebtorAccountReference>,
2079 }
2080
2081 #[serde_with::serde_as]
2082 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2083 #[serde(rename_all = "camelCase")]
2084 #[non_exhaustive]
2085 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2086 pub struct PaymentInitiation {
2087 #[serde(skip_serializing_if = "Option::is_none")]
2088 #[cfg_attr(feature = "uniffi", uniffi(default))]
2089 pub status: Option<PaymentStatus>,
2090 #[serde(skip_serializing_if = "Option::is_none")]
2091 #[cfg_attr(feature = "uniffi", uniffi(default))]
2092 pub debtor_name: Option<String>,
2093 #[serde(skip_serializing_if = "Option::is_none")]
2094 #[cfg_attr(feature = "uniffi", uniffi(default))]
2095 pub debtor_iban: Option<String>,
2096 #[serde(skip_serializing_if = "Option::is_none")]
2097 #[cfg_attr(feature = "uniffi", uniffi(default))]
2098 #[serde_as(as = "Option<Base64>")]
2099 pub encrypted_debtor_name: Option<Vec<u8>>,
2100 #[serde(skip_serializing_if = "Option::is_none")]
2101 #[cfg_attr(feature = "uniffi", uniffi(default))]
2102 #[serde_as(as = "Option<Base64>")]
2103 pub encrypted_debtor_iban: Option<Vec<u8>>,
2104 }
2105}
2106
2107pub mod transfer {
2108 pub use routex_models::{ChargeBearer, CreditorAddress, ISODateTimeOrDate, PaymentProduct};
2109 use serde::{Deserialize, Serialize};
2110
2111 use super::{
2112 Amount, CreditorAccountIdentifier, DebtorAccountReference, PaymentStatus, ServiceId,
2113 };
2114
2115 #[derive(Clone, Debug)]
2116 pub struct Service {}
2117
2118 impl super::Service for Service {
2119 const ID: ServiceId = ServiceId::Transfer;
2120
2121 type TicketData = ();
2122
2123 type RequestData = RequestData;
2124
2125 type Output = Transfer;
2126 }
2127
2128 #[derive(Serialize, Deserialize, Clone)]
2129 #[serde(rename_all = "camelCase")]
2130 pub struct RequestData {
2131 pub product: PaymentProduct,
2132 #[serde(skip_serializing_if = "Option::is_none")]
2133 pub debtor_account: Option<DebtorAccountReference>,
2134 #[serde(skip_serializing_if = "Option::is_none")]
2135 pub debtor_name: Option<String>,
2136 #[serde(skip_serializing_if = "Option::is_none")]
2137 pub requested_execution_date: Option<ISODateTimeOrDate>,
2138 pub details: Vec<TransferDetails>,
2139 }
2140
2141 #[derive(Serialize, Deserialize, Clone)]
2142 #[serde(rename_all = "camelCase")]
2143 #[non_exhaustive]
2144 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2145 pub struct TransferDetails {
2146 #[serde(skip_serializing_if = "Option::is_none")]
2147 #[cfg_attr(feature = "uniffi", uniffi(default))]
2148 pub end_to_end_identification: Option<String>,
2149 pub amount: Amount,
2150 pub creditor_account: CreditorAccountIdentifier,
2151 #[serde(skip_serializing_if = "Option::is_none")]
2152 #[cfg_attr(feature = "uniffi", uniffi(default))]
2153 pub creditor_agent_bic: Option<String>,
2154 pub creditor_name: String,
2155 #[serde(skip_serializing_if = "Option::is_none")]
2156 #[cfg_attr(feature = "uniffi", uniffi(default))]
2157 pub creditor_address: Option<CreditorAddress>,
2158 #[serde(skip_serializing_if = "Option::is_none")]
2159 #[cfg_attr(feature = "uniffi", uniffi(default))]
2160 pub remittance: Option<String>,
2161 #[serde(skip_serializing_if = "Option::is_none")]
2162 #[cfg_attr(feature = "uniffi", uniffi(default))]
2163 pub charge_bearer: Option<ChargeBearer>,
2164 }
2165
2166 impl TransferDetails {
2167 pub fn new(
2168 amount: Amount,
2169 creditor_account: CreditorAccountIdentifier,
2170 creditor_name: impl Into<String>,
2171 ) -> Self {
2172 Self {
2173 end_to_end_identification: None,
2174 amount,
2175 creditor_account,
2176 creditor_agent_bic: None,
2177 creditor_name: creditor_name.into(),
2178 creditor_address: None,
2179 remittance: None,
2180 charge_bearer: None,
2181 }
2182 }
2183 }
2184
2185 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
2186 #[serde(rename_all = "camelCase")]
2187 #[non_exhaustive]
2188 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
2189 pub struct Transfer {
2190 #[serde(skip_serializing_if = "Option::is_none")]
2191 #[cfg_attr(feature = "uniffi", uniffi(default))]
2192 pub status: Option<PaymentStatus>,
2193 }
2194}
2195
2196#[cfg(test)]
2197mod tests {
2198 use std::fmt::Debug;
2199
2200 use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
2201 use serde::{Deserialize, Serialize};
2202 use serde_json::json;
2203 use uuid::Uuid;
2204
2205 use crate::{
2206 Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
2207 InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
2208 };
2209
2210 #[test]
2211 fn read_response() {
2212 let serialized = serde_json::to_string(
2213 &encode(
2214 &Header::new(Algorithm::HS256),
2215 &Claims {
2216 data: "data",
2217 exp: 2_540_808_000,
2218 },
2219 &EncodingKey::from_secret(b"does_not_matter"),
2220 )
2221 .unwrap(),
2222 )
2223 .unwrap();
2224
2225 let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
2226
2227 assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
2228 }
2229
2230 struct Service;
2231
2232 impl crate::Service for Service {
2233 const ID: ServiceId = ServiceId::Accounts;
2234
2235 type TicketData = ();
2236
2237 type RequestData = ();
2238
2239 type Output = ();
2240 }
2241
2242 #[allow(
2243 dead_code,
2244 unconditional_recursion,
2245 clippy::extra_unused_type_parameters
2246 )]
2247 fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
2248 test_bounds::<InputContext<Service>>();
2249 test_bounds::<ConfirmationContext<Service>>();
2250 test_bounds::<DialogInput<Service>>();
2251 test_bounds::<Dialog<Service>>();
2252 test_bounds::<Redirect<Service>>();
2253 test_bounds::<RedirectHandle<Service>>();
2254 test_bounds::<OBResult<()>>();
2255 test_bounds::<Session>();
2256
2257 test_bounds::<Authenticated<Ticket<Service>>>();
2258 test_bounds::<Ticket<Service>>();
2259 test_bounds::<ResponseData<Service>>();
2260 test_bounds::<ConfirmationData<Service>>();
2261 }
2262
2263 fn test_eq_<T: PartialEq + Debug>(o: &T) {
2264 assert!(o.eq(o));
2265 }
2266
2267 #[test]
2268 fn test_eq() {
2269 let ic = InputContext::<Service>::from(vec![]);
2270 let cc = ConfirmationContext::<Service>::from(vec![]);
2271
2272 test_eq_(&ic);
2273 test_eq_(&cc);
2274 test_eq_(&Redirect {
2275 url: "url:".parse().unwrap(),
2276 context: cc.clone(),
2277 });
2278 test_eq_(&RedirectHandle {
2279 handle: String::new(),
2280 context: cc.clone(),
2281 });
2282 test_eq_(&Ticket::<Service> {
2283 service: ServiceId::Accounts,
2284 id: Uuid::new_v4(),
2285 data: (),
2286 });
2287 test_eq_(&ResponseData {
2288 context: ic,
2289 response: String::new(),
2290 });
2291 test_eq_(&ConfirmationData { context: cc });
2292 }
2293
2294 #[test]
2295 fn no_ticket_data() {
2296 assert!(
2297 serde_json::from_value::<Ticket<Service>>(json!({
2298 "id": Uuid::new_v4(),
2299 "service": ServiceId::Accounts,
2300 }))
2301 .is_ok()
2302 );
2303 }
2304
2305 #[test]
2306 fn missing_ticket_data() {
2307 let err = serde_json::from_value::<Ticket<super::collect_payment::Service>>(json!({
2308 "id": Uuid::new_v4(),
2309 "service": ServiceId::CollectPayment,
2310 }))
2311 .unwrap_err();
2312
2313 assert_eq!(err.to_string(), "missing field `data`");
2314 }
2315
2316 #[test]
2317 fn user_in_session_wire() {
2318 use super::UserInSession;
2319
2320 let at = UserInSession::At("203.0.113.7".parse().unwrap());
2321 assert_eq!(
2322 serde_json::to_value(UserInSession::OnThisConnection).unwrap(),
2323 json!("connection")
2324 );
2325 assert_eq!(serde_json::to_value(&at).unwrap(), json!("203.0.113.7"));
2326
2327 assert_eq!(
2328 serde_json::from_value::<UserInSession>(json!("connection")).unwrap(),
2329 UserInSession::OnThisConnection,
2330 );
2331 assert_eq!(
2332 serde_json::from_value::<UserInSession>(json!("203.0.113.7")).unwrap(),
2333 at
2334 );
2335
2336 assert!(serde_json::from_value::<UserInSession>(json!("nonsense")).is_err());
2337 }
2338}