Skip to main content

kleepay_ai/
models.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 KleePay
3
4use chrono::{DateTime, Utc};
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9// ── Shared ──────────────────────────────────────────────────────────
10
11#[derive(Debug, Clone, Deserialize, Serialize)]
12pub struct Pagination {
13    pub has_more: bool,
14    pub next_cursor: Option<String>,
15}
16
17// ── Auth ────────────────────────────────────────────────────────────
18
19#[derive(Debug, Clone, Serialize)]
20pub struct RegisterRequest {
21    pub email: String,
22    pub password: String,
23}
24
25#[derive(Debug, Clone, Deserialize)]
26pub struct RegisterResponse {
27    pub id: Uuid,
28    pub email: String,
29    pub kyc_status: String,
30    pub created_at: DateTime<Utc>,
31}
32
33#[derive(Debug, Clone, Serialize)]
34pub struct LoginRequest {
35    pub email: String,
36    pub password: String,
37}
38
39#[derive(Debug, Clone, Deserialize)]
40pub struct LoginResponse {
41    pub user_id: Uuid,
42    pub email: String,
43    pub access_token: String,
44    pub refresh_token: String,
45}
46
47#[derive(Debug, Clone, Deserialize)]
48pub struct RefreshResponse {
49    pub refreshed: bool,
50}
51
52#[derive(Debug, Clone, Deserialize)]
53pub struct MeResponse {
54    pub id: Uuid,
55    pub email: String,
56    pub kyc_status: String,
57    pub kyc_level: i32,
58    pub max_cards: i32,
59    pub created_at: DateTime<Utc>,
60}
61
62// ── API Keys ────────────────────────────────────────────────────────
63
64#[derive(Debug, Clone, Serialize, Default)]
65pub struct CreateApiKeyRequest {
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub label: Option<String>,
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub environment: Option<String>,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub expires_in_days: Option<i64>,
72}
73
74#[derive(Debug, Clone, Deserialize)]
75pub struct CreateApiKeyResponse {
76    pub key_id: String,
77    pub label: String,
78    pub environment: String,
79    pub expires_at: Option<DateTime<Utc>>,
80    pub download_url: String,
81    pub download_passphrase: String,
82    pub download_expires_at: Option<DateTime<Utc>>,
83    pub instructions: String,
84}
85
86#[derive(Debug, Clone, Deserialize)]
87pub struct ApiKeyItem {
88    pub key_id: String,
89    pub label: String,
90    pub environment: String,
91    pub bundle_downloaded: bool,
92    pub expires_at: Option<DateTime<Utc>>,
93    pub revoked_at: Option<DateTime<Utc>>,
94    pub last_used_at: Option<DateTime<Utc>>,
95    pub created_at: DateTime<Utc>,
96}
97
98#[derive(Debug, Clone, Deserialize)]
99pub struct RevokeKeyResponse {
100    pub revoked_at: Option<DateTime<Utc>>,
101}
102
103// ── Cards ───────────────────────────────────────────────────────────
104
105#[derive(Debug, Clone, Serialize)]
106pub struct CreateCardRequest {
107    pub card_product_id: String,
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub label: Option<String>,
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub initial_deposit: Option<Decimal>,
112}
113
114impl CreateCardRequest {
115    pub fn builder(card_product_id: impl Into<String>) -> CreateCardRequestBuilder {
116        CreateCardRequestBuilder {
117            card_product_id: card_product_id.into(),
118            label: None,
119            initial_deposit: None,
120        }
121    }
122}
123
124pub struct CreateCardRequestBuilder {
125    card_product_id: String,
126    label: Option<String>,
127    initial_deposit: Option<Decimal>,
128}
129
130impl CreateCardRequestBuilder {
131    pub fn label(mut self, label: impl Into<String>) -> Self {
132        self.label = Some(label.into());
133        self
134    }
135
136    pub fn initial_deposit(mut self, amount: Decimal) -> Self {
137        self.initial_deposit = Some(amount);
138        self
139    }
140
141    pub fn build(self) -> CreateCardRequest {
142        CreateCardRequest {
143            card_product_id: self.card_product_id,
144            label: self.label,
145            initial_deposit: self.initial_deposit,
146        }
147    }
148}
149
150#[derive(Debug, Clone, Serialize, Default)]
151pub struct UpdateCardRequest {
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub status: Option<String>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub label: Option<String>,
156}
157
158#[derive(Debug, Clone, Deserialize)]
159pub struct CardResponse {
160    pub id: Uuid,
161    pub last_four: String,
162    pub label: String,
163    pub status: String,
164    pub network: String,
165    pub currency: String,
166    pub card_product_id: String,
167    pub card_type_id: Option<Uuid>,
168    pub created_at: DateTime<Utc>,
169}
170
171#[derive(Debug, Clone, Deserialize)]
172pub struct CardDetailResponse {
173    pub id: Uuid,
174    pub last_four: String,
175    pub label: String,
176    pub status: String,
177    pub network: String,
178    pub currency: String,
179    pub card_product_id: String,
180    pub card_type_id: Option<Uuid>,
181    pub provider_balance: Option<Decimal>,
182    pub expiry_month: i32,
183    pub expiry_year: i32,
184    pub created_at: DateTime<Utc>,
185    pub updated_at: DateTime<Utc>,
186}
187
188#[derive(Debug, Clone, Deserialize)]
189pub struct CardListResponse {
190    pub data: Vec<CardResponse>,
191    pub pagination: Pagination,
192}
193
194#[derive(Debug, Clone, Deserialize)]
195pub struct RevealCardResponse {
196    pub card_number: String,
197    pub cvv: String,
198    pub expiry: String,
199}
200
201#[derive(Debug, Clone, Deserialize)]
202pub struct UpdateCardResponse {
203    pub id: Uuid,
204    pub status: String,
205    pub label: String,
206    pub updated_at: DateTime<Utc>,
207}
208
209#[derive(Debug, Clone, Deserialize)]
210pub struct DeleteCardResponse {
211    pub status: String,
212}
213
214// ── Card Products ─────────────────────────────────────────────────
215
216#[derive(Debug, Clone, Deserialize)]
217pub struct CardProduct {
218    pub id: String,
219    pub name: String,
220    pub card_network: String,
221    pub card_type: String,
222    pub currency: String,
223    pub creation_fee: String,
224    pub min_initial_deposit: String,
225    pub max_initial_deposit: String,
226    pub is_active: bool,
227}
228
229#[derive(Debug, Clone, Deserialize)]
230pub struct CardProductDetail {
231    pub id: String,
232    pub name: String,
233    pub card_network: String,
234    pub card_type: String,
235    pub currency: String,
236    pub creation_fee: String,
237    pub min_initial_deposit: String,
238    pub max_initial_deposit: String,
239    pub is_active: bool,
240    pub description: Option<String>,
241    pub daily_spend_limit: Option<String>,
242    pub monthly_spend_limit: Option<String>,
243    pub created_at: DateTime<Utc>,
244    pub updated_at: DateTime<Utc>,
245}
246
247#[derive(Debug, Clone, Deserialize)]
248pub struct CardProductListResponse {
249    pub data: Vec<CardProduct>,
250}
251
252// ── 3DS Challenges ─────────────────────────────────────────────────
253
254#[derive(Debug, Clone, Deserialize)]
255pub struct ThreeDSChallengeItem {
256    pub id: Uuid,
257    pub challenge_type: String,
258    pub otp: Option<String>,
259    pub auth_url: Option<String>,
260    pub expires_at: Option<String>,
261    pub created_at: DateTime<Utc>,
262}
263
264#[derive(Debug, Clone, Deserialize)]
265pub struct ThreeDSChallengeListResponse {
266    pub data: Vec<ThreeDSChallengeItem>,
267}
268
269// ── Card Holders ────────────────────────────────────────────────────
270
271#[derive(Debug, Clone, Serialize)]
272pub struct CreateCardHolderRequest {
273    pub card_type_id: Uuid,
274    pub first_name: String,
275    pub last_name: String,
276    pub email: String,
277    pub country: String,
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub address: Option<serde_json::Value>,
280}
281
282#[derive(Debug, Clone, Deserialize)]
283pub struct CardHolderResponse {
284    pub id: Uuid,
285    pub card_type_id: Uuid,
286    pub status: String,
287    pub first_name: String,
288    pub last_name: String,
289    pub created_at: DateTime<Utc>,
290}
291
292#[derive(Debug, Clone, Deserialize)]
293pub struct CardHolderDetailResponse {
294    pub id: Uuid,
295    pub card_type_id: Uuid,
296    pub status: String,
297    pub first_name: String,
298    pub last_name: String,
299    pub email: String,
300    pub country: String,
301    pub rejection_reason: Option<String>,
302    pub review_stage: Option<String>,
303    pub created_at: DateTime<Utc>,
304    pub updated_at: DateTime<Utc>,
305}
306
307#[derive(Debug, Clone, Deserialize)]
308pub struct CardHolderListResponse {
309    pub data: Vec<CardHolderResponse>,
310    pub pagination: Pagination,
311}
312
313// ── Card Funding ────────────────────────────────────────────────────
314
315#[derive(Debug, Clone, Serialize)]
316pub struct FundingRequest {
317    pub amount: Decimal,
318}
319
320#[derive(Debug, Clone, Deserialize)]
321pub struct FundingOperationResponse {
322    pub operation_id: Uuid,
323    pub amount: String,
324    pub fee: String,
325    pub status: String,
326}
327
328#[derive(Debug, Clone, Deserialize)]
329pub struct CardBalanceResponse {
330    pub provider_balance: String,
331    pub currency: String,
332}
333
334// ── Policies ────────────────────────────────────────────────────────
335
336#[derive(Debug, Clone, Serialize, Default)]
337pub struct CreatePolicyRequest {
338    pub card_id: Uuid,
339    #[serde(rename = "type")]
340    pub policy_type: String,
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub amount: Option<Decimal>,
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub amount_min: Option<Decimal>,
345    #[serde(skip_serializing_if = "Option::is_none")]
346    pub amount_max: Option<Decimal>,
347    pub currency: String,
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub ttl_seconds: Option<i32>,
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub valid_from: Option<DateTime<Utc>>,
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub valid_until: Option<DateTime<Utc>>,
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub max_attempts: Option<i32>,
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub auto_close_on_success: Option<bool>,
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub geo: Option<serde_json::Value>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub mcc_whitelist: Option<serde_json::Value>,
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub mcc_blacklist: Option<serde_json::Value>,
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub merchant_pattern: Option<String>,
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub schedule: Option<ScheduleRequest>,
368}
369
370impl CreatePolicyRequest {
371    pub fn builder(
372        card_id: Uuid,
373        policy_type: impl Into<String>,
374        currency: impl Into<String>,
375    ) -> CreatePolicyRequestBuilder {
376        CreatePolicyRequestBuilder {
377            card_id,
378            policy_type: policy_type.into(),
379            currency: currency.into(),
380            amount: None,
381            amount_min: None,
382            amount_max: None,
383            ttl_seconds: None,
384            valid_from: None,
385            valid_until: None,
386            max_attempts: None,
387            auto_close_on_success: None,
388            geo: None,
389            mcc_whitelist: None,
390            mcc_blacklist: None,
391            merchant_pattern: None,
392            schedule: None,
393        }
394    }
395}
396
397pub struct CreatePolicyRequestBuilder {
398    card_id: Uuid,
399    policy_type: String,
400    currency: String,
401    amount: Option<Decimal>,
402    amount_min: Option<Decimal>,
403    amount_max: Option<Decimal>,
404    ttl_seconds: Option<i32>,
405    valid_from: Option<DateTime<Utc>>,
406    valid_until: Option<DateTime<Utc>>,
407    max_attempts: Option<i32>,
408    auto_close_on_success: Option<bool>,
409    geo: Option<serde_json::Value>,
410    mcc_whitelist: Option<serde_json::Value>,
411    mcc_blacklist: Option<serde_json::Value>,
412    merchant_pattern: Option<String>,
413    schedule: Option<ScheduleRequest>,
414}
415
416impl CreatePolicyRequestBuilder {
417    pub fn amount(mut self, amount: Decimal) -> Self {
418        self.amount = Some(amount);
419        self
420    }
421
422    pub fn amount_min(mut self, amount: Decimal) -> Self {
423        self.amount_min = Some(amount);
424        self
425    }
426
427    pub fn amount_max(mut self, amount: Decimal) -> Self {
428        self.amount_max = Some(amount);
429        self
430    }
431
432    pub fn ttl_seconds(mut self, ttl: i32) -> Self {
433        self.ttl_seconds = Some(ttl);
434        self
435    }
436
437    pub fn valid_from(mut self, t: DateTime<Utc>) -> Self {
438        self.valid_from = Some(t);
439        self
440    }
441
442    pub fn valid_until(mut self, t: DateTime<Utc>) -> Self {
443        self.valid_until = Some(t);
444        self
445    }
446
447    pub fn max_attempts(mut self, n: i32) -> Self {
448        self.max_attempts = Some(n);
449        self
450    }
451
452    pub fn auto_close_on_success(mut self, v: bool) -> Self {
453        self.auto_close_on_success = Some(v);
454        self
455    }
456
457    pub fn geo(mut self, geo: serde_json::Value) -> Self {
458        self.geo = Some(geo);
459        self
460    }
461
462    pub fn mcc_whitelist(mut self, mcc: serde_json::Value) -> Self {
463        self.mcc_whitelist = Some(mcc);
464        self
465    }
466
467    pub fn mcc_blacklist(mut self, mcc: serde_json::Value) -> Self {
468        self.mcc_blacklist = Some(mcc);
469        self
470    }
471
472    pub fn merchant_pattern(mut self, pattern: impl Into<String>) -> Self {
473        self.merchant_pattern = Some(pattern.into());
474        self
475    }
476
477    pub fn schedule(mut self, schedule: ScheduleRequest) -> Self {
478        self.schedule = Some(schedule);
479        self
480    }
481
482    pub fn build(self) -> CreatePolicyRequest {
483        CreatePolicyRequest {
484            card_id: self.card_id,
485            policy_type: self.policy_type,
486            amount: self.amount,
487            amount_min: self.amount_min,
488            amount_max: self.amount_max,
489            currency: self.currency,
490            ttl_seconds: self.ttl_seconds,
491            valid_from: self.valid_from,
492            valid_until: self.valid_until,
493            max_attempts: self.max_attempts,
494            auto_close_on_success: self.auto_close_on_success,
495            geo: self.geo,
496            mcc_whitelist: self.mcc_whitelist,
497            mcc_blacklist: self.mcc_blacklist,
498            merchant_pattern: self.merchant_pattern,
499            schedule: self.schedule,
500        }
501    }
502}
503
504#[derive(Debug, Clone, Serialize, Deserialize)]
505pub struct ScheduleRequest {
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub day_of_month: Option<i32>,
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub window_hours: Option<i32>,
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub timezone: Option<String>,
512}
513
514#[derive(Debug, Clone, Deserialize)]
515pub struct PolicyResponse {
516    pub id: Uuid,
517    pub card_id: Uuid,
518    #[serde(rename = "type")]
519    pub policy_type: String,
520    pub status: String,
521    pub amount: Option<Decimal>,
522    pub currency: String,
523    pub valid_from: DateTime<Utc>,
524    pub valid_until: Option<DateTime<Utc>>,
525    pub created_at: DateTime<Utc>,
526}
527
528#[derive(Debug, Clone, Deserialize)]
529pub struct PolicyDetailResponse {
530    pub id: Uuid,
531    pub card_id: Uuid,
532    #[serde(rename = "type")]
533    pub policy_type: String,
534    pub status: String,
535    pub amount: Option<Decimal>,
536    pub amount_min: Option<Decimal>,
537    pub amount_max: Option<Decimal>,
538    pub currency: String,
539    pub valid_from: DateTime<Utc>,
540    pub valid_until: Option<DateTime<Utc>>,
541    pub ttl_seconds: Option<i32>,
542    pub used_attempts: i32,
543    pub max_attempts: i32,
544    pub auto_close_on_success: bool,
545    pub geo_constraints: Option<serde_json::Value>,
546    pub mcc_whitelist: Option<serde_json::Value>,
547    pub mcc_blacklist: Option<serde_json::Value>,
548    pub created_at: DateTime<Utc>,
549}
550
551#[derive(Debug, Clone, Deserialize)]
552pub struct PolicyListResponse {
553    pub data: Vec<PolicyListItem>,
554    pub pagination: Pagination,
555}
556
557#[derive(Debug, Clone, Deserialize)]
558pub struct PolicyListItem {
559    pub id: Uuid,
560    pub card_id: Uuid,
561    #[serde(rename = "type")]
562    pub policy_type: String,
563    pub status: String,
564    pub amount: Option<Decimal>,
565    pub currency: String,
566    pub valid_from: DateTime<Utc>,
567    pub valid_until: Option<DateTime<Utc>>,
568    pub used_attempts: i32,
569    pub max_attempts: i32,
570    pub created_at: DateTime<Utc>,
571}
572
573#[derive(Debug, Clone, Deserialize)]
574pub struct DeletePolicyResponse {
575    pub status: String,
576}
577
578// ── Balance ─────────────────────────────────────────────────────────
579
580#[derive(Debug, Clone, Deserialize)]
581pub struct BalanceItem {
582    pub available: String,
583    pub pending: String,
584    pub currency: String,
585}
586
587#[derive(Debug, Clone, Deserialize)]
588pub struct BalancesResponse {
589    pub balances: Vec<BalanceItem>,
590}
591
592// ── Webhooks ────────────────────────────────────────────────────────
593
594#[derive(Debug, Clone, Serialize)]
595pub struct CreateWebhookRequest {
596    pub url: String,
597    #[serde(skip_serializing_if = "Vec::is_empty")]
598    pub events: Vec<String>,
599}
600
601#[derive(Debug, Clone, Deserialize)]
602pub struct WebhookResponse {
603    pub id: Uuid,
604    pub url: String,
605    pub secret: String,
606    pub events: Option<Vec<String>>,
607    pub is_active: bool,
608    pub created_at: DateTime<Utc>,
609}
610
611#[derive(Debug, Clone, Deserialize)]
612pub struct WebhookListItem {
613    pub id: Uuid,
614    pub url: String,
615    pub events: Option<Vec<String>>,
616    pub is_active: bool,
617    pub created_at: DateTime<Utc>,
618}