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};
17use serde_json::Value;
18use serde_with::base64::Base64;
19use std::fmt;
20use std::marker::PhantomData;
21use std::str::FromStr;
22use url::Url;
23
24#[cfg(feature = "uniffi")]
25uniffi::setup_scaffolding!();
26
27#[cfg(feature = "uniffi")]
28uniffi::custom_type!(ConnectionData, Vec<u8>, {
29 try_lift: |val| Ok(val.into()),
30 lower: |obj| obj.into(),
31});
32
33#[cfg(feature = "uniffi")]
34uniffi::custom_type!(Session, Vec<u8>, {
35 try_lift: |val| Ok(val.into()),
36 lower: |obj| obj.into(),
37});
38
39#[cfg(feature = "uniffi")]
40uniffi::custom_type!(CountryCode, String, {
41 remote,
42 try_lift: |val| Ok(Self::for_alpha2(&val)?),
43 lower: |obj| obj.alpha2().to_string(),
44});
45
46#[cfg(feature = "uniffi")]
47uniffi::use_remote_type!(routex_models::Decimal);
48
49#[cfg(feature = "uniffi")]
50uniffi::custom_type!(NaiveDate, String, {
51 remote,
52 try_lift: |val| Ok(val.parse()?),
53 lower: |obj| obj.to_string(),
54});
55
56type DateTime = chrono::DateTime<Utc>;
57
58#[cfg(feature = "uniffi")]
59uniffi::custom_type!(DateTime, String, {
60 remote,
61 try_lift: |val| Ok(val.parse()?),
62 lower: |obj| obj.to_string(),
63});
64
65pub type Dialog<S> = routex_models::Dialog<ConfirmationContext<S>, InputContext<S>>;
66pub type DialogInput<S> = routex_models::DialogInput<ConfirmationContext<S>, InputContext<S>>;
67
68#[must_use]
75pub struct Path(Vec<String>);
76
77impl Path {
78 fn new<I>(segments: I) -> Self
79 where
80 I: IntoIterator,
81 I::Item: ToString,
82 {
83 Self(segments.into_iter().map(|s| s.to_string()).collect())
84 }
85
86 #[must_use]
87 pub fn to_url(&self, base: &Url) -> Url {
93 let mut url = base.clone();
94 url.path_segments_mut()
95 .expect("cannot be base")
96 .extend(&self.0);
97 url
98 }
99}
100
101impl fmt::Display for Path {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 for segment in &self.0 {
104 f.write_str("/")?;
105 f.write_str(segment)?;
106 }
107
108 Ok(())
109 }
110}
111
112pub mod headers {
113 use http::HeaderName;
114
115 pub static SESSION_ID: HeaderName = HeaderName::from_static("yaxi-session-id");
116
117 pub static TICKET: HeaderName = HeaderName::from_static("yaxi-ticket");
118
119 pub static TICKET_ID: HeaderName = HeaderName::from_static("yaxi-ticket-id");
120
121 pub static REDIRECT_URI: HeaderName = HeaderName::from_static("yaxi-redirect-uri");
122
123 pub static TRACE_ID: HeaderName = HeaderName::from_static("yaxi-trace-id");
124}
125
126pub const CURRENT_MEDIA_TYPE: &str = "application/vnd.yaxi.v5";
127
128pub mod keys {
129 use super::Path;
130 use clerk_report::PublishedVersionEntry;
131 use serde::{Deserialize, Serialize};
132 use serde_with::base64::Base64;
133
134 pub fn settlement_path() -> Path {
140 Path::new(["key-settlement"])
141 }
142
143 #[serde_with::serde_as]
144 #[derive(Serialize, Deserialize, Clone, Debug)]
145 #[serde(rename_all = "camelCase")]
146 pub struct Request {
147 #[serde_as(as = "Base64")]
149 pub public_key: [u8; 32],
150 }
151
152 #[serde_with::serde_as]
154 #[derive(Serialize, Deserialize)]
155 #[serde(rename_all = "camelCase")]
156 pub struct SettlementBoxMessage {
157 #[serde_as(as = "Base64")]
158 pub public_key: [u8; 32],
159 #[serde_as(as = "Base64")]
160 pub session_id: [u8; 32],
161 }
162
163 #[serde_with::serde_as]
165 #[derive(Serialize, Deserialize, Clone, Debug)]
166 #[serde(rename_all = "camelCase")]
167 pub struct Response {
168 #[serde_as(as = "Base64")]
170 pub attestation_report: Vec<u8>,
171 pub vcek: String,
173 #[serde_as(as = "Base64")]
175 pub chacha_box: Vec<u8>,
176 pub system_version: PublishedVersionEntry,
179 }
180}
181
182pub mod traces {
183 use super::Path;
184
185 pub fn path(trace_id: &str) -> Path {
190 Path::new(["traces", trace_id])
191 }
192}
193
194pub mod redirects {
195 use super::Path;
196 use serde::{Deserialize, Serialize};
197 use url::Url;
198
199 pub fn path() -> Path {
205 Path::new(["redirects"])
206 }
207
208 #[derive(Serialize, Deserialize, Clone, Debug)]
209 #[serde(rename_all = "camelCase")]
210 pub struct Request {
211 pub handle: String,
212 pub redirect_uri: String,
213 }
214
215 #[derive(Serialize, Deserialize, Clone, Debug)]
216 #[serde(rename_all = "camelCase")]
217 pub struct Response {
218 pub redirect_url: Url,
219 }
220}
221
222pub mod info {
223 use super::Path;
224 pub use isocountry::CountryCode;
225 use routex_models::{ConnectionId, CredentialsModel};
226 use serde::{Deserialize, Serialize};
227 #[cfg(feature = "server")]
228 use serde_with::base64::Base64;
229
230 pub fn search_path() -> Path {
236 Path::new(["search"])
237 }
238
239 #[derive(Serialize, Deserialize, Clone, Debug)]
240 #[serde(rename_all = "camelCase")]
241 pub struct Request {
242 pub filters: Vec<SearchFilter>,
243 pub iban_detection: bool,
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub limit: Option<usize>,
246 }
247
248 #[serde_with::serde_as]
252 #[derive(Serialize, Deserialize, Clone, Debug)]
253 #[serde(rename_all = "camelCase")]
254 #[serde(rename_all_fields = "camelCase")]
255 pub enum SearchFilter {
256 Types(Vec<ConnectionType>),
258 Countries(Vec<CountryCode>),
260 Name(String),
262 Bic(String),
264 BankCode(String),
266 Term(String),
268 #[cfg(feature = "server")]
269 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
270 }
271
272 #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
274 #[non_exhaustive]
275 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
276 pub enum ConnectionType {
277 Production,
279 Sandboxes,
281 }
282
283 pub fn fetch_path(connection_id: &str) -> Path {
288 Path::new(["info", connection_id])
289 }
290
291 #[allow(clippy::module_name_repetitions)]
293 #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
294 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
295 #[serde(rename_all = "camelCase")]
296 #[cfg_attr(not(feature = "server"), non_exhaustive)]
297 pub struct ConnectionInfo {
298 pub id: ConnectionId,
300
301 pub countries: Vec<CountryCode>,
303
304 pub display_name: String,
306
307 pub credentials: CredentialsModel,
309
310 #[serde(skip_serializing_if = "Option::is_none")]
312 #[cfg_attr(feature = "uniffi", uniffi(default))]
313 pub user_id: Option<String>,
314
315 #[serde(skip_serializing_if = "Option::is_none")]
317 #[cfg_attr(feature = "uniffi", uniffi(default))]
318 pub advice: Option<String>,
319
320 pub logo_id: String,
322 }
323}
324
325macro_rules! context {
326 ($name:ident) => {
327 #[serde_with::serde_as]
328 #[derive(Serialize, Deserialize)]
329 #[serde(transparent)]
330 pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
331
332 impl<S> PartialEq for $name<S> {
333 fn eq(&self, other: &Self) -> bool {
334 self.0 == other.0
335 }
336 }
337
338 impl<S> Eq for $name<S> {}
339
340 impl<S> Clone for $name<S> {
341 fn clone(&self) -> Self {
342 Self(self.0.clone(), self.1)
343 }
344 }
345
346 impl<S> fmt::Debug for $name<S> {
347 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
348 f.debug_tuple("$name")
349 .field(&self.0)
350 .field(&self.1)
351 .finish()
352 }
353 }
354
355 impl<S> From<Vec<u8>> for $name<S> {
356 fn from(value: Vec<u8>) -> Self {
357 Self(value, PhantomData)
358 }
359 }
360
361 impl<S> From<$name<S>> for Vec<u8> {
362 fn from(value: $name<S>) -> Self {
363 value.0
364 }
365 }
366
367 impl<S> AsRef<[u8]> for $name<S> {
368 fn as_ref(&self) -> &[u8] {
369 &self.0
370 }
371 }
372 };
373}
374
375context!(InputContext);
376
377context!(ConfirmationContext);
378
379#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
405#[serde(transparent)]
406pub struct Authenticated<T> {
407 jwt: String,
408 _phantom: PhantomData<T>,
409}
410
411impl<'de, T> Deserialize<'de> for Authenticated<T>
412where
413 T: for<'t> Deserialize<'t> + Clone,
414{
415 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
416 where
417 D: serde::Deserializer<'de>,
418 {
419 String::deserialize(deserializer)?
420 .parse()
421 .map_err(serde::de::Error::custom)
422 }
423}
424
425impl<T> Authenticated<T>
426where
427 T: Serialize,
428{
429 #[must_use]
430 pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
436 let header = Header {
437 kid: Some(key_id.into()),
438 ..Default::default()
439 };
440
441 let jwt = encode(
442 &header,
443 &Claims {
444 data,
445 exp: 2_540_808_000,
447 },
448 &EncodingKey::from_secret(key),
449 )
450 .expect("Encoding should work");
451
452 Self {
453 jwt,
454 _phantom: PhantomData,
455 }
456 }
457}
458
459impl<T> FromStr for Authenticated<T>
460where
461 T: for<'de> Deserialize<'de> + Clone,
462{
463 type Err = jsonwebtoken::errors::Error;
464
465 fn from_str(jwt: &str) -> Result<Self, Self::Err> {
466 decode::<T>(jwt)?;
468 Ok(Self {
469 jwt: jwt.to_string(),
470 _phantom: PhantomData,
471 })
472 }
473}
474
475impl<T> Authenticated<T>
476where
477 T: for<'de> Deserialize<'de> + Clone,
478{
479 #[must_use]
480 pub fn as_str(&self) -> &str {
481 &self.jwt
482 }
483
484 #[must_use]
485 pub fn key_id(&self) -> Option<String> {
491 decode_header(&self.jwt).unwrap().kid
492 }
493
494 #[cfg(feature = "client")]
495 #[must_use]
496 #[allow(clippy::missing_panics_doc)]
497 pub fn to_data(&self) -> T {
498 decode(&self.jwt).unwrap()
499 }
500
501 #[cfg(feature = "server")]
502 pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
508 decoder.decode::<T>(&self.jwt).map(|d| d.data)
509 }
510}
511
512#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
513#[serde(rename_all = "camelCase")]
514pub struct Claims<T> {
515 pub data: T,
516 pub exp: u64,
517}
518
519#[cfg(feature = "server")]
520pub trait Decoder {
521 type Error;
522
523 fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
529 where
530 T: for<'de> Deserialize<'de> + Clone;
531}
532
533#[derive(Serialize, Deserialize, Debug)]
538pub enum OBResponse<S: Service> {
539 #[serde(bound = "S:")]
540 Result(
541 Authenticated<OBResult<S::Output>>,
542 Option<Session>,
543 Option<ConnectionData>,
544 ),
545 #[serde(bound = "S:")]
546 Dialog(Dialog<S>),
547 #[serde(bound = "S:")]
548 Redirect(Redirect<S>),
549 #[serde(bound = "S:")]
550 RedirectHandle(RedirectHandle<S>),
551}
552
553impl<S: Service> Clone for OBResponse<S> {
554 fn clone(&self) -> Self {
555 match self {
556 OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
557 authenticated.clone(),
558 session.clone(),
559 connection_data.clone(),
560 ),
561 OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
562 OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
563 OBResponse::RedirectHandle(redirect_handle) => {
564 OBResponse::RedirectHandle(redirect_handle.clone())
565 }
566 }
567 }
568}
569
570#[derive(Serialize, Deserialize, Debug)]
571#[cfg_attr(not(feature = "server"), non_exhaustive)]
572pub struct NonInteractiveResponse<S: Service> {
573 #[serde(bound = "S:")]
574 pub result: S::Output,
575 pub connection_data: Option<ConnectionData>,
576}
577
578#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
583#[serde(rename_all = "camelCase")]
584pub struct OBResult<T> {
585 pub data: T,
586 pub ticket_id: String,
587 pub timestamp: DateTime,
588}
589
590#[serde_with::serde_as]
591#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
592pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
593
594impl From<Vec<u8>> for Session {
595 fn from(value: Vec<u8>) -> Self {
596 Session(value)
597 }
598}
599
600impl From<Session> for Vec<u8> {
601 fn from(session: Session) -> Self {
602 session.0
603 }
604}
605
606impl AsRef<[u8]> for Session {
607 fn as_ref(&self) -> &[u8] {
608 &self.0
609 }
610}
611
612#[serde_with::serde_as]
613#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
614#[serde(transparent)]
615pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
616
617impl From<Vec<u8>> for ConnectionData {
618 fn from(value: Vec<u8>) -> Self {
619 ConnectionData(value)
620 }
621}
622
623impl From<ConnectionData> for Vec<u8> {
624 fn from(connection_data: ConnectionData) -> Self {
625 connection_data.0
626 }
627}
628
629impl AsRef<[u8]> for ConnectionData {
630 fn as_ref(&self) -> &[u8] {
631 &self.0
632 }
633}
634
635#[derive(Serialize, Deserialize)]
643#[serde(rename_all = "camelCase")]
644pub struct Redirect<S> {
645 pub url: Url,
646 #[serde(bound = "S:")]
647 pub context: ConfirmationContext<S>,
648}
649
650impl<S> PartialEq for Redirect<S> {
651 fn eq(&self, other: &Self) -> bool {
652 let Self { url, context } = self;
653 url == &other.url && context == &other.context
654 }
655}
656
657impl<S> Eq for Redirect<S> {}
658
659impl<S> Clone for Redirect<S> {
660 fn clone(&self) -> Self {
661 Self {
662 url: self.url.clone(),
663 context: self.context.clone(),
664 }
665 }
666}
667
668impl<S> fmt::Debug for Redirect<S> {
669 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
670 f.debug_struct("Redirect")
671 .field("url", &self.url)
672 .field("context", &self.context)
673 .finish()
674 }
675}
676
677#[derive(Serialize, Deserialize)]
681#[serde(rename_all = "camelCase")]
682pub struct RedirectHandle<S> {
683 pub handle: String,
684 #[serde(bound = "S:")]
685 pub context: ConfirmationContext<S>,
686}
687
688impl<S> PartialEq for RedirectHandle<S> {
689 fn eq(&self, other: &Self) -> bool {
690 let Self { handle, context } = self;
691 handle == &other.handle && context == &other.context
692 }
693}
694
695impl<S> Eq for RedirectHandle<S> {}
696
697impl<S> Clone for RedirectHandle<S> {
698 fn clone(&self) -> Self {
699 Self {
700 handle: self.handle.clone(),
701 context: self.context.clone(),
702 }
703 }
704}
705
706impl<S> fmt::Debug for RedirectHandle<S> {
707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708 f.debug_struct("RedirectHandle")
709 .field("handle", &self.handle)
710 .field("context", &self.context)
711 .finish()
712 }
713}
714
715fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
717where
718 T: for<'de> Deserialize<'de> + Clone,
719{
720 insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
721}
722
723#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
724pub enum ServiceId {
725 Accounts,
726 CollectPayment,
727 Transactions,
728 Balances,
729}
730
731impl fmt::Display for ServiceId {
732 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733 self.serialize(f)
734 }
735}
736
737#[derive(Serialize, Deserialize)]
797#[serde(rename_all = "camelCase")]
798pub struct Ticket<S: Service> {
799 pub service: ServiceId,
800 pub id: String,
801 pub data: S::TicketData,
802}
803
804impl<S: Service> PartialEq for Ticket<S> {
805 fn eq(&self, other: &Self) -> bool {
806 let Self { service, id, data } = self;
807 service == &other.service && id == &other.id && data == &other.data
808 }
809}
810
811impl<S: Service> Eq for Ticket<S> {}
812
813impl<S: Service> fmt::Debug for Ticket<S> {
814 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
815 f.debug_struct("Ticket")
816 .field("service", &self.service)
817 .field("id", &self.id)
818 .field("data", &self.data)
819 .finish()
820 }
821}
822
823impl<S: Service> Clone for Ticket<S> {
824 fn clone(&self) -> Self {
825 Self {
826 service: self.service,
827 id: self.id.clone(),
828 data: self.data.clone(),
829 }
830 }
831}
832
833pub trait Service {
834 const ID: ServiceId;
835
836 type TicketData: Serialize
837 + for<'de> Deserialize<'de>
838 + PartialEq
839 + Eq
840 + Clone
841 + fmt::Debug
842 + Send
843 + Sync;
844
845 type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
846
847 type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
848
849 fn path() -> Path {
855 Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
856 }
857
858 fn response_path() -> Path {
859 Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
860 }
861
862 fn confirmation_path() -> Path {
863 Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
864 }
865}
866
867#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
868#[serde(tag = "ticketStatus")]
869pub enum TicketStatus<T> {
870 Unfinished,
871 Error(ErrorKind),
872 Success(T),
873}
874
875#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
876#[serde(tag = "error")]
877pub enum ErrorKind {
878 UnexpectedError,
879 Canceled,
880 InvalidCredentials,
881 ServiceBlocked,
882 Unauthorized,
883 ConsentExpired,
884 AccessExceeded,
885 PeriodOutOfBounds,
886 UnsupportedProduct,
887 PaymentFailed,
888 UnexpectedValue,
889 TicketError,
890 ProviderError,
891 NotFound,
892}
893
894#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
895#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
896#[serde(rename_all = "camelCase")]
897pub struct Credentials {
898 pub connection_id: ConnectionId,
899 #[serde(skip_serializing_if = "Option::is_none")]
900 #[cfg_attr(feature = "uniffi", uniffi(default))]
901 pub user_id: Option<String>,
902 #[serde(skip_serializing_if = "Option::is_none")]
903 #[cfg_attr(feature = "uniffi", uniffi(default))]
904 pub password: Option<String>,
905 #[serde(skip_serializing_if = "Option::is_none")]
906 #[cfg_attr(feature = "uniffi", uniffi(default))]
907 pub connection_data: Option<ConnectionData>,
908}
909
910#[derive(Serialize, Deserialize)]
911#[serde(rename_all = "camelCase")]
912pub struct ResponseData<S> {
913 #[serde(bound = "S:")]
914 pub context: InputContext<S>,
915 pub response: String,
916}
917
918impl<S> PartialEq for ResponseData<S> {
919 fn eq(&self, other: &Self) -> bool {
920 let Self { context, response } = self;
921 context == &other.context && response == &other.response
922 }
923}
924
925impl<S> Eq for ResponseData<S> {}
926
927impl<S> Clone for ResponseData<S> {
928 fn clone(&self) -> Self {
929 Self {
930 context: self.context.clone(),
931 response: self.response.clone(),
932 }
933 }
934}
935
936impl<S> fmt::Debug for ResponseData<S> {
937 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
938 f.debug_struct("ResponseData")
939 .field("context", &self.context)
940 .field("response", &self.response)
941 .finish()
942 }
943}
944
945#[derive(Serialize, Deserialize)]
946#[serde(rename_all = "camelCase")]
947pub struct ConfirmationData<S> {
948 #[serde(bound = "S:")]
949 pub context: ConfirmationContext<S>,
950}
951
952impl<S> PartialEq for ConfirmationData<S> {
953 fn eq(&self, other: &Self) -> bool {
954 let Self { context } = self;
955 context == &other.context
956 }
957}
958
959impl<S> Eq for ConfirmationData<S> {}
960
961impl<S> Clone for ConfirmationData<S> {
962 fn clone(&self) -> Self {
963 Self {
964 context: self.context.clone(),
965 }
966 }
967}
968
969impl<S> fmt::Debug for ConfirmationData<S> {
970 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
971 f.debug_struct("ConfirmationData")
972 .field("context", &self.context)
973 .finish()
974 }
975}
976
977#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
978#[serde(rename_all_fields = "camelCase")]
979#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
980pub enum Error {
981 #[cfg_attr(not(feature = "server"), non_exhaustive)]
982 UnexpectedError {
983 #[serde(skip_serializing_if = "Option::is_none")]
984 user_message: Option<String>,
985 },
986 #[cfg_attr(not(feature = "server"), non_exhaustive)]
987 Canceled {},
988 #[cfg_attr(not(feature = "server"), non_exhaustive)]
989 InvalidCredentials {
990 #[serde(skip_serializing_if = "Option::is_none")]
991 user_message: Option<String>,
992 },
993 #[cfg_attr(not(feature = "server"), non_exhaustive)]
994 ServiceBlocked {
995 #[serde(skip_serializing_if = "Option::is_none")]
996 user_message: Option<String>,
997 #[serde(skip_serializing_if = "Option::is_none")]
998 code: Option<ServiceBlockedCode>,
999 },
1000 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1001 Unauthorized {
1002 #[serde(skip_serializing_if = "Option::is_none")]
1003 user_message: Option<String>,
1004 },
1005 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1006 ConsentExpired {
1007 #[serde(skip_serializing_if = "Option::is_none")]
1008 user_message: Option<String>,
1009 },
1010 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1011 AccessExceeded {
1012 #[serde(skip_serializing_if = "Option::is_none")]
1013 user_message: Option<String>,
1014 },
1015 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1016 PeriodOutOfBounds {
1017 #[serde(skip_serializing_if = "Option::is_none")]
1018 user_message: Option<String>,
1019 },
1020 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1021 UnsupportedProduct {
1022 #[serde(skip_serializing_if = "Option::is_none")]
1023 reason: Option<UnsupportedProductReason>,
1024 #[serde(skip_serializing_if = "Option::is_none")]
1025 user_message: Option<String>,
1026 },
1027 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1028 PaymentFailed {
1029 #[serde(skip_serializing_if = "Option::is_none")]
1030 code: Option<PaymentErrorCode>,
1031 #[serde(skip_serializing_if = "Option::is_none")]
1032 user_message: Option<String>,
1033 },
1034 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1035 UnexpectedValue { error: String },
1036 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1037 TicketError {
1038 error: String,
1039 code: TicketErrorCode,
1040 },
1041 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1042 ProviderError {
1043 #[serde(skip_serializing_if = "Option::is_none")]
1044 code: Option<ProviderErrorCode>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1046 user_message: Option<String>,
1047 },
1048 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1049 InterruptError {},
1050}
1051
1052#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1053#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1054#[cfg_attr(not(feature = "server"), non_exhaustive)]
1055pub enum TicketErrorCode {
1056 Missing,
1058 Invalid,
1060 MissingKey,
1062 UnknownKey,
1064 Mismatch,
1066 Expired,
1068 InvalidLifetime,
1070 ExpiredKey,
1072 KeyEnvironmentMismatch,
1074}
1075
1076#[derive(Serialize, Deserialize, Clone, Debug)]
1077#[cfg_attr(not(feature = "server"), non_exhaustive)]
1078pub enum Filter<F> {
1079 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1080 Eq(F, Value),
1081 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1082 NotEq(F, Value),
1083 #[cfg(feature = "server")]
1084 Contains(F, Value),
1085 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1086 And(Box<Self>, Box<Self>),
1087 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1088 Or(Box<Self>, Box<Self>),
1089 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1090 Supports(SupportedService),
1091}
1092
1093#[derive(Serialize, Deserialize, Clone, Debug)]
1094#[cfg_attr(not(feature = "server"), non_exhaustive)]
1095#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1096pub enum SupportedService {
1097 CollectPayment,
1098}
1099
1100impl<F> Filter<F> {
1101 #[must_use]
1102 pub fn and(self, other: Filter<F>) -> Filter<F> {
1104 Filter::And(Box::new(self), Box::new(other))
1105 }
1106
1107 #[must_use]
1108 pub fn or(self, other: Filter<F>) -> Filter<F> {
1110 Filter::Or(Box::new(self), Box::new(other))
1111 }
1112}
1113
1114pub struct Field<F, T> {
1115 field: F,
1116 phantom: PhantomData<T>,
1117}
1118
1119impl<F, T> Field<F, T>
1120where
1121 F: Copy,
1122 T: Serialize,
1123{
1124 pub fn eq(&self, value: T) -> Filter<F> {
1130 Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1131 }
1132
1133 pub fn not_eq(&self, value: T) -> Filter<F> {
1139 Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1140 }
1141}
1142
1143pub trait GetValue {
1144 type Model;
1145
1146 fn get(&self, model: &Self::Model) -> Value;
1147}
1148
1149macro_rules! fields {
1150 {
1151 $model:ty, $enum_name:ident
1152 $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1153 $(
1154 server:
1155 $(($server_variant_name:ident, $server_field_name:ident))+
1156 )?
1157 } => {
1158 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1159 #[non_exhaustive]
1160 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1161 pub enum $enum_name {
1162 $($variant_name,)+
1163 $($(
1164 #[cfg(feature = "server")]
1165 $server_variant_name,
1166 )+)?
1167 }
1168
1169 impl $enum_name {
1170 $(
1171 pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1172 field: $enum_name::$variant_name,
1173 phantom: std::marker::PhantomData,
1174 };
1175 )+
1176 }
1177
1178 impl crate::GetValue for $enum_name {
1179 type Model = $model;
1180
1181 fn get(&self, model: &$model) -> serde_json::Value {
1182 match self {
1183 $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1184 $($(
1185 #[cfg(feature = "server")]
1186 $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1187 )+)?
1188 }.expect("Serialization should work")
1189 }
1190 }
1191 }
1192}
1193
1194#[derive(Serialize, Deserialize)]
1195#[serde(rename_all = "camelCase")]
1196pub struct ServiceRequest<S: Service> {
1197 pub credentials: Credentials,
1198 pub session: Option<Session>,
1199 #[serde(default, skip_serializing_if = "is_false")]
1200 pub recurring_consents: bool,
1201 #[serde(flatten, bound = "S:")]
1202 pub data: <S as Service>::RequestData,
1203}
1204
1205impl<S: Service> Clone for ServiceRequest<S> {
1206 fn clone(&self) -> Self {
1207 Self {
1208 credentials: self.credentials.clone(),
1209 session: self.session.clone(),
1210 recurring_consents: self.recurring_consents,
1211 data: self.data.clone(),
1212 }
1213 }
1214}
1215
1216#[derive(Serialize, Deserialize)]
1217#[serde(rename_all = "camelCase")]
1218pub struct NonInteractiveRequest<S: Service> {
1219 pub connection_data: ConnectionData,
1220 #[serde(flatten, bound = "S:")]
1221 pub data: <S as Service>::RequestData,
1222}
1223
1224#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1225#[serde(rename_all = "camelCase")]
1226#[non_exhaustive]
1227#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1228pub struct Account {
1229 #[serde(skip_serializing_if = "Option::is_none")]
1231 #[cfg_attr(feature = "uniffi", uniffi(default))]
1232 pub iban: Option<String>,
1233
1234 #[serde(skip_serializing_if = "Option::is_none")]
1236 #[cfg_attr(feature = "uniffi", uniffi(default))]
1237 pub number: Option<String>,
1238
1239 #[serde(skip_serializing_if = "Option::is_none")]
1241 #[cfg_attr(feature = "uniffi", uniffi(default))]
1242 pub bic: Option<String>,
1243
1244 #[serde(skip_serializing_if = "Option::is_none")]
1246 #[cfg_attr(feature = "uniffi", uniffi(default))]
1247 pub bank_code: Option<String>,
1248
1249 #[serde(skip_serializing_if = "Option::is_none")]
1251 #[cfg_attr(feature = "uniffi", uniffi(default))]
1252 pub currency: Option<String>,
1253
1254 #[serde(skip_serializing_if = "Option::is_none")]
1256 #[cfg_attr(feature = "uniffi", uniffi(default))]
1257 pub name: Option<String>,
1258
1259 #[serde(skip_serializing_if = "Option::is_none")]
1261 #[cfg_attr(feature = "uniffi", uniffi(default))]
1262 pub display_name: Option<String>,
1263
1264 #[serde(skip_serializing_if = "Option::is_none")]
1266 #[cfg_attr(feature = "uniffi", uniffi(default))]
1267 pub owner_name: Option<String>,
1268
1269 #[serde(skip_serializing_if = "Option::is_none")]
1271 #[cfg_attr(feature = "uniffi", uniffi(default))]
1272 pub product_name: Option<String>,
1273
1274 #[serde(skip_serializing_if = "Option::is_none")]
1276 #[cfg_attr(feature = "uniffi", uniffi(default))]
1277 pub status: Option<AccountStatus>,
1278
1279 #[serde(rename = "type")]
1281 #[serde(skip_serializing_if = "Option::is_none")]
1282 #[cfg_attr(feature = "uniffi", uniffi(default))]
1283 pub type_: Option<AccountType>,
1284}
1285
1286pub mod accounts {
1287 use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1288 use serde::{Deserialize, Serialize};
1289
1290 #[derive(Clone, Debug)]
1291 pub struct Service {}
1292
1293 impl super::Service for Service {
1294 const ID: ServiceId = ServiceId::Accounts;
1295
1296 type TicketData = ();
1297
1298 type RequestData = RequestData;
1299
1300 type Output = Vec<Account>;
1301 }
1302
1303 fields! {
1304 routex_models::Account, AccountField
1305 (IBAN, Iban, iban, Option<String>)
1306 (NUMBER, Number, number, Option<String>)
1307 (BIC, Bic, bic, Option<String>)
1308 (BANK_CODE, BankCode, bank_code, Option<String>)
1309 (CURRENCY, Currency, currency, String)
1310 (NAME, Name, name, Option<String>)
1311 (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1312 (OWNER_NAME, OwnerName, owner_name, Option<String>)
1313 (PRODUCT_NAME, ProductName, product_name, Option<String>)
1314 (STATUS, Status, status, Option<AccountStatus>)
1315 (TYPE, Type, type_, Option<AccountType>)
1316 server:
1317 (Capabilities, capabilities)
1318 }
1319
1320 impl Account {
1321 #[must_use]
1322 pub fn supports(service: SupportedService) -> Filter<AccountField> {
1324 Filter::Supports(service)
1325 }
1326 }
1327
1328 #[derive(Serialize, Deserialize, Clone)]
1329 #[serde(rename_all = "camelCase")]
1330 pub struct RequestData {
1331 pub fields: Vec<AccountField>,
1332 pub filter: Option<Filter<AccountField>>,
1333 }
1334}
1335
1336#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1337#[serde(rename_all = "camelCase")]
1338#[serde(rename_all_fields = "camelCase")]
1339#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1340pub enum AccountIdentifier {
1341 Iban(String),
1343}
1344
1345#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1346#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1347#[serde(rename_all = "camelCase")]
1348pub struct AccountReference {
1349 #[serde(flatten)]
1350 pub id: AccountIdentifier,
1351 #[serde(skip_serializing_if = "Option::is_none")]
1352 #[cfg_attr(feature = "uniffi", uniffi(default))]
1353 pub currency: Option<String>,
1354}
1355
1356pub mod balances {
1357 pub use rust_decimal::Decimal;
1358 use serde::{Deserialize, Serialize};
1359
1360 use crate::{AccountReference, Path, ServiceId};
1361
1362 pub fn non_interactive_path() -> Path {
1363 Path::new(["balances", "non-interactive"])
1364 }
1365
1366 #[derive(Clone, Debug)]
1367 pub struct Service {}
1368
1369 impl super::Service for Service {
1370 const ID: ServiceId = ServiceId::Balances;
1371
1372 type TicketData = ();
1373
1374 type RequestData = RequestData;
1375
1376 type Output = Balances;
1377 }
1378
1379 #[derive(Serialize, Deserialize, Clone)]
1380 #[serde(rename_all = "camelCase")]
1381 pub struct RequestData {
1382 pub accounts: Vec<AccountReference>,
1383 }
1384
1385 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1386 #[serde(rename_all = "camelCase")]
1387 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1388 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1389 pub struct Balances {
1390 pub balances: Vec<AccountBalances>,
1392
1393 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1395 #[cfg_attr(feature = "uniffi", uniffi(default))]
1396 pub missing_accounts: Vec<AccountReference>,
1397 }
1398
1399 #[allow(clippy::module_name_repetitions)]
1400 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1401 #[serde(rename_all = "camelCase")]
1402 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1403 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1404 pub struct AccountBalances {
1405 pub account: AccountReference,
1406
1407 pub balances: Vec<Balance>,
1409 }
1410
1411 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1412 #[serde(rename_all = "camelCase")]
1413 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1414 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1415 pub struct Balance {
1416 pub amount: Decimal,
1417 pub currency: String,
1418 pub balance_type: BalanceType,
1419 }
1420
1421 #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1422 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1423 pub enum BalanceType {
1424 Booked,
1426
1427 Available,
1429
1430 Expected,
1432 }
1433}
1434
1435pub mod transactions {
1436 use chrono::NaiveDate;
1437 use isocountry::CountryCode;
1438 pub use routex_models::{Amount, Fee, TransactionStatus};
1439 use rust_decimal::Decimal;
1440 use serde::{Deserialize, Serialize};
1441 use url::Url;
1442
1443 use crate::{AccountReference, Path, ServiceId};
1444
1445 pub fn non_interactive_path() -> Path {
1446 Path::new(["transactions", "non-interactive"])
1447 }
1448
1449 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1450 #[serde(rename_all = "camelCase")]
1451 pub struct TicketData {
1452 pub account: AccountReference,
1453 pub range: Range,
1454 pub webhook: Option<Url>,
1455 }
1456
1457 #[derive(Clone, Debug)]
1458 pub struct Service {}
1459
1460 impl super::Service for Service {
1461 const ID: ServiceId = ServiceId::Transactions;
1462
1463 type TicketData = TicketData;
1464
1465 type RequestData = RequestData;
1466
1467 type Output = Option<Vec<Transaction>>;
1468 }
1469
1470 #[derive(Serialize, Deserialize, Clone)]
1471 #[serde(rename_all = "camelCase")]
1472 pub struct RequestData {}
1473
1474 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1475 #[serde(rename_all = "camelCase")]
1476 #[serde(rename_all_fields = "camelCase")]
1477 pub enum Range {
1478 Reference(String),
1479 #[serde(untagged)]
1480 Period {
1481 from: NaiveDate,
1483 to: Option<NaiveDate>,
1485 },
1486 }
1487
1488 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1489 #[serde(rename_all = "camelCase")]
1490 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1491 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1492 pub struct Transaction {
1493 #[serde(skip_serializing_if = "Option::is_none")]
1495 #[cfg_attr(feature = "uniffi", uniffi(default))]
1496 pub entry_reference: Option<String>,
1497
1498 #[serde(skip_serializing_if = "Option::is_none")]
1499 #[cfg_attr(feature = "uniffi", uniffi(default))]
1500 pub batch: Option<BatchData>,
1501
1502 #[serde(skip_serializing_if = "Option::is_none")]
1504 #[cfg_attr(feature = "uniffi", uniffi(default))]
1505 pub booking_date: Option<NaiveDate>,
1506
1507 #[serde(skip_serializing_if = "Option::is_none")]
1509 #[cfg_attr(feature = "uniffi", uniffi(default))]
1510 pub value_date: Option<NaiveDate>,
1511
1512 #[serde(skip_serializing_if = "Option::is_none")]
1514 #[cfg_attr(feature = "uniffi", uniffi(default))]
1515 pub transaction_date: Option<NaiveDate>,
1516
1517 pub status: TransactionStatus,
1519
1520 #[serde(skip_serializing_if = "Option::is_none")]
1522 #[cfg_attr(feature = "uniffi", uniffi(default))]
1523 pub account_servicer_reference: Option<String>,
1524
1525 #[serde(skip_serializing_if = "Option::is_none")]
1527 #[cfg_attr(feature = "uniffi", uniffi(default))]
1528 pub payment_id: Option<String>,
1529
1530 #[serde(skip_serializing_if = "Option::is_none")]
1532 #[cfg_attr(feature = "uniffi", uniffi(default))]
1533 pub transaction_id: Option<String>,
1534
1535 #[serde(skip_serializing_if = "Option::is_none")]
1537 #[cfg_attr(feature = "uniffi", uniffi(default))]
1538 pub end_to_end_id: Option<String>,
1539
1540 #[serde(skip_serializing_if = "Option::is_none")]
1542 #[cfg_attr(feature = "uniffi", uniffi(default))]
1543 pub mandate_id: Option<String>,
1544
1545 #[serde(skip_serializing_if = "Option::is_none")]
1547 #[cfg_attr(feature = "uniffi", uniffi(default))]
1548 pub creditor_id: Option<String>,
1549
1550 pub amount: Amount,
1552
1553 #[serde(default, skip_serializing_if = "super::is_false")]
1555 #[cfg_attr(feature = "uniffi", uniffi(default))]
1556 pub reversal: bool,
1557
1558 #[serde(skip_serializing_if = "Option::is_none")]
1560 #[cfg_attr(feature = "uniffi", uniffi(default))]
1561 pub original_amount: Option<Amount>,
1562
1563 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1565 #[cfg_attr(feature = "uniffi", uniffi(default))]
1566 pub exchanges: Vec<ExchangeRate>,
1567
1568 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1570 #[cfg_attr(feature = "uniffi", uniffi(default))]
1571 pub fees: Vec<Fee>,
1572
1573 #[serde(skip_serializing_if = "Option::is_none")]
1575 #[cfg_attr(feature = "uniffi", uniffi(default))]
1576 pub creditor: Option<Party>,
1577
1578 #[serde(skip_serializing_if = "Option::is_none")]
1580 #[cfg_attr(feature = "uniffi", uniffi(default))]
1581 pub debtor: Option<Party>,
1582
1583 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1585 #[cfg_attr(feature = "uniffi", uniffi(default))]
1586 pub remittance_information: Vec<String>,
1587
1588 #[serde(skip_serializing_if = "Option::is_none")]
1590 #[cfg_attr(feature = "uniffi", uniffi(default))]
1591 pub purpose_code: Option<String>,
1592
1593 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1595 #[cfg_attr(feature = "uniffi", uniffi(default))]
1596 pub bank_transaction_codes: Vec<BankTransactionCode>,
1597
1598 #[serde(skip_serializing_if = "Option::is_none")]
1602 #[cfg_attr(feature = "uniffi", uniffi(default))]
1603 pub additional_information: Option<String>,
1604 }
1605
1606 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1607 #[serde(rename_all = "camelCase")]
1608 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1609 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1610 pub struct BatchData {
1611 #[serde(skip_serializing_if = "Option::is_none")]
1613 pub number_of_transactions: Option<u32>,
1614
1615 pub transactions: Vec<BatchTransactionDetails>,
1620 }
1621
1622 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1623 #[serde(rename_all = "camelCase")]
1624 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1625 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1626 pub struct BatchTransactionDetails {
1627 #[serde(skip_serializing_if = "Option::is_none")]
1629 #[cfg_attr(feature = "uniffi", uniffi(default))]
1630 pub account_servicer_reference: Option<String>,
1631
1632 #[serde(skip_serializing_if = "Option::is_none")]
1634 #[cfg_attr(feature = "uniffi", uniffi(default))]
1635 pub payment_id: Option<String>,
1636
1637 #[serde(skip_serializing_if = "Option::is_none")]
1639 #[cfg_attr(feature = "uniffi", uniffi(default))]
1640 pub transaction_id: Option<String>,
1641
1642 #[serde(skip_serializing_if = "Option::is_none")]
1644 #[cfg_attr(feature = "uniffi", uniffi(default))]
1645 pub end_to_end_id: Option<String>,
1646
1647 #[serde(skip_serializing_if = "Option::is_none")]
1649 #[cfg_attr(feature = "uniffi", uniffi(default))]
1650 pub mandate_id: Option<String>,
1651
1652 #[serde(skip_serializing_if = "Option::is_none")]
1654 #[cfg_attr(feature = "uniffi", uniffi(default))]
1655 pub creditor_id: Option<String>,
1656
1657 #[serde(skip_serializing_if = "Option::is_none")]
1659 #[cfg_attr(feature = "uniffi", uniffi(default))]
1660 pub amount: Option<Amount>,
1661
1662 #[serde(default, skip_serializing_if = "super::is_false")]
1664 #[cfg_attr(feature = "uniffi", uniffi(default))]
1665 pub reversal: bool,
1666
1667 #[serde(skip_serializing_if = "Option::is_none")]
1669 #[cfg_attr(feature = "uniffi", uniffi(default))]
1670 pub original_amount: Option<Amount>,
1671
1672 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1674 #[cfg_attr(feature = "uniffi", uniffi(default))]
1675 pub exchanges: Vec<ExchangeRate>,
1676
1677 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1679 #[cfg_attr(feature = "uniffi", uniffi(default))]
1680 pub fees: Vec<Fee>,
1681
1682 #[serde(skip_serializing_if = "Option::is_none")]
1684 #[cfg_attr(feature = "uniffi", uniffi(default))]
1685 pub creditor: Option<Party>,
1686
1687 #[serde(skip_serializing_if = "Option::is_none")]
1689 #[cfg_attr(feature = "uniffi", uniffi(default))]
1690 pub debtor: Option<Party>,
1691
1692 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1694 #[cfg_attr(feature = "uniffi", uniffi(default))]
1695 pub remittance_information: Vec<String>,
1696
1697 #[serde(skip_serializing_if = "Option::is_none")]
1699 #[cfg_attr(feature = "uniffi", uniffi(default))]
1700 pub purpose_code: Option<String>,
1701
1702 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1704 #[cfg_attr(feature = "uniffi", uniffi(default))]
1705 pub bank_transaction_codes: Vec<BankTransactionCode>,
1706
1707 #[serde(skip_serializing_if = "Option::is_none")]
1711 #[cfg_attr(feature = "uniffi", uniffi(default))]
1712 pub additional_information: Option<String>,
1713 }
1714
1715 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1716 #[serde(rename_all = "camelCase")]
1717 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1718 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1719 pub struct ExchangeRate {
1720 pub source_currency: String,
1722 #[serde(skip_serializing_if = "Option::is_none")]
1725 #[cfg_attr(feature = "uniffi", uniffi(default))]
1726 pub target_currency: Option<String>,
1727 #[serde(skip_serializing_if = "Option::is_none")]
1729 #[cfg_attr(feature = "uniffi", uniffi(default))]
1730 pub unit_currency: Option<String>,
1731 pub exchange_rate: Decimal,
1732 }
1733
1734 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1735 #[serde(rename_all = "camelCase")]
1736 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1737 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1738 pub struct Party {
1739 #[serde(skip_serializing_if = "Option::is_none")]
1741 #[cfg_attr(feature = "uniffi", uniffi(default))]
1742 pub name: Option<String>,
1743
1744 #[serde(skip_serializing_if = "Option::is_none")]
1746 #[cfg_attr(feature = "uniffi", uniffi(default))]
1747 pub iban: Option<String>,
1748
1749 #[serde(skip_serializing_if = "Option::is_none")]
1751 #[cfg_attr(feature = "uniffi", uniffi(default))]
1752 pub bic: Option<String>,
1753
1754 #[serde(skip_serializing_if = "Option::is_none")]
1756 #[cfg_attr(feature = "uniffi", uniffi(default))]
1757 pub ultimate: Option<String>,
1758 }
1759
1760 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1761 #[serde(rename_all = "camelCase")]
1762 #[serde(rename_all_fields = "camelCase")]
1763 #[non_exhaustive]
1764 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1765 pub enum BankTransactionCode {
1766 Iso {
1768 domain: String,
1770
1771 family: String,
1773
1774 sub_family: String,
1776 },
1777 Swift(String),
1779 Bai(String),
1781 National { code: String, country: CountryCode },
1783 Other {
1785 code: String,
1786 #[serde(skip_serializing_if = "Option::is_none")]
1787 issuer: Option<String>,
1788 },
1789 }
1790}
1791
1792#[allow(clippy::trivially_copy_pass_by_ref)]
1793fn is_false(val: &bool) -> bool {
1794 !*val
1795}
1796
1797#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1798#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1799#[non_exhaustive]
1800pub enum PaymentStatus {
1801 Accepted,
1805
1806 PartiallyAccepted,
1808
1809 CompletedDebtor,
1811
1812 CompletedCreditor,
1814}
1815
1816pub mod collect_payment {
1817 pub use routex_models::Amount;
1818 use serde::{Deserialize, Serialize};
1819 #[cfg(feature = "server")]
1820 use serde_with::base64::Base64;
1821
1822 #[cfg(not(feature = "server"))]
1823 pub use super::{
1824 AccountIdentifier as DebtorAccountIdentifier, AccountReference as DebtorAccountReference,
1825 };
1826 use super::{AccountIdentifier, Path, PaymentStatus, ServiceId};
1827
1828 pub fn status_path(ticket_id: &str) -> Path {
1833 Path::new(["collect-payment", "status", ticket_id])
1834 }
1835
1836 #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
1837 #[serde(rename_all = "camelCase")]
1838 #[non_exhaustive]
1839 pub struct SuccessStatusData {
1840 #[serde(skip_serializing_if = "Option::is_none")]
1841 pub payment_status: Option<PaymentStatus>,
1842 }
1843
1844 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1845 #[serde(rename_all = "camelCase")]
1846 pub struct TicketData {
1847 pub amount: Amount,
1848 pub creditor_account: AccountIdentifier,
1849 pub creditor_name: String,
1850 pub remittance: String,
1851 #[serde(skip_serializing_if = "Option::is_none")]
1852 pub instant: Option<bool>,
1853 #[serde(skip_serializing_if = "Option::is_none")]
1854 pub fields: Option<Vec<Field>>,
1855 }
1856
1857 #[derive(Clone, Debug)]
1858 pub struct Service {}
1859
1860 impl super::Service for Service {
1861 const ID: ServiceId = ServiceId::CollectPayment;
1862
1863 type TicketData = TicketData;
1864
1865 type RequestData = RequestData;
1866
1867 type Output = PaymentInitiation;
1868 }
1869
1870 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1871 #[serde(rename_all = "camelCase")]
1872 #[non_exhaustive]
1873 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1874 pub enum Field {
1875 DebtorIban,
1876 DebtorName,
1877 }
1878
1879 #[cfg(feature = "server")]
1880 #[serde_with::serde_as]
1881 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1882 #[serde(rename_all = "camelCase")]
1883 #[serde(rename_all_fields = "camelCase")]
1884 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1885 pub enum DebtorAccountIdentifier {
1886 #[serde(alias = "Iban")]
1888 Iban(String),
1889 EncryptedIban(#[serde_as(as = "Base64")] Vec<u8>),
1890 }
1891
1892 #[cfg(feature = "server")]
1893 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1894 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1895 #[serde(rename_all = "camelCase")]
1896 pub struct DebtorAccountReference {
1897 #[serde(flatten)]
1898 pub id: DebtorAccountIdentifier,
1899 #[serde(skip_serializing_if = "Option::is_none")]
1900 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1901 pub currency: Option<String>,
1902 }
1903
1904 #[derive(Serialize, Deserialize, Clone)]
1905 #[serde(rename_all = "camelCase")]
1906 pub struct RequestData {
1907 pub account: Option<DebtorAccountReference>,
1908 }
1909
1910 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1911 #[serde(rename_all = "camelCase")]
1912 #[non_exhaustive]
1913 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1914 pub struct PaymentInitiation {
1915 #[serde(skip_serializing_if = "Option::is_none")]
1916 pub status: Option<PaymentStatus>,
1917 #[serde(skip_serializing_if = "Option::is_none")]
1918 pub debtor_name: Option<String>,
1919 #[serde(skip_serializing_if = "Option::is_none")]
1920 pub debtor_iban: Option<String>,
1921 }
1922}
1923
1924#[cfg(test)]
1925mod tests {
1926 use std::fmt::Debug;
1927
1928 use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
1929 use serde::{Deserialize, Serialize};
1930
1931 use crate::{
1932 Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
1933 InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
1934 };
1935
1936 #[test]
1937 fn read_response() {
1938 let serialized = serde_json::to_string(
1939 &encode(
1940 &Header::new(Algorithm::HS256),
1941 &Claims {
1942 data: "data",
1943 exp: 2_540_808_000,
1944 },
1945 &EncodingKey::from_secret(b"does_not_matter"),
1946 )
1947 .unwrap(),
1948 )
1949 .unwrap();
1950
1951 let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
1952
1953 assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
1954 }
1955
1956 struct Service;
1957
1958 impl crate::Service for Service {
1959 const ID: ServiceId = ServiceId::Accounts;
1960
1961 type TicketData = ();
1962
1963 type RequestData = ();
1964
1965 type Output = ();
1966 }
1967
1968 #[allow(
1969 dead_code,
1970 unconditional_recursion,
1971 clippy::extra_unused_type_parameters
1972 )]
1973 fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
1974 test_bounds::<InputContext<Service>>();
1975 test_bounds::<ConfirmationContext<Service>>();
1976 test_bounds::<DialogInput<Service>>();
1977 test_bounds::<Dialog<Service>>();
1978 test_bounds::<Redirect<Service>>();
1979 test_bounds::<RedirectHandle<Service>>();
1980 test_bounds::<OBResult<()>>();
1981 test_bounds::<Session>();
1982
1983 test_bounds::<Authenticated<Ticket<Service>>>();
1984 test_bounds::<Ticket<Service>>();
1985 test_bounds::<ResponseData<Service>>();
1986 test_bounds::<ConfirmationData<Service>>();
1987 }
1988
1989 fn test_eq_<T: PartialEq + Debug>(o: &T) {
1990 assert!(o.eq(o));
1991 }
1992
1993 #[test]
1994 fn test_eq() {
1995 let ic = InputContext::<Service>::from(vec![]);
1996 let cc = ConfirmationContext::<Service>::from(vec![]);
1997
1998 test_eq_(&ic);
1999 test_eq_(&cc);
2000 test_eq_(&Redirect {
2001 url: "url:".parse().unwrap(),
2002 context: cc.clone(),
2003 });
2004 test_eq_(&RedirectHandle {
2005 handle: String::new(),
2006 context: cc.clone(),
2007 });
2008 test_eq_(&Ticket::<Service> {
2009 service: ServiceId::Accounts,
2010 id: String::new(),
2011 data: (),
2012 });
2013 test_eq_(&ResponseData {
2014 context: ic,
2015 response: String::new(),
2016 });
2017 test_eq_(&ConfirmationData { context: cc });
2018 }
2019}