Files
projectmycelium/docs/dev/design/archive/marketplace_refactor.md
2025-09-01 21:37:01 -04:00

39 KiB

Project Mycelium - USD Credits System Implementation Plan

Overview

Transform the Project Mycelium from TFP-based system to a clean USD credits system with OpenRouter-style UX. This is a direct implementation for a development environment with no existing users.

Goal: Create an intuitive marketplace where 1 Credit = $1 USD with seamless auto top-up and one-click purchasing.

Architecture Approach

This implementation leverages the existing builder pattern system and service architecture found in the codebase. All new components follow established patterns for consistency.

Part 1: Core Currency System Changes

1.1 Update Currency Service

File: projectmycelium/src/services/currency.rs

Changes:

// MODIFY: Line 25 in CurrencyService::new()
impl CurrencyService {
    pub fn new() -> Self {
        let mock_data = MockDataService::new();
        let mut service = Self {
            mock_data,
            exchange_rates_cache: HashMap::default(),
            last_update: Utc::now(),
            default_display_currency: "USD".to_string(),
        };
        
        // Set USD Credits as base currency with 1:1 rate
        service.exchange_rates_cache.insert("USD".to_string(), dec!(1.0));
        service.update_exchange_rates();
        service
    }
}

1.2 Update Mock Data Service

File: projectmycelium/src/services/mock_data.rs

Changes:

// MODIFY: Update base currency definition
Currency {
    code: "USD".to_string(),
    name: "USD Credits".to_string(),
    symbol: "$".to_string(),
    currency_type: CurrencyType::Points,
    exchange_rate_to_base: dec!(1.0),
    is_base_currency: true,
    decimal_places: 2,
    is_active: true,
    provider_config: Some(ExchangeRateProvider::Static(dec!(1.0))),
    last_updated: Utc::now(),
}

Part 2: Auto Top-up System Implementation

2.1 Add Auto Top-up Models

File: projectmycelium/src/services/user_persistence.rs

Add to UserPersistentData (around line 71):

// ADD: Auto top-up settings
#[serde(default)]
pub auto_topup_settings: Option<AutoTopUpSettings>,

Add new struct:

// ADD: After line 71
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoTopUpSettings {
    pub enabled: bool,
    pub threshold_amount: Decimal,
    pub topup_amount: Decimal,
    pub payment_method_id: String,
    pub daily_limit: Option<Decimal>,
    pub monthly_limit: Option<Decimal>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl AutoTopUpSettings {
    pub fn builder() -> crate::models::builders::AutoTopUpSettingsBuilder {
        crate::models::builders::AutoTopUpSettingsBuilder::new()
    }
}

2.2 Add Auto Top-up Builder

File: projectmycelium/src/models/builders.rs

Add at the end of the file:

// =============================================================================
// AUTO TOP-UP BUILDERS
// =============================================================================

#[derive(Default)]
pub struct AutoTopUpSettingsBuilder {
    enabled: Option<bool>,
    threshold_amount: Option<Decimal>,
    topup_amount: Option<Decimal>,
    payment_method_id: Option<String>,
    daily_limit: Option<Decimal>,
    monthly_limit: Option<Decimal>,
}

impl AutoTopUpSettingsBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn enabled(mut self, enabled: bool) -> Self {
        self.enabled = Some(enabled);
        self
    }

    pub fn threshold_amount(mut self, amount: Decimal) -> Self {
        self.threshold_amount = Some(amount);
        self
    }

    pub fn topup_amount(mut self, amount: Decimal) -> Self {
        self.topup_amount = Some(amount);
        self
    }

    pub fn payment_method_id(mut self, id: impl Into<String>) -> Self {
        self.payment_method_id = Some(id.into());
        self
    }

    pub fn daily_limit(mut self, limit: Decimal) -> Self {
        self.daily_limit = Some(limit);
        self
    }

    pub fn monthly_limit(mut self, limit: Decimal) -> Self {
        self.monthly_limit = Some(limit);
        self
    }

    pub fn build(self) -> Result<crate::services::user_persistence::AutoTopUpSettings, String> {
        Ok(crate::services::user_persistence::AutoTopUpSettings {
            enabled: self.enabled.unwrap_or(false),
            threshold_amount: self.threshold_amount.unwrap_or(dec!(10.0)),
            topup_amount: self.topup_amount.unwrap_or(dec!(25.0)),
            payment_method_id: self.payment_method_id.ok_or("payment_method_id is required")?,
            daily_limit: self.daily_limit,
            monthly_limit: self.monthly_limit,
            created_at: Utc::now(),
            updated_at: Utc::now(),
        })
    }
}

