1use crate::application::dto::{CreatePaymentRequest, RefundPaymentRequest};
2use crate::domain::entities::TransactionStatus;
3use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
4use crate::infrastructure::web::{AppState, AuthenticatedUser};
5use actix_web::{delete, get, post, put, web, HttpResponse, Responder, ResponseError};
6use uuid::Uuid;
7
8#[utoipa::path(
11 post,
12 path = "/payments",
13 tag = "Payments",
14 summary = "Create a payment",
15 request_body = CreatePaymentRequest,
16 responses(
17 (status = 201, description = "Payment created"),
18 (status = 400, description = "Bad request"),
19 (status = 401, description = "Unauthorized"),
20 ),
21 security(("bearer_auth" = []))
22)]
23#[post("/payments")]
24pub async fn create_payment(
25 state: web::Data<AppState>,
26 user: AuthenticatedUser,
27 request: web::Json<CreatePaymentRequest>,
28) -> impl Responder {
29 let organization_id = match user.require_organization() {
30 Ok(org_id) => org_id,
31 Err(e) => {
32 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
33 }
34 };
35
36 match state
37 .payment_use_cases
38 .create_payment(organization_id, request.into_inner())
39 .await
40 {
41 Ok(payment) => {
42 AuditLogEntry::new(
43 AuditEventType::PaymentCreated,
44 Some(user.user_id),
45 Some(organization_id),
46 )
47 .with_resource("Payment", payment.id)
48 .log();
49
50 HttpResponse::Created().json(payment)
51 }
52 Err(err) => {
53 AuditLogEntry::new(
54 AuditEventType::PaymentCreated,
55 Some(user.user_id),
56 Some(organization_id),
57 )
58 .with_error(err.clone())
59 .log();
60
61 HttpResponse::BadRequest().json(serde_json::json!({"error": err}))
62 }
63 }
64}
65
66#[utoipa::path(
67 get,
68 path = "/payments/{id}",
69 tag = "Payments",
70 summary = "Get a payment by ID",
71 params(("id" = Uuid, Path, description = "Payment ID")),
72 responses(
73 (status = 200, description = "Payment found"),
74 (status = 404, description = "Payment not found"),
75 (status = 500, description = "Internal server error"),
76 ),
77 security(("bearer_auth" = []))
78)]
79#[get("/payments/{id}")]
80pub async fn get_payment(
81 state: web::Data<AppState>,
82 user: AuthenticatedUser,
83 id: web::Path<Uuid>,
84) -> impl Responder {
85 match state.payment_use_cases.get_payment(*id).await {
86 Ok(Some(payment)) => {
87 if let Err(e) = user.verify_org_access(payment.organization_id) {
89 return HttpResponse::Forbidden().json(serde_json::json!({ "error": e }));
90 }
91 HttpResponse::Ok().json(payment)
92 }
93 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
94 "error": "Payment not found"
95 })),
96 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
97 }
98}
99
100#[utoipa::path(
101 get,
102 path = "/payments/stripe/{stripe_payment_intent_id}",
103 tag = "Payments",
104 summary = "Get a payment by Stripe payment intent ID",
105 params(("stripe_payment_intent_id" = String, Path, description = "Stripe payment intent ID")),
106 responses(
107 (status = 200, description = "Payment found"),
108 (status = 404, description = "Payment not found"),
109 (status = 500, description = "Internal server error"),
110 ),
111 security(("bearer_auth" = []))
112)]
113#[get("/payments/stripe/{stripe_payment_intent_id}")]
114pub async fn get_payment_by_stripe_intent(
115 state: web::Data<AppState>,
116 user: AuthenticatedUser,
117 stripe_payment_intent_id: web::Path<String>,
118) -> impl Responder {
119 match state
120 .payment_use_cases
121 .get_payment_by_stripe_intent(&stripe_payment_intent_id)
122 .await
123 {
124 Ok(Some(payment)) => {
125 if let Err(e) = user.verify_org_access(payment.organization_id) {
131 return HttpResponse::Forbidden().json(serde_json::json!({ "error": e }));
132 }
133 HttpResponse::Ok().json(payment)
134 }
135 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
136 "error": "Payment not found"
137 })),
138 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
139 }
140}
141
142#[utoipa::path(
143 get,
144 path = "/owners/{owner_id}/payments",
145 tag = "Payments",
146 summary = "List all payments for an owner",
147 params(("owner_id" = Uuid, Path, description = "Owner ID")),
148 responses(
149 (status = 200, description = "List of owner payments"),
150 (status = 500, description = "Internal server error"),
151 ),
152 security(("bearer_auth" = []))
153)]
154#[get("/owners/{owner_id}/payments")]
155pub async fn list_owner_payments(
156 state: web::Data<AppState>,
157 owner_id: web::Path<Uuid>,
158 user: AuthenticatedUser,
159) -> impl Responder {
160 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
164 &user,
165 *owner_id,
166 &state.owner_use_cases,
167 )
168 .await
169 {
170 return err.error_response();
171 }
172
173 match state.payment_use_cases.list_owner_payments(*owner_id).await {
174 Ok(payments) => HttpResponse::Ok().json(payments),
175 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
176 }
177}
178
179#[utoipa::path(
180 get,
181 path = "/buildings/{building_id}/payments",
182 tag = "Payments",
183 summary = "List all payments for a building",
184 params(("building_id" = Uuid, Path, description = "Building ID")),
185 responses(
186 (status = 200, description = "List of building payments"),
187 (status = 500, description = "Internal server error"),
188 ),
189 security(("bearer_auth" = []))
190)]
191#[get("/buildings/{building_id}/payments")]
192pub async fn list_building_payments(
193 state: web::Data<AppState>,
194 building_id: web::Path<Uuid>,
195 user: AuthenticatedUser,
196) -> impl Responder {
197 if let Err(err) =
201 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
202 &user,
203 *building_id,
204 &state.building_use_cases,
205 &state.acp_use_cases,
206 )
207 .await
208 {
209 return err.error_response();
210 }
211
212 match state
213 .payment_use_cases
214 .list_building_payments(*building_id)
215 .await
216 {
217 Ok(payments) => HttpResponse::Ok().json(payments),
218 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
219 }
220}
221
222#[utoipa::path(
223 get,
224 path = "/expenses/{expense_id}/payments",
225 tag = "Payments",
226 summary = "List all payments for an expense",
227 params(("expense_id" = Uuid, Path, description = "Expense ID")),
228 responses(
229 (status = 200, description = "List of expense payments"),
230 (status = 500, description = "Internal server error"),
231 ),
232 security(("bearer_auth" = []))
233)]
234#[get("/expenses/{expense_id}/payments")]
235pub async fn list_expense_payments(
236 state: web::Data<AppState>,
237 expense_id: web::Path<Uuid>,
238 user: AuthenticatedUser,
239) -> impl Responder {
240 if let Err(err) =
243 crate::infrastructure::web::middleware::scope_guard::verify_expense_org_access(
244 &user,
245 *expense_id,
246 &state.expense_use_cases,
247 &state.building_use_cases,
248 &state.acp_use_cases,
249 )
250 .await
251 {
252 return err.error_response();
253 }
254
255 match state
256 .payment_use_cases
257 .list_expense_payments(*expense_id)
258 .await
259 {
260 Ok(payments) => HttpResponse::Ok().json(payments),
261 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
262 }
263}
264
265#[utoipa::path(
266 get,
267 path = "/organizations/{organization_id}/payments",
268 tag = "Payments",
269 summary = "List all payments for an organization",
270 params(("organization_id" = Uuid, Path, description = "Organization ID")),
271 responses(
272 (status = 200, description = "List of organization payments"),
273 (status = 500, description = "Internal server error"),
274 ),
275 security(("bearer_auth" = []))
276)]
277#[get("/organizations/{organization_id}/payments")]
278pub async fn list_organization_payments(
279 state: web::Data<AppState>,
280 user: AuthenticatedUser,
281 organization_id: web::Path<Uuid>,
282) -> impl Responder {
283 if let Err(e) = user.verify_org_access(*organization_id) {
284 return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
285 }
286 match state
287 .payment_use_cases
288 .list_organization_payments(*organization_id)
289 .await
290 {
291 Ok(payments) => HttpResponse::Ok().json(payments),
292 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
293 }
294}
295
296#[utoipa::path(
297 get,
298 path = "/payments/status/{status}",
299 tag = "Payments",
300 summary = "List payments by transaction status",
301 params(("status" = String, Path, description = "Transaction status (pending, processing, requires_action, succeeded, failed, cancelled, refunded)")),
302 responses(
303 (status = 200, description = "List of payments with given status"),
304 (status = 400, description = "Invalid status value"),
305 (status = 401, description = "Unauthorized"),
306 (status = 500, description = "Internal server error"),
307 ),
308 security(("bearer_auth" = []))
309)]
310#[get("/payments/status/{status}")]
311pub async fn list_payments_by_status(
312 state: web::Data<AppState>,
313 user: AuthenticatedUser,
314 status_str: web::Path<String>,
315) -> impl Responder {
316 let organization_id = match user.require_organization() {
317 Ok(org_id) => org_id,
318 Err(e) => {
319 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
320 }
321 };
322
323 let status = match status_str.as_str() {
325 "pending" => TransactionStatus::Pending,
326 "processing" => TransactionStatus::Processing,
327 "requires_action" => TransactionStatus::RequiresAction,
328 "succeeded" => TransactionStatus::Succeeded,
329 "failed" => TransactionStatus::Failed,
330 "cancelled" => TransactionStatus::Cancelled,
331 "refunded" => TransactionStatus::Refunded,
332 _ => {
333 return HttpResponse::BadRequest().json(serde_json::json!({
334 "error": "Invalid status. Must be one of: pending, processing, requires_action, succeeded, failed, cancelled, refunded"
335 }))
336 }
337 };
338
339 match state
340 .payment_use_cases
341 .list_payments_by_status(organization_id, status)
342 .await
343 {
344 Ok(payments) => HttpResponse::Ok().json(payments),
345 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
346 }
347}
348
349#[utoipa::path(
350 get,
351 path = "/payments/pending",
352 tag = "Payments",
353 summary = "List all pending payments for the organization",
354 responses(
355 (status = 200, description = "List of pending payments"),
356 (status = 401, description = "Unauthorized"),
357 (status = 500, description = "Internal server error"),
358 ),
359 security(("bearer_auth" = []))
360)]
361#[get("/payments/pending")]
362pub async fn list_pending_payments(
363 state: web::Data<AppState>,
364 user: AuthenticatedUser,
365) -> impl Responder {
366 let organization_id = match user.require_organization() {
367 Ok(org_id) => org_id,
368 Err(e) => {
369 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
370 }
371 };
372
373 match state
374 .payment_use_cases
375 .list_pending_payments(organization_id)
376 .await
377 {
378 Ok(payments) => HttpResponse::Ok().json(payments),
379 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
380 }
381}
382
383#[utoipa::path(
384 get,
385 path = "/payments/failed",
386 tag = "Payments",
387 summary = "List all failed payments for the organization",
388 responses(
389 (status = 200, description = "List of failed payments"),
390 (status = 401, description = "Unauthorized"),
391 (status = 500, description = "Internal server error"),
392 ),
393 security(("bearer_auth" = []))
394)]
395#[get("/payments/failed")]
396pub async fn list_failed_payments(
397 state: web::Data<AppState>,
398 user: AuthenticatedUser,
399) -> impl Responder {
400 let organization_id = match user.require_organization() {
401 Ok(org_id) => org_id,
402 Err(e) => {
403 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
404 }
405 };
406
407 match state
408 .payment_use_cases
409 .list_failed_payments(organization_id)
410 .await
411 {
412 Ok(payments) => HttpResponse::Ok().json(payments),
413 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
414 }
415}
416
417#[utoipa::path(
420 put,
421 path = "/payments/{id}/processing",
422 tag = "Payments",
423 summary = "Mark a payment as processing",
424 params(("id" = Uuid, Path, description = "Payment ID")),
425 responses(
426 (status = 200, description = "Payment marked as processing"),
427 (status = 400, description = "Invalid state transition"),
428 (status = 401, description = "Unauthorized"),
429 ),
430 security(("bearer_auth" = []))
431)]
432#[put("/payments/{id}/processing")]
433pub async fn mark_payment_processing(
434 state: web::Data<AppState>,
435 user: AuthenticatedUser,
436 id: web::Path<Uuid>,
437) -> impl Responder {
438 let organization_id = match user.require_organization() {
439 Ok(org_id) => org_id,
440 Err(e) => {
441 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
442 }
443 };
444
445 match state.payment_use_cases.mark_processing(*id).await {
446 Ok(payment) => {
447 AuditLogEntry::new(
448 AuditEventType::PaymentProcessing,
449 Some(user.user_id),
450 Some(organization_id),
451 )
452 .with_resource("Payment", payment.id)
453 .log();
454
455 HttpResponse::Ok().json(payment)
456 }
457 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
458 }
459}
460
461#[utoipa::path(
462 put,
463 path = "/payments/{id}/requires-action",
464 tag = "Payments",
465 summary = "Mark a payment as requiring action",
466 params(("id" = Uuid, Path, description = "Payment ID")),
467 responses(
468 (status = 200, description = "Payment marked as requires action"),
469 (status = 400, description = "Invalid state transition"),
470 (status = 401, description = "Unauthorized"),
471 ),
472 security(("bearer_auth" = []))
473)]
474#[put("/payments/{id}/requires-action")]
475pub async fn mark_payment_requires_action(
476 state: web::Data<AppState>,
477 user: AuthenticatedUser,
478 id: web::Path<Uuid>,
479) -> impl Responder {
480 let organization_id = match user.require_organization() {
481 Ok(org_id) => org_id,
482 Err(e) => {
483 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
484 }
485 };
486
487 match state.payment_use_cases.mark_requires_action(*id).await {
488 Ok(payment) => {
489 AuditLogEntry::new(
490 AuditEventType::PaymentRequiresAction,
491 Some(user.user_id),
492 Some(organization_id),
493 )
494 .with_resource("Payment", payment.id)
495 .log();
496
497 HttpResponse::Ok().json(payment)
498 }
499 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
500 }
501}
502
503#[utoipa::path(
504 put,
505 path = "/payments/{id}/succeeded",
506 tag = "Payments",
507 summary = "Mark a payment as succeeded",
508 params(("id" = Uuid, Path, description = "Payment ID")),
509 responses(
510 (status = 200, description = "Payment marked as succeeded"),
511 (status = 400, description = "Invalid state transition"),
512 (status = 401, description = "Unauthorized"),
513 ),
514 security(("bearer_auth" = []))
515)]
516#[put("/payments/{id}/succeeded")]
517pub async fn mark_payment_succeeded(
518 state: web::Data<AppState>,
519 user: AuthenticatedUser,
520 id: web::Path<Uuid>,
521) -> impl Responder {
522 let organization_id = match user.require_organization() {
523 Ok(org_id) => org_id,
524 Err(e) => {
525 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
526 }
527 };
528
529 match state.payment_use_cases.mark_succeeded(*id).await {
530 Ok(payment) => {
531 AuditLogEntry::new(
532 AuditEventType::PaymentSucceeded,
533 Some(user.user_id),
534 Some(organization_id),
535 )
536 .with_resource("Payment", payment.id)
537 .log();
538
539 HttpResponse::Ok().json(payment)
540 }
541 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
542 }
543}
544
545#[utoipa::path(
546 put,
547 path = "/payments/{id}/failed",
548 tag = "Payments",
549 summary = "Mark a payment as failed",
550 params(("id" = Uuid, Path, description = "Payment ID")),
551 request_body = inline(serde_json::Value),
552 responses(
553 (status = 200, description = "Payment marked as failed"),
554 (status = 400, description = "Invalid state transition"),
555 (status = 401, description = "Unauthorized"),
556 ),
557 security(("bearer_auth" = []))
558)]
559#[put("/payments/{id}/failed")]
560pub async fn mark_payment_failed(
561 state: web::Data<AppState>,
562 user: AuthenticatedUser,
563 id: web::Path<Uuid>,
564 request: web::Json<serde_json::Value>,
565) -> impl Responder {
566 let organization_id = match user.require_organization() {
567 Ok(org_id) => org_id,
568 Err(e) => {
569 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
570 }
571 };
572
573 let reason = request
574 .get("reason")
575 .and_then(|v| v.as_str())
576 .unwrap_or("Unknown failure reason")
577 .to_string();
578
579 match state.payment_use_cases.mark_failed(*id, reason).await {
580 Ok(payment) => {
581 AuditLogEntry::new(
582 AuditEventType::PaymentFailed,
583 Some(user.user_id),
584 Some(organization_id),
585 )
586 .with_resource("Payment", payment.id)
587 .log();
588
589 HttpResponse::Ok().json(payment)
590 }
591 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
592 }
593}
594
595#[utoipa::path(
596 put,
597 path = "/payments/{id}/cancelled",
598 tag = "Payments",
599 summary = "Mark a payment as cancelled",
600 params(("id" = Uuid, Path, description = "Payment ID")),
601 responses(
602 (status = 200, description = "Payment marked as cancelled"),
603 (status = 400, description = "Invalid state transition"),
604 (status = 401, description = "Unauthorized"),
605 ),
606 security(("bearer_auth" = []))
607)]
608#[put("/payments/{id}/cancelled")]
609pub async fn mark_payment_cancelled(
610 state: web::Data<AppState>,
611 user: AuthenticatedUser,
612 id: web::Path<Uuid>,
613) -> impl Responder {
614 let organization_id = match user.require_organization() {
615 Ok(org_id) => org_id,
616 Err(e) => {
617 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
618 }
619 };
620
621 match state.payment_use_cases.mark_cancelled(*id).await {
622 Ok(payment) => {
623 AuditLogEntry::new(
624 AuditEventType::PaymentCancelled,
625 Some(user.user_id),
626 Some(organization_id),
627 )
628 .with_resource("Payment", payment.id)
629 .log();
630
631 HttpResponse::Ok().json(payment)
632 }
633 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
634 }
635}
636
637#[utoipa::path(
638 post,
639 path = "/payments/{id}/refund",
640 tag = "Payments",
641 summary = "Refund a payment (partial or full)",
642 params(("id" = Uuid, Path, description = "Payment ID")),
643 request_body = RefundPaymentRequest,
644 responses(
645 (status = 200, description = "Payment refunded"),
646 (status = 400, description = "Refund not allowed or exceeds payment amount"),
647 (status = 401, description = "Unauthorized"),
648 ),
649 security(("bearer_auth" = []))
650)]
651#[post("/payments/{id}/refund")]
652pub async fn refund_payment(
653 state: web::Data<AppState>,
654 user: AuthenticatedUser,
655 id: web::Path<Uuid>,
656 request: web::Json<RefundPaymentRequest>,
657) -> impl Responder {
658 let organization_id = match user.require_organization() {
659 Ok(org_id) => org_id,
660 Err(e) => {
661 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
662 }
663 };
664
665 match state
666 .payment_use_cases
667 .refund_payment(*id, request.into_inner())
668 .await
669 {
670 Ok(payment) => {
671 AuditLogEntry::new(
672 AuditEventType::PaymentRefunded,
673 Some(user.user_id),
674 Some(organization_id),
675 )
676 .with_resource("Payment", payment.id)
677 .log();
678
679 HttpResponse::Ok().json(payment)
680 }
681 Err(err) => {
682 AuditLogEntry::new(
683 AuditEventType::PaymentRefunded,
684 Some(user.user_id),
685 Some(organization_id),
686 )
687 .with_error(err.clone())
688 .log();
689
690 HttpResponse::BadRequest().json(serde_json::json!({"error": err}))
691 }
692 }
693}
694
695#[utoipa::path(
696 delete,
697 path = "/payments/{id}",
698 tag = "Payments",
699 summary = "Delete a payment",
700 params(("id" = Uuid, Path, description = "Payment ID")),
701 responses(
702 (status = 204, description = "Payment deleted"),
703 (status = 401, description = "Unauthorized"),
704 (status = 404, description = "Payment not found"),
705 (status = 500, description = "Internal server error"),
706 ),
707 security(("bearer_auth" = []))
708)]
709#[delete("/payments/{id}")]
710pub async fn delete_payment(
711 state: web::Data<AppState>,
712 user: AuthenticatedUser,
713 id: web::Path<Uuid>,
714) -> impl Responder {
715 let organization_id = match user.require_organization() {
716 Ok(org_id) => org_id,
717 Err(e) => {
718 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
719 }
720 };
721
722 match state.payment_use_cases.delete_payment(*id).await {
723 Ok(true) => {
724 AuditLogEntry::new(
725 AuditEventType::PaymentDeleted,
726 Some(user.user_id),
727 Some(organization_id),
728 )
729 .with_resource("Payment", *id)
730 .log();
731
732 HttpResponse::NoContent().finish()
733 }
734 Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
735 "error": "Payment not found"
736 })),
737 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
738 }
739}
740
741#[utoipa::path(
744 get,
745 path = "/owners/{owner_id}/payments/stats",
746 tag = "Payments",
747 summary = "Get payment statistics for an owner",
748 params(("owner_id" = Uuid, Path, description = "Owner ID")),
749 responses(
750 (status = 200, description = "Owner payment statistics"),
751 (status = 500, description = "Internal server error"),
752 ),
753 security(("bearer_auth" = []))
754)]
755#[get("/owners/{owner_id}/payments/stats")]
756pub async fn get_owner_payment_stats(
757 state: web::Data<AppState>,
758 owner_id: web::Path<Uuid>,
759 user: AuthenticatedUser,
760) -> impl Responder {
761 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
765 &user,
766 *owner_id,
767 &state.owner_use_cases,
768 )
769 .await
770 {
771 return err.error_response();
772 }
773
774 match state
775 .payment_use_cases
776 .get_owner_payment_stats(*owner_id)
777 .await
778 {
779 Ok(stats) => HttpResponse::Ok().json(stats),
780 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
781 }
782}
783
784#[utoipa::path(
785 get,
786 path = "/buildings/{building_id}/payments/stats",
787 tag = "Payments",
788 summary = "Get payment statistics for a building",
789 params(("building_id" = Uuid, Path, description = "Building ID")),
790 responses(
791 (status = 200, description = "Building payment statistics"),
792 (status = 500, description = "Internal server error"),
793 ),
794 security(("bearer_auth" = []))
795)]
796#[get("/buildings/{building_id}/payments/stats")]
797pub async fn get_building_payment_stats(
798 state: web::Data<AppState>,
799 building_id: web::Path<Uuid>,
800 user: AuthenticatedUser,
801) -> impl Responder {
802 if let Err(err) =
806 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
807 &user,
808 *building_id,
809 &state.building_use_cases,
810 &state.acp_use_cases,
811 )
812 .await
813 {
814 return err.error_response();
815 }
816
817 match state
818 .payment_use_cases
819 .get_building_payment_stats(*building_id)
820 .await
821 {
822 Ok(stats) => HttpResponse::Ok().json(stats),
823 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
824 }
825}
826
827#[utoipa::path(
828 get,
829 path = "/expenses/{expense_id}/payments/total",
830 tag = "Payments",
831 summary = "Get total amount paid for an expense",
832 params(("expense_id" = Uuid, Path, description = "Expense ID")),
833 responses(
834 (status = 200, description = "Total paid amount in cents"),
835 (status = 500, description = "Internal server error"),
836 ),
837 security(("bearer_auth" = []))
838)]
839#[get("/expenses/{expense_id}/payments/total")]
840pub async fn get_expense_total_paid(
841 state: web::Data<AppState>,
842 expense_id: web::Path<Uuid>,
843 user: AuthenticatedUser,
844) -> impl Responder {
845 if let Err(err) =
848 crate::infrastructure::web::middleware::scope_guard::verify_expense_org_access(
849 &user,
850 *expense_id,
851 &state.expense_use_cases,
852 &state.building_use_cases,
853 &state.acp_use_cases,
854 )
855 .await
856 {
857 return err.error_response();
858 }
859
860 match state
861 .payment_use_cases
862 .get_total_paid_for_expense(*expense_id)
863 .await
864 {
865 Ok(total) => HttpResponse::Ok().json(serde_json::json!({
866 "expense_id": *expense_id,
867 "total_paid_cents": total
868 })),
869 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
870 }
871}
872
873#[utoipa::path(
874 get,
875 path = "/owners/{owner_id}/payments/total",
876 tag = "Payments",
877 summary = "Get total amount paid by an owner",
878 params(("owner_id" = Uuid, Path, description = "Owner ID")),
879 responses(
880 (status = 200, description = "Total paid amount in cents"),
881 (status = 500, description = "Internal server error"),
882 ),
883 security(("bearer_auth" = []))
884)]
885#[get("/owners/{owner_id}/payments/total")]
886pub async fn get_owner_total_paid(
887 state: web::Data<AppState>,
888 owner_id: web::Path<Uuid>,
889 user: AuthenticatedUser,
890) -> impl Responder {
891 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
895 &user,
896 *owner_id,
897 &state.owner_use_cases,
898 )
899 .await
900 {
901 return err.error_response();
902 }
903
904 match state
905 .payment_use_cases
906 .get_total_paid_by_owner(*owner_id)
907 .await
908 {
909 Ok(total) => HttpResponse::Ok().json(serde_json::json!({
910 "owner_id": *owner_id,
911 "total_paid_cents": total
912 })),
913 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
914 }
915}
916
917#[utoipa::path(
918 get,
919 path = "/buildings/{building_id}/payments/total",
920 tag = "Payments",
921 summary = "Get total amount paid for a building",
922 params(("building_id" = Uuid, Path, description = "Building ID")),
923 responses(
924 (status = 200, description = "Total paid amount in cents"),
925 (status = 500, description = "Internal server error"),
926 ),
927 security(("bearer_auth" = []))
928)]
929#[get("/buildings/{building_id}/payments/total")]
930pub async fn get_building_total_paid(
931 state: web::Data<AppState>,
932 building_id: web::Path<Uuid>,
933 user: AuthenticatedUser,
934) -> impl Responder {
935 if let Err(err) =
939 crate::infrastructure::web::middleware::scope_guard::verify_building_org_access(
940 &user,
941 *building_id,
942 &state.building_use_cases,
943 &state.acp_use_cases,
944 )
945 .await
946 {
947 return err.error_response();
948 }
949
950 match state
951 .payment_use_cases
952 .get_total_paid_for_building(*building_id)
953 .await
954 {
955 Ok(total) => HttpResponse::Ok().json(serde_json::json!({
956 "building_id": *building_id,
957 "total_paid_cents": total
958 })),
959 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
960 }
961}