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    organization_id: web::Path<Uuid>,
144) -> impl Responder {
145    match state
146        .payment_method_use_cases
147        .list_organization_payment_methods(*organization_id)
148        .await
149    {
150        Ok(methods) => HttpResponse::Ok().json(methods),
151        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
152    }
153}
154
155#[get("/owners/{owner_id}/payment-methods/type/{method_type}")]
156pub async fn list_payment_methods_by_type(
157    state: web::Data<AppState>,
158    path: web::Path<(Uuid, String)>,
159) -> impl Responder {
160    let (owner_id, method_type_str) = path.into_inner();
161
162    // Parse method type string to enum
163    let method_type = match method_type_str.as_str() {
164        "card" => PaymentMethodType::Card,
165        "sepa_debit" => PaymentMethodType::SepaDebit,
166        _ => {
167            return HttpResponse::BadRequest().json(serde_json::json!({
168                "error": "Invalid payment method type. Must be one of: card, sepa_debit"
169            }))
170        }
171    };
172
173    match state
174        .payment_method_use_cases
175        .list_payment_methods_by_type(owner_id, method_type)
176        .await
177    {
178        Ok(methods) => HttpResponse::Ok().json(methods),
179        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
180    }
181}
182
183#[put("/payment-methods/{id}")]
184pub async fn update_payment_method(
185    state: web::Data<AppState>,
186    user: AuthenticatedUser,
187    id: web::Path<Uuid>,
188    request: web::Json<UpdatePaymentMethodRequest>,
189) -> impl Responder {
190    let organization_id = match user.require_organization() {
191        Ok(org_id) => org_id,
192        Err(e) => {
193            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
194        }
195    };
196
197    match state
198        .payment_method_use_cases
199        .update_payment_method(*id, request.into_inner())
200        .await
201    {
202        Ok(method) => {
203            AuditLogEntry::new(
204                AuditEventType::PaymentMethodUpdated,
205                Some(user.user_id),
206                Some(organization_id),
207            )
208            .with_resource("PaymentMethod", method.id)
209            .log();
210
211            HttpResponse::Ok().json(method)
212        }
213        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
214    }
215}
216
217#[put("/payment-methods/{id}/set-default")]
218pub async fn set_payment_method_as_default(
219    state: web::Data<AppState>,
220    user: AuthenticatedUser,
221    id: web::Path<Uuid>,
222    owner_id_json: web::Json<serde_json::Value>,
223) -> impl Responder {
224    let organization_id = match user.require_organization() {
225        Ok(org_id) => org_id,
226        Err(e) => {
227            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
228        }
229    };
230
231    let owner_id = match owner_id_json.get("owner_id").and_then(|v| v.as_str()) {
232        Some(id_str) => match Uuid::parse_str(id_str) {
233            Ok(id) => id,
234            Err(_) => {
235                return HttpResponse::BadRequest().json(serde_json::json!({
236                    "error": "Invalid owner_id format"
237                }))
238            }
239        },
240        None => {
241            return HttpResponse::BadRequest().json(serde_json::json!({
242                "error": "owner_id is required"
243            }))
244        }
245    };
246
247    match state
248        .payment_method_use_cases
249        .set_as_default(*id, owner_id)
250        .await
251    {
252        Ok(method) => {
253            AuditLogEntry::new(
254                AuditEventType::PaymentMethodSetDefault,
255                Some(user.user_id),
256                Some(organization_id),
257            )
258            .with_resource("PaymentMethod", method.id)
259            .log();
260
261            HttpResponse::Ok().json(method)
262        }
263        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
264    }
265}
266
267#[put("/payment-methods/{id}/deactivate")]
268pub async fn deactivate_payment_method(
269    state: web::Data<AppState>,
270    user: AuthenticatedUser,
271    id: web::Path<Uuid>,
272) -> impl Responder {
273    let organization_id = match user.require_organization() {
274        Ok(org_id) => org_id,
275        Err(e) => {
276            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
277        }
278    };
279
280    match state
281        .payment_method_use_cases
282        .deactivate_payment_method(*id)
283        .await
284    {
285        Ok(method) => {
286            AuditLogEntry::new(
287                AuditEventType::PaymentMethodDeactivated,
288                Some(user.user_id),
289                Some(organization_id),
290            )
291            .with_resource("PaymentMethod", method.id)
292            .log();
293
294            HttpResponse::Ok().json(method)
295        }
296        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
297    }
298}
299
300#[put("/payment-methods/{id}/reactivate")]
301pub async fn reactivate_payment_method(
302    state: web::Data<AppState>,
303    user: AuthenticatedUser,
304    id: web::Path<Uuid>,
305) -> impl Responder {
306    let organization_id = match user.require_organization() {
307        Ok(org_id) => org_id,
308        Err(e) => {
309            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
310        }
311    };
312
313    match state
314        .payment_method_use_cases
315        .reactivate_payment_method(*id)
316        .await
317    {
318        Ok(method) => {
319            AuditLogEntry::new(
320                AuditEventType::PaymentMethodReactivated,
321                Some(user.user_id),
322                Some(organization_id),
323            )
324            .with_resource("PaymentMethod", method.id)
325            .log();
326
327            HttpResponse::Ok().json(method)
328        }
329        Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
330    }
331}
332
333#[delete("/payment-methods/{id}")]
334pub async fn delete_payment_method(
335    state: web::Data<AppState>,
336    user: AuthenticatedUser,
337    id: web::Path<Uuid>,
338) -> impl Responder {
339    let organization_id = match user.require_organization() {
340        Ok(org_id) => org_id,
341        Err(e) => {
342            return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
343        }
344    };
345
346    match state
347        .payment_method_use_cases
348        .delete_payment_method(*id)
349        .await
350    {
351        Ok(true) => {
352            AuditLogEntry::new(
353                AuditEventType::PaymentMethodDeleted,
354                Some(user.user_id),
355                Some(organization_id),
356            )
357            .with_resource("PaymentMethod", *id)
358            .log();
359
360            HttpResponse::NoContent().finish()
361        }
362        Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
363            "error": "Payment method not found"
364        })),
365        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
366    }
367}
368
369// ==================== Payment Method Statistics Endpoints ====================
370
371#[get("/owners/{owner_id}/payment-methods/count")]
372pub async fn count_active_payment_methods(
373    state: web::Data<AppState>,
374    owner_id: web::Path<Uuid>,
375) -> impl Responder {
376    match state
377        .payment_method_use_cases
378        .count_active_payment_methods(*owner_id)
379        .await
380    {
381        Ok(count) => HttpResponse::Ok().json(serde_json::json!({
382            "owner_id": *owner_id,
383            "active_payment_methods_count": count
384        })),
385        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
386    }
387}
388
389#[get("/owners/{owner_id}/payment-methods/has-active")]
390pub async fn has_active_payment_methods(
391    state: web::Data<AppState>,
392    owner_id: web::Path<Uuid>,
393) -> impl Responder {
394    match state
395        .payment_method_use_cases
396        .has_active_payment_methods(*owner_id)
397        .await
398    {
399        Ok(has_active) => HttpResponse::Ok().json(serde_json::json!({
400            "owner_id": *owner_id,
401            "has_active_payment_methods": has_active
402        })),
403        Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
404    }
405}