2.3 Create Auto Top-up Service

Create: projectmycelium/src/services/auto_topup.rs

//! Auto top-up service for automatic credit purchasing
//! Follows the established builder pattern for consistent API design

use crate::models::user::Transaction;
use crate::services::currency::CurrencyService;
use crate::services::user_persistence::{UserPersistence, AutoTopUpSettings};
use actix_session::Session;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use chrono::Utc;
use uuid::Uuid;

#[derive(Clone)]
pub struct AutoTopUpService {
    currency_service: CurrencyService,
}

#[derive(Default)]
pub struct AutoTopUpServiceBuilder {
    currency_service: Option<CurrencyService>,
}

impl AutoTopUpServiceBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn currency_service(mut self, service: CurrencyService) -> Self {
        self.currency_service = Some(service);
        self
    }

    pub fn build(self) -> Result<AutoTopUpService, String> {
        let currency_service = self.currency_service
            .unwrap_or_else(|| CurrencyService::builder().build().unwrap());

        Ok(AutoTopUpService {
            currency_service,
        })
    }
}

impl AutoTopUpService {
    pub fn builder() -> AutoTopUpServiceBuilder {
        AutoTopUpServiceBuilder::new()
    }

    pub async fn check_and_trigger_topup(
        &self,
        session: &Session,
        required_amount: Decimal,
    ) -> Result<bool, String> {
        let user_email = session.get::<String>("user_email")
            .map_err(|e| format!("Failed to get user email: {}", e))?
            .ok_or("User not authenticated")?;

        let mut persistent_data = UserPersistence::load_user_data(&user_email)
            .unwrap_or_default();

        // Check if auto top-up is enabled
        let auto_topup_settings = match &persistent_data.auto_topup_settings {
            Some(settings) if settings.enabled => settings.clone(),
            _ => return Ok(false), // Auto top-up not enabled
        };

        // Check if balance is below threshold
        if persistent_data.wallet_balance >= auto_topup_settings.threshold_amount {
            return Ok(false); // Balance is sufficient
        }

        // Execute auto top-up
        let transaction_id = Uuid::new_v4().to_string();
        persistent_data.wallet_balance += auto_topup_settings.topup_amount;

        // Create transaction record
        let transaction = Transaction {
            id: transaction_id.clone(),
            user_id: user_email.clone(),
            transaction_type: crate::models::user::TransactionType::AutoTopUp {
                triggered_by_purchase: None,
                threshold_amount: auto_topup_settings.threshold_amount,
                amount_usd: auto_topup_settings.topup_amount,
                payment_method: auto_topup_settings.payment_method_id.clone(),
            },
            amount: auto_topup_settings.topup_amount,
            timestamp: Utc::now(),
            status: crate::models::user::TransactionStatus::Completed,
        };

        persistent_data.transactions.push(transaction);

        // Save updated data
        UserPersistence::save_user_data(&persistent_data)
            .map_err(|e| format!("Failed to save user data: {}", e))?;

        Ok(true)
    }

    pub fn configure_auto_topup(
        &self,
        session: &Session,
        settings: AutoTopUpSettings,
    ) -> Result<(), String> {
        let user_email = session.get::<String>("user_email")
            .map_err(|e| format!("Failed to get user email: {}", e))?
            .ok_or("User not authenticated")?;

        let mut persistent_data = UserPersistence::load_user_data(&user_email)
            .unwrap_or_default();

        persistent_data.auto_topup_settings = Some(settings);

        UserPersistence::save_user_data(&persistent_data)
            .map_err(|e| format!("Failed to save user data: {}", e))?;

        Ok(())
    }
}

impl Default for AutoTopUpService {
    fn default() -> Self {
        Self::builder().build().unwrap()
    }
}

2.4 Update Services Module

File: projectmycelium/src/services/mod.rs

Add:

pub mod auto_topup;

