chio_kernel/payment/
sim.rs1use crate::payment::{
9 PaymentAdapter, PaymentAuthorization, PaymentAuthorizationState, PaymentAuthorizeRequest,
10 PaymentError, PaymentResult, RailSettlementStatus,
11};
12
13#[derive(Debug, Clone, Default)]
15pub struct SimPaymentAdapter;
16
17impl SimPaymentAdapter {
18 #[must_use]
19 pub fn new() -> Self {
20 Self
21 }
22
23 fn deterministic_id(
24 prefix: &str,
25 reference: &str,
26 amount_units: u64,
27 currency: &str,
28 ) -> String {
29 let seed = format!("{reference}|{amount_units}|{currency}");
30 let digest = chio_core::sha256_hex(seed.as_bytes());
31 format!("{prefix}-{}", &digest[..32])
32 }
33}
34
35impl PaymentAdapter for SimPaymentAdapter {
36 fn authorize(
37 &self,
38 request: &PaymentAuthorizeRequest,
39 ) -> Result<PaymentAuthorization, PaymentError> {
40 let authorization_id = Self::deterministic_id(
41 "sim",
42 &request.reference,
43 request.amount_units,
44 &request.currency,
45 );
46 Ok(PaymentAuthorization {
47 authorization_id,
48 state: PaymentAuthorizationState::PrepaidFinal,
49 metadata: serde_json::json!({
50 "adapter": "sim",
51 "mode": "prepaid_no_broadcast",
52 "governed": request.governed,
53 "commerce": request.commerce,
54 }),
55 })
56 }
57
58 fn capture(
59 &self,
60 authorization_id: &str,
61 _amount_units: u64,
62 _currency: &str,
63 _reference: &str,
64 ) -> Result<PaymentResult, PaymentError> {
65 Ok(PaymentResult {
66 transaction_id: authorization_id.to_string(),
67 settlement_status: RailSettlementStatus::Settled,
68 metadata: serde_json::json!({ "adapter": "sim", "action": "capture" }),
69 })
70 }
71
72 fn release(
73 &self,
74 authorization_id: &str,
75 _reference: &str,
76 ) -> Result<PaymentResult, PaymentError> {
77 Ok(PaymentResult {
78 transaction_id: authorization_id.to_string(),
79 settlement_status: RailSettlementStatus::Released,
80 metadata: serde_json::json!({ "adapter": "sim", "action": "release" }),
81 })
82 }
83
84 fn refund(
85 &self,
86 transaction_id: &str,
87 _amount_units: u64,
88 _currency: &str,
89 _reference: &str,
90 ) -> Result<PaymentResult, PaymentError> {
91 Ok(PaymentResult {
92 transaction_id: transaction_id.to_string(),
93 settlement_status: RailSettlementStatus::Refunded,
94 metadata: serde_json::json!({ "adapter": "sim", "action": "refund" }),
95 })
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 fn request(reference: &str, amount: u64) -> PaymentAuthorizeRequest {
104 PaymentAuthorizeRequest {
105 amount_units: amount,
106 currency: "USD".to_string(),
107 payer: "agent-1".to_string(),
108 payee: "srv-1".to_string(),
109 reference: reference.to_string(),
110 governed: None,
111 commerce: None,
112 }
113 }
114
115 #[test]
116 fn authorize_is_deterministic_and_prepaid() {
117 let adapter = SimPaymentAdapter::new();
118 let a = adapter.authorize(&request("req-1", 100)).unwrap();
119 let b = adapter.authorize(&request("req-1", 100)).unwrap();
120 assert_eq!(a.authorization_id, b.authorization_id);
121 assert!(a.authorization_id.starts_with("sim-"));
122 assert!(a.state.is_final(), "sim authorization is prepaid and final");
123 }
124
125 #[test]
126 fn distinct_requests_get_distinct_ids() {
127 let adapter = SimPaymentAdapter::new();
128 let a = adapter.authorize(&request("req-1", 100)).unwrap();
129 let c = adapter.authorize(&request("req-2", 100)).unwrap();
130 assert_ne!(a.authorization_id, c.authorization_id);
131 }
132
133 #[test]
134 fn capture_and_release_map_to_settled_and_released() {
135 let adapter = SimPaymentAdapter::new();
136 let captured = adapter.capture("sim-abc", 100, "USD", "req-1").unwrap();
137 assert_eq!(captured.settlement_status, RailSettlementStatus::Settled);
138 let released = adapter.release("sim-abc", "req-1").unwrap();
139 assert_eq!(released.settlement_status, RailSettlementStatus::Released);
140 }
141}