koprogo_api/application/use_cases/
payment_use_cases.rs

1use crate::application::dto::{
2    CreatePaymentRequest, PaymentResponse, PaymentStatsResponse, RefundPaymentRequest,
3};
4use crate::application::ports::{PaymentMethodRepository, PaymentRepository, PaymentStats};
5use crate::domain::entities::{Payment, TransactionStatus};
6use std::sync::Arc;
7use uuid::Uuid;
8
9pub struct PaymentUseCases {
10    payment_repository: Arc<dyn PaymentRepository>,
11    payment_method_repository: Arc<dyn PaymentMethodRepository>,
12}
13
14impl PaymentUseCases {
15    pub fn new(
16        payment_repository: Arc<dyn PaymentRepository>,
17        payment_method_repository: Arc<dyn PaymentMethodRepository>,
18    ) -> Self {
19        Self {
20            payment_repository,
21            payment_method_repository,
22        }
23    }
24
25    /// Create a new payment
26    ///
27    /// Generates a unique idempotency key to prevent duplicate charges.
28    /// Checks for existing payment with same idempotency key (prevents retries from creating duplicates).
29    pub async fn create_payment(
30        &self,
31        organization_id: Uuid,
32        request: CreatePaymentRequest,
33    ) -> Result<PaymentResponse, String> {
34        // Generate idempotency key (organization_id + building_id + owner_id + timestamp + random)
35        let idempotency_key = format!(
36            "{}-{}-{}-{}",
37            organization_id,
38            request.building_id,
39            request.owner_id,
40            Uuid::new_v4()
41        );
42
43        // Check if payment with same idempotency key already exists
44        if let Some(existing_payment) = self
45            .payment_repository
46            .find_by_idempotency_key(organization_id, &idempotency_key)
47            .await?
48        {
49            // Return existing payment (idempotent)
50            return Ok(PaymentResponse::from(existing_payment));
51        }
52
53        // Create new payment
54        let payment = Payment::new(
55            organization_id,
56            request.building_id,
57            request.owner_id,
58            request.expense_id,
59            request.amount_cents,
60            request.payment_method_type,
61            idempotency_key,
62            request.description,
63        )?;
64
65        let created = self.payment_repository.create(&payment).await?;
66        Ok(PaymentResponse::from(created))
67    }
68
69    /// Get payment by ID
70    pub async fn get_payment(&self, id: Uuid) -> Result<Option<PaymentResponse>, String> {
71        match self.payment_repository.find_by_id(id).await? {
72            Some(payment) => Ok(Some(PaymentResponse::from(payment))),
73            None => Ok(None),
74        }
75    }
76
77    /// Get payment by Stripe payment intent ID
78    pub async fn get_payment_by_stripe_intent(
79        &self,
80        stripe_payment_intent_id: &str,
81    ) -> Result<Option<PaymentResponse>, String> {
82        match self
83            .payment_repository
84            .find_by_stripe_payment_intent_id(stripe_payment_intent_id)
85            .await?
86        {
87            Some(payment) => Ok(Some(PaymentResponse::from(payment))),
88            None => Ok(None),
89        }
90    }
91
92    /// List payments for an owner
93    pub async fn list_owner_payments(
94        &self,
95        owner_id: Uuid,
96    ) -> Result<Vec<PaymentResponse>, String> {
97        let payments = self.payment_repository.find_by_owner(owner_id).await?;
98        Ok(payments.into_iter().map(PaymentResponse::from).collect())
99    }
100
101    /// List payments for a building
102    pub async fn list_building_payments(
103        &self,
104        building_id: Uuid,
105    ) -> Result<Vec<PaymentResponse>, String> {
106        let payments = self
107            .payment_repository
108            .find_by_building(building_id)
109            .await?;
110        Ok(payments.into_iter().map(PaymentResponse::from).collect())
111    }
112
113    /// List payments for an expense
114    pub async fn list_expense_payments(
115        &self,
116        expense_id: Uuid,
117    ) -> Result<Vec<PaymentResponse>, String> {
118        let payments = self.payment_repository.find_by_expense(expense_id).await?;
119        Ok(payments.into_iter().map(PaymentResponse::from).collect())
120    }
121
122    /// List payments for an organization
123    pub async fn list_organization_payments(
124        &self,
125        organization_id: Uuid,
126    ) -> Result<Vec<PaymentResponse>, String> {
127        let payments = self
128            .payment_repository
129            .find_by_organization(organization_id)
130            .await?;
131        Ok(payments.into_iter().map(PaymentResponse::from).collect())
132    }
133
134    /// List payments by status
135    pub async fn list_payments_by_status(
136        &self,
137        organization_id: Uuid,
138        status: TransactionStatus,
139    ) -> Result<Vec<PaymentResponse>, String> {
140        let payments = self
141            .payment_repository
142            .find_by_status(organization_id, status)
143            .await?;
144        Ok(payments.into_iter().map(PaymentResponse::from).collect())
145    }
146
147    /// List pending payments (for background processing)
148    pub async fn list_pending_payments(
149        &self,
150        organization_id: Uuid,
151    ) -> Result<Vec<PaymentResponse>, String> {
152        let payments = self
153            .payment_repository
154            .find_pending(organization_id)
155            .await?;
156        Ok(payments.into_iter().map(PaymentResponse::from).collect())
157    }
158
159    /// List failed payments (for retry or analysis)
160    pub async fn list_failed_payments(
161        &self,
162        organization_id: Uuid,
163    ) -> Result<Vec<PaymentResponse>, String> {
164        let payments = self.payment_repository.find_failed(organization_id).await?;
165        Ok(payments.into_iter().map(PaymentResponse::from).collect())
166    }
167
168    /// Mark payment as processing
169    pub async fn mark_processing(&self, id: Uuid) -> Result<PaymentResponse, String> {
170        let mut payment = self
171            .payment_repository
172            .find_by_id(id)
173            .await?
174            .ok_or_else(|| "Payment not found".to_string())?;
175
176        payment.mark_processing()?;
177
178        let updated = self.payment_repository.update(&payment).await?;
179        Ok(PaymentResponse::from(updated))
180    }
181
182    /// Mark payment as requiring action (e.g., 3D Secure)
183    pub async fn mark_requires_action(&self, id: Uuid) -> Result<PaymentResponse, String> {
184        let mut payment = self
185            .payment_repository
186            .find_by_id(id)
187            .await?
188            .ok_or_else(|| "Payment not found".to_string())?;
189
190        payment.mark_requires_action()?;
191
192        let updated = self.payment_repository.update(&payment).await?;
193        Ok(PaymentResponse::from(updated))
194    }
195
196    /// Mark payment as succeeded
197    pub async fn mark_succeeded(&self, id: Uuid) -> Result<PaymentResponse, String> {
198        let mut payment = self
199            .payment_repository
200            .find_by_id(id)
201            .await?
202            .ok_or_else(|| "Payment not found".to_string())?;
203
204        payment.mark_succeeded()?;
205
206        let updated = self.payment_repository.update(&payment).await?;
207        Ok(PaymentResponse::from(updated))
208    }
209
210    /// Mark payment as failed
211    pub async fn mark_failed(&self, id: Uuid, reason: String) -> Result<PaymentResponse, String> {
212        let mut payment = self
213            .payment_repository
214            .find_by_id(id)
215            .await?
216            .ok_or_else(|| "Payment not found".to_string())?;
217
218        payment.mark_failed(reason)?;
219
220        let updated = self.payment_repository.update(&payment).await?;
221        Ok(PaymentResponse::from(updated))
222    }
223
224    /// Mark payment as cancelled
225    pub async fn mark_cancelled(&self, id: Uuid) -> Result<PaymentResponse, String> {
226        let mut payment = self
227            .payment_repository
228            .find_by_id(id)
229            .await?
230            .ok_or_else(|| "Payment not found".to_string())?;
231
232        payment.mark_cancelled()?;
233
234        let updated = self.payment_repository.update(&payment).await?;
235        Ok(PaymentResponse::from(updated))
236    }
237
238    /// Refund payment (partial or full)
239    pub async fn refund_payment(
240        &self,
241        id: Uuid,
242        request: RefundPaymentRequest,
243    ) -> Result<PaymentResponse, String> {
244        let mut payment = self
245            .payment_repository
246            .find_by_id(id)
247            .await?
248            .ok_or_else(|| "Payment not found".to_string())?;
249
250        payment.refund(request.amount_cents)?;
251
252        let updated = self.payment_repository.update(&payment).await?;
253        Ok(PaymentResponse::from(updated))
254    }
255
256    /// Set Stripe payment intent ID
257    pub async fn set_stripe_payment_intent_id(
258        &self,
259        id: Uuid,
260        stripe_payment_intent_id: String,
261    ) -> Result<PaymentResponse, String> {
262        let mut payment = self
263            .payment_repository
264            .find_by_id(id)
265            .await?
266            .ok_or_else(|| "Payment not found".to_string())?;
267
268        payment.set_stripe_payment_intent_id(stripe_payment_intent_id);
269
270        let updated = self.payment_repository.update(&payment).await?;
271        Ok(PaymentResponse::from(updated))
272    }
273
274    /// Set Stripe customer ID
275    pub async fn set_stripe_customer_id(
276        &self,
277        id: Uuid,
278        stripe_customer_id: String,
279    ) -> Result<PaymentResponse, String> {
280        let mut payment = self
281            .payment_repository
282            .find_by_id(id)
283            .await?
284            .ok_or_else(|| "Payment not found".to_string())?;
285
286        payment.set_stripe_customer_id(stripe_customer_id);
287
288        let updated = self.payment_repository.update(&payment).await?;
289        Ok(PaymentResponse::from(updated))
290    }
291
292    /// Set payment method ID
293    pub async fn set_payment_method_id(
294        &self,
295        id: Uuid,
296        payment_method_id: Uuid,
297    ) -> Result<PaymentResponse, String> {
298        // Verify payment method exists
299        let _payment_method = self
300            .payment_method_repository
301            .find_by_id(payment_method_id)
302            .await?
303            .ok_or_else(|| "Payment method not found".to_string())?;
304
305        let mut payment = self
306            .payment_repository
307            .find_by_id(id)
308            .await?
309            .ok_or_else(|| "Payment not found".to_string())?;
310
311        payment.set_payment_method_id(payment_method_id);
312
313        let updated = self.payment_repository.update(&payment).await?;
314        Ok(PaymentResponse::from(updated))
315    }
316
317    /// Delete payment
318    pub async fn delete_payment(&self, id: Uuid) -> Result<bool, String> {
319        self.payment_repository.delete(id).await
320    }
321
322    /// Get total paid for expense
323    pub async fn get_total_paid_for_expense(&self, expense_id: Uuid) -> Result<i64, String> {
324        self.payment_repository
325            .get_total_paid_for_expense(expense_id)
326            .await
327    }
328
329    /// Get total paid by owner
330    pub async fn get_total_paid_by_owner(&self, owner_id: Uuid) -> Result<i64, String> {
331        self.payment_repository
332            .get_total_paid_by_owner(owner_id)
333            .await
334    }
335
336    /// Get total paid for building
337    pub async fn get_total_paid_for_building(&self, building_id: Uuid) -> Result<i64, String> {
338        self.payment_repository
339            .get_total_paid_for_building(building_id)
340            .await
341    }
342
343    /// Get payment statistics for owner
344    pub async fn get_owner_payment_stats(
345        &self,
346        owner_id: Uuid,
347    ) -> Result<PaymentStatsResponse, String> {
348        let stats = self
349            .payment_repository
350            .get_owner_payment_stats(owner_id)
351            .await?;
352        Ok(Self::payment_stats_to_response(stats))
353    }
354
355    /// Get payment statistics for building
356    pub async fn get_building_payment_stats(
357        &self,
358        building_id: Uuid,
359    ) -> Result<PaymentStatsResponse, String> {
360        let stats = self
361            .payment_repository
362            .get_building_payment_stats(building_id)
363            .await?;
364        Ok(Self::payment_stats_to_response(stats))
365    }
366
367    /// Convert PaymentStats to PaymentStatsResponse
368    fn payment_stats_to_response(stats: PaymentStats) -> PaymentStatsResponse {
369        PaymentStatsResponse {
370            total_count: stats.total_count,
371            succeeded_count: stats.succeeded_count,
372            failed_count: stats.failed_count,
373            pending_count: stats.pending_count,
374            total_amount_cents: stats.total_amount_cents,
375            total_succeeded_cents: stats.total_succeeded_cents,
376            total_refunded_cents: stats.total_refunded_cents,
377            net_amount_cents: stats.net_amount_cents,
378        }
379    }
380}