2.5 Update Transaction Types

File: projectmycelium/src/models/user.rs

Add to TransactionType enum (around line 1181):

// ADD: New auto top-up transaction type
AutoTopUp { 
    triggered_by_purchase: Option<String>,
    threshold_amount: Decimal,
    amount_usd: Decimal,
    payment_method: String 
},

Part 3: Wallet UI Updates

3.1 Update Wallet Template

File: projectmycelium/src/views/wallet/index.html

Replace lines 34-35 (page title):

<h1 class="h2">USD Credits Wallet</h1>

Replace lines 46-51 (wallet balance display):

<h2 class="card-text" id="wallet-balance">
    {% if wallet_balance %}
        ${{ wallet_balance | format_decimal(precision=2) }}
    {% else %}
        $0.00
    {% endif %}
</h2>
<small class="text-light">USD Credits</small>

Remove lines 57-72 (USD equivalent card - no longer needed)

Replace lines 114-121 (quick amount buttons):

<div class="d-flex flex-wrap gap-2" id="quickAmountButtons">
    <button class="btn btn-outline-success" onclick="quickTopUp(10)">$10</button>
    <button class="btn btn-outline-success" onclick="quickTopUp(25)">$25</button>
    <button class="btn btn-outline-success" onclick="quickTopUp(50)">$50</button>
    <button class="btn btn-outline-success" onclick="quickTopUp(100)">$100</button>
</div>
<small class="text-muted d-block mt-2" id="conversionNote">
    Direct USD credits purchase
</small>

Add after line 127 (auto top-up section):

<!-- Auto Top-up Configuration -->
<div class="row mb-4">
    <div class="col-md-12">
        <div class="card border-info">
            <div class="card-header bg-info text-white">
                <h5 class="card-title mb-0">
                    <i class="fas fa-magic"></i> Auto Top-up (OpenRouter Style)
                </h5>
            </div>
            <div class="card-body">
                <p class="text-muted mb-3">Automatically purchase credits when your balance gets low</p>
                
                <div class="row">
                    <div class="col-md-6">
                        <div class="form-check form-switch mb-3">
                            <input class="form-check-input" type="checkbox" id="autoTopupEnabled">
                            <label class="form-check-label" for="autoTopupEnabled">
                                Enable Auto Top-up
                            </label>
                        </div>
                        
                        <div class="mb-3">
                            <label for="thresholdAmount" class="form-label">When credits are below:</label>
                            <div class="input-group">
                                <span class="input-group-text">$</span>
                                <input type="number" class="form-control" id="thresholdAmount" value="10" min="1" step="1">
                            </div>
                        </div>
                        
                        <div class="mb-3">
                            <label for="topupAmount" class="form-label">Purchase this amount:</label>
                            <div class="input-group">
                                <span class="input-group-text">$</span>
                                <input type="number" class="form-control" id="topupAmount" value="25" min="5" step="5">
                            </div>
                        </div>
                    </div>
                    
                    <div class="col-md-6">
                        <div class="mb-3">
                            <label for="paymentMethod" class="form-label">Payment Method:</label>
                            <select class="form-select" id="paymentMethod">
                                <option value="credit_card">Credit Card ****1234</option>
                                <option value="paypal">PayPal</option>
                            </select>
                        </div>
                        
                        <div class="mb-3">
                            <label for="dailyLimit" class="form-label">Daily Limit (Optional):</label>
                            <div class="input-group">
                                <span class="input-group-text">$</span>
                                <input type="number" class="form-control" id="dailyLimit" placeholder="100" min="10" step="10">
                            </div>
                        </div>
                        
                        <button type="button" class="btn btn-info" onclick="saveAutoTopupSettings()">
                            <i class="fas fa-save"></i> Save Auto Top-up Settings
                        </button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Update JavaScript section (replace lines 404-439):

