1use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11use crate::TrustLevel;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum PaymentIntentState {
21 Draft,
23 PendingApproval,
25 Approved,
27 Executing,
29 Settled,
31 Rejected,
33 Cancelled,
35}
36
37impl PaymentIntentState {
38 pub fn is_terminal(self) -> bool {
40 matches!(self, Self::Settled | Self::Rejected | Self::Cancelled)
41 }
42}
43
44impl std::fmt::Display for PaymentIntentState {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 match self {
47 Self::Draft => write!(f, "draft"),
48 Self::PendingApproval => write!(f, "pending_approval"),
49 Self::Approved => write!(f, "approved"),
50 Self::Executing => write!(f, "executing"),
51 Self::Settled => write!(f, "settled"),
52 Self::Rejected => write!(f, "rejected"),
53 Self::Cancelled => write!(f, "cancelled"),
54 }
55 }
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct PaymentIntent {
65 pub id: Uuid,
67 pub sku: String,
69 pub amount: u64,
71 pub currency: String,
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub payment_token: Option<String>,
76 pub state: PaymentIntentState,
78 pub trust_level: TrustLevel,
80 pub signature: String,
82 pub created_at: DateTime<Utc>,
84 pub updated_at: DateTime<Utc>,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct PaymentPolicy {
92 pub auto_approve_threshold: u64,
94 pub require_approval_threshold: u64,
96 pub min_trust_for_auto_approve: TrustLevel,
98 #[serde(default, skip_serializing_if = "Vec::is_empty")]
100 pub notification_channels: Vec<String>,
101}
102
103impl Default for PaymentPolicy {
104 fn default() -> Self {
105 Self {
106 auto_approve_threshold: 5000, require_approval_threshold: 50_000, min_trust_for_auto_approve: TrustLevel::Known,
109 notification_channels: vec![],
110 }
111 }
112}
113
114impl PaymentPolicy {
115 pub fn evaluate(&self, amount: u64, trust_level: TrustLevel) -> PaymentPolicyDecision {
118 if trust_level < self.min_trust_for_auto_approve {
119 return PaymentPolicyDecision::RequireApproval;
120 }
121 if amount <= self.auto_approve_threshold {
122 return PaymentPolicyDecision::AutoApprove;
123 }
124 if amount > self.require_approval_threshold {
125 return PaymentPolicyDecision::RequireApproval;
126 }
127 PaymentPolicyDecision::RequireApproval
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum PaymentPolicyDecision {
134 AutoApprove,
136 RequireApproval,
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn test_payment_intent_state_terminal() {
146 assert!(!PaymentIntentState::Draft.is_terminal());
147 assert!(!PaymentIntentState::PendingApproval.is_terminal());
148 assert!(!PaymentIntentState::Approved.is_terminal());
149 assert!(!PaymentIntentState::Executing.is_terminal());
150 assert!(PaymentIntentState::Settled.is_terminal());
151 assert!(PaymentIntentState::Rejected.is_terminal());
152 assert!(PaymentIntentState::Cancelled.is_terminal());
153 }
154
155 #[test]
156 fn test_payment_intent_state_display() {
157 assert_eq!(PaymentIntentState::Draft.to_string(), "draft");
158 assert_eq!(PaymentIntentState::PendingApproval.to_string(), "pending_approval");
159 assert_eq!(PaymentIntentState::Settled.to_string(), "settled");
160 }
161
162 #[test]
163 fn test_payment_intent_state_serde() {
164 let states = [
165 PaymentIntentState::Draft,
166 PaymentIntentState::PendingApproval,
167 PaymentIntentState::Approved,
168 PaymentIntentState::Executing,
169 PaymentIntentState::Settled,
170 PaymentIntentState::Rejected,
171 PaymentIntentState::Cancelled,
172 ];
173 for state in states {
174 let json = serde_json::to_string(&state).unwrap();
175 let parsed: PaymentIntentState = serde_json::from_str(&json).unwrap();
176 assert_eq!(state, parsed);
177 }
178 }
179
180 #[test]
181 fn test_payment_intent_serde_round_trip() {
182 let intent = PaymentIntent {
183 id: Uuid::now_v7(),
184 sku: "WIDGET-001".to_string(),
185 amount: 2500,
186 currency: "USD".to_string(),
187 payment_token: None,
188 state: PaymentIntentState::Draft,
189 trust_level: TrustLevel::Known,
190 signature: "sha256=abc123".to_string(),
191 created_at: Utc::now(),
192 updated_at: Utc::now(),
193 };
194 let json = serde_json::to_string(&intent).unwrap();
195 let parsed: PaymentIntent = serde_json::from_str(&json).unwrap();
196 assert_eq!(intent, parsed);
197 }
198
199 #[test]
200 fn test_payment_intent_optional_token_skipped() {
201 let intent = PaymentIntent {
202 id: Uuid::now_v7(),
203 sku: "SKU".to_string(),
204 amount: 100,
205 currency: "KES".to_string(),
206 payment_token: None,
207 state: PaymentIntentState::Draft,
208 trust_level: TrustLevel::Anonymous,
209 signature: "sig".to_string(),
210 created_at: Utc::now(),
211 updated_at: Utc::now(),
212 };
213 let json = serde_json::to_string(&intent).unwrap();
214 assert!(!json.contains("paymentToken"));
215 }
216
217 #[test]
218 fn test_payment_policy_default() {
219 let policy = PaymentPolicy::default();
220 assert_eq!(policy.auto_approve_threshold, 5000);
221 assert_eq!(policy.require_approval_threshold, 50_000);
222 assert_eq!(policy.min_trust_for_auto_approve, TrustLevel::Known);
223 }
224
225 #[test]
226 fn test_payment_policy_auto_approve() {
227 let policy = PaymentPolicy::default();
228 assert_eq!(policy.evaluate(2500, TrustLevel::Known), PaymentPolicyDecision::AutoApprove);
229 }
230
231 #[test]
232 fn test_payment_policy_require_approval_high_amount() {
233 let policy = PaymentPolicy::default();
234 assert_eq!(
235 policy.evaluate(60_000, TrustLevel::Partner),
236 PaymentPolicyDecision::RequireApproval
237 );
238 }
239
240 #[test]
241 fn test_payment_policy_require_approval_low_trust() {
242 let policy = PaymentPolicy::default();
243 assert_eq!(
244 policy.evaluate(1000, TrustLevel::Anonymous),
245 PaymentPolicyDecision::RequireApproval
246 );
247 }
248
249 #[test]
250 fn test_payment_policy_serde_round_trip() {
251 let policy = PaymentPolicy {
252 auto_approve_threshold: 1000,
253 require_approval_threshold: 10_000,
254 min_trust_for_auto_approve: TrustLevel::Partner,
255 notification_channels: vec!["email".to_string(), "sms".to_string()],
256 };
257 let json = serde_json::to_string(&policy).unwrap();
258 let parsed: PaymentPolicy = serde_json::from_str(&json).unwrap();
259 assert_eq!(policy, parsed);
260 }
261}