use crate::AppService; use crate::error::AppError; use chrono::{DateTime, Utc}; use models::ai::billing_error; use models::projects::project_billing_history; use models::users::user_billing; use sea_orm::sea_query::prelude::rust_decimal::prelude::ToPrimitive; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; use utoipa::{IntoParams, ToSchema}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserBillingResponse { pub user_uid: Uuid, pub balance: f64, pub currency: String, pub is_pro: bool, pub monthly_quota: f64, pub month_used: f64, pub cycle_start_utc: Option>, pub cycle_end_utc: Option>, pub updated_at: DateTime, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserBillingErrorsResponse { pub list: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserBillingErrorItem { pub id: Uuid, pub scope: String, pub scope_id: Uuid, pub error_type: String, pub message: String, pub details: Option, pub resolved: bool, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)] pub struct UserBillingHistoryQuery { pub page: Option, pub per_page: Option, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserBillingHistoryItem { pub uid: Uuid, pub project_uid: Uuid, pub user_uid: Option, pub amount: f64, pub currency: String, pub reason: String, pub extra: Option, pub created_at: DateTime, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UserBillingHistoryResponse { pub page: u64, pub per_page: u64, pub total: u64, pub list: Vec, } impl AppService { pub async fn user_billing_current( &self, ctx: &Session, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let billing = match user_billing::Entity::find_by_id(user_uid) .one(&self.db) .await? { Some(b) => b, None => { let now = Utc::now(); let active = user_billing::ActiveModel { user: Set(user_uid), balance: Set(rust_decimal::Decimal::from(10)), currency: Set("USD".to_string()), is_pro: Set(false), monthly_quota: Set(rust_decimal::Decimal::from(0)), month_used: Set(rust_decimal::Decimal::from(0)), cycle_start: Set(None), cycle_end: Set(None), updated_at: Set(now), created_at: Set(now), }; active.insert(&self.db).await? } }; Ok(UserBillingResponse { user_uid: billing.user, balance: billing.balance.to_f64().unwrap_or_default(), currency: billing.currency, is_pro: billing.is_pro, monthly_quota: billing.monthly_quota.to_f64().unwrap_or_default(), month_used: billing.month_used.to_f64().unwrap_or_default(), cycle_start_utc: billing.cycle_start, cycle_end_utc: billing.cycle_end, updated_at: billing.updated_at, created_at: billing.created_at, }) } pub async fn user_billing_errors( &self, ctx: &Session, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let errors = billing_error::Entity::find() .filter(billing_error::Column::Scope.eq("user")) .filter(billing_error::Column::ScopeId.eq(user_uid)) .filter(billing_error::Column::Resolved.eq(false)) .order_by_desc(billing_error::Column::CreatedAt) .all(&self.db) .await?; Ok(UserBillingErrorsResponse { list: errors .into_iter() .map(|e| UserBillingErrorItem { id: e.id, scope: e.scope, scope_id: e.scope_id, error_type: e.error_type, message: e.message, details: e.details, resolved: e.resolved, created_at: e.created_at, }) .collect(), }) } pub async fn user_billing_history( &self, ctx: &Session, query: UserBillingHistoryQuery, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let page = std::cmp::max(query.page.unwrap_or(1), 1); let per_page = query.per_page.unwrap_or(20).clamp(1, 200); let paginator = project_billing_history::Entity::find() .filter(project_billing_history::Column::User.eq(user_uid)) .order_by_desc(project_billing_history::Column::CreatedAt) .paginate(&self.db, per_page); let total = paginator.num_items().await?; let rows = paginator.fetch_page(page - 1).await?; let list = rows .into_iter() .map(|x| UserBillingHistoryItem { uid: x.uid, project_uid: x.project, user_uid: x.user, amount: x.amount.to_f64().unwrap_or_default(), currency: x.currency, reason: x.reason, extra: x.extra.map(|v| v.into()), created_at: x.created_at, }) .collect(); Ok(UserBillingHistoryResponse { page, per_page, total, list, }) } }