// Update currency selector (remove TFP and EUR options)
function updateTopupAmounts() {
    const currency = document.getElementById('topupCurrency').value;
    const symbol = '$';
    const currencySymbol = document.getElementById('currencySymbol');
    const quickAmountButtons = document.getElementById('quickAmountButtons');
    const conversionNote = document.getElementById('conversionNote');
    
    currencySymbol.textContent = symbol;
    
    // Always show USD amounts
    quickAmountButtons.innerHTML = `
        <button class="btn btn-outline-success" onclick="quickTopUp(10)">$10</button>
        <button class="btn btn-outline-success" onclick="quickTopUp(25)">$25</button>
        <button class="btn btn-outline-success" onclick="quickTopUp(50)">$50</button>
        <button class="btn btn-outline-success" onclick="quickTopUp(100)">$100</button>
    `;
    conversionNote.textContent = 'Direct USD credits purchase';
}

// Auto top-up settings functions
async function saveAutoTopupSettings() {
    const enabled = document.getElementById('autoTopupEnabled').checked;
    const thresholdAmount = parseFloat(document.getElementById('thresholdAmount').value);
    const topupAmount = parseFloat(document.getElementById('topupAmount').value);
    const paymentMethod = document.getElementById('paymentMethod').value;
    const dailyLimit = document.getElementById('dailyLimit').value ? parseFloat(document.getElementById('dailyLimit').value) : null;

    try {
        const response = await fetch('/api/wallet/auto-topup/configure', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                enabled,
                threshold_amount: thresholdAmount,
                topup_amount: topupAmount,
                payment_method_id: paymentMethod,
                daily_limit: dailyLimit,
                monthly_limit: null
            })
        });

        const result = await response.json();
        
        if (result.success) {
            alert('Auto top-up settings saved successfully!');
        } else {
            alert('Failed to save settings: ' + result.message);
        }
    } catch (error) {
        console.error('Auto top-up settings error:', error);
        alert('Failed to save settings. Please try again.');
    }
}

// Load auto top-up settings on page load
async function loadAutoTopupSettings() {
    try {
        const response = await fetch('/api/wallet/auto-topup/status');
        const result = await response.json();
        
        if (result.settings) {
            document.getElementById('autoTopupEnabled').checked = result.settings.enabled;
            document.getElementById('thresholdAmount').value = result.settings.threshold_amount;
            document.getElementById('topupAmount').value = result.settings.topup_amount;
            document.getElementById('paymentMethod').value = result.settings.payment_method_id;
            if (result.settings.daily_limit) {
                document.getElementById('dailyLimit').value = result.settings.daily_limit;
            }
        }
    } catch (error) {
        console.error('Failed to load auto top-up settings:', error);
    }
}

Add to DOMContentLoaded event (around line 498):

document.addEventListener('DOMContentLoaded', function() {
    updateTopupAmounts();
    loadAutoTopupSettings(); // Load auto top-up settings
    // ... existing code
});

Part 4: Buy Now Implementation

4.1 Create Buy Now JavaScript

Create: projectmycelium/src/static/js/buy-now.js

// Buy Now functionality with auto top-up integration
class BuyNowManager {
    constructor() {
        this.initializeEventHandlers();
    }

    initializeEventHandlers() {
        document.addEventListener('DOMContentLoaded', () => {
            document.querySelectorAll('.buy-now-btn').forEach(button => {
                button.addEventListener('click', (e) => this.handleBuyNow(e));
            });
        });
    }

