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