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