//! Self-service refund endpoint for creators. use axum::extract::{Path, State}; use axum::response::IntoResponse; use axum::Json; use serde::Deserialize; use crate::{ auth::AuthUser, db::{self, ItemId, TransactionId}, error::{AppError, Result}, AppState, }; use super::super::verify_item_ownership; #[derive(Debug, Deserialize)] pub struct RefundRequest { pub transaction_id: TransactionId, } /// Issue a full refund for a transaction on this item. /// /// The refund is sent to Stripe; the existing `charge.refunded` webhook /// handler marks the transaction as refunded, revokes license keys, and /// decrements the sales count. #[tracing::instrument(skip_all, name = "items::refund_transaction")] pub(in crate::routes::api) async fn refund_transaction( State(state): State, AuthUser(user): AuthUser, Path(id): Path, Json(req): Json, ) -> Result { user.check_not_suspended()?; verify_item_ownership(&state, id, user.id).await?; // Fetch the transaction and validate it belongs to this item let tx = db::transactions::get_transaction_by_id(&state.db, req.transaction_id) .await? .ok_or(AppError::NotFound)?; if tx.item_id != Some(id) { return Err(AppError::Forbidden); } if tx.seller_id != Some(user.id) { return Err(AppError::Forbidden); } if tx.status != db::TransactionStatus::Completed { return Err(AppError::BadRequest("Transaction is not in a refundable state".into())); } let payment_intent_id = tx.stripe_payment_intent_id.as_deref() .ok_or_else(|| AppError::BadRequest("No payment intent — free claims cannot be refunded".into()))?; // Get the creator's Stripe connected account ID let seller = db::users::get_user_by_id(&state.db, user.id) .await? .ok_or(AppError::NotFound)?; let stripe_account_id = seller.stripe_account_id.as_deref() .ok_or_else(|| AppError::BadRequest("No Stripe account connected".into()))?; // Issue the refund via Stripe — the webhook handler does the rest let stripe = state.stripe.as_ref() .ok_or_else(|| AppError::ServiceUnavailable("Stripe is not configured".to_string()))?; stripe.create_refund(payment_intent_id, stripe_account_id).await?; Ok(Json(serde_json::json!({ "ok": true }))) }