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};
6use uuid::Uuid;
7
8#[post("/payments")]
11pub async fn create_payment(
12 state: web::Data<AppState>,
13 user: AuthenticatedUser,
14 request: web::Json<CreatePaymentRequest>,
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_use_cases
25 .create_payment(organization_id, request.into_inner())
26 .await
27 {
28 Ok(payment) => {
29 AuditLogEntry::new(
30 AuditEventType::PaymentCreated,
31 Some(user.user_id),
32 Some(organization_id),
33 )
34 .with_resource("Payment", payment.id)
35 .log();
36
37 HttpResponse::Created().json(payment)
38 }
39 Err(err) => {
40 AuditLogEntry::new(
41 AuditEventType::PaymentCreated,
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("/payments/{id}")]
54pub async fn get_payment(state: web::Data<AppState>, id: web::Path<Uuid>) -> impl Responder {
55 match state.payment_use_cases.get_payment(*id).await {
56 Ok(Some(payment)) => HttpResponse::Ok().json(payment),
57 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
58 "error": "Payment not found"
59 })),
60 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
61 }
62}
63
64#[get("/payments/stripe/{stripe_payment_intent_id}")]
65pub async fn get_payment_by_stripe_intent(
66 state: web::Data<AppState>,
67 stripe_payment_intent_id: web::Path<String>,
68) -> impl Responder {
69 match state
70 .payment_use_cases
71 .get_payment_by_stripe_intent(&stripe_payment_intent_id)
72 .await
73 {
74 Ok(Some(payment)) => HttpResponse::Ok().json(payment),
75 Ok(None) => HttpResponse::NotFound().json(serde_json::json!({
76 "error": "Payment not found"
77 })),
78 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
79 }
80}
81
82#[get("/owners/{owner_id}/payments")]
83pub async fn list_owner_payments(
84 state: web::Data<AppState>,
85 owner_id: web::Path<Uuid>,
86) -> impl Responder {
87 match state.payment_use_cases.list_owner_payments(*owner_id).await {
88 Ok(payments) => HttpResponse::Ok().json(payments),
89 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
90 }
91}
92
93#[get("/buildings/{building_id}/payments")]
94pub async fn list_building_payments(
95 state: web::Data<AppState>,
96 building_id: web::Path<Uuid>,
97) -> impl Responder {
98 match state
99 .payment_use_cases
100 .list_building_payments(*building_id)
101 .await
102 {
103 Ok(payments) => HttpResponse::Ok().json(payments),
104 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
105 }
106}
107
108#[get("/expenses/{expense_id}/payments")]
109pub async fn list_expense_payments(
110 state: web::Data<AppState>,
111 expense_id: web::Path<Uuid>,
112) -> impl Responder {
113 match state
114 .payment_use_cases
115 .list_expense_payments(*expense_id)
116 .await
117 {
118 Ok(payments) => HttpResponse::Ok().json(payments),
119 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
120 }
121}
122
123#[get("/organizations/{organization_id}/payments")]
124pub async fn list_organization_payments(
125 state: web::Data<AppState>,
126 organization_id: web::Path<Uuid>,
127) -> impl Responder {
128 match state
129 .payment_use_cases
130 .list_organization_payments(*organization_id)
131 .await
132 {
133 Ok(payments) => HttpResponse::Ok().json(payments),
134 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
135 }
136}
137
138#[get("/payments/status/{status}")]
139pub async fn list_payments_by_status(
140 state: web::Data<AppState>,
141 user: AuthenticatedUser,
142 status_str: web::Path<String>,
143) -> impl Responder {
144 let organization_id = match user.require_organization() {
145 Ok(org_id) => org_id,
146 Err(e) => {
147 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
148 }
149 };
150
151 let status = match status_str.as_str() {
153 "pending" => TransactionStatus::Pending,
154 "processing" => TransactionStatus::Processing,
155 "requires_action" => TransactionStatus::RequiresAction,
156 "succeeded" => TransactionStatus::Succeeded,
157 "failed" => TransactionStatus::Failed,
158 "cancelled" => TransactionStatus::Cancelled,
159 "refunded" => TransactionStatus::Refunded,
160 _ => {
161 return HttpResponse::BadRequest().json(serde_json::json!({
162 "error": "Invalid status. Must be one of: pending, processing, requires_action, succeeded, failed, cancelled, refunded"
163 }))
164 }
165 };
166
167 match state
168 .payment_use_cases
169 .list_payments_by_status(organization_id, status)
170 .await
171 {
172 Ok(payments) => HttpResponse::Ok().json(payments),
173 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
174 }
175}
176
177#[get("/payments/pending")]
178pub async fn list_pending_payments(
179 state: web::Data<AppState>,
180 user: AuthenticatedUser,
181) -> impl Responder {
182 let organization_id = match user.require_organization() {
183 Ok(org_id) => org_id,
184 Err(e) => {
185 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
186 }
187 };
188
189 match state
190 .payment_use_cases
191 .list_pending_payments(organization_id)
192 .await
193 {
194 Ok(payments) => HttpResponse::Ok().json(payments),
195 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
196 }
197}
198
199#[get("/payments/failed")]
200pub async fn list_failed_payments(
201 state: web::Data<AppState>,
202 user: AuthenticatedUser,
203) -> impl Responder {
204 let organization_id = match user.require_organization() {
205 Ok(org_id) => org_id,
206 Err(e) => {
207 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
208 }
209 };
210
211 match state
212 .payment_use_cases
213 .list_failed_payments(organization_id)
214 .await
215 {
216 Ok(payments) => HttpResponse::Ok().json(payments),
217 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
218 }
219}
220
221#[put("/payments/{id}/processing")]
224pub async fn mark_payment_processing(
225 state: web::Data<AppState>,
226 user: AuthenticatedUser,
227 id: web::Path<Uuid>,
228) -> impl Responder {
229 let organization_id = match user.require_organization() {
230 Ok(org_id) => org_id,
231 Err(e) => {
232 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
233 }
234 };
235
236 match state.payment_use_cases.mark_processing(*id).await {
237 Ok(payment) => {
238 AuditLogEntry::new(
239 AuditEventType::PaymentProcessing,
240 Some(user.user_id),
241 Some(organization_id),
242 )
243 .with_resource("Payment", payment.id)
244 .log();
245
246 HttpResponse::Ok().json(payment)
247 }
248 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
249 }
250}
251
252#[put("/payments/{id}/requires-action")]
253pub async fn mark_payment_requires_action(
254 state: web::Data<AppState>,
255 user: AuthenticatedUser,
256 id: web::Path<Uuid>,
257) -> impl Responder {
258 let organization_id = match user.require_organization() {
259 Ok(org_id) => org_id,
260 Err(e) => {
261 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
262 }
263 };
264
265 match state.payment_use_cases.mark_requires_action(*id).await {
266 Ok(payment) => {
267 AuditLogEntry::new(
268 AuditEventType::PaymentRequiresAction,
269 Some(user.user_id),
270 Some(organization_id),
271 )
272 .with_resource("Payment", payment.id)
273 .log();
274
275 HttpResponse::Ok().json(payment)
276 }
277 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
278 }
279}
280
281#[put("/payments/{id}/succeeded")]
282pub async fn mark_payment_succeeded(
283 state: web::Data<AppState>,
284 user: AuthenticatedUser,
285 id: web::Path<Uuid>,
286) -> impl Responder {
287 let organization_id = match user.require_organization() {
288 Ok(org_id) => org_id,
289 Err(e) => {
290 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
291 }
292 };
293
294 match state.payment_use_cases.mark_succeeded(*id).await {
295 Ok(payment) => {
296 AuditLogEntry::new(
297 AuditEventType::PaymentSucceeded,
298 Some(user.user_id),
299 Some(organization_id),
300 )
301 .with_resource("Payment", payment.id)
302 .log();
303
304 HttpResponse::Ok().json(payment)
305 }
306 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
307 }
308}
309
310#[put("/payments/{id}/failed")]
311pub async fn mark_payment_failed(
312 state: web::Data<AppState>,
313 user: AuthenticatedUser,
314 id: web::Path<Uuid>,
315 request: web::Json<serde_json::Value>,
316) -> impl Responder {
317 let organization_id = match user.require_organization() {
318 Ok(org_id) => org_id,
319 Err(e) => {
320 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
321 }
322 };
323
324 let reason = request
325 .get("reason")
326 .and_then(|v| v.as_str())
327 .unwrap_or("Unknown failure reason")
328 .to_string();
329
330 match state.payment_use_cases.mark_failed(*id, reason).await {
331 Ok(payment) => {
332 AuditLogEntry::new(
333 AuditEventType::PaymentFailed,
334 Some(user.user_id),
335 Some(organization_id),
336 )
337 .with_resource("Payment", payment.id)
338 .log();
339
340 HttpResponse::Ok().json(payment)
341 }
342 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
343 }
344}
345
346#[put("/payments/{id}/cancelled")]
347pub async fn mark_payment_cancelled(
348 state: web::Data<AppState>,
349 user: AuthenticatedUser,
350 id: web::Path<Uuid>,
351) -> impl Responder {
352 let organization_id = match user.require_organization() {
353 Ok(org_id) => org_id,
354 Err(e) => {
355 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
356 }
357 };
358
359 match state.payment_use_cases.mark_cancelled(*id).await {
360 Ok(payment) => {
361 AuditLogEntry::new(
362 AuditEventType::PaymentCancelled,
363 Some(user.user_id),
364 Some(organization_id),
365 )
366 .with_resource("Payment", payment.id)
367 .log();
368
369 HttpResponse::Ok().json(payment)
370 }
371 Err(err) => HttpResponse::BadRequest().json(serde_json::json!({"error": err})),
372 }
373}
374
375#[post("/payments/{id}/refund")]
376pub async fn refund_payment(
377 state: web::Data<AppState>,
378 user: AuthenticatedUser,
379 id: web::Path<Uuid>,
380 request: web::Json<RefundPaymentRequest>,
381) -> impl Responder {
382 let organization_id = match user.require_organization() {
383 Ok(org_id) => org_id,
384 Err(e) => {
385 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
386 }
387 };
388
389 match state
390 .payment_use_cases
391 .refund_payment(*id, request.into_inner())
392 .await
393 {
394 Ok(payment) => {
395 AuditLogEntry::new(
396 AuditEventType::PaymentRefunded,
397 Some(user.user_id),
398 Some(organization_id),
399 )
400 .with_resource("Payment", payment.id)
401 .log();
402
403 HttpResponse::Ok().json(payment)
404 }
405 Err(err) => {
406 AuditLogEntry::new(
407 AuditEventType::PaymentRefunded,
408 Some(user.user_id),
409 Some(organization_id),
410 )
411 .with_error(err.clone())
412 .log();
413
414 HttpResponse::BadRequest().json(serde_json::json!({"error": err}))
415 }
416 }
417}
418
419#[delete("/payments/{id}")]
420pub async fn delete_payment(
421 state: web::Data<AppState>,
422 user: AuthenticatedUser,
423 id: web::Path<Uuid>,
424) -> impl Responder {
425 let organization_id = match user.require_organization() {
426 Ok(org_id) => org_id,
427 Err(e) => {
428 return HttpResponse::Unauthorized().json(serde_json::json!({"error": e.to_string()}))
429 }
430 };
431
432 match state.payment_use_cases.delete_payment(*id).await {
433 Ok(true) => {
434 AuditLogEntry::new(
435 AuditEventType::PaymentDeleted,
436 Some(user.user_id),
437 Some(organization_id),
438 )
439 .with_resource("Payment", *id)
440 .log();
441
442 HttpResponse::NoContent().finish()
443 }
444 Ok(false) => HttpResponse::NotFound().json(serde_json::json!({
445 "error": "Payment not found"
446 })),
447 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
448 }
449}
450
451#[get("/owners/{owner_id}/payments/stats")]
454pub async fn get_owner_payment_stats(
455 state: web::Data<AppState>,
456 owner_id: web::Path<Uuid>,
457) -> impl Responder {
458 match state
459 .payment_use_cases
460 .get_owner_payment_stats(*owner_id)
461 .await
462 {
463 Ok(stats) => HttpResponse::Ok().json(stats),
464 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
465 }
466}
467
468#[get("/buildings/{building_id}/payments/stats")]
469pub async fn get_building_payment_stats(
470 state: web::Data<AppState>,
471 building_id: web::Path<Uuid>,
472) -> impl Responder {
473 match state
474 .payment_use_cases
475 .get_building_payment_stats(*building_id)
476 .await
477 {
478 Ok(stats) => HttpResponse::Ok().json(stats),
479 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
480 }
481}
482
483#[get("/expenses/{expense_id}/payments/total")]
484pub async fn get_expense_total_paid(
485 state: web::Data<AppState>,
486 expense_id: web::Path<Uuid>,
487) -> impl Responder {
488 match state
489 .payment_use_cases
490 .get_total_paid_for_expense(*expense_id)
491 .await
492 {
493 Ok(total) => HttpResponse::Ok().json(serde_json::json!({
494 "expense_id": *expense_id,
495 "total_paid_cents": total
496 })),
497 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
498 }
499}
500
501#[get("/owners/{owner_id}/payments/total")]
502pub async fn get_owner_total_paid(
503 state: web::Data<AppState>,
504 owner_id: web::Path<Uuid>,
505) -> impl Responder {
506 match state
507 .payment_use_cases
508 .get_total_paid_by_owner(*owner_id)
509 .await
510 {
511 Ok(total) => HttpResponse::Ok().json(serde_json::json!({
512 "owner_id": *owner_id,
513 "total_paid_cents": total
514 })),
515 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
516 }
517}
518
519#[get("/buildings/{building_id}/payments/total")]
520pub async fn get_building_total_paid(
521 state: web::Data<AppState>,
522 building_id: web::Path<Uuid>,
523) -> impl Responder {
524 match state
525 .payment_use_cases
526 .get_total_paid_for_building(*building_id)
527 .await
528 {
529 Ok(total) => HttpResponse::Ok().json(serde_json::json!({
530 "building_id": *building_id,
531 "total_paid_cents": total
532 })),
533 Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({"error": err})),
534 }
535}