This repository has been archived on 2025-12-01. You can view files and clone it, but cannot push or open issues or pull requests.
Files
projectmycelium_old/src/services/order.rs

1242 lines
47 KiB
Rust

use crate::models::order::{Order, OrderItem, OrderStatus, Cart, CartItem, PaymentDetails, PaymentMethod};
use crate::models::product::Product;
use crate::services::{currency::CurrencyService, product::ProductService, user_persistence::UserPersistence};
use rust_decimal::Decimal;
use rust_decimal::prelude::ToPrimitive;
use rust_decimal_macros::dec;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use chrono::Utc;
use uuid::Uuid;
use actix_session::Session;
use serde::Serialize;
/// Service for handling order and cart operations
pub struct OrderService {
currency_service: CurrencyService,
product_service: ProductService,
}
/// Singleton order storage for demo purposes
pub struct OrderStorage {
orders: HashMap<String, Order>,
}
impl OrderStorage {
fn new() -> Self {
Self {
orders: HashMap::default(),
}
}
/// Get singleton instance of order storage
pub fn instance() -> Arc<Mutex<OrderStorage>> {
use std::sync::Once;
static mut INSTANCE: Option<Arc<Mutex<OrderStorage>>> = None;
static ONCE: Once = Once::new();
unsafe {
ONCE.call_once(|| {
INSTANCE = Some(Arc::new(Mutex::new(OrderStorage::new())));
});
INSTANCE.as_ref().unwrap().clone()
}
}
pub fn insert_order(&mut self, order_id: String, order: Order) {
self.orders.insert(order_id, order);
}
pub fn get_order(&self, order_id: &str) -> Option<&Order> {
self.orders.get(order_id)
}
pub fn get_user_orders(&self, user_id: &str) -> Vec<&Order> {
self.orders.values()
.filter(|order| order.user_id == user_id)
.collect()
}
pub fn update_order_status(&mut self, order_id: &str, status: OrderStatus) -> Result<(), String> {
if let Some(order) = self.orders.get_mut(order_id) {
order.update_status(status);
Ok(())
} else {
Err("Order not found".to_string())
}
}
pub fn get_order_mut(&mut self, order_id: &str) -> Option<&mut Order> {
self.orders.get_mut(order_id)
}
pub fn get_orders_count(&self) -> usize {
self.orders.len()
}
pub fn get_all_orders(&self) -> Vec<&Order> {
self.orders.values().collect()
}
}
/// Mock payment gateway for simulating payment processing
pub struct MockPaymentGateway {
pub success_rate: f32, // Configurable for testing
}
/// Payment request structure
#[derive(Debug, Clone)]
pub struct PaymentRequest {
pub order_id: String,
pub amount: Decimal,
pub currency: String,
pub payment_method: PaymentMethod,
pub user_id: String,
}
/// Payment result
#[derive(Debug, Clone)]
pub struct PaymentResult {
pub success: bool,
pub transaction_id: Option<String>,
pub error_message: Option<String>,
pub payment_details: Option<PaymentDetails>,
}
impl OrderService {
pub fn new() -> Self {
Self {
currency_service: CurrencyService::new(),
product_service: ProductService::new(),
}
}
pub fn new_with_config(
currency_service: CurrencyService,
product_service: ProductService,
_auto_save: bool,
) -> Self {
Self {
currency_service,
product_service,
}
}
pub fn builder() -> crate::models::builders::OrderServiceBuilder {
crate::models::builders::OrderServiceBuilder::new()
}
// Cart Operations
/// Get or create cart for user from session
pub fn get_or_create_cart(&self, user_id: &str, session: &Session) -> Cart {
let cart_key = if user_id.is_empty() {
"guest_cart"
} else {
"user_cart"
};
// For logged-in users, ALWAYS load from persistent storage first
if !user_id.is_empty() {
let persistent_cart = self.load_user_cart_from_storage(user_id);
// Update session with persistent cart data to ensure consistency
if let Err(e) = self.save_cart_to_session(user_id, session, &persistent_cart) {
}
return persistent_cart;
}
// For guest users, try to get cart from session
if let Ok(Some(cart_json)) = session.get::<String>(cart_key) {
if let Ok(cart) = serde_json::from_str::<Cart>(&cart_json) {
return cart;
}
}
// Create new cart for guest users
Cart::new(user_id.to_string())
}
/// Load user cart from persistent storage
fn load_user_cart_from_storage(&self, user_id: &str) -> Cart {
// Sanitize user ID for filesystem compatibility
let sanitized_id = user_id.replace('@', "_at_").replace('.', "_");
let cart_file = format!("user_data/{}_cart.json", sanitized_id);
if let Ok(cart_json) = std::fs::read_to_string(&cart_file) {
match serde_json::from_str::<Cart>(&cart_json) {
Ok(cart) => {
return cart;
},
Err(e) => {
// Return empty cart instead of corrupted data
return Cart::new(user_id.to_string());
}
}
} else {
}
Cart::new(user_id.to_string())
}
/// Save user cart to persistent storage
fn save_user_cart_to_storage(&self, user_id: &str, cart: &Cart) -> Result<(), String> {
// Sanitize user ID for filesystem compatibility
let sanitized_id = user_id.replace('@', "_at_").replace('.', "_");
let cart_file = format!("user_data/{}_cart.json", sanitized_id);
// Ensure user_data directory exists
let _ = std::fs::create_dir_all("user_data");
let cart_json = serde_json::to_string_pretty(cart)
.map_err(|e| format!("Failed to serialize cart: {}", e))?;
std::fs::write(&cart_file, cart_json)
.map_err(|e| format!("Failed to save cart: {}", e))
}
/// Save cart to session
fn save_cart_to_session(&self, user_id: &str, session: &Session, cart: &Cart) -> Result<(), String> {
let cart_key = if user_id.is_empty() {
"guest_cart"
} else {
"user_cart"
};
let cart_json = serde_json::to_string(cart)
.map_err(|e| format!("Failed to serialize cart: {}", e))?;
session.insert(cart_key, cart_json)
.map_err(|e| format!("Failed to save cart to session: {}", e))?;
Ok(())
}
/// Add item to cart
pub fn add_to_cart(
&self,
user_id: &str,
session: &Session,
product_id: String,
quantity: u32,
specifications: Option<HashMap<String, serde_json::Value>>,
) -> Result<(), String> {
// Debug: Log available products
let all_products = self.product_service.get_all_products();
// Validate product exists
if let Some(found_product) = self.product_service.get_product_by_id(&product_id) {
} else {
return Err("Product not found".to_string());
}
eprintln!("[ORDER SERVICE] Getting or creating cart for user_id: '{}'", user_id);
let mut cart = self.get_or_create_cart(user_id, session);
eprintln!("[ORDER SERVICE] Cart retrieved/created - current items: {}", cart.items.len());
let cart_item = if let Some(specs) = specifications {
CartItem::with_specifications(product_id.clone(), quantity, specs)
} else {
CartItem::new(product_id.clone(), quantity)
};
eprintln!("[ORDER SERVICE] Created cart item for product: {}", product_id);
cart.add_item(cart_item);
eprintln!("[ORDER SERVICE] Added item to cart - total items now: {}", cart.items.len());
eprintln!("[ORDER SERVICE] Saving cart to session...");
match self.save_cart_to_session(user_id, session, &cart) {
Ok(()) => eprintln!("[ORDER SERVICE] Cart saved to session successfully"),
Err(e) => {
eprintln!("[ORDER SERVICE ERROR] Failed to save cart to session: {}", e);
return Err(e);
}
}
// Save to persistent storage for logged-in users
if !user_id.is_empty() {
eprintln!("[ORDER SERVICE] Saving cart to persistent storage for user: {}", user_id);
match self.save_user_cart_to_storage(user_id, &cart) {
Ok(()) => eprintln!("[ORDER SERVICE] Cart saved to persistent storage successfully"),
Err(e) => {
eprintln!("[ORDER SERVICE ERROR] Failed to save cart to persistent storage: {}", e);
return Err(e);
}
}
} else {
eprintln!("[ORDER SERVICE] Guest user - skipping persistent storage");
}
eprintln!("[ORDER SERVICE] add_to_cart completed successfully");
Ok(())
}
/// Remove item from cart
pub fn remove_from_cart(
&self,
user_id: &str,
session: &Session,
product_id: &str,
) -> Result<bool, String> {
let mut cart = self.get_or_create_cart(user_id, session);
let removed = cart.remove_item(product_id);
if removed {
// Save to session first
self.save_cart_to_session(user_id, session, &cart)?;
// For logged-in users, also save to persistent storage
if !user_id.is_empty() {
self.save_user_cart_to_storage(user_id, &cart)?;
}
}
Ok(removed)
}
/// Update cart item quantity
pub fn update_cart_item_quantity(
&self,
user_id: &str,
session: &Session,
product_id: &str,
quantity: u32,
) -> Result<bool, String> {
let mut cart = self.get_or_create_cart(user_id, session);
let result = cart.update_item_quantity(product_id, quantity);
self.save_cart_to_session(user_id, session, &cart)?;
// Save to persistent storage for logged-in users
if !user_id.is_empty() {
self.save_user_cart_to_storage(user_id, &cart)?;
}
Ok(result)
}
/// Get cart contents with product details and pricing
pub fn get_cart_with_details(
&self,
user_id: &str,
session: &Session,
display_currency: &str,
) -> Result<CartDetails, String> {
let mut cart = self.get_or_create_cart(user_id, session);
let mut cart_details = CartDetails {
cart: cart.clone(),
items: Vec::default(),
subtotal: dec!(0),
total: dec!(0),
currency: display_currency.to_string(),
item_count: 0,
};
let mut removed_invalid = false;
for cart_item in &cart.items {
if let Some(product) = self.product_service.get_product_by_id(&cart_item.product_id) {
let price = self.currency_service.create_price(
product.base_price,
&product.base_currency,
display_currency,
)?;
let item_total = price.display_amount * Decimal::from(cart_item.quantity);
cart_details.subtotal += item_total;
cart_details.items.push(CartItemDetails {
cart_item: cart_item.clone(),
product: product.clone(),
unit_price: price,
total_price: item_total,
});
} else {
// Product no longer exists; mark for cleanup and exclude from details
removed_invalid = true;
}
}
// Compute item_count based on valid items only
cart_details.item_count = cart_details
.items
.iter()
.map(|i| i.cart_item.quantity)
.sum();
cart_details.total = cart_details.subtotal; // Add taxes, fees, etc. here
// If invalid items were found, clean them from the persisted cart and session
if removed_invalid {
// Retain only items that still have valid products
cart.items.retain(|ci| self.product_service.get_product_by_id(&ci.product_id).is_some());
// Update metadata
cart.updated_at = chrono::Utc::now();
// Persist the cleaned cart
let _ = self.save_cart_to_session(user_id, session, &cart);
if !user_id.is_empty() {
let _ = self.save_user_cart_to_storage(user_id, &cart);
}
// Ensure returned details reflect cleaned cart structure
cart_details.cart = cart.clone();
}
Ok(cart_details)
}
/// Clear cart
pub fn clear_cart(&self, user_id: &str, session: &Session) -> Result<(), String> {
let mut cart = self.get_or_create_cart(user_id, session);
cart.clear();
// Save cleared cart to session
self.save_cart_to_session(user_id, session, &cart)?;
// For logged-in users, also clear persistent storage
if !user_id.is_empty() {
self.save_user_cart_to_storage(user_id, &cart)?;
}
Ok(())
}
/// Transfer guest cart items to user cart during login
pub fn transfer_guest_cart_to_user(&self, user_id: &str, session: &Session) -> Result<usize, String> {
// Get guest cart if it exists
let guest_cart = if let Ok(Some(guest_cart_json)) = session.get::<String>("guest_cart") {
if let Ok(guest_cart) = serde_json::from_str::<Cart>(&guest_cart_json) {
guest_cart
} else {
return Ok(0);
}
} else {
return Ok(0);
};
// If guest cart is empty, nothing to transfer
if guest_cart.items.is_empty() {
return Ok(0);
}
// Load user's existing persistent cart from user data
let mut user_cart = self.load_user_cart_from_storage(user_id);
let items_transferred = guest_cart.items.len();
// Transfer all items from guest cart to user cart
for guest_item in guest_cart.items {
// Update the cart item to reflect the new user
let mut user_item = guest_item;
user_item.updated_at = chrono::Utc::now();
// Check if item already exists in user cart
if let Some(existing_item) = user_cart.items.iter_mut().find(|item| item.product_id == user_item.product_id) {
// Merge quantities if same product exists
let old_qty = existing_item.quantity;
existing_item.quantity += user_item.quantity;
existing_item.updated_at = chrono::Utc::now();
} else {
// Add new item to user cart
user_cart.items.push(user_item.clone());
}
}
// Update user cart metadata
user_cart.user_id = user_id.to_string();
user_cart.updated_at = chrono::Utc::now();
// Save updated user cart to persistent storage FIRST
match self.save_user_cart_to_storage(user_id, &user_cart) {
Ok(()) => {
// Cart saved successfully to storage
},
Err(e) => {
return Err(format!("Failed to save cart to storage: {}", e));
}
}
// Then save to session
match self.save_cart_to_session(user_id, session, &user_cart) {
Ok(()) => {
// Don't fail the transfer if session save fails, persistent storage is more important
},
Err(_) => {
// Don't fail the transfer if session save fails, persistent storage is more important
}
}
// Clear guest cart after successful transfer
session.remove("guest_cart");
Ok(items_transferred)
}
// Order Operations
/// Create order from cart
pub fn create_order_from_cart(
&self,
user_id: &str,
session: &Session,
payment_currency: &str,
) -> Result<String, String> {
let cart_details = self.get_cart_with_details(user_id, session, payment_currency)?;
if cart_details.items.is_empty() {
return Err("Cart is empty".to_string());
}
let order_id = Uuid::new_v4().to_string();
// Use CurrencyService base currency (USD) rather than mock data
let base_currency = self.currency_service.get_base_currency().code.clone();
let conversion_rate = if payment_currency == base_currency {
dec!(1.0)
} else {
self.currency_service.get_exchange_rate_to_base(payment_currency)?
};
// Normalize user ID for consistent guest user handling
let normalized_user_id = if user_id.is_empty() {
"guest".to_string()
} else {
user_id.to_string()
};
let mut order = Order::new(
order_id.clone(),
normalized_user_id,
base_currency.clone(),
payment_currency.to_string(),
conversion_rate,
);
// Convert cart items to order items
for item_detail in cart_details.items {
let order_item = OrderItem::new(
item_detail.product.id,
item_detail.product.name,
item_detail.product.category_id,
item_detail.cart_item.quantity,
item_detail.product.base_price,
item_detail.product.provider_id,
item_detail.product.provider_name,
);
order.add_item(order_item);
}
// Store order in singleton storage
{
let order_storage = OrderStorage::instance();
let mut storage = order_storage.lock().unwrap();
storage.insert_order(order_id.clone(), order);
}
// Clear the cart after creating order
self.clear_cart(user_id, session)?;
Ok(order_id)
}
/// Get order by ID
pub fn get_order(&self, order_id: &str) -> Option<Order> {
let order_storage = OrderStorage::instance();
let storage = order_storage.lock().unwrap();
storage.get_order(order_id).cloned()
}
/// Get orders for user (sorted by creation date, latest first)
pub fn get_user_orders(&self, user_id: &str) -> Vec<Order> {
// Load user's persistent data and return their orders
if let Some(user_data) = crate::services::user_persistence::UserPersistence::load_user_data(user_id) {
let mut orders = user_data.orders;
// Sort by created_at in descending order (latest first)
orders.sort_by(|a, b| b.created_at.cmp(&a.created_at));
orders
} else {
Vec::new()
}
}
/// Update order status
pub fn update_order_status(&self, order_id: &str, status: OrderStatus) -> Result<(), String> {
let order_storage = OrderStorage::instance();
let mut storage = order_storage.lock().unwrap();
storage.update_order_status(order_id, status)
}
/// Process payment for order
pub fn process_payment(
&self,
order_id: &str,
payment_method: PaymentMethod,
) -> Result<PaymentResult, String> {
let order = {
let order_storage = OrderStorage::instance();
let storage = order_storage.lock().unwrap();
storage.get_order(order_id).cloned()
.ok_or("Order not found")?
};
// Check if this is a wallet payment and handle it with persistent data
let is_wallet_payment = matches!(payment_method,
PaymentMethod::Token { ref token_type, .. } if token_type == "USD"
);
let payment_result = if is_wallet_payment {
// Handle wallet payment with persistent data
self.process_wallet_payment_with_persistent_data(&order, &payment_method)?
} else {
// Use MockPaymentGateway for other payment methods
let payment_request = PaymentRequest {
order_id: order_id.to_string(),
amount: order.currency_total,
currency: order.currency_used.clone(),
payment_method: payment_method.clone(),
user_id: order.user_id.clone(),
};
let gateway = MockPaymentGateway { success_rate: 0.95 };
gateway.process_payment(payment_request)
};
// Update order with payment details
{
let order_storage = OrderStorage::instance();
let mut storage = order_storage.lock().unwrap();
if let Some(order) = storage.get_order_mut(order_id) {
if payment_result.success {
order.update_status(OrderStatus::Completed);
if let Some(payment_details) = &payment_result.payment_details {
order.set_payment_details(payment_details.clone());
}
// PHASE 1: Create application deployments for successful app orders
if let Err(e) = self.create_application_deployments_from_order(&order) {
}
// PHASE 2: Create service bookings for successful service orders
if let Err(e) = self.create_service_bookings_from_order(&order) {
}
} else {
order.update_status(OrderStatus::Failed);
}
}
}
Ok(payment_result)
}
/// Process wallet payment using persistent data (no mock code)
fn process_wallet_payment_with_persistent_data(
&self,
order: &Order,
payment_method: &PaymentMethod,
) -> Result<PaymentResult, String> {
use crate::services::user_persistence::UserPersistence;
// Extract user email from user_id (assuming user_id is email)
let user_email = &order.user_id;
// Load user data
let mut persistent_data = UserPersistence::load_user_data(user_email)
.ok_or_else(|| "User data not found".to_string())?;
// Check if user has sufficient balance
if persistent_data.wallet_balance_usd < order.currency_total {
let mut payment_details = PaymentDetails::new(
Uuid::new_v4().to_string(),
payment_method.clone(),
);
payment_details.mark_failed(format!(
"Insufficient balance. Required: ${:.2}, Available: ${:.2}",
order.currency_total,
persistent_data.wallet_balance_usd
));
return Ok(PaymentResult {
success: false,
transaction_id: None,
error_message: Some(format!(
"Insufficient balance. Required: ${:.2}, Available: ${:.2}",
order.currency_total,
persistent_data.wallet_balance_usd
)),
payment_details: Some(payment_details),
});
}
// Deduct balance
persistent_data.wallet_balance_usd -= order.currency_total;
// Create transaction record
let transaction_id = Uuid::new_v4().to_string();
// Use the first product ID for the transaction (TransactionType::Purchase only supports single product_id)
let product_id = order.items.first()
.map(|item| item.product_id.clone())
.unwrap_or_else(|| "unknown".to_string());
let transaction = crate::models::user::Transaction {
id: transaction_id.clone(),
user_id: user_email.clone(),
transaction_type: crate::models::user::TransactionType::Purchase {
product_id,
},
amount: order.currency_total,
currency: Some("USD".to_string()),
exchange_rate_usd: Some(rust_decimal::Decimal::ONE),
amount_usd: Some(order.currency_total),
description: Some(format!("Order {} payment", order.id)),
reference_id: Some(order.id.clone()),
metadata: None,
timestamp: chrono::Utc::now(),
status: crate::models::user::TransactionStatus::Completed,
};
// Add transaction to history
persistent_data.transactions.push(transaction);
// Build payment details now so we can persist them with the order
let mut payment_details = PaymentDetails::new(
transaction_id.clone(),
payment_method.clone(),
);
payment_details.mark_completed(transaction_id.clone());
// Persist the order as Completed with payment details for dashboard views
let mut order_to_persist = order.clone();
order_to_persist.update_status(OrderStatus::Completed);
order_to_persist.set_payment_details(payment_details.clone());
persistent_data.orders.push(order_to_persist);
// Save updated user data (includes transactions and updated order)
UserPersistence::save_user_data(&persistent_data)
.map_err(|e| format!("Failed to save user data: {}", e))?;
// Create successful payment result
Ok(PaymentResult {
success: true,
transaction_id: Some(transaction_id),
error_message: None,
payment_details: Some(payment_details),
})
}
/// Get order statistics
pub fn get_order_statistics(&self) -> HashMap<String, serde_json::Value> {
let order_storage = OrderStorage::instance();
let storage = order_storage.lock().unwrap();
let mut stats = HashMap::default();
stats.insert("total_orders".to_string(),
serde_json::Value::Number(serde_json::Number::from(storage.get_orders_count())));
// Orders by status
let mut status_counts: HashMap<String, i32> = HashMap::default();
for order in storage.get_all_orders() {
let status_str = format!("{:?}", order.status);
*status_counts.entry(status_str).or_insert(0) += 1;
}
let status_stats: Vec<serde_json::Value> = status_counts.iter()
.map(|(status, count)| {
serde_json::json!({
"status": status,
"count": count
})
})
.collect();
stats.insert("orders_by_status".to_string(), serde_json::Value::Array(status_stats));
// Revenue calculation
let total_revenue: Decimal = storage.get_all_orders().iter()
.filter(|o| matches!(o.status, OrderStatus::Completed | OrderStatus::Confirmed))
.map(|o| o.total_base)
.sum();
// Use CurrencyService base currency (USD) rather than mock data
let currency = self.currency_service.get_base_currency().code.clone();
stats.insert("total_revenue".to_string(), serde_json::json!({
"amount": total_revenue.to_string(),
"currency": currency
}));
stats
}
/// PHASE 1: Create application deployments when apps are successfully ordered
fn create_application_deployments_from_order(&self, order: &Order) -> Result<(), String> {
use crate::services::user_persistence::{UserPersistence, AppDeployment};
use crate::models::user::ResourceUtilization;
use chrono::Utc;
// Get customer information from order
let customer_email = order.user_id.clone();
let customer_name = if customer_email == "guest" {
"Guest User".to_string()
} else {
// Try to get customer name from persistent data
if let Some(customer_data) = UserPersistence::load_user_data(&customer_email) {
customer_data.name.unwrap_or_else(|| customer_email.clone())
} else {
customer_email.clone()
}
};
// Process each order item
for item in &order.items {
// Only create deployments for application products
if item.product_category == "application" {
// Find the application provider by looking up who published this app
if let Some(application_provider_email) = self.find_application_provider(&item.product_id) {
// Create deployment for each quantity ordered
for _i in 0..item.quantity {
let deployment_id = format!("dep-{}-{}",
&order.id[..8],
&uuid::Uuid::new_v4().to_string()[..8]
);
let deployment = AppDeployment::builder()
.id(&deployment_id)
.app_id(&item.product_id)
.app_name(&item.product_name)
.customer_name(&customer_name)
.customer_email(&customer_email)
.deployed_date(&Utc::now().format("%Y-%m-%d").to_string())
.status("Active")
.health_score(98.0 + (rand::random::<f32>() * 2.0)) // 98-100%
.region(&self.select_deployment_region())
.instances(1) // Default to 1 instance per deployment
.resource_usage(ResourceUtilization {
cpu: 15 + (rand::random::<i32>() % 20), // 15-35%
memory: 25 + (rand::random::<i32>() % 30), // 25-55%
storage: 10 + (rand::random::<i32>() % 15), // 10-25%
network: 5 + (rand::random::<i32>() % 10), // 5-15%
})
.monthly_revenue_usd((item.unit_price_base.to_f64().unwrap_or(0.0) * 0.8) as i32) // 80% to provider
.last_updated(&Utc::now().format("%Y-%m-%d %H:%M:%S").to_string())
.auto_healing(true) // Default to enabled for new deployments
.build()
.unwrap();
// Add deployment to application provider's data
if let Err(e) = UserPersistence::add_user_application_deployment(&application_provider_email, deployment.clone()) {
} else {
}
// Also add deployment to customer's data (for future user dashboard)
if customer_email != "guest" {
if let Err(e) = UserPersistence::add_user_application_deployment(&customer_email, deployment) {
} else {
}
}
}
} else {
}
}
}
Ok(())
}
/// Find the application provider (user who published the app) by product ID
fn find_application_provider(&self, product_id: &str) -> Option<String> {
// Get all user data files and search for the app
let user_data_dir = std::path::Path::new("user_data");
if !user_data_dir.exists() {
return None;
}
if let Ok(entries) = std::fs::read_dir(user_data_dir) {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json")
&& file_name.contains("_at_")
&& !file_name.contains("_cart")
&& file_name != "session_data.json"
{
// Extract email from filename
let user_email = file_name
.trim_end_matches(".json")
.replace("_at_", "@")
.replace("_", ".");
// Check if this user has the app
let user_apps = UserPersistence::get_user_apps(&user_email);
for app in user_apps {
if app.id == product_id {
return Some(user_email);
}
}
}
}
}
}
None
}
/// Select a deployment region (simple round-robin for demo)
fn select_deployment_region(&self) -> String {
let regions = vec![
"US-East-1", "US-West-2", "EU-Central-1",
"Asia-Pacific-1", "Canada-Central-1"
];
let index = (rand::random::<usize>()) % regions.len();
regions[index].to_string()
}
/// PHASE 2: Create service bookings when services are successfully ordered
pub fn create_service_bookings_from_order(&self, order: &Order) -> Result<(), String> {
use crate::services::user_persistence::{UserPersistence};
use crate::models::user::{ServiceRequest};
use chrono::Utc;
// Get customer information from order
let customer_email = order.user_id.clone();
let customer_name = if customer_email == "guest" {
"Guest User".to_string()
} else {
// Try to get customer name from persistent data
if let Some(customer_data) = UserPersistence::load_user_data(&customer_email) {
customer_data.name.unwrap_or_else(|| customer_email.clone())
} else {
customer_email.clone()
}
};
log::info!(
target: "order_service",
"create_service_bookings_from_order:start order_id={} customer_email={}",
order.id,
customer_email
);
// Process each order item
for item in &order.items {
// Only create bookings for service products
if item.product_category == "service" {
// Find the service provider by looking up who published this service
if let Some(service_provider_email) = self.find_service_provider(&item.product_id) {
log::info!(
target: "order_service",
"provider_found order_id={} product_id={} provider_email={}",
order.id,
item.product_id,
service_provider_email
);
// Create service request for provider (same as existing pattern)
for _i in 0..item.quantity {
let request_id = format!("req-{}-{}",
&order.id[..8],
&uuid::Uuid::new_v4().to_string()[..8]
);
let service_request = ServiceRequest {
id: request_id.clone(),
customer_email: customer_email.clone(),
service_id: item.product_id.clone(),
description: Some(format!("Service booking from marketplace order {}", order.id)),
status: "Pending".to_string(),
estimated_hours: Some((item.unit_price_base.to_f64().unwrap_or(0.0) / 75.0) as i32), // Assume $75/hour
hourly_rate_usd: Some(rust_decimal::Decimal::from(75)),
total_cost_usd: Some(item.unit_price_base),
progress_percentage: Some(0.0),
created_date: Some(Utc::now().format("%Y-%m-%d").to_string()),
completed_date: None,
progress: None,
priority: "Medium".to_string(),
hours_worked: None,
notes: None,
service_name: item.product_name.clone(),
budget: item.unit_price_base,
requested_date: Utc::now().format("%Y-%m-%d").to_string(),
client_phone: None,
client_name: Some(customer_name.clone()),
client_email: Some(customer_email.clone()),
};
// Add request to service provider's data
if let Err(e) = UserPersistence::add_user_service_request(&service_provider_email, service_request.clone()) {
log::error!(
target: "order_service",
"add_user_service_request:failed order_id={} provider_email={} request_id={} err={}",
order.id,
service_provider_email,
request_id,
e
);
return Err(format!(
"Failed to persist service request for provider {}: {}",
service_provider_email, e
));
} else {
log::info!(
target: "order_service",
"add_user_service_request:succeeded order_id={} provider_email={} request_id={}",
order.id,
service_provider_email,
request_id
);
}
// Create service booking for customer
let service_booking = UserPersistence::create_service_booking_from_request(
&service_request,
&customer_email,
&service_provider_email
);
// Add booking to customer's data
if customer_email != "guest" {
if let Err(e) = UserPersistence::add_user_service_booking(&customer_email, service_booking.clone()) {
log::error!(
target: "order_service",
"add_user_service_booking:failed order_id={} customer_email={} booking_id={} err={}",
order.id,
customer_email,
service_booking.id,
e
);
return Err(format!(
"Failed to persist service booking for customer {}: {}",
customer_email, e
));
} else {
log::info!(
target: "order_service",
"add_user_service_booking:succeeded order_id={} customer_email={} booking_id={}",
order.id,
customer_email,
service_booking.id
);
}
}
}
} else {
log::warn!(
target: "order_service",
"provider_not_found order_id={} product_id={} product_name={}",
order.id,
item.product_id,
item.product_name
);
}
}
}
log::info!(
target: "order_service",
"create_service_bookings_from_order:success order_id={}",
order.id
);
Ok(())
}
/// Find the service provider (user who published the service) by product ID
fn find_service_provider(&self, product_id: &str) -> Option<String> {
// Search per-user persisted data first
let user_data_dir = std::path::Path::new("user_data");
if user_data_dir.exists() {
if let Ok(entries) = std::fs::read_dir(user_data_dir) {
for entry in entries.flatten() {
if let Some(file_name) = entry.file_name().to_str() {
if file_name.ends_with(".json")
&& file_name.contains("_at_")
&& !file_name.contains("_cart")
&& file_name != "session_data.json"
{
// Extract email from filename
let user_email = file_name
.trim_end_matches(".json")
.replace("_at_", "@")
.replace("_", ".");
// Check if this user has the service
let user_services = UserPersistence::get_user_services(&user_email);
for service in user_services {
if service.id == product_id {
return Some(user_email);
}
}
// Also check if this user has the product (published services live under products)
let user_products = UserPersistence::get_user_products(&user_email);
for product in user_products {
if product.id == product_id {
return Some(user_email);
}
}
}
}
}
}
}
// Fallback: look up in the global product catalog to resolve provider
let all_products = self.product_service.get_all_products();
if let Some(p) = all_products.into_iter().find(|p| p.id == product_id) {
return Some(p.provider_id);
}
// If nothing matched, return None
None
}
}
/// Cart details with product information and pricing
#[derive(Debug, Clone, Serialize)]
pub struct CartDetails {
pub cart: Cart,
pub items: Vec<CartItemDetails>,
pub subtotal: Decimal,
pub total: Decimal,
pub currency: String,
pub item_count: u32,
}
/// Cart item with product details and pricing
#[derive(Debug, Clone, Serialize)]
pub struct CartItemDetails {
pub cart_item: CartItem,
pub product: Product,
pub unit_price: crate::models::currency::Price,
pub total_price: Decimal,
}
impl MockPaymentGateway {
pub fn process_payment(&self, request: PaymentRequest) -> PaymentResult {
// Simulate payment processing delay
std::thread::sleep(std::time::Duration::from_millis(100));
// Simulate success/failure based on success rate
let success = rand::random::<f32>() < self.success_rate;
if success {
let transaction_id = Uuid::new_v4().to_string();
let mut payment_details = PaymentDetails::new(
Uuid::new_v4().to_string(),
request.payment_method,
);
payment_details.mark_completed(transaction_id.clone());
PaymentResult {
success: true,
transaction_id: Some(transaction_id),
error_message: None,
payment_details: Some(payment_details),
}
} else {
let mut payment_details = PaymentDetails::new(
Uuid::new_v4().to_string(),
request.payment_method,
);
payment_details.mark_failed("Payment processing failed".to_string());
PaymentResult {
success: false,
transaction_id: None,
error_message: Some("Payment processing failed. Please try again.".to_string()),
payment_details: Some(payment_details),
}
}
}
}
impl Default for OrderService {
fn default() -> Self {
Self::new()
}
}
/// Utility functions for order operations
pub mod utils {
use super::*;
/// Calculate order total with taxes and fees
pub fn calculate_order_total(
subtotal: Decimal,
tax_rate: Option<f32>,
shipping_fee: Option<Decimal>,
discount: Option<Decimal>,
) -> Decimal {
let mut total = subtotal;
// Apply tax
if let Some(rate) = tax_rate {
total += subtotal * Decimal::from_f32_retain(rate).unwrap_or(dec!(0));
}
// Add shipping
if let Some(shipping) = shipping_fee {
total += shipping;
}
// Apply discount
if let Some(discount_amount) = discount {
total -= discount_amount;
}
total.max(dec!(0)) // Ensure total is not negative
}
/// Generate order confirmation number
pub fn generate_order_confirmation(order_id: &str) -> String {
let timestamp = Utc::now().timestamp();
let short_id = &order_id[..8];
format!("TF-{}-{}", timestamp, short_id.to_uppercase())
}
/// Validate payment method
pub fn validate_payment_method(payment_method: &PaymentMethod) -> Result<(), String> {
match payment_method {
PaymentMethod::CreditCard { last_four, .. } => {
if last_four.len() != 4 || !last_four.chars().all(|c| c.is_ascii_digit()) {
return Err("Invalid credit card last four digits".to_string());
}
},
PaymentMethod::BankTransfer { account_last_four, .. } => {
if account_last_four.len() != 4 || !account_last_four.chars().all(|c| c.is_ascii_digit()) {
return Err("Invalid bank account last four digits".to_string());
}
},
PaymentMethod::Cryptocurrency { wallet_address, .. } => {
if wallet_address.is_empty() {
return Err("Wallet address is required".to_string());
}
},
PaymentMethod::Token { wallet_address, .. } => {
if wallet_address.is_empty() {
return Err("Wallet address is required".to_string());
}
},
PaymentMethod::Mock { .. } => {
// Mock payment method is always valid
},
}
Ok(())
}
}