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
68pub 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
125pub const CURRENT_MEDIA_TYPE: &str = "application/vnd.yaxi.v5";
126
127pub mod keys {
128 use super::Path;
129 use clerk_report::PublishedVersionEntry;
130 use serde::{Deserialize, Serialize};
131 use serde_with::base64::Base64;
132
133 #[must_use]
139 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 #[must_use]
190 pub fn path(trace_id: &str) -> Path {
191 Path::new(["traces", trace_id])
192 }
193}
194
195pub mod redirects {
196 use super::Path;
197 use serde::{Deserialize, Serialize};
198 use url::Url;
199
200 #[must_use]
206 pub fn path() -> Path {
207 Path::new(["redirects"])
208 }
209
210 #[derive(Serialize, Deserialize, Clone, Debug)]
211 #[serde(rename_all = "camelCase")]
212 pub struct Request {
213 pub handle: String,
214 #[serde(alias = "redirect_uri")]
215 pub redirect_uri: String,
216 }
217
218 #[derive(Serialize, Deserialize, Clone, Debug)]
219 #[serde(rename_all = "camelCase")]
220 pub struct Response {
221 pub redirect_url: Url,
222 }
223}
224
225pub mod info {
226 use super::Path;
227 pub use isocountry::CountryCode;
228 use routex_models::{ConnectionId, CredentialsModel};
229 use serde::{Deserialize, Serialize};
230
231 #[must_use]
237 pub fn search_path() -> Path {
238 Path::new(["search"])
239 }
240
241 #[derive(Serialize, Deserialize, Clone, Debug)]
242 #[serde(rename_all = "camelCase")]
243 pub struct Request {
244 pub filters: Vec<SearchFilter>,
245 #[serde(alias = "iban_detection")]
246 pub iban_detection: bool,
247 #[serde(skip_serializing_if = "Option::is_none")]
248 pub limit: Option<usize>,
249 }
250
251 #[derive(Serialize, Deserialize, Clone, Debug)]
255 #[serde(rename_all = "camelCase")]
256 #[serde(rename_all_fields = "camelCase")]
257 pub enum SearchFilter {
258 #[serde(alias = "Types")]
260 Types(Vec<ConnectionType>),
261 #[serde(alias = "Countries")]
263 Countries(Vec<CountryCode>),
264 #[serde(alias = "Name")]
266 Name(String),
267 #[serde(alias = "Bic")]
269 Bic(String),
270 #[serde(alias = "BankCode")]
272 BankCode(String),
273 #[serde(alias = "Term")]
275 Term(String),
276 }
277
278 #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Copy, Clone, Debug)]
280 #[non_exhaustive]
281 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
282 pub enum ConnectionType {
283 Production,
285 Sandboxes,
287 }
288
289 #[must_use]
294 pub fn fetch_path(connection_id: &str) -> Path {
295 Path::new(["info", connection_id])
296 }
297
298 #[allow(clippy::module_name_repetitions)]
300 #[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
301 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
302 #[serde(rename_all = "camelCase")]
303 #[cfg_attr(not(feature = "server"), non_exhaustive)]
304 pub struct ConnectionInfo {
305 pub id: ConnectionId,
307
308 pub countries: Vec<CountryCode>,
310
311 pub display_name: String,
313
314 pub credentials: CredentialsModel,
316
317 #[serde(skip_serializing_if = "Option::is_none")]
319 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
320 pub user_id: Option<String>,
321
322 #[serde(skip_serializing_if = "Option::is_none")]
324 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
325 pub advice: Option<String>,
326
327 pub logo_id: String,
329 }
330}
331
332macro_rules! context {
333 ($name:ident) => {
334 #[serde_with::serde_as]
335 #[derive(Serialize, Deserialize)]
336 #[serde(transparent)]
337 pub struct $name<S>(#[serde_as(as = "Base64")] Vec<u8>, PhantomData<fn(S)>);
338
339 impl<S> PartialEq for $name<S> {
340 fn eq(&self, other: &Self) -> bool {
341 self.0 == other.0
342 }
343 }
344
345 impl<S> Eq for $name<S> {}
346
347 impl<S> Clone for $name<S> {
348 fn clone(&self) -> Self {
349 Self(self.0.clone(), self.1)
350 }
351 }
352
353 impl<S> fmt::Debug for $name<S> {
354 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
355 f.debug_tuple("$name")
356 .field(&self.0)
357 .field(&self.1)
358 .finish()
359 }
360 }
361
362 impl<S> From<Vec<u8>> for $name<S> {
363 fn from(value: Vec<u8>) -> Self {
364 Self(value, PhantomData)
365 }
366 }
367
368 impl<S> From<$name<S>> for Vec<u8> {
369 fn from(value: $name<S>) -> Self {
370 value.0
371 }
372 }
373
374 impl<S> AsRef<[u8]> for $name<S> {
375 fn as_ref(&self) -> &[u8] {
376 &self.0
377 }
378 }
379 };
380}
381
382context!(InputContext);
383
384context!(ConfirmationContext);
385
386#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
412#[serde(transparent)]
413pub struct Authenticated<T> {
414 jwt: String,
415 _phantom: PhantomData<T>,
416}
417
418impl<'de, T> Deserialize<'de> for Authenticated<T>
419where
420 T: for<'t> Deserialize<'t> + Clone,
421{
422 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
423 where
424 D: serde::Deserializer<'de>,
425 {
426 String::deserialize(deserializer)?
427 .parse()
428 .map_err(serde::de::Error::custom)
429 }
430}
431
432impl<T> Authenticated<T>
433where
434 T: Serialize,
435{
436 #[must_use]
437 pub fn new(data: T, key_id: impl Into<String>, key: &[u8]) -> Self {
443 let header = Header {
444 kid: Some(key_id.into()),
445 ..Default::default()
446 };
447
448 let jwt = encode(
449 &header,
450 &Claims {
451 data,
452 exp: 2_540_808_000,
454 },
455 &EncodingKey::from_secret(key),
456 )
457 .expect("Encoding should work");
458
459 Self {
460 jwt,
461 _phantom: PhantomData,
462 }
463 }
464}
465
466impl<T> FromStr for Authenticated<T>
467where
468 T: for<'de> Deserialize<'de> + Clone,
469{
470 type Err = jsonwebtoken::errors::Error;
471
472 fn from_str(jwt: &str) -> Result<Self, Self::Err> {
473 decode::<T>(jwt)?;
475 Ok(Self {
476 jwt: jwt.to_string(),
477 _phantom: PhantomData,
478 })
479 }
480}
481
482impl<T> Authenticated<T>
483where
484 T: for<'de> Deserialize<'de> + Clone,
485{
486 #[must_use]
487 pub fn as_str(&self) -> &str {
488 &self.jwt
489 }
490
491 #[must_use]
492 pub fn key_id(&self) -> Option<String> {
498 decode_header(&self.jwt).unwrap().kid
499 }
500
501 #[cfg(feature = "client")]
502 #[must_use]
503 #[allow(clippy::missing_panics_doc)]
504 pub fn to_data(&self) -> T {
505 decode(&self.jwt).unwrap()
506 }
507
508 #[cfg(feature = "server")]
509 pub fn decode<D: Decoder>(&self, decoder: &D) -> Result<T, D::Error> {
515 decoder.decode::<T>(&self.jwt).map(|d| d.data)
516 }
517}
518
519#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
520#[serde(rename_all = "camelCase")]
521pub struct Claims<T> {
522 pub data: T,
523 pub exp: u64,
524}
525
526#[cfg(feature = "server")]
527pub trait Decoder {
528 type Error;
529
530 fn decode<T>(&self, jwt: &str) -> Result<Claims<T>, Self::Error>
536 where
537 T: for<'de> Deserialize<'de> + Clone;
538}
539
540#[derive(Serialize, Deserialize, Debug)]
545pub enum OBResponse<S: Service> {
546 #[serde(bound = "S:")]
547 Result(
548 Authenticated<OBResult<S::Output>>,
549 Option<Session>,
550 Option<ConnectionData>,
551 ),
552 #[serde(bound = "S:")]
553 Dialog(Dialog<S>),
554 #[serde(bound = "S:")]
555 Redirect(Redirect<S>),
556 #[serde(bound = "S:")]
557 RedirectHandle(RedirectHandle<S>),
558}
559
560impl<S: Service> Clone for OBResponse<S> {
561 fn clone(&self) -> Self {
562 match self {
563 OBResponse::Result(authenticated, session, connection_data) => OBResponse::Result(
564 authenticated.clone(),
565 session.clone(),
566 connection_data.clone(),
567 ),
568 OBResponse::Dialog(dialog) => OBResponse::Dialog(dialog.clone()),
569 OBResponse::Redirect(redirect) => OBResponse::Redirect(redirect.clone()),
570 OBResponse::RedirectHandle(redirect_handle) => {
571 OBResponse::RedirectHandle(redirect_handle.clone())
572 }
573 }
574 }
575}
576
577#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
582#[serde(rename_all = "camelCase")]
583pub struct OBResult<T> {
584 pub data: T,
585 pub ticket_id: String,
586 pub timestamp: DateTime,
587}
588
589#[serde_with::serde_as]
590#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
591pub struct Session(#[serde_as(as = "Base64")] Vec<u8>);
592
593impl From<Vec<u8>> for Session {
594 fn from(value: Vec<u8>) -> Self {
595 Session(value)
596 }
597}
598
599impl From<Session> for Vec<u8> {
600 fn from(session: Session) -> Self {
601 session.0
602 }
603}
604
605impl AsRef<[u8]> for Session {
606 fn as_ref(&self) -> &[u8] {
607 &self.0
608 }
609}
610
611#[serde_with::serde_as]
612#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
613#[serde(transparent)]
614pub struct ConnectionData(#[serde_as(as = "Base64")] Vec<u8>);
615
616impl From<Vec<u8>> for ConnectionData {
617 fn from(value: Vec<u8>) -> Self {
618 ConnectionData(value)
619 }
620}
621
622impl From<ConnectionData> for Vec<u8> {
623 fn from(connection_data: ConnectionData) -> Self {
624 connection_data.0
625 }
626}
627
628impl AsRef<[u8]> for ConnectionData {
629 fn as_ref(&self) -> &[u8] {
630 &self.0
631 }
632}
633
634#[derive(Serialize, Deserialize)]
642#[serde(rename_all = "camelCase")]
643pub struct Redirect<S> {
644 pub url: Url,
645 #[serde(bound = "S:")]
646 pub context: ConfirmationContext<S>,
647}
648
649impl<S> PartialEq for Redirect<S> {
650 fn eq(&self, other: &Self) -> bool {
651 let Self { url, context } = self;
652 url == &other.url && context == &other.context
653 }
654}
655
656impl<S> Eq for Redirect<S> {}
657
658impl<S> Clone for Redirect<S> {
659 fn clone(&self) -> Self {
660 Self {
661 url: self.url.clone(),
662 context: self.context.clone(),
663 }
664 }
665}
666
667impl<S> fmt::Debug for Redirect<S> {
668 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669 f.debug_struct("Redirect")
670 .field("url", &self.url)
671 .field("context", &self.context)
672 .finish()
673 }
674}
675
676#[derive(Serialize, Deserialize)]
680#[serde(rename_all = "camelCase")]
681pub struct RedirectHandle<S> {
682 pub handle: String,
683 #[serde(bound = "S:")]
684 pub context: ConfirmationContext<S>,
685}
686
687impl<S> PartialEq for RedirectHandle<S> {
688 fn eq(&self, other: &Self) -> bool {
689 let Self { handle, context } = self;
690 handle == &other.handle && context == &other.context
691 }
692}
693
694impl<S> Eq for RedirectHandle<S> {}
695
696impl<S> Clone for RedirectHandle<S> {
697 fn clone(&self) -> Self {
698 Self {
699 handle: self.handle.clone(),
700 context: self.context.clone(),
701 }
702 }
703}
704
705impl<S> fmt::Debug for RedirectHandle<S> {
706 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
707 f.debug_struct("RedirectHandle")
708 .field("handle", &self.handle)
709 .field("context", &self.context)
710 .finish()
711 }
712}
713
714fn decode<T>(jwt: &str) -> jsonwebtoken::errors::Result<T>
716where
717 T: for<'de> Deserialize<'de> + Clone,
718{
719 insecure_decode::<Claims<T>>(jwt).map(|data| data.claims.data)
720}
721
722#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
723pub enum ServiceId {
724 Accounts,
725 CollectPayment,
726 Transactions,
727 Balances,
728}
729
730impl fmt::Display for ServiceId {
731 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
732 self.serialize(f)
733 }
734}
735
736#[derive(Serialize, Deserialize)]
796#[serde(rename_all = "camelCase")]
797pub struct Ticket<S: Service> {
798 pub service: ServiceId,
799 pub id: String,
800 pub data: S::TicketData,
801}
802
803impl<S: Service> PartialEq for Ticket<S> {
804 fn eq(&self, other: &Self) -> bool {
805 let Self { service, id, data } = self;
806 service == &other.service && id == &other.id && data == &other.data
807 }
808}
809
810impl<S: Service> Eq for Ticket<S> {}
811
812impl<S: Service> fmt::Debug for Ticket<S> {
813 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
814 f.debug_struct("Ticket")
815 .field("service", &self.service)
816 .field("id", &self.id)
817 .field("data", &self.data)
818 .finish()
819 }
820}
821
822impl<S: Service> Clone for Ticket<S> {
823 fn clone(&self) -> Self {
824 Self {
825 service: self.service,
826 id: self.id.clone(),
827 data: self.data.clone(),
828 }
829 }
830}
831
832pub trait Service {
833 const ID: ServiceId;
834
835 type TicketData: Serialize
836 + for<'de> Deserialize<'de>
837 + PartialEq
838 + Eq
839 + Clone
840 + fmt::Debug
841 + Send
842 + Sync;
843
844 type RequestData: Serialize + for<'de> Deserialize<'de> + Send + Sync + Clone;
845
846 type Output: Serialize + for<'de> Deserialize<'de> + Send + fmt::Debug + Clone;
847
848 #[must_use]
854 fn path() -> Path {
855 Path::new([&Self::ID.to_string().to_kebab_case(), "service"])
856 }
857
858 #[must_use]
859 fn response_path() -> Path {
860 Path::new([&Self::ID.to_string().to_kebab_case(), "response"])
861 }
862
863 #[must_use]
864 fn confirmation_path() -> Path {
865 Path::new([&Self::ID.to_string().to_kebab_case(), "confirmation"])
866 }
867}
868
869#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
870#[serde(tag = "ticketStatus")]
871pub enum TicketStatus<T> {
872 Unfinished,
873 Error(ErrorKind),
874 Success(T),
875}
876
877#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
878#[serde(tag = "error")]
879pub enum ErrorKind {
880 UnexpectedError,
881 Canceled,
882 InvalidCredentials,
883 ServiceBlocked,
884 Unauthorized,
885 ConsentExpired,
886 AccessExceeded,
887 PeriodOutOfBounds,
888 UnsupportedProduct,
889 PaymentFailed,
890 UnexpectedValue,
891 TicketError,
892 ProviderError,
893 NotFound,
894}
895
896impl From<&Error> for ErrorKind {
897 fn from(err: &Error) -> Self {
898 match err {
899 Error::UnexpectedError { .. } => Self::UnexpectedError,
900 Error::Canceled { .. } => Self::Canceled,
901 Error::InvalidCredentials { .. } => Self::InvalidCredentials,
902 Error::ServiceBlocked { .. } => Self::ServiceBlocked,
903 Error::Unauthorized { .. } => Self::Unauthorized,
904 Error::ConsentExpired { .. } => Self::ConsentExpired,
905 Error::AccessExceeded { .. } => Self::AccessExceeded,
906 Error::PeriodOutOfBounds { .. } => Self::PeriodOutOfBounds,
907 Error::UnsupportedProduct { .. } => Self::UnsupportedProduct,
908 Error::PaymentFailed { .. } => Self::PaymentFailed,
909 Error::UnexpectedValue { .. } => Self::UnexpectedValue,
910 Error::TicketError { .. } => Self::TicketError,
911 Error::ProviderError { .. } => Self::ProviderError,
912 }
913 }
914}
915
916#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
917#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
918#[serde(rename_all = "camelCase")]
919pub struct Credentials {
920 #[serde(alias = "connection_id")]
921 pub connection_id: ConnectionId,
922 #[serde(skip_serializing_if = "Option::is_none")]
923 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
924 #[serde(alias = "user_id")]
925 pub user_id: Option<String>,
926 #[serde(skip_serializing_if = "Option::is_none")]
927 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
928 pub password: Option<String>,
929 #[serde(skip_serializing_if = "Option::is_none")]
930 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
931 #[serde(alias = "connection_data")]
932 pub connection_data: Option<ConnectionData>,
933}
934
935#[derive(Serialize, Deserialize)]
936#[serde(rename_all = "camelCase")]
937pub struct ResponseData<S> {
938 #[serde(bound = "S:")]
939 pub context: InputContext<S>,
940 pub response: String,
941}
942
943impl<S> PartialEq for ResponseData<S> {
944 fn eq(&self, other: &Self) -> bool {
945 let Self { context, response } = self;
946 context == &other.context && response == &other.response
947 }
948}
949
950impl<S> Eq for ResponseData<S> {}
951
952impl<S> Clone for ResponseData<S> {
953 fn clone(&self) -> Self {
954 Self {
955 context: self.context.clone(),
956 response: self.response.clone(),
957 }
958 }
959}
960
961impl<S> fmt::Debug for ResponseData<S> {
962 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963 f.debug_struct("ResponseData")
964 .field("context", &self.context)
965 .field("response", &self.response)
966 .finish()
967 }
968}
969
970#[derive(Serialize, Deserialize)]
971#[serde(rename_all = "camelCase")]
972pub struct ConfirmationData<S> {
973 #[serde(bound = "S:")]
974 pub context: ConfirmationContext<S>,
975}
976
977impl<S> PartialEq for ConfirmationData<S> {
978 fn eq(&self, other: &Self) -> bool {
979 let Self { context } = self;
980 context == &other.context
981 }
982}
983
984impl<S> Eq for ConfirmationData<S> {}
985
986impl<S> Clone for ConfirmationData<S> {
987 fn clone(&self) -> Self {
988 Self {
989 context: self.context.clone(),
990 }
991 }
992}
993
994impl<S> fmt::Debug for ConfirmationData<S> {
995 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
996 f.debug_struct("ConfirmationData")
997 .field("context", &self.context)
998 .finish()
999 }
1000}
1001
1002#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1003#[serde(rename_all_fields = "camelCase")]
1004#[cfg_attr(not(feature = "exhaustive-error"), non_exhaustive)]
1005pub enum Error {
1006 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1007 UnexpectedError {
1008 #[serde(skip_serializing_if = "Option::is_none")]
1009 user_message: Option<String>,
1010 },
1011 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1012 Canceled {},
1013 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1014 InvalidCredentials {
1015 #[serde(skip_serializing_if = "Option::is_none")]
1016 user_message: Option<String>,
1017 },
1018 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1019 ServiceBlocked {
1020 #[serde(skip_serializing_if = "Option::is_none")]
1021 user_message: Option<String>,
1022 #[serde(skip_serializing_if = "Option::is_none")]
1023 code: Option<ServiceBlockedCode>,
1024 },
1025 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1026 Unauthorized {
1027 #[serde(skip_serializing_if = "Option::is_none")]
1028 user_message: Option<String>,
1029 },
1030 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1031 ConsentExpired {
1032 #[serde(skip_serializing_if = "Option::is_none")]
1033 user_message: Option<String>,
1034 },
1035 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1036 AccessExceeded {
1037 #[serde(skip_serializing_if = "Option::is_none")]
1038 user_message: Option<String>,
1039 },
1040 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1041 PeriodOutOfBounds {
1042 #[serde(skip_serializing_if = "Option::is_none")]
1043 user_message: Option<String>,
1044 },
1045 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1046 UnsupportedProduct {
1047 #[serde(skip_serializing_if = "Option::is_none")]
1048 reason: Option<UnsupportedProductReason>,
1049 #[serde(skip_serializing_if = "Option::is_none")]
1050 user_message: Option<String>,
1051 },
1052 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1053 PaymentFailed {
1054 #[serde(skip_serializing_if = "Option::is_none")]
1055 code: Option<PaymentErrorCode>,
1056 #[serde(skip_serializing_if = "Option::is_none")]
1057 user_message: Option<String>,
1058 },
1059 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1060 UnexpectedValue { error: String },
1061 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1062 TicketError {
1063 error: String,
1064 code: TicketErrorCode,
1065 },
1066 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1067 ProviderError {
1068 #[serde(skip_serializing_if = "Option::is_none")]
1069 code: Option<ProviderErrorCode>,
1070 #[serde(skip_serializing_if = "Option::is_none")]
1071 user_message: Option<String>,
1072 },
1073}
1074
1075#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1076#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1077#[cfg_attr(not(feature = "server"), non_exhaustive)]
1078pub enum TicketErrorCode {
1079 Missing,
1081 Invalid,
1083 MissingKey,
1085 UnknownKey,
1087 Mismatch,
1089 Expired,
1091 InvalidLifetime,
1093 ExpiredKey,
1095 KeyEnvironmentMismatch,
1097}
1098
1099#[derive(Serialize, Deserialize, Clone, Debug)]
1100#[cfg_attr(not(feature = "server"), non_exhaustive)]
1101pub enum Filter<F> {
1102 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1103 Eq(F, Value),
1104 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1105 NotEq(F, Value),
1106 #[cfg(feature = "server")]
1107 Contains(F, Value),
1108 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1109 And(Box<Self>, Box<Self>),
1110 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1111 Or(Box<Self>, Box<Self>),
1112 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1113 Supports(SupportedService),
1114}
1115
1116#[derive(Serialize, Deserialize, Clone, Debug)]
1117#[cfg_attr(not(feature = "server"), non_exhaustive)]
1118#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1119pub enum SupportedService {
1120 CollectPayment,
1121}
1122
1123impl<F> Filter<F> {
1124 #[must_use]
1125 pub fn and(self, other: Filter<F>) -> Filter<F> {
1127 Filter::And(Box::new(self), Box::new(other))
1128 }
1129
1130 #[must_use]
1131 pub fn or(self, other: Filter<F>) -> Filter<F> {
1133 Filter::Or(Box::new(self), Box::new(other))
1134 }
1135}
1136
1137pub struct Field<F, T> {
1138 field: F,
1139 phantom: PhantomData<T>,
1140}
1141
1142impl<F, T> Field<F, T>
1143where
1144 F: Copy,
1145 T: Serialize,
1146{
1147 pub fn eq(&self, value: T) -> Filter<F> {
1153 Filter::Eq(self.field, serde_json::to_value(&value).unwrap())
1154 }
1155
1156 pub fn not_eq(&self, value: T) -> Filter<F> {
1162 Filter::NotEq(self.field, serde_json::to_value(&value).unwrap())
1163 }
1164}
1165
1166pub trait GetValue {
1167 type Model;
1168
1169 fn get(&self, model: &Self::Model) -> Value;
1170}
1171
1172macro_rules! fields {
1173 {
1174 $model:ty, $enum_name:ident
1175 $(($const_name:ident, $variant_name:ident, $field_name:ident, $($type:ty)+))+
1176 $(
1177 server:
1178 $(($server_variant_name:ident, $server_field_name:ident))+
1179 )?
1180 } => {
1181 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1182 #[non_exhaustive]
1183 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1184 pub enum $enum_name {
1185 $($variant_name,)+
1186 $($(
1187 #[cfg(feature = "server")]
1188 $server_variant_name,
1189 )+)?
1190 }
1191
1192 impl $enum_name {
1193 $(
1194 pub const $const_name: crate::Field<$enum_name, $($type)+> = crate::Field {
1195 field: $enum_name::$variant_name,
1196 phantom: std::marker::PhantomData,
1197 };
1198 )+
1199 }
1200
1201 impl crate::GetValue for $enum_name {
1202 type Model = $model;
1203
1204 fn get(&self, model: &$model) -> serde_json::Value {
1205 match self {
1206 $($enum_name::$variant_name => serde_json::to_value(&model.$field_name),)+
1207 $($(
1208 #[cfg(feature = "server")]
1209 $enum_name::$server_variant_name => serde_json::to_value(&model.$server_field_name),
1210 )+)?
1211 }.expect("Serialization should work")
1212 }
1213 }
1214 }
1215}
1216
1217#[derive(Serialize, Deserialize)]
1218#[serde(rename_all = "camelCase")]
1219pub struct ServiceRequest<S: Service> {
1220 pub credentials: Credentials,
1221 pub session: Option<Session>,
1222 #[serde(default, skip_serializing_if = "is_false")]
1223 pub recurring_consents: bool,
1224 #[serde(flatten, bound = "S:")]
1225 pub data: <S as Service>::RequestData,
1226}
1227
1228impl<S: Service> Clone for ServiceRequest<S> {
1229 fn clone(&self) -> Self {
1230 Self {
1231 credentials: self.credentials.clone(),
1232 session: self.session.clone(),
1233 recurring_consents: self.recurring_consents,
1234 data: self.data.clone(),
1235 }
1236 }
1237}
1238
1239#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1240#[serde(rename_all = "camelCase")]
1241#[non_exhaustive]
1242#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1243pub struct Account {
1244 #[serde(skip_serializing_if = "Option::is_none")]
1246 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1247 pub iban: Option<String>,
1248
1249 #[serde(skip_serializing_if = "Option::is_none")]
1251 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1252 pub number: Option<String>,
1253
1254 #[serde(skip_serializing_if = "Option::is_none")]
1256 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1257 pub bic: Option<String>,
1258
1259 #[serde(skip_serializing_if = "Option::is_none")]
1261 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1262 pub bank_code: Option<String>,
1263
1264 #[serde(skip_serializing_if = "Option::is_none")]
1266 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1267 pub currency: Option<String>,
1268
1269 #[serde(skip_serializing_if = "Option::is_none")]
1271 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1272 pub name: Option<String>,
1273
1274 #[serde(skip_serializing_if = "Option::is_none")]
1276 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1277 pub display_name: Option<String>,
1278
1279 #[serde(skip_serializing_if = "Option::is_none")]
1281 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1282 pub owner_name: Option<String>,
1283
1284 #[serde(skip_serializing_if = "Option::is_none")]
1286 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1287 pub product_name: Option<String>,
1288
1289 #[serde(skip_serializing_if = "Option::is_none")]
1291 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1292 pub status: Option<AccountStatus>,
1293
1294 #[serde(rename = "type")]
1296 #[serde(skip_serializing_if = "Option::is_none")]
1297 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1298 pub type_: Option<AccountType>,
1299}
1300
1301pub mod accounts {
1302 use super::{Account, AccountStatus, AccountType, Filter, ServiceId, SupportedService};
1303 use serde::{Deserialize, Serialize};
1304
1305 #[derive(Clone, Debug)]
1306 pub struct Service {}
1307
1308 impl super::Service for Service {
1309 const ID: ServiceId = ServiceId::Accounts;
1310
1311 type TicketData = ();
1312
1313 type RequestData = RequestData;
1314
1315 type Output = Vec<Account>;
1316 }
1317
1318 fields! {
1319 routex_models::Account, AccountField
1320 (IBAN, Iban, iban, Option<String>)
1321 (NUMBER, Number, number, Option<String>)
1322 (BIC, Bic, bic, Option<String>)
1323 (BANK_CODE, BankCode, bank_code, Option<String>)
1324 (CURRENCY, Currency, currency, String)
1325 (NAME, Name, name, Option<String>)
1326 (DISPLAY_NAME, DisplayName, display_name, Option<String>)
1327 (OWNER_NAME, OwnerName, owner_name, Option<String>)
1328 (PRODUCT_NAME, ProductName, product_name, Option<String>)
1329 (STATUS, Status, status, Option<AccountStatus>)
1330 (TYPE, Type, type_, Option<AccountType>)
1331 server:
1332 (Capabilities, capabilities)
1333 }
1334
1335 impl Account {
1336 #[must_use]
1337 pub fn supports(service: SupportedService) -> Filter<AccountField> {
1339 Filter::Supports(service)
1340 }
1341 }
1342
1343 #[derive(Serialize, Deserialize, Clone)]
1344 #[serde(rename_all = "camelCase")]
1345 pub struct RequestData {
1346 pub fields: Vec<AccountField>,
1347 pub filter: Option<Filter<AccountField>>,
1348 }
1349}
1350
1351#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1352#[serde(rename_all = "camelCase")]
1353#[serde(rename_all_fields = "camelCase")]
1354#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1355pub enum AccountIdentifier {
1356 #[serde(alias = "Iban")]
1358 Iban(String),
1359}
1360
1361#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1362#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1363#[serde(rename_all = "camelCase")]
1364pub struct AccountReference {
1365 #[serde(flatten)]
1366 pub id: AccountIdentifier,
1367 #[serde(skip_serializing_if = "Option::is_none")]
1368 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1369 pub currency: Option<String>,
1370}
1371
1372pub mod balances {
1373 pub use rust_decimal::Decimal;
1374 use serde::{Deserialize, Serialize};
1375
1376 use crate::{AccountReference, ServiceId};
1377
1378 #[derive(Clone, Debug)]
1379 pub struct Service {}
1380
1381 impl super::Service for Service {
1382 const ID: ServiceId = ServiceId::Balances;
1383
1384 type TicketData = ();
1385
1386 type RequestData = RequestData;
1387
1388 type Output = Balances;
1389 }
1390
1391 #[derive(Serialize, Deserialize, Clone)]
1392 #[serde(rename_all = "camelCase")]
1393 pub struct RequestData {
1394 pub accounts: Vec<AccountReference>,
1395 }
1396
1397 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1398 #[serde(rename_all = "camelCase")]
1399 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1400 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1401 pub struct Balances {
1402 pub balances: Vec<AccountBalances>,
1404
1405 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1407 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1408 pub missing_accounts: Vec<AccountReference>,
1409 }
1410
1411 #[allow(clippy::module_name_repetitions)]
1412 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1413 #[serde(rename_all = "camelCase")]
1414 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1415 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1416 pub struct AccountBalances {
1417 pub account: AccountReference,
1418
1419 pub balances: Vec<Balance>,
1421 }
1422
1423 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1424 #[serde(rename_all = "camelCase")]
1425 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1426 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1427 pub struct Balance {
1428 pub amount: Decimal,
1429 pub currency: String,
1430 pub balance_type: BalanceType,
1431 }
1432
1433 #[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)]
1434 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1435 pub enum BalanceType {
1436 Booked,
1438
1439 Available,
1441
1442 Expected,
1444 }
1445}
1446
1447pub mod transactions {
1448 use chrono::NaiveDate;
1449 use isocountry::CountryCode;
1450 pub use routex_models::{Amount, Fee, TransactionStatus};
1451 use rust_decimal::Decimal;
1452 use serde::{Deserialize, Serialize};
1453 use url::Url;
1454
1455 use crate::{AccountReference, ServiceId};
1456
1457 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1458 #[serde(rename_all = "camelCase")]
1459 pub struct TicketData {
1460 pub account: AccountReference,
1461 pub range: Range,
1462 pub webhook: Option<Url>,
1463 }
1464
1465 #[derive(Clone, Debug)]
1466 pub struct Service {}
1467
1468 impl super::Service for Service {
1469 const ID: ServiceId = ServiceId::Transactions;
1470
1471 type TicketData = TicketData;
1472
1473 type RequestData = RequestData;
1474
1475 type Output = Option<Vec<Transaction>>;
1476 }
1477
1478 #[derive(Serialize, Deserialize, Clone)]
1479 #[serde(rename_all = "camelCase")]
1480 pub struct RequestData {}
1481
1482 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1483 #[serde(rename_all = "camelCase")]
1484 #[serde(rename_all_fields = "camelCase")]
1485 pub enum Range {
1486 Reference(String),
1487 #[serde(untagged)]
1488 Period {
1489 from: NaiveDate,
1491 to: Option<NaiveDate>,
1493 },
1494 }
1495
1496 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1497 #[serde(rename_all = "camelCase")]
1498 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1499 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1500 pub struct Transaction {
1501 #[serde(skip_serializing_if = "Option::is_none")]
1503 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1504 pub entry_reference: Option<String>,
1505
1506 #[serde(skip_serializing_if = "Option::is_none")]
1507 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1508 pub batch: Option<BatchData>,
1509
1510 #[serde(skip_serializing_if = "Option::is_none")]
1512 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1513 pub booking_date: Option<NaiveDate>,
1514
1515 #[serde(skip_serializing_if = "Option::is_none")]
1517 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1518 pub value_date: Option<NaiveDate>,
1519
1520 #[serde(skip_serializing_if = "Option::is_none")]
1522 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1523 pub transaction_date: Option<NaiveDate>,
1524
1525 pub status: TransactionStatus,
1527
1528 #[serde(skip_serializing_if = "Option::is_none")]
1530 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1531 pub account_servicer_reference: Option<String>,
1532
1533 #[serde(skip_serializing_if = "Option::is_none")]
1535 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1536 pub payment_id: Option<String>,
1537
1538 #[serde(skip_serializing_if = "Option::is_none")]
1540 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1541 pub transaction_id: Option<String>,
1542
1543 #[serde(skip_serializing_if = "Option::is_none")]
1545 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1546 pub end_to_end_id: Option<String>,
1547
1548 #[serde(skip_serializing_if = "Option::is_none")]
1550 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1551 pub mandate_id: Option<String>,
1552
1553 #[serde(skip_serializing_if = "Option::is_none")]
1555 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1556 pub creditor_id: Option<String>,
1557
1558 pub amount: Amount,
1560
1561 #[serde(default, skip_serializing_if = "super::is_false")]
1563 #[cfg_attr(feature = "uniffi", uniffi(default = false))]
1564 pub reversal: bool,
1565
1566 #[serde(skip_serializing_if = "Option::is_none")]
1568 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1569 pub original_amount: Option<Amount>,
1570
1571 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1573 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1574 pub exchanges: Vec<ExchangeRate>,
1575
1576 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1578 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1579 pub fees: Vec<Fee>,
1580
1581 #[serde(skip_serializing_if = "Option::is_none")]
1583 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1584 pub creditor: Option<Party>,
1585
1586 #[serde(skip_serializing_if = "Option::is_none")]
1588 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1589 pub debtor: Option<Party>,
1590
1591 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1593 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1594 pub remittance_information: Vec<String>,
1595
1596 #[serde(skip_serializing_if = "Option::is_none")]
1598 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1599 pub purpose_code: Option<String>,
1600
1601 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1603 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1604 pub bank_transaction_codes: Vec<BankTransactionCode>,
1605
1606 #[serde(skip_serializing_if = "Option::is_none")]
1610 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1611 pub additional_information: Option<String>,
1612 }
1613
1614 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1615 #[serde(rename_all = "camelCase")]
1616 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1617 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1618 pub struct BatchData {
1619 #[serde(skip_serializing_if = "Option::is_none")]
1621 pub number_of_transactions: Option<u32>,
1622
1623 pub transactions: Vec<BatchTransactionDetails>,
1628 }
1629
1630 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1631 #[serde(rename_all = "camelCase")]
1632 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1633 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1634 pub struct BatchTransactionDetails {
1635 #[serde(skip_serializing_if = "Option::is_none")]
1637 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1638 pub account_servicer_reference: Option<String>,
1639
1640 #[serde(skip_serializing_if = "Option::is_none")]
1642 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1643 pub payment_id: Option<String>,
1644
1645 #[serde(skip_serializing_if = "Option::is_none")]
1647 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1648 pub transaction_id: Option<String>,
1649
1650 #[serde(skip_serializing_if = "Option::is_none")]
1652 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1653 pub end_to_end_id: Option<String>,
1654
1655 #[serde(skip_serializing_if = "Option::is_none")]
1657 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1658 pub mandate_id: Option<String>,
1659
1660 #[serde(skip_serializing_if = "Option::is_none")]
1662 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1663 pub creditor_id: Option<String>,
1664
1665 #[serde(skip_serializing_if = "Option::is_none")]
1667 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1668 pub amount: Option<Amount>,
1669
1670 #[serde(default, skip_serializing_if = "super::is_false")]
1672 #[cfg_attr(feature = "uniffi", uniffi(default = false))]
1673 pub reversal: bool,
1674
1675 #[serde(skip_serializing_if = "Option::is_none")]
1677 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1678 pub original_amount: Option<Amount>,
1679
1680 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1682 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1683 pub exchanges: Vec<ExchangeRate>,
1684
1685 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1687 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1688 pub fees: Vec<Fee>,
1689
1690 #[serde(skip_serializing_if = "Option::is_none")]
1692 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1693 pub creditor: Option<Party>,
1694
1695 #[serde(skip_serializing_if = "Option::is_none")]
1697 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1698 pub debtor: Option<Party>,
1699
1700 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1702 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1703 pub remittance_information: Vec<String>,
1704
1705 #[serde(skip_serializing_if = "Option::is_none")]
1707 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1708 pub purpose_code: Option<String>,
1709
1710 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1712 #[cfg_attr(feature = "uniffi", uniffi(default = []))]
1713 pub bank_transaction_codes: Vec<BankTransactionCode>,
1714
1715 #[serde(skip_serializing_if = "Option::is_none")]
1719 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1720 pub additional_information: Option<String>,
1721 }
1722
1723 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1724 #[serde(rename_all = "camelCase")]
1725 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1726 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1727 pub struct ExchangeRate {
1728 pub source_currency: String,
1730 #[serde(skip_serializing_if = "Option::is_none")]
1733 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1734 pub target_currency: Option<String>,
1735 #[serde(skip_serializing_if = "Option::is_none")]
1737 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1738 pub unit_currency: Option<String>,
1739 pub exchange_rate: Decimal,
1740 }
1741
1742 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1743 #[serde(rename_all = "camelCase")]
1744 #[cfg_attr(not(feature = "server"), non_exhaustive)]
1745 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1746 pub struct Party {
1747 #[serde(skip_serializing_if = "Option::is_none")]
1749 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1750 pub name: Option<String>,
1751
1752 #[serde(skip_serializing_if = "Option::is_none")]
1754 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1755 pub iban: Option<String>,
1756
1757 #[serde(skip_serializing_if = "Option::is_none")]
1759 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1760 pub bic: Option<String>,
1761
1762 #[serde(skip_serializing_if = "Option::is_none")]
1764 #[cfg_attr(feature = "uniffi", uniffi(default = None))]
1765 pub ultimate: Option<String>,
1766 }
1767
1768 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)]
1769 #[serde(rename_all = "camelCase")]
1770 #[serde(rename_all_fields = "camelCase")]
1771 #[non_exhaustive]
1772 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1773 pub enum BankTransactionCode {
1774 Iso {
1776 domain: String,
1778
1779 family: String,
1781
1782 sub_family: String,
1784 },
1785 Swift(String),
1787 Bai(String),
1789 National { code: String, country: CountryCode },
1791 Other {
1793 code: String,
1794 #[serde(skip_serializing_if = "Option::is_none")]
1795 issuer: Option<String>,
1796 },
1797 }
1798}
1799
1800#[allow(clippy::trivially_copy_pass_by_ref)]
1801fn is_false(val: &bool) -> bool {
1802 !*val
1803}
1804
1805pub mod collect_payment {
1806 pub use routex_models::Amount;
1807 use serde::{Deserialize, Serialize};
1808
1809 use super::{AccountIdentifier, AccountReference, Path, ServiceId};
1810
1811 #[must_use]
1816 pub fn status_path(ticket_id: &str) -> Path {
1817 Path::new(["collect-payment", "status", ticket_id])
1818 }
1819
1820 #[derive(Serialize, Deserialize, PartialEq, Eq, Default, Clone, Debug)]
1821 #[serde(rename_all = "camelCase")]
1822 #[non_exhaustive]
1823 pub struct SuccessStatusData {}
1824
1825 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
1826 #[serde(rename_all = "camelCase")]
1827 pub struct TicketData {
1828 pub amount: Amount,
1829 pub creditor_account: AccountIdentifier,
1830 pub creditor_name: String,
1831 pub remittance: String,
1832 #[serde(skip_serializing_if = "Option::is_none")]
1833 pub instant: Option<bool>,
1834 #[serde(skip_serializing_if = "Option::is_none")]
1835 pub fields: Option<Vec<Field>>,
1836 }
1837
1838 #[derive(Clone, Debug)]
1839 pub struct Service {}
1840
1841 impl super::Service for Service {
1842 const ID: ServiceId = ServiceId::CollectPayment;
1843
1844 type TicketData = TicketData;
1845
1846 type RequestData = RequestData;
1847
1848 type Output = PaymentInitiation;
1849 }
1850
1851 #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
1852 #[serde(rename_all = "camelCase")]
1853 #[non_exhaustive]
1854 #[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1855 pub enum Field {
1856 DebtorIban,
1857 DebtorName,
1858 }
1859
1860 #[derive(Serialize, Deserialize, Clone)]
1861 #[serde(rename_all = "camelCase")]
1862 pub struct RequestData {
1863 pub account: Option<AccountReference>,
1864 }
1865
1866 #[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Default, Debug)]
1867 #[serde(rename_all = "camelCase")]
1868 #[non_exhaustive]
1869 #[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
1870 pub struct PaymentInitiation {
1871 #[serde(skip_serializing_if = "Option::is_none")]
1872 pub debtor_name: Option<String>,
1873 #[serde(skip_serializing_if = "Option::is_none")]
1874 pub debtor_iban: Option<String>,
1875 }
1876}
1877
1878#[cfg(test)]
1879mod tests {
1880 use std::fmt::Debug;
1881
1882 use jsonwebtoken::{Algorithm, EncodingKey, Header, encode};
1883 use serde::{Deserialize, Serialize};
1884
1885 use crate::{
1886 Authenticated, Claims, ConfirmationContext, ConfirmationData, Dialog, DialogInput,
1887 InputContext, OBResult, Redirect, RedirectHandle, ResponseData, ServiceId, Session, Ticket,
1888 };
1889
1890 #[test]
1891 fn read_response() {
1892 let serialized = serde_json::to_string(
1893 &encode(
1894 &Header::new(Algorithm::HS256),
1895 &Claims {
1896 data: "data",
1897 exp: 2_540_808_000,
1898 },
1899 &EncodingKey::from_secret(b"does_not_matter"),
1900 )
1901 .unwrap(),
1902 )
1903 .unwrap();
1904
1905 let response = serde_json::from_str::<Authenticated<String>>(&serialized).unwrap();
1906
1907 assert_eq!(&super::decode::<String>(&response.jwt).unwrap(), "data");
1908 }
1909
1910 struct Service;
1911
1912 impl crate::Service for Service {
1913 const ID: ServiceId = ServiceId::Accounts;
1914
1915 type TicketData = ();
1916
1917 type RequestData = ();
1918
1919 type Output = ();
1920 }
1921
1922 #[allow(
1923 dead_code,
1924 unconditional_recursion,
1925 clippy::extra_unused_type_parameters
1926 )]
1927 fn test_bounds<T: Serialize + for<'de> Deserialize<'de> + Clone + Eq + PartialEq + Debug>() {
1928 test_bounds::<InputContext<Service>>();
1929 test_bounds::<ConfirmationContext<Service>>();
1930 test_bounds::<DialogInput<Service>>();
1931 test_bounds::<Dialog<Service>>();
1932 test_bounds::<Redirect<Service>>();
1933 test_bounds::<RedirectHandle<Service>>();
1934 test_bounds::<OBResult<()>>();
1935 test_bounds::<Session>();
1936
1937 test_bounds::<Authenticated<Ticket<Service>>>();
1938 test_bounds::<Ticket<Service>>();
1939 test_bounds::<ResponseData<Service>>();
1940 test_bounds::<ConfirmationData<Service>>();
1941 }
1942
1943 fn test_eq_<T: PartialEq + Debug>(o: &T) {
1944 assert!(o.eq(o));
1945 }
1946
1947 #[test]
1948 fn test_eq() {
1949 let ic = InputContext::<Service>::from(vec![]);
1950 let cc = ConfirmationContext::<Service>::from(vec![]);
1951
1952 test_eq_(&ic);
1953 test_eq_(&cc);
1954 test_eq_(&Redirect {
1955 url: "url:".parse().unwrap(),
1956 context: cc.clone(),
1957 });
1958 test_eq_(&RedirectHandle {
1959 handle: String::new(),
1960 context: cc.clone(),
1961 });
1962 test_eq_(&Ticket::<Service> {
1963 service: ServiceId::Accounts,
1964 id: String::new(),
1965 data: (),
1966 });
1967 test_eq_(&ResponseData {
1968 context: ic,
1969 response: String::new(),
1970 });
1971 test_eq_(&ConfirmationData { context: cc });
1972 }
1973}