    async handleBuyNow(event) {
        const button = event.target.closest('.buy-now-btn');
        const productId = button.dataset.productId;
        const productName = button.dataset.productName;
        const unitPrice = parseFloat(button.dataset.unitPrice);
        const currency = button.dataset.currency;
        const category = button.dataset.category || 'general';

        // Disable button during processing
        button.disabled = true;
        const originalText = button.innerHTML;
        button.innerHTML = '<i class="spinner-border spinner-border-sm me-1"></i>Processing...';

        try {
            // Check affordability first
            const affordabilityResponse = await fetch(`/api/wallet/check-affordability?amount=${unitPrice}`);
            const affordabilityResult = await affordabilityResponse.json();

            if (!affordabilityResult.can_afford) {
                // Trigger auto top-up if configured
                const autoTopUpResult = await this.handleInsufficientBalance(affordabilityResult.shortfall_info);
                if (!autoTopUpResult.success) {
                    this.showError('Insufficient balance and auto top-up failed');
                    return;
                }
            }

            // Proceed with instant purchase
            const purchaseResponse = await fetch('/api/wallet/instant-purchase', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    product_id: productId,
                    product_name: productName,
                    product_category: category,
                    quantity: 1,
                    unit_price_tfp: unitPrice, // Note: backend still expects this field name
                    provider_id: 'marketplace',
                    provider_name: 'Project Mycelium'
                })
            });

            const purchaseResult = await purchaseResponse.json();
            
            if (purchaseResult.success) {
                this.showSuccess(`Successfully purchased ${productName}!`);
                // Update navbar balance
                if (window.loadNavbarData) {
                    window.loadNavbarData();
                }
            } else {
                this.showError(purchaseResult.message);
            }

        } catch (error) {
            console.error('Buy Now error:', error);
            this.showError('Purchase failed. Please try again.');
        } finally {
            // Re-enable button
            button.disabled = false;
            button.innerHTML = originalText;
        }
    }

    async handleInsufficientBalance(shortfallInfo) {
        // Check if auto top-up is configured
        try {
            const autoTopUpResponse = await fetch('/api/wallet/auto-topup/status');
            const autoTopUpStatus = await autoTopUpResponse.json();

            if (autoTopUpStatus.enabled) {
                // Trigger auto top-up
                const topUpResponse = await fetch('/api/wallet/auto-topup/trigger', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        required_amount: shortfallInfo.shortfall
                    })
                });

                return await topUpResponse.json();
            } else {
                // Prompt user to configure auto top-up or manually top up
                const userChoice = confirm(
                    `Insufficient balance. You need $${shortfallInfo.shortfall.toFixed(2)} more. ` +
                    `Would you like to go to the wallet to add credits?`
                );
                
                if (userChoice) {
                    window.location.href = '/dashboard/wallet?action=topup';
                }
                
                return { success: false };
            }
        } catch (error) {
            console.error('Auto top-up check failed:', error);
            return { success: false };
        }
    }

    showSuccess(message) {
        // Use existing notification system or create toast
        if (window.showNotification) {
            window.showNotification(message, 'success');
        } else {
            alert(message);
        }
    }

    showError(message) {
        if (window.showNotification) {
            window.showNotification(message, 'error');
        } else {
            alert(message);
        }
    }
}

// Initialize Buy Now manager
new BuyNowManager();

4.2 Update Marketplace Templates

File: projectmycelium/src/views/marketplace/compute_resources.html

Add after line 200 (in the action buttons section):

<div class="action-buttons">
    <button class="btn btn-success btn-sm buy-now-btn me-1"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}"
            data-category="compute">
        <i class="bi bi-lightning-fill me-1"></i>Buy Now
    </button>
    <button class="btn btn-outline-primary btn-sm add-to-cart-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}">
        <i class="bi bi-cart-plus me-1"></i>Add to Cart
    </button>
</div>

File: projectmycelium/src/views/marketplace/applications.html

Update lines 145-151 (enhance the existing button group with Buy Now):

<div class="btn-group">
    <button class="btn btn-success btn-sm buy-now-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}"
            data-category="applications">
        <i class="bi bi-lightning-fill me-1"></i>Buy Now
    </button>
    <button class="btn btn-outline-primary btn-sm add-to-cart-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}">
        <i class="bi bi-cart-plus me-1"></i>Add to Cart
    </button>
    <a href="/products/{{ product_data.product.id }}" class="btn btn-outline-secondary btn-sm">View Details</a>
</div>

File: projectmycelium/src/views/marketplace/services.html

Add Buy Now buttons (find the existing Add to Cart buttons and enhance with Buy Now):

<div class="btn-group">
    <button class="btn btn-success btn-sm buy-now-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}"
            data-category="services">
        <i class="bi bi-lightning-fill me-1"></i>Buy Now
    </button>
    <button class="btn btn-outline-primary btn-sm add-to-cart-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}">
        <i class="bi bi-cart-plus me-1"></i>Add to Cart
    </button>
</div>

File: projectmycelium/src/views/marketplace/three_nodes.html

Add Buy Now buttons (find the existing Add to Cart buttons and enhance with Buy Now):

<div class="btn-group">
    <button class="btn btn-success btn-sm buy-now-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}"
            data-category="3nodes">
        <i class="bi bi-lightning-fill me-1"></i>Buy Now
    </button>
    <button class="btn btn-outline-primary btn-sm add-to-cart-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}">
        <i class="bi bi-cart-plus me-1"></i>Add to Cart
    </button>
