1use crate::types::*;
6use rustkernel_core::{domain::Domain, kernel::KernelMetadata, traits::GpuKernel};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
18pub struct PaymentProcessing {
19 metadata: KernelMetadata,
20}
21
22impl Default for PaymentProcessing {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl PaymentProcessing {
29 #[must_use]
31 pub fn new() -> Self {
32 Self {
33 metadata: KernelMetadata::ring("payments/processing", Domain::PaymentProcessing)
34 .with_description("Real-time payment transaction execution")
35 .with_throughput(50_000)
36 .with_latency_us(100.0),
37 }
38 }
39
40 pub fn process_payments(
42 payments: &[Payment],
43 accounts: &HashMap<String, PaymentAccount>,
44 config: &ProcessingConfig,
45 ) -> ProcessingResult {
46 let total_count = payments.len();
47
48 let mut processed = Vec::new();
49 let mut failed = Vec::new();
50 let mut pending = Vec::new();
51 let mut total_amount = 0.0;
52 let mut processing_times = Vec::new();
53
54 for payment in payments {
55 let payment_start = std::time::Instant::now();
56
57 match Self::process_single_payment(payment, accounts, config) {
58 PaymentOutcome::Processed => {
59 processed.push(payment.id.clone());
60 total_amount += payment.amount;
61 }
62 PaymentOutcome::Failed(reason) => {
63 failed.push((payment.id.clone(), reason));
64 }
65 PaymentOutcome::Pending => {
66 pending.push(payment.id.clone());
67 }
68 }
69
70 processing_times.push(payment_start.elapsed().as_micros() as f64);
71 }
72
73 let processed_count = processed.len();
74 let failed_count = failed.len();
75 let avg_processing_time_us = if !processing_times.is_empty() {
76 processing_times.iter().sum::<f64>() / processing_times.len() as f64
77 } else {
78 0.0
79 };
80
81 ProcessingResult {
82 processed,
83 failed,
84 pending,
85 stats: ProcessingStats {
86 total_count,
87 processed_count,
88 failed_count,
89 total_amount,
90 avg_processing_time_us,
91 },
92 }
93 }
94
95 fn process_single_payment(
97 payment: &Payment,
98 accounts: &HashMap<String, PaymentAccount>,
99 config: &ProcessingConfig,
100 ) -> PaymentOutcome {
101 if let Err(reason) = Self::validate_payment(payment, accounts, config) {
103 return PaymentOutcome::Failed(reason);
104 }
105
106 if !Self::is_payment_type_enabled(payment.payment_type, config) {
108 return PaymentOutcome::Failed("Payment type not enabled".to_string());
109 }
110
111 if !Self::is_within_processing_window(payment, config) {
113 return PaymentOutcome::Pending;
114 }
115
116 if config.fraud_check_enabled {
118 if let Err(reason) = Self::fraud_check(payment, config) {
119 return PaymentOutcome::Failed(reason);
120 }
121 }
122
123 match payment.payment_type {
125 PaymentType::RealTime | PaymentType::Internal => PaymentOutcome::Processed,
126 PaymentType::Wire if payment.priority >= PaymentPriority::High => {
127 PaymentOutcome::Processed
128 }
129 _ => {
130 if config.batch_mode {
132 PaymentOutcome::Pending
133 } else {
134 PaymentOutcome::Processed
135 }
136 }
137 }
138 }
139
140 fn validate_payment(
142 payment: &Payment,
143 accounts: &HashMap<String, PaymentAccount>,
144 config: &ProcessingConfig,
145 ) -> std::result::Result<(), String> {
146 if payment.amount <= 0.0 {
148 return Err("Invalid amount: must be positive".to_string());
149 }
150
151 let payer = accounts
153 .get(&payment.payer_account)
154 .ok_or_else(|| "Payer account not found".to_string())?;
155
156 if payer.status != AccountStatus::Active {
158 return Err("Payer account is not active".to_string());
159 }
160
161 if payer.available_balance < payment.amount {
163 return Err("Insufficient funds".to_string());
164 }
165
166 if let Some(limit) = payer.daily_limit {
168 if payer.daily_used + payment.amount > limit {
169 return Err("Daily limit exceeded".to_string());
170 }
171 }
172
173 if !accounts.contains_key(&payment.payee_account) {
175 return Err("Payee account not found".to_string());
176 }
177
178 if payer.currency != payment.currency {
180 return Err("Currency mismatch".to_string());
181 }
182
183 if let Some(min) = config.min_amount {
185 if payment.amount < min {
186 return Err(format!("Amount below minimum: {}", min));
187 }
188 }
189
190 if let Some(max) = config.max_amount {
191 if payment.amount > max {
192 return Err(format!("Amount exceeds maximum: {}", max));
193 }
194 }
195
196 Ok(())
197 }
198
199 fn is_payment_type_enabled(payment_type: PaymentType, config: &ProcessingConfig) -> bool {
201 config.enabled_payment_types.contains(&payment_type)
202 }
203
204 fn is_within_processing_window(payment: &Payment, config: &ProcessingConfig) -> bool {
206 if matches!(
208 payment.payment_type,
209 PaymentType::RealTime | PaymentType::Internal
210 ) {
211 return true;
212 }
213
214 if config.process_outside_hours {
216 return true;
217 }
218
219 let timestamp = payment
222 .attributes
223 .get("submission_time")
224 .and_then(|s| s.parse::<u64>().ok())
225 .unwrap_or_else(|| {
226 std::time::SystemTime::now()
227 .duration_since(std::time::UNIX_EPOCH)
228 .map(|d| d.as_secs())
229 .unwrap_or(0)
230 });
231
232 let adjusted_timestamp = timestamp as i64 + config.timezone_offset_seconds;
235 let seconds_in_day = 86400i64;
236 let seconds_since_epoch_start = adjusted_timestamp;
237
238 let days_since_epoch = seconds_since_epoch_start / seconds_in_day;
241 let day_of_week = ((days_since_epoch + 4) % 7) as u8;
242
243 let seconds_into_day = (seconds_since_epoch_start % seconds_in_day) as u32;
245 let hour = (seconds_into_day / 3600) as u8;
246
247 let is_weekday = (1..=5).contains(&day_of_week);
249
250 let is_business_hours =
252 hour >= config.business_hours_start && hour < config.business_hours_end;
253
254 is_weekday && is_business_hours
256 }
257
258 fn fraud_check(
260 payment: &Payment,
261 config: &ProcessingConfig,
262 ) -> std::result::Result<(), String> {
263 if let Some(velocity_limit) = config.velocity_limit {
265 if payment.amount > velocity_limit * 10.0 {
267 return Err("Velocity check failed".to_string());
268 }
269 }
270
271 if payment.payment_type == PaymentType::RealTime {
273 if let Some(threshold) = config.large_payment_threshold {
274 if payment.amount > threshold {
275 return Err("Large payment requires manual review".to_string());
276 }
277 }
278 }
279
280 Ok(())
281 }
282
283 pub fn validate_only(
285 payment: &Payment,
286 accounts: &HashMap<String, PaymentAccount>,
287 config: &ProcessingConfig,
288 ) -> ValidationResult {
289 let mut errors = Vec::new();
290 let mut warnings = Vec::new();
291
292 if let Err(msg) = Self::validate_payment(payment, accounts, config) {
294 errors.push(ValidationError {
295 code: "VALIDATION_ERROR".to_string(),
296 message: msg,
297 field: None,
298 });
299 }
300
301 if payment.amount > 5000.0 {
303 warnings.push(ValidationWarning {
304 code: "LARGE_AMOUNT".to_string(),
305 message: "Payment amount is above reporting threshold".to_string(),
306 });
307 }
308
309 if payment.payment_type == PaymentType::Check {
310 warnings.push(ValidationWarning {
311 code: "SLOW_PAYMENT".to_string(),
312 message: "Check payments may take 3-5 business days".to_string(),
313 });
314 }
315
316 ValidationResult {
317 is_valid: errors.is_empty(),
318 errors,
319 warnings,
320 }
321 }
322
323 pub fn get_routing(payment: &Payment) -> PaymentRouting {
325 let (rail, estimated_settlement) = match payment.payment_type {
326 PaymentType::RealTime => ("RTP".to_string(), 0),
327 PaymentType::Wire => (
328 "FEDWIRE".to_string(),
329 if payment.priority >= PaymentPriority::High {
330 0
331 } else {
332 1
333 },
334 ),
335 PaymentType::ACH => ("ACH".to_string(), 2),
336 PaymentType::Internal => ("INTERNAL".to_string(), 0),
337 PaymentType::Check => ("CHECK".to_string(), 5),
338 PaymentType::Card => ("CARD_NETWORK".to_string(), 1),
339 };
340
341 let requires_approval =
342 payment.amount > 10000.0 || payment.priority >= PaymentPriority::Urgent;
343
344 PaymentRouting {
345 payment_id: payment.id.clone(),
346 rail,
347 estimated_settlement_days: estimated_settlement,
348 requires_approval,
349 fees: Self::calculate_fees(payment),
350 }
351 }
352
353 fn calculate_fees(payment: &Payment) -> f64 {
355 match payment.payment_type {
356 PaymentType::RealTime => 0.50, PaymentType::Wire => {
358 if payment.priority >= PaymentPriority::Urgent {
359 25.0
360 } else {
361 15.0
362 }
363 }
364 PaymentType::ACH => payment.amount * 0.001, PaymentType::Internal => 0.0,
366 PaymentType::Check => 1.0,
367 PaymentType::Card => payment.amount * 0.029 + 0.30, }
369 }
370
371 pub fn process_by_priority(
373 payments: &[Payment],
374 accounts: &HashMap<String, PaymentAccount>,
375 config: &ProcessingConfig,
376 ) -> Vec<ProcessingResult> {
377 let mut priority_groups: HashMap<PaymentPriority, Vec<&Payment>> = HashMap::new();
379 for payment in payments {
380 priority_groups
381 .entry(payment.priority)
382 .or_default()
383 .push(payment);
384 }
385
386 let mut results = Vec::new();
388 let priorities = [
389 PaymentPriority::Urgent,
390 PaymentPriority::High,
391 PaymentPriority::Normal,
392 PaymentPriority::Low,
393 ];
394
395 for priority in priorities {
396 if let Some(group) = priority_groups.get(&priority) {
397 let payments_vec: Vec<Payment> = group.iter().map(|p| (*p).clone()).collect();
398 let result = Self::process_payments(&payments_vec, accounts, config);
399 results.push(result);
400 }
401 }
402
403 results
404 }
405}
406
407impl GpuKernel for PaymentProcessing {
408 fn metadata(&self) -> &KernelMetadata {
409 &self.metadata
410 }
411}
412
413enum PaymentOutcome {
419 Processed,
420 Failed(String),
421 Pending,
422}
423
424#[derive(Debug, Clone)]
426pub struct ProcessingConfig {
427 pub enabled_payment_types: Vec<PaymentType>,
429 pub min_amount: Option<f64>,
431 pub max_amount: Option<f64>,
433 pub fraud_check_enabled: bool,
435 pub velocity_limit: Option<f64>,
437 pub large_payment_threshold: Option<f64>,
439 pub process_outside_hours: bool,
441 pub batch_mode: bool,
443 pub business_hours_start: u8,
445 pub business_hours_end: u8,
447 pub timezone_offset_seconds: i64,
449}
450
451impl Default for ProcessingConfig {
452 fn default() -> Self {
453 Self {
454 enabled_payment_types: vec![
455 PaymentType::ACH,
456 PaymentType::Wire,
457 PaymentType::RealTime,
458 PaymentType::Internal,
459 PaymentType::Check,
460 PaymentType::Card,
461 ],
462 min_amount: Some(0.01),
463 max_amount: Some(1_000_000.0),
464 fraud_check_enabled: true,
465 velocity_limit: Some(100.0),
466 large_payment_threshold: Some(50_000.0),
467 process_outside_hours: true,
468 batch_mode: false,
469 business_hours_start: 9, business_hours_end: 17, timezone_offset_seconds: 0, }
473 }
474}
475
476#[derive(Debug, Clone)]
478pub struct PaymentRouting {
479 pub payment_id: String,
481 pub rail: String,
483 pub estimated_settlement_days: u32,
485 pub requires_approval: bool,
487 pub fees: f64,
489}
490
491#[cfg(test)]
496mod tests {
497 use super::*;
498
499 fn create_test_accounts() -> HashMap<String, PaymentAccount> {
500 let mut accounts = HashMap::new();
501 accounts.insert(
502 "ACC001".to_string(),
503 PaymentAccount {
504 id: "ACC001".to_string(),
505 account_type: AccountType::Checking,
506 balance: 10000.0,
507 available_balance: 9500.0,
508 currency: "USD".to_string(),
509 status: AccountStatus::Active,
510 daily_limit: Some(25000.0),
511 daily_used: 1000.0,
512 },
513 );
514 accounts.insert(
515 "ACC002".to_string(),
516 PaymentAccount {
517 id: "ACC002".to_string(),
518 account_type: AccountType::Checking,
519 balance: 5000.0,
520 available_balance: 5000.0,
521 currency: "USD".to_string(),
522 status: AccountStatus::Active,
523 daily_limit: None,
524 daily_used: 0.0,
525 },
526 );
527 accounts.insert(
528 "ACC003".to_string(),
529 PaymentAccount {
530 id: "ACC003".to_string(),
531 account_type: AccountType::Savings,
532 balance: 20000.0,
533 available_balance: 20000.0,
534 currency: "USD".to_string(),
535 status: AccountStatus::Frozen,
536 daily_limit: None,
537 daily_used: 0.0,
538 },
539 );
540 accounts
541 }
542
543 fn create_test_payment(id: &str, payer: &str, payee: &str, amount: f64) -> Payment {
544 Payment {
545 id: id.to_string(),
546 payer_account: payer.to_string(),
547 payee_account: payee.to_string(),
548 amount,
549 currency: "USD".to_string(),
550 payment_type: PaymentType::ACH,
551 status: PaymentStatus::Initiated,
552 initiated_at: 1000,
553 completed_at: None,
554 reference: format!("REF-{}", id),
555 priority: PaymentPriority::Normal,
556 attributes: HashMap::new(),
557 }
558 }
559
560 #[test]
561 fn test_process_valid_payment() {
562 let accounts = create_test_accounts();
563 let config = ProcessingConfig::default();
564
565 let payments = vec![create_test_payment("P001", "ACC001", "ACC002", 100.0)];
566 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
567
568 assert_eq!(result.stats.total_count, 1);
569 assert_eq!(result.stats.processed_count, 1);
570 assert_eq!(result.stats.failed_count, 0);
571 assert!(result.processed.contains(&"P001".to_string()));
572 }
573
574 #[test]
575 fn test_insufficient_funds() {
576 let accounts = create_test_accounts();
577 let config = ProcessingConfig::default();
578
579 let payments = vec![create_test_payment("P001", "ACC001", "ACC002", 15000.0)];
580 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
581
582 assert_eq!(result.stats.failed_count, 1);
583 assert!(
584 result
585 .failed
586 .iter()
587 .any(|(id, reason)| { id == "P001" && reason.contains("Insufficient funds") })
588 );
589 }
590
591 #[test]
592 fn test_frozen_account() {
593 let accounts = create_test_accounts();
594 let config = ProcessingConfig::default();
595
596 let payments = vec![create_test_payment("P001", "ACC003", "ACC002", 100.0)];
597 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
598
599 assert_eq!(result.stats.failed_count, 1);
600 assert!(
601 result
602 .failed
603 .iter()
604 .any(|(id, reason)| { id == "P001" && reason.contains("not active") })
605 );
606 }
607
608 #[test]
609 fn test_daily_limit_exceeded() {
610 let mut accounts = create_test_accounts();
611 accounts.get_mut("ACC001").unwrap().available_balance = 30000.0;
613 accounts.get_mut("ACC001").unwrap().balance = 30000.0;
614 let config = ProcessingConfig::default();
615
616 let payments = vec![create_test_payment("P001", "ACC001", "ACC002", 24500.0)];
618 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
619
620 assert_eq!(result.stats.failed_count, 1);
621 assert!(
622 result
623 .failed
624 .iter()
625 .any(|(id, reason)| { id == "P001" && reason.contains("Daily limit") })
626 );
627 }
628
629 #[test]
630 fn test_account_not_found() {
631 let accounts = create_test_accounts();
632 let config = ProcessingConfig::default();
633
634 let payments = vec![create_test_payment("P001", "ACC999", "ACC002", 100.0)];
635 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
636
637 assert_eq!(result.stats.failed_count, 1);
638 assert!(
639 result
640 .failed
641 .iter()
642 .any(|(_, reason)| { reason.contains("not found") })
643 );
644 }
645
646 #[test]
647 fn test_validate_only() {
648 let accounts = create_test_accounts();
649 let config = ProcessingConfig::default();
650
651 let payment = create_test_payment("P001", "ACC001", "ACC002", 100.0);
652 let result = PaymentProcessing::validate_only(&payment, &accounts, &config);
653
654 assert!(result.is_valid);
655 assert!(result.errors.is_empty());
656 }
657
658 #[test]
659 fn test_validate_with_warnings() {
660 let accounts = create_test_accounts();
661 let config = ProcessingConfig::default();
662
663 let payment = create_test_payment("P001", "ACC001", "ACC002", 6000.0);
664 let result = PaymentProcessing::validate_only(&payment, &accounts, &config);
665
666 assert!(result.is_valid);
667 assert!(result.warnings.iter().any(|w| w.code == "LARGE_AMOUNT"));
668 }
669
670 #[test]
671 fn test_payment_routing_realtime() {
672 let mut payment = create_test_payment("P001", "ACC001", "ACC002", 100.0);
673 payment.payment_type = PaymentType::RealTime;
674
675 let routing = PaymentProcessing::get_routing(&payment);
676
677 assert_eq!(routing.rail, "RTP");
678 assert_eq!(routing.estimated_settlement_days, 0);
679 assert_eq!(routing.fees, 0.50);
680 }
681
682 #[test]
683 fn test_payment_routing_wire() {
684 let mut payment = create_test_payment("P001", "ACC001", "ACC002", 10000.0);
685 payment.payment_type = PaymentType::Wire;
686 payment.priority = PaymentPriority::Normal;
687
688 let routing = PaymentProcessing::get_routing(&payment);
689
690 assert_eq!(routing.rail, "FEDWIRE");
691 assert_eq!(routing.estimated_settlement_days, 1);
692 assert_eq!(routing.fees, 15.0);
693 }
694
695 #[test]
696 fn test_payment_routing_urgent_wire() {
697 let mut payment = create_test_payment("P001", "ACC001", "ACC002", 10000.0);
698 payment.payment_type = PaymentType::Wire;
699 payment.priority = PaymentPriority::Urgent;
700
701 let routing = PaymentProcessing::get_routing(&payment);
702
703 assert_eq!(routing.estimated_settlement_days, 0);
704 assert_eq!(routing.fees, 25.0);
705 assert!(routing.requires_approval);
706 }
707
708 #[test]
709 fn test_batch_payments() {
710 let accounts = create_test_accounts();
711 let config = ProcessingConfig::default();
712
713 let payments = vec![
714 create_test_payment("P001", "ACC001", "ACC002", 100.0),
715 create_test_payment("P002", "ACC001", "ACC002", 200.0),
716 create_test_payment("P003", "ACC001", "ACC002", 300.0),
717 ];
718
719 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
720
721 assert_eq!(result.stats.total_count, 3);
722 assert_eq!(result.stats.processed_count, 3);
723 assert_eq!(result.stats.total_amount, 600.0);
724 }
725
726 #[test]
727 fn test_process_by_priority() {
728 let accounts = create_test_accounts();
729 let config = ProcessingConfig::default();
730
731 let mut p1 = create_test_payment("P001", "ACC001", "ACC002", 100.0);
732 p1.priority = PaymentPriority::Low;
733
734 let mut p2 = create_test_payment("P002", "ACC001", "ACC002", 200.0);
735 p2.priority = PaymentPriority::Urgent;
736
737 let mut p3 = create_test_payment("P003", "ACC001", "ACC002", 300.0);
738 p3.priority = PaymentPriority::Normal;
739
740 let payments = vec![p1, p2, p3];
741 let results = PaymentProcessing::process_by_priority(&payments, &accounts, &config);
742
743 assert_eq!(results.len(), 3);
745 assert_eq!(results[0].stats.total_count, 1);
747 assert!(results[0].processed.contains(&"P002".to_string()));
748 }
749
750 #[test]
751 fn test_fraud_check_large_payment() {
752 let accounts = create_test_accounts();
753 let mut config = ProcessingConfig::default();
754 config.large_payment_threshold = Some(5000.0);
755 config.max_amount = Some(100000.0);
756 config.velocity_limit = Some(1000.0); let mut payment = create_test_payment("P001", "ACC001", "ACC002", 8000.0);
759 payment.payment_type = PaymentType::RealTime;
760
761 let payments = vec![payment];
762 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
763
764 assert_eq!(result.stats.failed_count, 1);
765 assert!(
766 result
767 .failed
768 .iter()
769 .any(|(_, reason)| { reason.contains("manual review") })
770 );
771 }
772
773 #[test]
774 fn test_payment_type_disabled() {
775 let accounts = create_test_accounts();
776 let mut config = ProcessingConfig::default();
777 config.enabled_payment_types = vec![PaymentType::ACH]; let mut payment = create_test_payment("P001", "ACC001", "ACC002", 100.0);
780 payment.payment_type = PaymentType::Wire;
781
782 let payments = vec![payment];
783 let result = PaymentProcessing::process_payments(&payments, &accounts, &config);
784
785 assert_eq!(result.stats.failed_count, 1);
786 assert!(
787 result
788 .failed
789 .iter()
790 .any(|(_, reason)| { reason.contains("not enabled") })
791 );
792 }
793
794 #[test]
795 fn test_amount_limits() {
796 let accounts = create_test_accounts();
797 let mut config = ProcessingConfig::default();
798 config.min_amount = Some(10.0);
799 config.max_amount = Some(1000.0);
800
801 let p1 = create_test_payment("P001", "ACC001", "ACC002", 5.0);
803 let result = PaymentProcessing::process_payments(&[p1], &accounts, &config);
804 assert!(
805 result
806 .failed
807 .iter()
808 .any(|(_, r)| r.contains("below minimum"))
809 );
810
811 let p2 = create_test_payment("P002", "ACC001", "ACC002", 2000.0);
813 let result = PaymentProcessing::process_payments(&[p2], &accounts, &config);
814 assert!(
815 result
816 .failed
817 .iter()
818 .any(|(_, r)| r.contains("exceeds maximum"))
819 );
820 }
821}