koprogo_api/application/use_cases/
payment_use_cases.rs1use 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 pub async fn create_payment(
30 &self,
31 organization_id: Uuid,
32 request: CreatePaymentRequest,
33 ) -> Result<PaymentResponse, String> {
34 let idempotency_key = format!(
36 "{}-{}-{}-{}",
37 organization_id,
38 request.building_id,
39 request.owner_id,
40 Uuid::new_v4()
41 );
42
43 if let Some(existing_payment) = self
45 .payment_repository
46 .find_by_idempotency_key(organization_id, &idempotency_key)
47 .await?
48 {
49 return Ok(PaymentResponse::from(existing_payment));
51 }
52
53 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub async fn set_payment_method_id(
294 &self,
295 id: Uuid,
296 payment_method_id: Uuid,
297 ) -> Result<PaymentResponse, String> {
298 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 pub async fn delete_payment(&self, id: Uuid) -> Result<bool, String> {
319 self.payment_repository.delete(id).await
320 }
321
322 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 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 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 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 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 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}