use crate::domain::error::{AppError, AppResult}; use crate::domain::models::{OrderRecord, ProductLink}; use crate::infrastructure::db::DbPool; use crate::infrastructure::repositories::{OrderRepository, ProductRepository}; use async_trait::async_trait; use std::sync::Arc; #[async_trait] pub trait CheckoutService: Send + Sync { async fn get_checkout_view(&self, link_id: &str) -> AppResult; #[allow(clippy::too_many_arguments)] async fn execute_checkout( &self, link_id: &str, buyer_phone: &str, buyer_name: &str, buyer_email: &str, shipping_pincode: &str, delivery_address: &str, coupon_code: Option, request_id: Option, client_ip: &str, lat: Option, lng: Option, device_fingerprint: Option, ) -> AppResult; #[allow(clippy::too_many_arguments)] async fn execute_cart_checkout( &self, items: Vec<(String, u32)>, // (link_id, quantity) buyer_phone: &str, buyer_name: &str, buyer_email: &str, shipping_pincode: &str, delivery_address: &str, coupon_code: Option, request_id: Option, client_ip: &str, lat: Option, lng: Option, device_fingerprint: Option, ) -> AppResult; async fn submit_delivery_proof( &self, transaction_id: &str, proof_data: &str, proof_token: &str, lat: Option, lng: Option, ) -> AppResult<()>; async fn estimate_delivery( &self, link_id: &str, pincode: &str, _buyer_phone: Option, ) -> AppResult<(f64, f64)>; } pub struct RtixCheckoutService { product_repo: Arc, merchant_repo: Arc, order_repo: Arc, assets: Arc, tx: tokio::sync::broadcast::Sender, pool: DbPool, } impl RtixCheckoutService { pub fn new( product_repo: Arc, merchant_repo: Arc, order_repo: Arc, assets: Arc, tx: tokio::sync::broadcast::Sender, pool: DbPool, ) -> Self { Self { product_repo, merchant_repo, order_repo, assets, tx, pool, } } } #[async_trait] impl CheckoutService for RtixCheckoutService { async fn get_checkout_view(&self, link_id: &str) -> AppResult { let product = self.product_repo.find_by_id(link_id).await?; let mut product = product.ok_or_else(|| AppError::NotFound("Product link not found".to_string()))?; let _ = self.product_repo.increment_views(link_id).await; product.image_data = crate::core::utils::hydrate_file_to_base64(product.image_data).await; Ok(product) } #[allow(clippy::too_many_arguments)] async fn execute_checkout( &self, link_id: &str, buyer_phone: &str, buyer_name: &str, buyer_email: &str, shipping_pincode: &str, delivery_address: &str, coupon_code: Option, request_id: Option, client_ip: &str, lat: Option, lng: Option, device_fingerprint: Option, ) -> AppResult { crate::application::services::checkout_impl::execute_checkout_helper( &self.product_repo, &self.merchant_repo, &self.order_repo, &self.pool, &self.tx, link_id, buyer_phone, buyer_name, buyer_email, shipping_pincode, delivery_address, coupon_code, request_id, client_ip, lat, lng, device_fingerprint, ) .await } #[allow(clippy::too_many_arguments)] async fn execute_cart_checkout( &self, items: Vec<(String, u32)>, buyer_phone: &str, buyer_name: &str, buyer_email: &str, shipping_pincode: &str, delivery_address: &str, coupon_code: Option, request_id: Option, client_ip: &str, lat: Option, lng: Option, device_fingerprint: Option, ) -> AppResult { crate::application::services::checkout_impl::execute_cart_checkout_helper( &self.product_repo, &self.merchant_repo, &self.order_repo, &self.pool, &self.tx, items, buyer_phone, buyer_name, buyer_email, shipping_pincode, delivery_address, coupon_code, request_id, client_ip, lat, lng, device_fingerprint, ) .await } async fn submit_delivery_proof( &self, transaction_id: &str, proof_data: &str, proof_token: &str, lat: Option, lng: Option, ) -> AppResult<()> { crate::application::services::checkout_impl::execute_submit_delivery_proof_helper( &self.order_repo, &self.assets, &self.tx, transaction_id, proof_data, proof_token, lat, lng, ) .await } async fn estimate_delivery( &self, link_id: &str, pincode: &str, _buyer_phone: Option, ) -> AppResult<(f64, f64)> { let product = self .product_repo .find_by_id(link_id) .await? .ok_or_else(|| AppError::NotFound("Product link not found".to_string()))?; let merchant = self .merchant_repo .find_by_id(&product.merchant_id) .await? .ok_or_else(|| AppError::NotFound("Merchant not found".to_string()))?; let distance_km = crate::domain::distance::estimate_distance_km(&merchant.base_pincode, pincode); let pricing_features = crate::application::services::pricing::PricingFeatures { distance_km, user_rate_per_km: merchant.delivery_rate_per_km, product_weight: product.expected_weight, base_charge: merchant.delivery_base_fee, config: serde_json::from_value(merchant.logistics_config.clone()).unwrap_or_default(), }; let fee = crate::application::services::pricing::PricingEngine::estimate_delivery_fee( pricing_features, ); Ok((fee, distance_km)) } }