RTIX / src /application /services /checkout.rs
github-actions
deploy: clean backend production release
18d6188
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<ProductLink>;
#[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<String>,
request_id: Option<String>,
client_ip: &str,
lat: Option<f64>,
lng: Option<f64>,
device_fingerprint: Option<String>,
) -> AppResult<OrderRecord>;
#[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<String>,
request_id: Option<String>,
client_ip: &str,
lat: Option<f64>,
lng: Option<f64>,
device_fingerprint: Option<String>,
) -> AppResult<OrderRecord>;
async fn submit_delivery_proof(
&self,
transaction_id: &str,
proof_data: &str,
proof_token: &str,
lat: Option<f64>,
lng: Option<f64>,
) -> AppResult<()>;
async fn estimate_delivery(
&self,
link_id: &str,
pincode: &str,
_buyer_phone: Option<String>,
) -> AppResult<(f64, f64)>;
}
pub struct RtixCheckoutService {
product_repo: Arc<dyn ProductRepository>,
merchant_repo: Arc<dyn crate::infrastructure::repositories::MerchantRepository>,
order_repo: Arc<dyn OrderRepository>,
assets: Arc<dyn crate::infrastructure::storage::assets::AssetProvider>,
tx: tokio::sync::broadcast::Sender<crate::interfaces::http::api::RealtimeEvent>,
pool: DbPool,
}
impl RtixCheckoutService {
pub fn new(
product_repo: Arc<dyn ProductRepository>,
merchant_repo: Arc<dyn crate::infrastructure::repositories::MerchantRepository>,
order_repo: Arc<dyn OrderRepository>,
assets: Arc<dyn crate::infrastructure::storage::assets::AssetProvider>,
tx: tokio::sync::broadcast::Sender<crate::interfaces::http::api::RealtimeEvent>,
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<ProductLink> {
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<String>,
request_id: Option<String>,
client_ip: &str,
lat: Option<f64>,
lng: Option<f64>,
device_fingerprint: Option<String>,
) -> AppResult<OrderRecord> {
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<String>,
request_id: Option<String>,
client_ip: &str,
lat: Option<f64>,
lng: Option<f64>,
device_fingerprint: Option<String>,
) -> AppResult<OrderRecord> {
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<f64>,
lng: Option<f64>,
) -> 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<String>,
) -> 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))
}
}