use super::super::domain::{ CreatePaymentCommand, PaymentEntity, PaymentRepository, PaymentService, SERVICE_FEE, }; use crate::sessions::domain::SessionRepository; use async_trait::async_trait; use chrono::{Duration, Utc}; use imphnen_entities::seaorm::auth::mentors::Entity as MentorsEntity; use imphnen_entities::seaorm::auth::users::Entity as UsersEntity; use imphnen_utils::AppError; use sea_orm::prelude::*; use std::sync::Arc; use uuid::Uuid; pub struct PaymentServiceImpl { payment_repo: Arc, session_repo: Arc, db: Arc, } impl PaymentServiceImpl { pub fn new( payment_repo: Arc, session_repo: Arc, db: Arc, ) -> Self { Self { payment_repo, session_repo, db, } } } fn generate_external_ref(method: &str, session_id: Uuid) -> String { match method { "va" => format!("VA-{}-{}", session_id.to_string().split('-').next().unwrap_or("X"), Utc::now().format("%Y%m%d%H%M%S")), "qris" => format!("QR-{}", session_id.to_string().replace('-', "").chars().take(16).collect::()), _ => format!("MANUAL-{}", Utc::now().format("%Y%m%d%H%M%S")), } } #[async_trait] impl PaymentService for PaymentServiceImpl { async fn create_payment( &self, session_id: Uuid, mentee_id: Uuid, cmd: CreatePaymentCommand, ) -> Result { // Only a valid session can be paid for. let session = self .session_repo .find_by_id(session_id) .await .map_err(|_| AppError::NotFoundError("Session not found".into()))? .ok_or_else(|| AppError::NotFoundError("Session not found".into()))?; // The mentee paying must be the session's mentee. if session.mentee_id != mentee_id { return Err(AppError::ForbiddenError( "You can only pay for your own sessions".into(), )); } // Load mentor rate from the mentors table (mentors.user_id = the session's // mentor user id). let mentor_uuid = session.mentor_id; let mentor = MentorsEntity::find() .filter(imphnen_entities::seaorm::auth::mentors::Column::UserId.eq(mentor_uuid)) .one(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))? .ok_or_else(|| AppError::NotFoundError("Mentor not found".into()))?; let rate = mentor.mentoring_rate.unwrap_or(50_000.0).round() as i64; let total = rate + SERVICE_FEE; let method = cmd.method.clone(); let provider = "manual".to_string(); // swap to midtrans/xendit later let expires_at = Utc::now() + Duration::hours(24); let payment = PaymentEntity { id: Uuid::new_v4(), session_id, mentee_id, mentor_id: mentor_uuid, amount: rate, service_fee: SERVICE_FEE, total, method: method.clone(), provider, status: "pending".into(), external_ref: Some(generate_external_ref(&method, session_id)), expires_at, created_at: Utc::now(), paid_at: None, }; self.payment_repo.create(payment).await } async fn get_payment_by_id( &self, id: Uuid, user_id: Uuid, ) -> Result { let payment = self.payment_repo.find_by_id(id).await?; if payment.mentee_id != user_id { return Err(AppError::ForbiddenError( "You can only view your own payments".into(), )); } Ok(payment) } async fn confirm_payment( &self, id: Uuid, actor_id: Uuid, ) -> Result { // Admin / "Admin Pembayaran" only — check role via users table. let user = UsersEntity::find_by_id(actor_id) .one(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))? .ok_or_else(|| AppError::NotFoundError("Actor not found".into()))?; let role_id = user.role_id.ok_or_else(|| { AppError::ForbiddenError("User has no role assigned".into()) })?; let roles = imphnen_entities::seaorm::auth::roles::Entity::find_by_id(role_id) .one(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))? .ok_or_else(|| AppError::ForbiddenError("Role not found".into()))?; if roles.name != "Admin" && roles.name != "Admin Pembayaran" { return Err(AppError::ForbiddenError( "Only payment admin can confirm payments".into(), )); } let payment = self.payment_repo.find_by_id(id).await?; if payment.status != "pending" { return Err(AppError::ConflictError( "Payment is not pending".into(), )); } self.payment_repo .update_status(id, "paid", Some(payment.external_ref.clone().unwrap_or_default())) .await } async fn get_mentee_payments( &self, mentee_id: Uuid, ) -> Result, AppError> { self.payment_repo.find_by_mentee(mentee_id).await } }