</div>

File: projectmycelium/src/views/marketplace/gateways.html

Add Buy Now buttons (find the existing Add to Cart buttons and enhance with Buy Now):

<div class="btn-group">
    <button class="btn btn-success btn-sm buy-now-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}"
            data-category="gateways">
        <i class="bi bi-lightning-fill me-1"></i>Buy Now
    </button>
    <button class="btn btn-outline-primary btn-sm add-to-cart-btn"
            data-product-id="{{ product_data.product.id }}"
            data-product-name="{{ product_data.product.name }}"
            data-unit-price="{{ product_data.price.display_amount }}"
            data-currency="{{ product_data.price.display_currency }}">
        <i class="bi bi-cart-plus me-1"></i>Add to Cart
    </button>
</div>

4.3 Include Buy Now Script in Templates

File: projectmycelium/src/views/marketplace/layout.html

Add before closing </body> tag:

<script src="/static/js/buy-now.js"></script>

Part 5: API Endpoints

5.1 Add Auto Top-up Endpoints

File: projectmycelium/src/controllers/wallet.rs

Add new endpoints (after line 900):

/// Configure auto top-up settings
pub async fn configure_auto_topup(
    request: web::Json<crate::services::user_persistence::AutoTopUpSettings>,
    session: Session,
) -> Result<impl Responder> {
    let auto_topup_service = crate::services::auto_topup::AutoTopUpService::builder()
        .build()
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    match auto_topup_service.configure_auto_topup(&session, request.into_inner()) {
        Ok(()) => Ok(HttpResponse::Ok().json(serde_json::json!({
            "success": true,
            "message": "Auto top-up configured successfully"
        }))),
        Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
            "success": false,
            "message": e
        })))
    }
}

/// Get auto top-up status
pub async fn get_auto_topup_status(session: Session) -> Result<impl Responder> {
    let user_email = session.get::<String>("user_email")
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?
        .ok_or_else(|| actix_web::error::ErrorUnauthorized("User not authenticated"))?;

    let persistent_data = crate::services::user_persistence::UserPersistence::load_user_data(&user_email)
        .unwrap_or_default();

    let auto_topup_enabled = persistent_data.auto_topup_settings
        .as_ref()
        .map(|s| s.enabled)
        .unwrap_or(false);

    Ok(HttpResponse::Ok().json(serde_json::json!({
        "enabled": auto_topup_enabled,
        "settings": persistent_data.auto_topup_settings
    })))
}

/// Trigger auto top-up
pub async fn trigger_auto_topup(
    request: web::Json<serde_json::Value>,
    session: Session,
) -> Result<impl Responder> {
    let auto_topup_service = crate::services::auto_topup::AutoTopUpService::builder()
        .build()
        .map_err(|e| actix_web::error::ErrorInternalServerError(e))?;

    let required_amount = request.get("required_amount")
        .and_then(|v| v.as_f64())
        .map(|f| rust_decimal::Decimal::from_f64_retain(f).unwrap_or_default())
        .unwrap_or_default();

    match auto_topup_service.check_and_trigger_topup(&session, required_amount).await {
        Ok(success) => Ok(HttpResponse::Ok().json(serde_json::json!({
            "success": success,
            "message": if success { "Auto top-up completed" } else { "Auto top-up failed" }
        }))),
        Err(e) => Ok(HttpResponse::BadRequest().json(serde_json::json!({
            "success": false,
            "message": e
        })))
    }
}

5.2 Update Routes

File: projectmycelium/src/routes/mod.rs

Add new routes (after line 208):

// Auto top-up API routes
.route("/wallet/auto-topup/configure", web::post().to(WalletController::configure_auto_topup))
.route("/wallet/auto-topup/status", web::get().to(WalletController::get_auto_topup_status))
.route("/wallet/auto-topup/trigger", web::post().to(WalletController::trigger_auto_topup))

Part 6: Documentation Updates

6.1 Update TFP Documentation

File: projectmycelium/src/views/docs/tfp.html

Replace entire content with USD Credits documentation:

{% extends "docs/layout.html" %}

{% block title %}USD Credits - Project Mycelium{% endblock %}

