1use crate::application::dto::{
2 CreatePaymentMethodRequest, PaymentMethodResponse, UpdatePaymentMethodRequest,
3};
4use crate::domain::entities::payment_method::PaymentMethodType;
5use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
6use crate::infrastructure::web::{AppState, AuthenticatedUser};
7use actix_web::{delete, get, post, put, web, HttpResponse, Responder, ResponseError};
8use uuid::Uuid;
9
10#[utoipa::path(
13 post,
14 path = "/payment-methods",
15 tag = "PaymentMethods",
16 summary = "Enregistrer un moyen de paiement pour un copropriétaire",
17 request_body = CreatePaymentMethodRequest,
18 responses(
19 (status = 201, description = "Moyen de paiement créé", body = PaymentMethodResponse),
20 (status = 400, description = "Requête invalide — champs manquants ou moyen déjà enregistré"),
21 (status = 401, description = "L'utilisateur n'appartient à aucune organisation"),
22 ),
23 security(("bearer_auth" = []))
24)]
25#[post("/payment-methods")]
26pub async fn create_payment_method(
27 state: web::Data<AppState>,
28 user: AuthenticatedUser,
29 request: web::Json<CreatePaymentMethodRequest>,
30) -> impl Responder {
31 let organization_id = match user.require_organization() {
32 Ok(org_id) => org_id,
33 Err(e) => {
34 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
35 }
36 };
37
38 match state
39 .payment_method_use_cases
40 .create_payment_method(organization_id, request.into_inner())
41 .await
42 {
43 Ok(payment_method) => {
44 AuditLogEntry::new(
45 AuditEventType::PaymentMethodCreated,
46 Some(user.user_id),
47 Some(organization_id),
48 )
49 .with_resource("PaymentMethod", payment_method.id)
50 .log();
51
52 HttpResponse::Created().json(payment_method)
53 }
54 Err(err) => {
55 AuditLogEntry::new(
56 AuditEventType::PaymentMethodCreated,
57 Some(user.user_id),
58 Some(organization_id),
59 )
60 .with_error(err.clone())
61 .log();
62
63 HttpResponse::BadRequest().json(serde_json::json!({"error": err}))
64 }
65 }
66}
67
68#[utoipa::path(
69 get,
70 path = "/payment-methods/{id}",
71 tag = "PaymentMethods",
72 summary = "Récupérer un moyen de paiement",
73 params(("id" = Uuid, Path, description = "Identifiant du moyen de paiement")),
74 responses(
75 (status = 200, description = "Moyen de paiement", body = PaymentMethodResponse),
76 (status = 404, description = "Introuvable"),
77 ),
78 security(("bearer_auth" = []))
79)]
80#[get("/payment-methods/{id}")]
81pub async fn get_payment_method(
82 state: web::Data<AppState>,
83 user: AuthenticatedUser,
84 id: web::Path<Uuid>,
85) -> impl Responder {
86 match state.payment_method_use_cases.get_payment_method(*id).await {
87 Ok(Some(method)) => {
88 if let Err(err) = user.verify_org_access(method.organization_id) {
90 return HttpResponse::Forbidden().json(serde_json::json!({"error": err}));
91 }
92 HttpResponse::Ok().json(method)
93 }
94 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
95 "error": "Payment method not found"
96 })),
97 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
98 }
99}
100
101#[utoipa::path(
102 get,
103 path = "/payment-methods/stripe/{stripe_payment_method_id}",
104 tag = "PaymentMethods",
105 summary = "Récupérer un moyen de paiement par son identifiant Stripe",
106 params(("stripe_payment_method_id" = String, Path, description = "Identifiant Stripe")),
107 responses(
108 (status = 200, description = "Moyen de paiement", body = PaymentMethodResponse),
109 (status = 404, description = "Introuvable"),
110 ),
111 security(("bearer_auth" = []))
112)]
113#[get("/payment-methods/stripe/{stripe_payment_method_id}")]
114pub async fn get_payment_method_by_stripe_id(
115 state: web::Data<AppState>,
116 user: AuthenticatedUser,
117 stripe_payment_method_id: web::Path<String>,
118) -> impl Responder {
119 match state
120 .payment_method_use_cases
121 .get_payment_method_by_stripe_id(&stripe_payment_method_id)
122 .await
123 {
124 Ok(Some(method)) => {
125 if let Err(e) = user.verify_org_access(method.organization_id) {
131 return HttpResponse::Forbidden().json(serde_json::json!({ "error": e }));
132 }
133 HttpResponse::Ok().json(method)
134 }
135 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
136 "error": "Payment method 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}/payment-methods",
145 tag = "PaymentMethods",
146 summary = "Lister les moyens de paiement d'un copropriétaire",
147 params(("owner_id" = Uuid, Path, description = "Identifiant du copropriétaire")),
148 responses(
149 (status = 200, description = "Liste des moyens de paiement", body = Vec<PaymentMethodResponse>),
150 ),
151 security(("bearer_auth" = []))
152)]
153#[get("/owners/{owner_id}/payment-methods")]
154pub async fn list_owner_payment_methods(
155 state: web::Data<AppState>,
156 owner_id: web::Path<Uuid>,
157 user: AuthenticatedUser,
158) -> impl Responder {
159 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
163 &user,
164 *owner_id,
165 &state.owner_use_cases,
166 )
167 .await
168 {
169 return err.error_response();
170 }
171
172 match state
173 .payment_method_use_cases
174 .list_owner_payment_methods(*owner_id)
175 .await
176 {
177 Ok(methods) => HttpResponse::Ok().json(methods),
178 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
179 }
180}
181
182#[utoipa::path(
183 get,
184 path = "/owners/{owner_id}/payment-methods/active",
185 tag = "PaymentMethods",
186 summary = "Lister les moyens de paiement actifs d'un copropriétaire",
187 params(("owner_id" = Uuid, Path, description = "Identifiant du copropriétaire")),
188 responses(
189 (status = 200, description = "Liste des moyens actifs", body = Vec<PaymentMethodResponse>),
190 ),
191 security(("bearer_auth" = []))
192)]
193#[get("/owners/{owner_id}/payment-methods/active")]
194pub async fn list_active_owner_payment_methods(
195 state: web::Data<AppState>,
196 owner_id: web::Path<Uuid>,
197 user: AuthenticatedUser,
198) -> impl Responder {
199 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
203 &user,
204 *owner_id,
205 &state.owner_use_cases,
206 )
207 .await
208 {
209 return err.error_response();
210 }
211
212 match state
213 .payment_method_use_cases
214 .list_active_owner_payment_methods(*owner_id)
215 .await
216 {
217 Ok(methods) => HttpResponse::Ok().json(methods),
218 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
219 }
220}
221
222#[utoipa::path(
223 get,
224 path = "/owners/{owner_id}/payment-methods/default",
225 tag = "PaymentMethods",
226 summary = "Récupérer le moyen de paiement par défaut d'un copropriétaire",
227 params(("owner_id" = Uuid, Path, description = "Identifiant du copropriétaire")),
228 responses(
229 (status = 200, description = "Moyen par défaut", body = PaymentMethodResponse),
230 (status = 404, description = "Aucun moyen par défaut"),
231 ),
232 security(("bearer_auth" = []))
233)]
234#[get("/owners/{owner_id}/payment-methods/default")]
235pub async fn get_default_payment_method(
236 state: web::Data<AppState>,
237 owner_id: web::Path<Uuid>,
238 user: AuthenticatedUser,
239) -> impl Responder {
240 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
244 &user,
245 *owner_id,
246 &state.owner_use_cases,
247 )
248 .await
249 {
250 return err.error_response();
251 }
252
253 match state
254 .payment_method_use_cases
255 .get_default_payment_method(*owner_id)
256 .await
257 {
258 Ok(Some(method)) => HttpResponse::Ok().json(method),
259 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
260 "error": "No default payment method found for owner"
261 })),
262 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
263 }
264}
265
266#[utoipa::path(
267 get,
268 path = "/organizations/{organization_id}/payment-methods",
269 tag = "PaymentMethods",
270 summary = "Lister les moyens de paiement d'une organisation",
271 params(("organization_id" = Uuid, Path, description = "Identifiant de l'organisation")),
272 responses(
273 (status = 200, description = "Liste des moyens de paiement", body = Vec<PaymentMethodResponse>),
274 ),
275 security(("bearer_auth" = []))
276)]
277#[get("/organizations/{organization_id}/payment-methods")]
278pub async fn list_organization_payment_methods(
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_method_use_cases
288 .list_organization_payment_methods(*organization_id)
289 .await
290 {
291 Ok(methods) => HttpResponse::Ok().json(methods),
292 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
293 }
294}
295
296#[utoipa::path(
297 get,
298 path = "/owners/{owner_id}/payment-methods/type/{method_type}",
299 tag = "PaymentMethods",
300 summary = "Lister les moyens de paiement d'un copropriétaire par type",
301 params(
302 ("owner_id" = Uuid, Path, description = "Identifiant du copropriétaire"),
303 ("method_type" = String, Path, description = "Type de moyen : card, sepa_debit, bancontact"),
304 ),
305 responses(
306 (status = 200, description = "Liste filtrée", body = Vec<PaymentMethodResponse>),
307 (status = 400, description = "Type de moyen inconnu"),
308 ),
309 security(("bearer_auth" = []))
310)]
311#[get("/owners/{owner_id}/payment-methods/type/{method_type}")]
312pub async fn list_payment_methods_by_type(
313 state: web::Data<AppState>,
314 path: web::Path<(Uuid, String)>,
315 user: AuthenticatedUser,
316) -> impl Responder {
317 let (owner_id, method_type_str) = path.into_inner();
318 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
322 &user,
323 owner_id,
324 &state.owner_use_cases,
325 )
326 .await
327 {
328 return err.error_response();
329 }
330
331 let method_type = match method_type_str.as_str() {
333 "card" => PaymentMethodType::Card,
334 "sepa_debit" => PaymentMethodType::SepaDebit,
335 _ => {
336 return HttpResponse::BadRequest().json(serde_json::json!({
337 "error": "Invalid payment method type. Must be one of: card, sepa_debit"
338 }))
339 }
340 };
341
342 match state
343 .payment_method_use_cases
344 .list_payment_methods_by_type(owner_id, method_type)
345 .await
346 {
347 Ok(methods) => HttpResponse::Ok().json(methods),
348 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
349 }
350}
351
352#[utoipa::path(
353 put,
354 path = "/payment-methods/{id}",
355 tag = "PaymentMethods",
356 summary = "Mettre à jour un moyen de paiement",
357 params(("id" = Uuid, Path, description = "Identifiant du moyen de paiement")),
358 request_body = UpdatePaymentMethodRequest,
359 responses(
360 (status = 200, description = "Moyen mis à jour", body = PaymentMethodResponse),
361 (status = 400, description = "Requête invalide"),
362 (status = 404, description = "Introuvable"),
363 ),
364 security(("bearer_auth" = []))
365)]
366#[put("/payment-methods/{id}")]
367pub async fn update_payment_method(
368 state: web::Data<AppState>,
369 user: AuthenticatedUser,
370 id: web::Path<Uuid>,
371 request: web::Json<UpdatePaymentMethodRequest>,
372) -> impl Responder {
373 let organization_id = match user.require_organization() {
374 Ok(org_id) => org_id,
375 Err(e) => {
376 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
377 }
378 };
379
380 match state
381 .payment_method_use_cases
382 .update_payment_method(*id, request.into_inner())
383 .await
384 {
385 Ok(method) => {
386 AuditLogEntry::new(
387 AuditEventType::PaymentMethodUpdated,
388 Some(user.user_id),
389 Some(organization_id),
390 )
391 .with_resource("PaymentMethod", method.id)
392 .log();
393
394 HttpResponse::Ok().json(method)
395 }
396 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
397 }
398}
399
400#[utoipa::path(
401 put,
402 path = "/payment-methods/{id}/set-default",
403 tag = "PaymentMethods",
404 summary = "Désigner un moyen de paiement comme moyen par défaut",
405 params(("id" = Uuid, Path, description = "Identifiant du moyen de paiement")),
406 responses(
407 (status = 200, description = "Moyen désigné par défaut", body = PaymentMethodResponse),
408 (status = 404, description = "Introuvable"),
409 ),
410 security(("bearer_auth" = []))
411)]
412#[put("/payment-methods/{id}/set-default")]
413pub async fn set_payment_method_as_default(
414 state: web::Data<AppState>,
415 user: AuthenticatedUser,
416 id: web::Path<Uuid>,
417 owner_id_json: web::Json<serde_json::Value>,
418) -> impl Responder {
419 let organization_id = match user.require_organization() {
420 Ok(org_id) => org_id,
421 Err(e) => {
422 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
423 }
424 };
425
426 let owner_id = match owner_id_json.get("owner_id").and_then(|v| v.as_str()) {
427 Some(id_str) => match Uuid::parse_str(id_str) {
428 Ok(id) => id,
429 Err(_) => {
430 return HttpResponse::BadRequest().json(serde_json::json!({
431 "error": "Invalid owner_id format"
432 }))
433 }
434 },
435 None => {
436 return HttpResponse::BadRequest().json(serde_json::json!({
437 "error": "owner_id is required"
438 }))
439 }
440 };
441
442 match state
443 .payment_method_use_cases
444 .set_as_default(*id, owner_id)
445 .await
446 {
447 Ok(method) => {
448 AuditLogEntry::new(
449 AuditEventType::PaymentMethodSetDefault,
450 Some(user.user_id),
451 Some(organization_id),
452 )
453 .with_resource("PaymentMethod", method.id)
454 .log();
455
456 HttpResponse::Ok().json(method)
457 }
458 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
459 }
460}
461
462#[utoipa::path(
463 put,
464 path = "/payment-methods/{id}/deactivate",
465 tag = "PaymentMethods",
466 summary = "Désactiver un moyen de paiement",
467 params(("id" = Uuid, Path, description = "Identifiant du moyen de paiement")),
468 responses(
469 (status = 200, description = "Moyen désactivé", body = PaymentMethodResponse),
470 (status = 404, description = "Introuvable"),
471 ),
472 security(("bearer_auth" = []))
473)]
474#[put("/payment-methods/{id}/deactivate")]
475pub async fn deactivate_payment_method(
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
488 .payment_method_use_cases
489 .deactivate_payment_method(*id)
490 .await
491 {
492 Ok(method) => {
493 AuditLogEntry::new(
494 AuditEventType::PaymentMethodDeactivated,
495 Some(user.user_id),
496 Some(organization_id),
497 )
498 .with_resource("PaymentMethod", method.id)
499 .log();
500
501 HttpResponse::Ok().json(method)
502 }
503 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
504 }
505}
506
507#[utoipa::path(
508 put,
509 path = "/payment-methods/{id}/reactivate",
510 tag = "PaymentMethods",
511 summary = "Réactiver un moyen de paiement",
512 params(("id" = Uuid, Path, description = "Identifiant du moyen de paiement")),
513 responses(
514 (status = 200, description = "Moyen réactivé", body = PaymentMethodResponse),
515 (status = 404, description = "Introuvable"),
516 ),
517 security(("bearer_auth" = []))
518)]
519#[put("/payment-methods/{id}/reactivate")]
520pub async fn reactivate_payment_method(
521 state: web::Data<AppState>,
522 user: AuthenticatedUser,
523 id: web::Path<Uuid>,
524) -> impl Responder {
525 let organization_id = match user.require_organization() {
526 Ok(org_id) => org_id,
527 Err(e) => {
528 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
529 }
530 };
531
532 match state
533 .payment_method_use_cases
534 .reactivate_payment_method(*id)
535 .await
536 {
537 Ok(method) => {
538 AuditLogEntry::new(
539 AuditEventType::PaymentMethodReactivated,
540 Some(user.user_id),
541 Some(organization_id),
542 )
543 .with_resource("PaymentMethod", method.id)
544 .log();
545
546 HttpResponse::Ok().json(method)
547 }
548 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
549 }
550}
551
552#[utoipa::path(
553 delete,
554 path = "/payment-methods/{id}",
555 tag = "PaymentMethods",
556 summary = "Supprimer un moyen de paiement",
557 params(("id" = Uuid, Path, description = "Identifiant du moyen de paiement")),
558 responses(
559 (status = 204, description = "Moyen supprimé"),
560 (status = 404, description = "Introuvable"),
561 ),
562 security(("bearer_auth" = []))
563)]
564#[delete("/payment-methods/{id}")]
565pub async fn delete_payment_method(
566 state: web::Data<AppState>,
567 user: AuthenticatedUser,
568 id: web::Path<Uuid>,
569) -> impl Responder {
570 let organization_id = match user.require_organization() {
571 Ok(org_id) => org_id,
572 Err(e) => {
573 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
574 }
575 };
576
577 match state
578 .payment_method_use_cases
579 .delete_payment_method(*id)
580 .await
581 {
582 Ok(true) => {
583 AuditLogEntry::new(
584 AuditEventType::PaymentMethodDeleted,
585 Some(user.user_id),
586 Some(organization_id),
587 )
588 .with_resource("PaymentMethod", *id)
589 .log();
590
591 HttpResponse::NoContent().finish()
592 }
593 Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
594 "error": "Payment method not found"
595 })),
596 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
597 }
598}
599
600#[utoipa::path(
603 get,
604 path = "/owners/{owner_id}/payment-methods/count",
605 tag = "PaymentMethods",
606 summary = "Compter les moyens de paiement actifs d'un copropriétaire",
607 params(("owner_id" = Uuid, Path, description = "Identifiant du copropriétaire")),
608 responses(
609 (status = 200, description = "Nombre de moyens actifs"),
610 ),
611 security(("bearer_auth" = []))
612)]
613#[get("/owners/{owner_id}/payment-methods/count")]
614pub async fn count_active_payment_methods(
615 state: web::Data<AppState>,
616 owner_id: web::Path<Uuid>,
617 user: AuthenticatedUser,
618) -> impl Responder {
619 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
623 &user,
624 *owner_id,
625 &state.owner_use_cases,
626 )
627 .await
628 {
629 return err.error_response();
630 }
631
632 match state
633 .payment_method_use_cases
634 .count_active_payment_methods(*owner_id)
635 .await
636 {
637 Ok(count) => HttpResponse::Ok().json(serde_json::json!({
638 "owner_id": *owner_id,
639 "active_payment_methods_count": count
640 })),
641 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
642 }
643}
644
645#[utoipa::path(
646 get,
647 path = "/owners/{owner_id}/payment-methods/has-active",
648 tag = "PaymentMethods",
649 summary = "Indiquer si un copropriétaire a au moins un moyen de paiement actif",
650 params(("owner_id" = Uuid, Path, description = "Identifiant du copropriétaire")),
651 responses(
652 (status = 200, description = "Présence d'un moyen actif"),
653 ),
654 security(("bearer_auth" = []))
655)]
656#[get("/owners/{owner_id}/payment-methods/has-active")]
657pub async fn has_active_payment_methods(
658 state: web::Data<AppState>,
659 owner_id: web::Path<Uuid>,
660 user: AuthenticatedUser,
661) -> impl Responder {
662 if let Err(err) = crate::infrastructure::web::middleware::scope_guard::verify_owner_org_access(
666 &user,
667 *owner_id,
668 &state.owner_use_cases,
669 )
670 .await
671 {
672 return err.error_response();
673 }
674
675 match state
676 .payment_method_use_cases
677 .has_active_payment_methods(*owner_id)
678 .await
679 {
680 Ok(has_active) => HttpResponse::Ok().json(serde_json::json!({
681 "owner_id": *owner_id,
682 "has_active_payment_methods": has_active
683 })),
684 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
685 }
686}