1use std::sync::Arc;
2
3use rust_decimal::Decimal;
4use sha2::Digest;
5
6#[derive(Debug, serde::Serialize, serde::Deserialize)]
7pub struct ClientId(String);
8
9impl ClientId {
10 pub fn new(value: String) -> Self {
11 Self(value)
12 }
13
14 pub fn as_str(&self) -> &str {
15 return self.0.as_str();
16 }
17}
18
19impl core::fmt::Display for ClientId {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 return write!(f, "ClientId([redacted])");
22 }
23}
24
25#[derive(Debug, serde::Serialize, serde::Deserialize)]
26pub struct ClientSecret(String);
27
28impl ClientSecret {
29 pub fn new(value: String) -> Self {
30 Self(value)
31 }
32
33 pub fn as_str(&self) -> &str {
34 return self.0.as_str();
35 }
36}
37
38impl core::fmt::Display for ClientSecret {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 return write!(f, "ClientSecret([redacted])");
41 }
42}
43
44#[derive(Debug, serde::Serialize, serde::Deserialize)]
45pub struct AccessToken(pub(crate) String);
46
47impl AccessToken {
48 pub fn new(value: String) -> Self {
49 Self(value)
50 }
51
52 pub fn as_str(&self) -> &str {
54 return self.0.as_str();
55 }
56}
57
58impl core::fmt::Display for AccessToken {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 return write!(f, "AccessToken([redacted])");
61 }
62}
63
64#[derive(Debug)]
65pub struct AccessTokenDuration(core::time::Duration);
66
67impl AccessTokenDuration {
68 pub const fn new(value: core::time::Duration) -> Self {
69 return Self(value);
70 }
71}
72
73impl From<AccessTokenDuration> for core::time::Duration {
74 fn from(value: AccessTokenDuration) -> Self {
75 return value.0;
76 }
77}
78
79impl core::ops::Add<std::time::Instant> for AccessTokenDuration {
80 type Output = std::time::Instant;
81
82 fn add(self, rhs: std::time::Instant) -> Self::Output {
83 rhs + self.0
84 }
85}
86
87#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
88pub struct QRId(String);
89
90impl QRId {
91 pub fn new(value: String) -> Self {
92 return Self(value);
93 }
94
95 pub fn as_str(&self) -> &str {
96 return self.0.as_str();
97 }
98}
99
100impl std::cmp::PartialEq<str> for QRId {
101 fn eq(&self, other: &str) -> bool {
102 return self.0.eq(other);
103 }
104}
105
106impl core::fmt::Display for QRId {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 return write!(f, "{}", self.0);
109 }
110}
111
112impl AsRef<str> for QRId {
113 fn as_ref(&self) -> &str {
114 return &self.0;
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
119pub struct Signature(String);
120
121impl Signature {
122 pub fn new(value: String) -> Self {
123 return Self(value);
124 }
125
126 pub fn as_str(&self) -> &str {
127 return self.0.as_str();
128 }
129}
130
131impl core::fmt::Display for Signature {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 return write!(f, "Signature([redacted])");
134 }
135}
136
137#[derive(Debug, Clone)]
139pub struct SignatureKey(Arc<str>);
140
141impl SignatureKey {
142 pub fn as_str(&self) -> &str {
143 return &self.0;
144 }
145}
146
147impl From<String> for SignatureKey {
148 fn from(value: String) -> Self {
149 return Self(Arc::from(value));
150 }
151}
152
153#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
154pub struct ExtensionId(String);
155
156impl ExtensionId {
157 pub fn new(value: String) -> Self {
158 return Self(value);
159 }
160
161 pub fn as_str(&self) -> &str {
162 return self.0.as_str();
163 }
164}
165
166impl core::fmt::Display for ExtensionId {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 return write!(f, "{}", self.0);
169 }
170}
171
172impl AsRef<str> for ExtensionId {
173 fn as_ref(&self) -> &str {
174 return &self.0;
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
179pub struct PaymentId(String);
180
181impl PaymentId {
182 pub fn new(value: String) -> Self {
183 return Self(value);
184 }
185
186 pub fn as_str(&self) -> &str {
187 return self.0.as_str();
188 }
189}
190
191impl core::fmt::Display for PaymentId {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 return write!(f, "{}", self.0);
194 }
195}
196
197impl AsRef<str> for PaymentId {
198 fn as_ref(&self) -> &str {
199 return &self.0;
200 }
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
204#[serde(rename_all = "PascalCase")]
205pub enum PaymentType {
206 Fixed,
207 Controlled,
208 Free,
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
212#[serde(rename_all = "PascalCase")]
213pub enum PaymentStatus {
214 Executed,
215 Refunded,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
219pub enum TokenType {
220 Bearer,
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
224#[serde(rename_all = "PascalCase")]
225pub enum QRType {
226 Static,
229
230 Dynamic,
232
233 Hybrid,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
241pub enum Currency {
242 MDL,
243}
244
245impl Currency {
246 pub fn code(self) -> &'static str {
247 match self {
248 Currency::MDL => "MDL",
249 }
250 }
251
252 pub fn minor_currency_unit(self) -> i32 {
253 match self {
254 Currency::MDL => 100,
255 }
256 }
257}
258
259impl core::fmt::Display for Currency {
260 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261 return write!(f, "{}", self.code());
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
266#[serde(rename_all = "PascalCase")]
267pub enum QRStatus {
268 Active,
269 Inactive,
270 Expired,
271 Paid,
272 Cancelled,
273}
274
275impl core::fmt::Display for QRStatus {
276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 match *self {
278 QRStatus::Active => write!(f, "Active"),
279 QRStatus::Inactive => write!(f, "Inactive"),
280 QRStatus::Expired => write!(f, "Expired"),
281 QRStatus::Paid => write!(f, "Paid"),
282 QRStatus::Cancelled => write!(f, "Cancelled"),
283 }
284 }
285}
286
287#[derive(Debug, serde::Deserialize, serde::Serialize)]
288#[serde(rename_all = "camelCase")]
289pub struct Notification {
290 pub(crate) amount: Decimal,
291 pub(crate) commission: Decimal,
292 pub(crate) currency: Currency,
293 pub(crate) executed_at: String,
294 pub(crate) extension_id: ExtensionId,
295 pub(crate) order_id: Option<String>,
296 pub(crate) pay_id: PaymentId,
297 pub(crate) payer_iban: String,
298 pub(crate) payer_name: String,
299 pub(crate) qr_id: QRId,
300 pub(crate) qr_status: QRStatus,
301 pub(crate) reference_id: String,
302 pub(crate) terminal_id: Option<String>,
303}
304
305impl Notification {
306 pub fn pay_id(&self) -> &PaymentId {
307 &self.pay_id
308 }
309
310 pub fn qr_id(&self) -> &QRId {
311 &self.qr_id
312 }
313
314 pub fn amount(&self) -> Decimal {
315 self.amount
316 }
317
318 pub fn commission(&self) -> Decimal {
319 self.commission
320 }
321
322 pub fn currency(&self) -> Currency {
323 self.currency
324 }
325
326 pub fn executed_at(&self) -> &str {
327 &self.executed_at
328 }
329
330 pub fn extension_id(&self) -> &ExtensionId {
331 &self.extension_id
332 }
333
334 pub fn order_id(&self) -> Option<&str> {
335 self.order_id.as_deref()
336 }
337
338 pub fn payer_iban(&self) -> &str {
339 &self.payer_iban
340 }
341
342 pub fn payer_name(&self) -> &str {
343 &self.payer_name
344 }
345
346 pub fn qr_status(&self) -> QRStatus {
347 self.qr_status
348 }
349
350 pub fn reference_id(&self) -> &str {
351 &self.reference_id
352 }
353
354 pub fn terminal_id(&self) -> Option<&str> {
355 self.terminal_id.as_deref()
356 }
357}
358
359#[derive(Debug)]
360pub struct ValidSignatureNotification(pub Notification);
361
362#[derive(Debug, serde::Deserialize, serde::Serialize)]
363#[serde(rename_all = "camelCase")]
364pub struct NotificationPayload {
365 pub(crate) result: Notification,
366 pub(crate) signature: Signature,
367}
368
369impl NotificationPayload {
370 pub(crate) fn build_signature(&self, key: SignatureKey) -> Signature {
371 use base64::prelude::*;
372
373 let n = &self.result;
374 let mut this_signature = format!(
375 "{:.2}:{:.2}:{}:{}:{}",
376 n.amount, n.commission, n.currency, n.executed_at, n.extension_id
377 );
378
379 if let Some(ref order_id) = n.order_id {
380 this_signature = format!("{this_signature}:{order_id}");
381 }
382
383 this_signature = format!(
384 "{this_signature}:{}:{}:{}:{}:{}:{}",
385 n.payer_iban, n.payer_name, n.pay_id, n.qr_id, n.qr_status, n.reference_id
386 );
387
388 if let Some(ref terminal_id) = n.terminal_id {
389 this_signature = format!("{this_signature}:{terminal_id}");
390 }
391
392 this_signature = format!("{this_signature}:{}", key.0);
393
394 let digest: [u8; 32] = sha2::Sha256::digest(&this_signature).into();
395 let signature = Signature::new(BASE64_STANDARD.encode(digest));
396
397 return signature;
398 }
399
400 pub fn validate_signature(self, key: SignatureKey) -> Option<ValidSignatureNotification> {
404 let signature = self.build_signature(key);
405
406 if signature.eq(&self.signature) {
407 return Some(ValidSignatureNotification(self.result));
408 }
409
410 return None;
411 }
412
413 pub fn notification(&self) -> &Notification {
414 &self.result
415 }
416}
417
418pub mod request {
419 use rust_decimal::Decimal;
420
421 use super::{ClientId, ClientSecret, Currency, PaymentType, QRType};
422
423 #[derive(Debug, serde::Serialize)]
424 #[serde(rename_all = "camelCase")]
425 pub struct GetAccessToken<'a> {
426 pub client_id: &'a ClientId,
427 pub client_secret: &'a ClientSecret,
428 }
429
430 #[derive(Debug, serde::Serialize)]
431 #[serde(rename_all = "camelCase")]
432 pub struct CreateQR<'a> {
433 pub r#type: super::QRType,
434 pub expires_at: Option<&'a str>,
438 pub amount_type: super::PaymentType,
439
440 pub amount: rust_decimal::Decimal,
441 pub amount_min: Option<rust_decimal::Decimal>,
442 pub amount_max: Option<rust_decimal::Decimal>,
443
444 pub currency: super::Currency,
445 pub description: String,
446 pub order_id: Option<&'a str>,
447 pub callback_url: String,
448 pub redirect_url: String,
449 pub terminal_id: Option<String>,
450 }
451
452 impl<'a> CreateQR<'a> {
453 pub fn new_dynamic_with_fixed_amount(
454 amount: Decimal,
455 expires_at: &'a str,
456 description: String,
457 callback_url: String,
458 redirect_url: String,
459 ) -> Self {
460 return CreateQR {
461 r#type: QRType::Dynamic,
462 expires_at: Some(expires_at),
463 amount_type: PaymentType::Fixed,
464 amount,
465 amount_min: None,
466 amount_max: None,
467 currency: Currency::MDL,
468 description,
469 order_id: None,
470 callback_url,
471 redirect_url,
472 terminal_id: None,
473 };
474 }
475 }
476
477 #[derive(Debug, serde::Serialize)]
478 pub struct QRExpiresAt<'a>(&'a str);
479
480 #[derive(Debug, serde::Serialize)]
481 #[serde(rename_all = "camelCase")]
482 pub struct CancelQR {
483 pub reason: String,
484 }
485
486 #[derive(Debug, serde::Serialize)]
487 #[serde(rename_all = "camelCase")]
488 pub struct RefundPayment {
489 pub reason: String,
490 }
491}
492
493pub mod response {
494 use chrono::{DateTime, Utc};
495 use rust_decimal::Decimal;
496
497 use super::{Currency, ExtensionId, PaymentId, PaymentStatus, QRId};
498
499 #[derive(Debug, serde::Deserialize)]
500 pub struct ApiResponse<R> {
501 pub(crate) result: Option<R>,
502 pub(crate) errors: Option<Vec<crate::error::ApiError>>,
503 }
504
505 impl<R> From<ApiResponse<R>> for core::result::Result<R, crate::error::Error> {
506 fn from(value: ApiResponse<R>) -> Self {
507 if let Some(value) = value.result {
508 return Ok(value);
509 }
510
511 if let Some(value) = value.errors {
512 return Err(crate::error::Error::Api(value));
513 }
514
515 panic!();
516 }
517 }
518
519 #[derive(Debug, serde::Deserialize)]
520 #[serde(rename_all = "camelCase")]
521 pub struct AuthToken {
522 access_token: super::AccessToken,
523 expires_in: u64,
524 token_type: super::TokenType,
525 }
526
527 impl AuthToken {
528 pub fn expires_in(&self) -> super::AccessTokenDuration {
530 return super::AccessTokenDuration(core::time::Duration::from_secs(self.expires_in));
531 }
532
533 pub fn access_token(&self) -> &super::AccessToken {
534 &self.access_token
535 }
536
537 pub fn take_access_token(self) -> super::AccessToken {
538 self.access_token
539 }
540
541 pub fn token_type(&self) -> super::TokenType {
542 self.token_type
543 }
544 }
545
546 #[derive(Debug, serde::Deserialize)]
547 #[serde(rename_all = "camelCase")]
548 pub struct CreateQRResponse {
549 pub qr_id: super::QRId,
550 pub order_id: Option<String>,
551 pub r#type: super::QRType,
552 pub url: String,
553 pub expires_at: String,
554 }
555
556 #[derive(Debug, serde::Deserialize)]
557 #[serde(rename_all = "camelCase")]
558 pub struct GetQRDetails {
559 pub qr_id: super::QRId,
560 pub order_id: Option<String>,
561 pub status: super::QRStatus,
562 pub r#type: super::QRType,
563 pub url: String,
564 pub amount_type: super::PaymentType,
565 pub currency: super::Currency,
566 pub amount: Decimal,
567 pub amount_min: Option<Decimal>,
568 pub amount_max: Option<Decimal>,
569 pub description: String,
570 pub callback_url: String,
571 pub redirect_url: String,
572 pub terminal_id: String,
573 pub created_at: DateTime<Utc>,
574 pub updated_at: DateTime<Utc>,
575 pub expires_at: DateTime<Utc>,
576 }
577
578 #[derive(Debug, serde::Deserialize)]
579 #[serde(rename_all = "camelCase")]
580 pub struct CancelQR {
581 pub qr_id: super::QRId,
582 pub status: super::QRStatus,
583 }
584
585 #[derive(Debug, serde::Deserialize)]
586 #[serde(rename_all = "camelCase")]
587 pub struct PaymentDetails {
588 pub pay_id: PaymentId,
589 pub reference_id: String,
590 pub qr_id: QRId,
591 pub extension_id: Option<ExtensionId>,
592 pub order_id: Option<String>,
593 pub amount: Decimal,
594 pub commission: Decimal,
595 pub currency: Currency,
596 pub description: String,
597 pub payer_name: String,
598 pub payer_iban: String,
599 pub status: PaymentStatus,
600 pub executed_at: String,
601 pub refunded_at: Option<String>,
602 pub terminal_id: Option<String>,
603 }
604
605 #[derive(Debug, serde::Deserialize)]
606 #[serde(rename_all = "camelCase")]
607 pub struct RefundPayment {
608 pub pay_id: PaymentId,
609 pub status: PaymentStatus,
610 }
611}