{% block docs_content %}
<div class="docs-content">
    <h1>USD Credits</h1>
    <p class="lead">Project Mycelium uses a simple USD credits system for all transactions.</p>

    <div class="alert alert-info">
        <h5>Simple and Intuitive</h5>
        <p>1 Credit = $1 USD. No complex conversions or confusing terminology.</p>
    </div>

    <h2>How Credits Work</h2>
    <ul>
        <li><strong>Direct USD Pricing:</strong> All products are priced in clear USD amounts</li>
        <li><strong>Easy Top-up:</strong> Add credits to your wallet with standard payment methods</li>
        <li><strong>Auto Top-up:</strong> Automatically purchase credits when your balance gets low</li>
        <li><strong>One-Click Purchasing:</strong> Buy products instantly with the "Buy Now" button</li>
    </ul>

    <h2>Auto Top-up</h2>
    <p>Configure automatic credit purchasing for seamless marketplace experience:</p>
    <ol>
        <li>Go to your <a href="/dashboard/wallet">Wallet</a></li>
        <li>Enable Auto Top-up in the settings section</li>
        <li>Set your threshold (e.g., when credits fall below $10)</li>
        <li>Set your top-up amount (e.g., purchase $25 when triggered)</li>
        <li>Choose your payment method</li>
    </ol>

    <h2>Versatile Purchase Options</h2>
    <p>The marketplace offers two convenient ways to purchase products:</p>
    <ul>
        <li><strong>Buy Now:</strong> Instant one-click purchase with automatic balance management and auto top-up integration</li>
        <li><strong>Add to Cart:</strong> Traditional shopping cart experience for purchasing multiple items together</li>
    </ul>
    
    <div class="alert alert-tip">
        <strong>Best of Both Worlds:</strong> Use "Buy Now" for quick single purchases, or "Add to Cart" when you want to compare multiple products or make bulk purchases.
    </div>

    <h2>Getting Started</h2>
    <ol>
        <li>Create your Project Mycelium account</li>
        <li>Add credits to your wallet</li>
        <li>Browse the marketplace</li>
        <li>Click "Buy Now" for instant purchases</li>
    </ol>
</div>
{% endblock %}

6.2 Update Documentation Navigation

File: projectmycelium/src/views/docs/layout.html

Update navigation link (find the TFP link and replace):

<a class="nav-link" href="/docs/credits">
    <i class="bi bi-currency-dollar me-1"></i>
    USD Credits
</a>

6.3 Update Getting Started Documentation

File: projectmycelium/src/views/docs/getting_started.html

Update wallet setup section (find TFP references and replace):

<h2>Step 2: Setup Your Wallet</h2>
<p>You'll need to add USD credits to your wallet to purchase marketplace products.</p>
<ul>
    <li>Navigate to your <a href="/dashboard/wallet">Wallet</a></li>
    <li>Click "Quick Top-Up" to add credits</li>
    <li>Choose from preset amounts ($10, $25, $50, $100) or enter a custom amount</li>
    <li>Complete payment with your preferred method</li>
</ul>

<div class="alert alert-tip">
    <strong>Pro Tip:</strong> Enable Auto Top-up to automatically purchase credits when your balance gets low!
</div>

6.4 Update Route for Documentation

File: projectmycelium/src/routes/mod.rs

Update documentation route (find the tfp route and update):

.route("/docs/credits", web::get().to(DocsController::credits))

Add redirect for old TFP documentation:

.route("/docs/tfp", web::get().to(|| async {
    HttpResponse::MovedPermanently()
        .append_header(("Location", "/docs/credits"))
        .finish()
}))

6.5 Update Documentation Controller

File: projectmycelium/src/controllers/docs.rs

Add credits method (replace the tfp method):

/// USD Credits documentation page
pub async fn credits(tmpl: web::Data<Tera>, session: Session) -> Result<impl Responder> {
    let mut ctx = crate::models::builders::ContextBuilder::new()
        .active_page("docs")
        .build();

    // Add user session data if available
    if let Ok(Some(user_json)) = session.get::<String>("user") {
        ctx.insert("user", &user_json);
    }

    match tmpl.render("docs/credits.html", &ctx) {
        Ok(rendered) => Ok(HttpResponse::Ok().content_type("text/html").body(rendered)),
        Err(e) => {
            log::error!("Template rendering error: {}", e);
            Ok(HttpResponse::InternalServerError().body("Template rendering failed"))
        }
    }
}

