koprogo_api/infrastructure/web/handlers/
payment_method_handlers.rs

1use crate::application::dto::{CreatePaymentMethodRequest, UpdatePaymentMethodRequest};
2use crate::domain::entities::payment_method::PaymentMethodType;
3use crate::infrastructure::audit::{AuditEventType, AuditLogEntry};
4use crate::infrastructure::web::{AppState, AuthenticatedUser};
5use actix_web::{delete, get, post, put, web, HttpResponse, Responder};
6use uuid::Uuid;
7
8// ==================== Payment Method CRUD Endpoints ====================
9
10#[post("/payment-methods")]
11pub async fn create_payment_method(
12    state: web::Data<AppState>,
13    user: AuthenticatedUser,
14    request: web::Json<CreatePaymentMethodRequest>,
15) -> impl Responder {
16    let organization_id = match user.require_organization() {
17        Ok(org_id) => org_id,
18        Err(e) => {
19            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
20        }
21    };
22
23    match state
24        .payment_method_use_cases
25        .create_payment_method(organization_id, request.into_inner())
26        .await
27    {
28        Ok(payment_method) => {
29            AuditLogEntry::new(
30                AuditEventType::PaymentMethodCreated,
31                Some(user.user_id),
32                Some(organization_id),
33            )
34            .with_resource("PaymentMethod", payment_method.id)
35            .log();
36
37            HttpResponse::Created().json(payment_method)
38        }
39        Err(err) => {
40            AuditLogEntry::new(
41                AuditEventType::PaymentMethodCreated,
42                Some(user.user_id),
43                Some(organization_id),
44            )
45            .with_error(err.clone())
46            .log();
47
48            HttpResponse::BadRequest().json(serde_json::json!({"error": err}))
49        }
50    }
51}
52
53#[get("/payment-methods/{id}")]
54pub async fn get_payment_method(
55    state: web::Data<AppState>,
56    user: AuthenticatedUser,
57    id: web::Path<Uuid>,
58) -> impl Responder {
59    match state.payment_method_use_cases.get_payment_method(*id).await {
60        Ok(Some(method)) => {
61            // Verify organization access
62            if let Err(err) = user.verify_org_access(method.organization_id) {
63                return HttpResponse::Forbidden().json(serde_json::json!({"error": err}));
64            }
65            HttpResponse::Ok().json(method)
66        }
67        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
68            "error": "Payment method not found"
69        })),
70        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
71    }
72}
73
74#[get("/payment-methods/stripe/{stripe_payment_method_id}")]
75pub async fn get_payment_method_by_stripe_id(
76    state: web::Data<AppState>,
77    stripe_payment_method_id: web::Path<String>,
78) -> impl Responder {
79    match state
80        .payment_method_use_cases
81        .get_payment_method_by_stripe_id(&stripe_payment_method_id)
82        .await
83    {
84        Ok(Some(method)) => HttpResponse::Ok().json(method),
85        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
86            "error": "Payment method not found"
87        })),
88        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
89    }
90}
91
92#[get("/owners/{owner_id}/payment-methods")]
93pub async fn list_owner_payment_methods(
94    state: web::Data<AppState>,
95    owner_id: web::Path<Uuid>,
96) -> impl Responder {
97    match state
98        .payment_method_use_cases
99        .list_owner_payment_methods(*owner_id)
100        .await
101    {
102        Ok(methods) => HttpResponse::Ok().json(methods),
103        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
104    }
105}
106
107#[get("/owners/{owner_id}/payment-methods/active")]
108pub async fn list_active_owner_payment_methods(
109    state: web::Data<AppState>,
110    owner_id: web::Path<Uuid>,
111) -> impl Responder {
112    match state
113        .payment_method_use_cases
114        .list_active_owner_payment_methods(*owner_id)
115        .await
116    {
117        Ok(methods) => HttpResponse::Ok().json(methods),
118        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
119    }
120}
121
122#[get("/owners/{owner_id}/payment-methods/default")]
123pub async fn get_default_payment_method(
124    state: web::Data<AppState>,
125    owner_id: web::Path<Uuid>,
126) -> impl Responder {
127    match state
128        .payment_method_use_cases
129        .get_default_payment_method(*owner_id)
130        .await
131    {
132        Ok(Some(method)) => HttpResponse::Ok().json(method),
133        Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
134            "error": "No default payment method found for owner"
135        })),
136        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
137    }
138}
139
140#[get("/organizations/{organization_id}/payment-methods")]
141pub async fn list_organization_payment_methods(
142    state: web::Data<AppState>,
143    user: AuthenticatedUser,
144    organization_id: web::Path<Uuid>,
145) -> impl Responder {
146    if let Err(e) = user.verify_org_access(*organization_id) {
147        return HttpResponse::Forbidden().json(serde_json::json!({"error": e}));
148    }
149    match state
150        .payment_method_use_cases
151        .list_organization_payment_methods(*organization_id)
152        .await
153    {
154        Ok(methods) => HttpResponse::Ok().json(methods),
155        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
156    }
157}
158
159#[get("/owners/{owner_id}/payment-methods/type/{method_type}")]
160pub async fn list_payment_methods_by_type(
161    state: web::Data<AppState>,
162    path: web::Path<(Uuid, String)>,
163) -> impl Responder {
164    let (owner_id, method_type_str) = path.into_inner();
165
166    // Parse method type string to enum
167    let method_type = match method_type_str.as_str() {
168        "card" => PaymentMethodType::Card,
169        "sepa_debit" => PaymentMethodType::SepaDebit,
170        _ => {
171            return HttpResponse::BadRequest().json(serde_json::json!({
172                "error": "Invalid payment method type. Must be one of: card, sepa_debit"
173            }))
174        }
175    };
176
177    match state
178        .payment_method_use_cases
179        .list_payment_methods_by_type(owner_id, method_type)
180        .await
181    {
182        Ok(methods) => HttpResponse::Ok().json(methods),
183        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
184    }
185}
186
187#[put("/payment-methods/{id}")]
188pub async fn update_payment_method(
189    state: web::Data<AppState>,
190    user: AuthenticatedUser,
191    id: web::Path<Uuid>,
192    request: web::Json<UpdatePaymentMethodRequest>,
193) -> impl Responder {
194    let organization_id = match user.require_organization() {
195        Ok(org_id) => org_id,
196        Err(e) => {
197            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
198        }
199    };
200
201    match state
202        .payment_method_use_cases
203        .update_payment_method(*id, request.into_inner())
204        .await
205    {
206        Ok(method) => {
207            AuditLogEntry::new(
208                AuditEventType::PaymentMethodUpdated,
209                Some(user.user_id),
210                Some(organization_id),
211            )
212            .with_resource("PaymentMethod", method.id)
213            .log();
214
215            HttpResponse::Ok().json(method)
216        }
217        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
218    }
219}
220
221#[put("/payment-methods/{id}/set-default")]
222pub async fn set_payment_method_as_default(
223    state: web::Data<AppState>,
224    user: AuthenticatedUser,
225    id: web::Path<Uuid>,
226    owner_id_json: web::Json<serde_json::Value>,
227) -> impl Responder {
228    let organization_id = match user.require_organization() {
229        Ok(org_id) => org_id,
230        Err(e) => {
231            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
232        }
233    };
234
235    let owner_id = match owner_id_json.get("owner_id").and_then(|v| v.as_str()) {
236        Some(id_str) => match Uuid::parse_str(id_str) {
237            Ok(id) => id,
238            Err(_) => {
239                return HttpResponse::BadRequest().json(serde_json::json!({
240                    "error": "Invalid owner_id format"
241                }))
242            }
243        },
244        None => {
245            return HttpResponse::BadRequest().json(serde_json::json!({
246                "error": "owner_id is required"
247            }))
248        }
249    };
250
251    match state
252        .payment_method_use_cases
253        .set_as_default(*id, owner_id)
254        .await
255    {
256        Ok(method) => {
257            AuditLogEntry::new(
258                AuditEventType::PaymentMethodSetDefault,
259                Some(user.user_id),
260                Some(organization_id),
261            )
262            .with_resource("PaymentMethod", method.id)
263            .log();
264
265            HttpResponse::Ok().json(method)
266        }
267        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
268    }
269}
270
271#[put("/payment-methods/{id}/deactivate")]
272pub async fn deactivate_payment_method(
273    state: web::Data<AppState>,
274    user: AuthenticatedUser,
275    id: web::Path<Uuid>,
276) -> impl Responder {
277    let organization_id = match user.require_organization() {
278        Ok(org_id) => org_id,
279        Err(e) => {
280            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
281        }
282    };
283
284    match state
285        .payment_method_use_cases
286        .deactivate_payment_method(*id)
287        .await
288    {
289        Ok(method) => {
290            AuditLogEntry::new(
291                AuditEventType::PaymentMethodDeactivated,
292                Some(user.user_id),
293                Some(organization_id),
294            )
295            .with_resource("PaymentMethod", method.id)
296            .log();
297
298            HttpResponse::Ok().json(method)
299        }
300        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
301    }
302}
303
304#[put("/payment-methods/{id}/reactivate")]
305pub async fn reactivate_payment_method(
306    state: web::Data<AppState>,
307    user: AuthenticatedUser,
308    id: web::Path<Uuid>,
309) -> impl Responder {
310    let organization_id = match user.require_organization() {
311        Ok(org_id) => org_id,
312        Err(e) => {
313            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
314        }
315    };
316
317    match state
318        .payment_method_use_cases
319        .reactivate_payment_method(*id)
320        .await
321    {
322        Ok(method) => {
323            AuditLogEntry::new(
324                AuditEventType::PaymentMethodReactivated,
325                Some(user.user_id),
326                Some(organization_id),
327            )
328            .with_resource("PaymentMethod", method.id)
329            .log();
330
331            HttpResponse::Ok().json(method)
332        }
333        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
334    }
335}
336
337#[delete("/payment-methods/{id}")]
338pub async fn delete_payment_method(
339    state: web::Data<AppState>,
340    user: AuthenticatedUser,
341    id: web::Path<Uuid>,
342) -> impl Responder {
343    let organization_id = match user.require_organization() {
344        Ok(org_id) => org_id,
345        Err(e) => {
346            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
347        }
348    };
349
350    match state
351        .payment_method_use_cases
352        .delete_payment_method(*id)
353        .await
354    {
355        Ok(true) => {
356            AuditLogEntry::new(
357                AuditEventType::PaymentMethodDeleted,
358                Some(user.user_id),
359                Some(organization_id),
360            )
361            .with_resource("PaymentMethod", *id)
362            .log();
363
364            HttpResponse::NoContent().finish()
365        }
366        Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
367            "error": "Payment method not found"
368        })),
369        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
370    }
371}
372
373// ==================== Payment Method Statistics Endpoints ====================
374
375#[get("/owners/{owner_id}/payment-methods/count")]
376pub async fn count_active_payment_methods(
377    state: web::Data<AppState>,
378    owner_id: web::Path<Uuid>,
379) -> impl Responder {
380    match state
381        .payment_method_use_cases
382        .count_active_payment_methods(*owner_id)
383        .await
384    {
385        Ok(count) => HttpResponse::Ok().json(serde_json::json!({
386            "owner_id": *owner_id,
387            "active_payment_methods_count": count
388        })),
389        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
390    }
391}
392
393#[get("/owners/{owner_id}/payment-methods/has-active")]
394pub async fn has_active_payment_methods(
395    state: web::Data<AppState>,
396    owner_id: web::Path<Uuid>,
397) -> impl Responder {
398    match state
399        .payment_method_use_cases
400        .has_active_payment_methods(*owner_id)
401        .await
402    {
403        Ok(has_active) => HttpResponse::Ok().json(serde_json::json!({
404            "owner_id": *owner_id,
405            "has_active_payment_methods": has_active
406        })),
407        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
408    }
409}