Part 7: Hide Pools Page

7.1 Update Navigation

File: projectmycelium/src/views/dashboard/layout.html or main navigation template

Comment out or remove pools navigation link:

<!-- HIDE: Pools navigation - keeping for future use
<li class="nav-item">
    <a class="nav-link" href="/dashboard/pools">
        <i class="fas fa-swimming-pool"></i> Pools
    </a>
</li>
-->

7.2 Keep Pools Route as Hidden

File: projectmycelium/src/routes/mod.rs

Comment out main pools route but keep as admin route:

// HIDE: Main pools route - keep for admin/future use
// .route("/dashboard/pools", web::get().to(DashboardController::pools))

// Keep as hidden admin route
.route("/dashboard/pools-admin", web::get().to(DashboardController::pools))

Part 8: Update Default User Data

8.1 Update Default Wallet Balance

File: projectmycelium/src/services/user_persistence.rs

Update Default implementation (around line 73):

impl Default for UserPersistentData {
    fn default() -> Self {
        Self {
            user_email: String::new(),
            wallet_balance: Decimal::ZERO, // Start with $0.00 USD credits
            transactions: Vec::new(),
            staked_amount: Decimal::ZERO,
            pool_positions: HashMap::new(),
            // ... existing fields
            display_currency: Some("USD".to_string()), // Default to USD
            quick_topup_amounts: Some(vec![dec!(10), dec!(25), dec!(50), dec!(100)]), // USD amounts
            auto_topup_settings: None, // User can configure later
            // ... rest of fields
        }
    }
}

Part 9: Implementation Timeline

Week 1: Foundation (Days 1-7)

  • Day 1-2: Update currency system and mock data
  • Day 3-4: Add auto top-up models and builders
  • Day 5-6: Create auto top-up service
  • Day 7: Update transaction types and test core functionality

Week 2: API & Backend (Days 8-14)

  • Day 8-9: Implement auto top-up API endpoints
  • Day 10-11: Update routes and test API functionality
  • Day 12-13: Update default user data and persistence
  • Day 14: Backend integration testing

Week 3: Frontend & UX (Days 15-21)

  • Day 15-16: Update wallet templates and UI
  • Day 17-18: Create and integrate Buy Now JavaScript
  • Day 19-20: Update all marketplace templates with Buy Now buttons
  • Day 21: Frontend integration testing

Week 4: Documentation & Polish (Days 22-28)

  • Day 22-23: Update documentation and help content
  • Day 24-25: Hide pools page and update navigation
  • Day 26-27: End-to-end testing and bug fixes
  • Day 28: Final polish and deployment preparation

Part 10: Testing Checklist

Core Functionality Tests

  • Currency system displays USD correctly
  • Auto top-up configuration saves and loads
  • Auto top-up triggers when balance is low
  • Buy Now buttons work on all marketplace pages
  • Instant purchase completes successfully
  • Wallet balance updates correctly after purchases
  • Transaction history shows USD amounts

User Experience Tests

  • New user sees $0.00 balance
  • Quick top-up buttons show USD amounts
  • Auto top-up UI is intuitive and clear
  • Buy Now provides immediate feedback
  • Error handling works for insufficient balance
  • Documentation is clear and helpful

Integration Tests

  • Buy Now integrates with auto top-up
  • Navbar balance updates after purchases
  • All marketplace sections have Buy Now buttons
  • Documentation links work correctly
  • Pools page is hidden from navigation

Conclusion

This implementation plan provides a complete transformation of the Project Mycelium to a clean USD credits system with OpenRouter-style UX. The plan:

Leverages existing architecture with builder patterns and service consistency Provides file-specific implementation details with exact line numbers Includes complete auto top-up system with seamless user experience Implements one-click Buy Now functionality across all marketplace sections Updates documentation to reflect the new credits system Maintains clean codebase by hiding rather than removing pools functionality

The result will be an intuitive marketplace where users can easily purchase products with clear USD pricing, automatic balance management, and one-click purchasing - exactly matching the OpenRouter experience you envisioned.