init projectmycelium

This commit is contained in:
mik-tf
2025-09-01 21:37:01 -04:00
commit b41efb0e99
319 changed files with 128160 additions and 0 deletions

View File

@@ -0,0 +1,633 @@
# ThreeFold Grid Deployment Automation Specification
**Document Purpose**: Technical specification for automated deployment pipeline from TFC payment to ThreeFold Grid service activation.
**Last Updated**: 2025-08-04
**Status**: Implementation Ready
---
## Overview
This document outlines the automated deployment pipeline that converts TFC credits to TFT tokens and deploys services on the ThreeFold Grid, providing seamless Web2-to-Web3 bridge functionality.
---
## Deployment Pipeline Architecture
```mermaid
graph TD
A[User Purchase Complete] --> B[TFC Deducted]
B --> C[Deployment Queued]
C --> D[Commission Calculated]
D --> E[USD → TFT Conversion]
E --> F[Grid Node Selection]
F --> G[Resource Allocation]
G --> H[Service Deployment]
H --> I[Health Check]
I --> J{Deployment Success?}
J -->|Yes| K[Service Active]
J -->|No| L[Rollback & Refund]
K --> M[User Notification]
L --> N[Error Notification]
```
---
## Core Components
### 1. Deployment Queue Service
```rust
// src/services/deployment_queue.rs
use tokio::sync::mpsc;
use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentJob {
pub id: String,
pub user_id: String,
pub service_spec: ServiceSpec,
pub tfc_amount: Decimal,
pub commission_amount: Decimal,
pub grid_payment_amount: Decimal,
pub priority: DeploymentPriority,
pub created_at: DateTime<Utc>,
pub max_retries: u32,
pub retry_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeploymentPriority {
Low,
Normal,
High,
Critical,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceSpec {
pub service_type: ServiceType,
pub cpu_cores: u32,
pub memory_gb: u32,
pub storage_gb: u32,
pub network_config: NetworkConfig,
pub environment_vars: HashMap<String, String>,
pub docker_image: Option<String>,
pub kubernetes_manifest: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServiceType {
VM { os: String, version: String },
Container { image: String, ports: Vec<u16> },
KubernetesCluster { node_count: u32 },
Storage { storage_type: String },
Application { app_id: String },
}
pub struct DeploymentQueueService {
queue: Arc<Mutex<VecDeque<DeploymentJob>>>,
workers: Vec<DeploymentWorker>,
tx: mpsc::UnboundedSender<DeploymentJob>,
rx: Arc<Mutex<mpsc::UnboundedReceiver<DeploymentJob>>>,
}
impl DeploymentQueueService {
pub fn new(worker_count: usize) -> Self {
let (tx, rx) = mpsc::unbounded_channel();
let queue = Arc::new(Mutex::new(VecDeque::new()));
let mut workers = Vec::new();
for i in 0..worker_count {
workers.push(DeploymentWorker::new(i, rx.clone()));
}
Self {
queue,
workers,
tx,
rx: Arc::new(Mutex::new(rx)),
}
}
pub async fn queue_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
// Add to persistent queue
self.queue.lock().await.push_back(job.clone());
// Send to worker pool
self.tx.send(job).map_err(|_| DeploymentError::QueueFull)?;
Ok(())
}
pub async fn start_workers(&self) {
for worker in &self.workers {
worker.start().await;
}
}
}
```
### 2. ThreeFold Grid Integration
```rust
// src/services/threefold_grid.rs
use reqwest::Client;
use serde_json::json;
pub struct ThreeFoldGridService {
client: Client,
grid_proxy_url: String,
tft_wallet: TFTWallet,
}
impl ThreeFoldGridService {
pub async fn deploy_vm(
&self,
spec: &ServiceSpec,
tft_amount: Decimal,
) -> Result<GridDeployment, GridError> {
// 1. Find suitable nodes
let nodes = self.find_suitable_nodes(spec).await?;
let selected_node = self.select_best_node(&nodes, spec)?;
// 2. Create deployment contract
let contract = self.create_deployment_contract(spec, &selected_node, tft_amount).await?;
// 3. Deploy on grid
let deployment = self.execute_deployment(contract).await?;
// 4. Wait for deployment to be ready
self.wait_for_deployment_ready(&deployment.id, Duration::from_secs(300)).await?;
// 5. Get connection details
let connection_info = self.get_deployment_info(&deployment.id).await?;
Ok(GridDeployment {
id: deployment.id,
node_id: selected_node.id,
contract_id: contract.id,
connection_info,
status: DeploymentStatus::Active,
created_at: Utc::now(),
})
}
async fn find_suitable_nodes(&self, spec: &ServiceSpec) -> Result<Vec<GridNode>, GridError> {
let query = json!({
"cpu": spec.cpu_cores,
"memory": spec.memory_gb * 1024 * 1024 * 1024, // Convert GB to bytes
"storage": spec.storage_gb * 1024 * 1024 * 1024,
"status": "up",
"available": true
});
let response = self.client
.post(&format!("{}/nodes/search", self.grid_proxy_url))
.json(&query)
.send()
.await?;
let nodes: Vec<GridNode> = response.json().await?;
Ok(nodes)
}
fn select_best_node(&self, nodes: &[GridNode], spec: &ServiceSpec) -> Result<GridNode, GridError> {
// Node selection algorithm:
// 1. Filter by resource availability
// 2. Prefer nodes with better uptime
// 3. Consider geographic proximity
// 4. Balance load across nodes
let suitable_nodes: Vec<_> = nodes.iter()
.filter(|node| {
node.available_cpu >= spec.cpu_cores &&
node.available_memory >= spec.memory_gb * 1024 * 1024 * 1024 &&
node.available_storage >= spec.storage_gb * 1024 * 1024 * 1024 &&
node.uptime_percentage > 95.0
})
.collect();
if suitable_nodes.is_empty() {
return Err(GridError::NoSuitableNodes);
}
// Select node with best score (uptime + available resources)
let best_node = suitable_nodes.iter()
.max_by(|a, b| {
let score_a = a.uptime_percentage + (a.available_cpu as f64 * 10.0);
let score_b = b.uptime_percentage + (b.available_cpu as f64 * 10.0);
score_a.partial_cmp(&score_b).unwrap()
})
.unwrap();
Ok((*best_node).clone())
}
async fn create_deployment_contract(
&self,
spec: &ServiceSpec,
node: &GridNode,
tft_amount: Decimal,
) -> Result<DeploymentContract, GridError> {
match &spec.service_type {
ServiceType::VM { os, version } => {
self.create_vm_contract(spec, node, os, version, tft_amount).await
}
ServiceType::Container { image, ports } => {
self.create_container_contract(spec, node, image, ports, tft_amount).await
}
ServiceType::KubernetesCluster { node_count } => {
self.create_k8s_contract(spec, node, *node_count, tft_amount).await
}
ServiceType::Storage { storage_type } => {
self.create_storage_contract(spec, node, storage_type, tft_amount).await
}
ServiceType::Application { app_id } => {
self.create_app_contract(spec, node, app_id, tft_amount).await
}
}
}
async fn create_vm_contract(
&self,
spec: &ServiceSpec,
node: &GridNode,
os: &str,
version: &str,
tft_amount: Decimal,
) -> Result<DeploymentContract, GridError> {
let vm_config = json!({
"type": "vm",
"node_id": node.id,
"cpu": spec.cpu_cores,
"memory": spec.memory_gb,
"storage": spec.storage_gb,
"os": os,
"version": version,
"network": spec.network_config,
"environment": spec.environment_vars,
"payment": {
"amount": tft_amount,
"currency": "TFT"
}
});
let response = self.client
.post(&format!("{}/deployments/vm", self.grid_proxy_url))
.json(&vm_config)
.send()
.await?;
let contract: DeploymentContract = response.json().await?;
Ok(contract)
}
}
```
### 3. TFT Conversion Service
```rust
// src/services/tft_conversion.rs
use rust_decimal::Decimal;
pub struct TFTConversionService {
grid_client: ThreeFoldGridService,
price_oracle: TFTPriceOracle,
}
impl TFTConversionService {
pub async fn convert_usd_to_tft(&self, usd_amount: Decimal) -> Result<TFTConversion, ConversionError> {
// Get current TFT/USD rate
let tft_rate = self.price_oracle.get_tft_usd_rate().await?;
let tft_amount = usd_amount / tft_rate;
// Add small buffer for price fluctuations (2%)
let tft_with_buffer = tft_amount * Decimal::from_str("1.02")?;
Ok(TFTConversion {
usd_amount,
tft_rate,
tft_amount: tft_with_buffer,
conversion_timestamp: Utc::now(),
})
}
pub async fn execute_conversion(&self, conversion: &TFTConversion) -> Result<TFTTransaction, ConversionError> {
// Execute the actual TFT purchase/conversion
// This would integrate with TFT DEX or direct wallet operations
let transaction = self.grid_client.tft_wallet.convert_usd_to_tft(
conversion.usd_amount,
conversion.tft_amount,
).await?;
Ok(transaction)
}
}
#[derive(Debug, Clone)]
pub struct TFTConversion {
pub usd_amount: Decimal,
pub tft_rate: Decimal,
pub tft_amount: Decimal,
pub conversion_timestamp: DateTime<Utc>,
}
pub struct TFTPriceOracle {
client: Client,
}
impl TFTPriceOracle {
pub async fn get_tft_usd_rate(&self) -> Result<Decimal, PriceError> {
// Get TFT price from multiple sources and average
let sources = vec![
self.get_rate_from_dex().await,
self.get_rate_from_coingecko().await,
self.get_rate_from_grid_stats().await,
];
let valid_rates: Vec<Decimal> = sources.into_iter()
.filter_map(|r| r.ok())
.collect();
if valid_rates.is_empty() {
return Err(PriceError::NoValidSources);
}
// Calculate average rate
let sum: Decimal = valid_rates.iter().sum();
let average = sum / Decimal::from(valid_rates.len());
Ok(average)
}
}
```
### 4. Deployment Worker
```rust
// src/services/deployment_worker.rs
pub struct DeploymentWorker {
id: usize,
grid_service: Arc<ThreeFoldGridService>,
tft_service: Arc<TFTConversionService>,
notification_service: Arc<NotificationService>,
}
impl DeploymentWorker {
pub async fn process_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
log::info!("Worker {} processing deployment {}", self.id, job.id);
// Update status to "processing"
self.update_deployment_status(&job.id, DeploymentStatus::Processing).await?;
// 1. Convert USD to TFT
let conversion = self.tft_service.convert_usd_to_tft(job.grid_payment_amount).await?;
let tft_transaction = self.tft_service.execute_conversion(&conversion).await?;
// 2. Deploy on ThreeFold Grid
let grid_deployment = self.grid_service.deploy_vm(&job.service_spec, conversion.tft_amount).await?;
// 3. Update deployment record
self.update_deployment_with_grid_info(&job.id, &grid_deployment, &tft_transaction).await?;
// 4. Verify deployment health
self.verify_deployment_health(&grid_deployment).await?;
// 5. Update status to "active"
self.update_deployment_status(&job.id, DeploymentStatus::Active).await?;
// 6. Notify user
self.notification_service.send_deployment_success(&job.user_id, &job.id).await?;
log::info!("Worker {} completed deployment {}", self.id, job.id);
Ok(())
}
async fn handle_deployment_failure(&self, job: &DeploymentJob, error: &DeploymentError) {
log::error!("Deployment {} failed: {:?}", job.id, error);
// Update status to failed
self.update_deployment_status(&job.id, DeploymentStatus::Failed).await.ok();
// Refund TFC credits to user
if let Err(refund_error) = self.refund_tfc_credits(job).await {
log::error!("Failed to refund TFC for deployment {}: {:?}", job.id, refund_error);
}
// Notify user of failure
self.notification_service.send_deployment_failure(&job.user_id, &job.id, error).await.ok();
}
async fn refund_tfc_credits(&self, job: &DeploymentJob) -> Result<(), RefundError> {
// Refund the full TFC amount back to user
self.tfc_service.add_tfc_credits(
&job.user_id,
job.tfc_amount,
&format!("Refund for failed deployment {}", job.id),
).await?;
Ok(())
}
}
```
---
## Service Management Dashboard
### 1. Real-time Deployment Status
```html
<!-- Deployment Status Component -->
<div class="deployment-status-card">
<div class="card-header">
<h5>Deployment Status</h5>
<span class="status-badge status-{{deployment.status}}">{{deployment.status}}</span>
</div>
<div class="card-body">
<div class="progress-timeline">
<div class="timeline-step {{#if deployment.payment_completed}}completed{{/if}}">
<i class="bi bi-credit-card"></i>
<span>Payment Processed</span>
</div>
<div class="timeline-step {{#if deployment.queued}}completed{{/if}}">
<i class="bi bi-clock"></i>
<span>Queued for Deployment</span>
</div>
<div class="timeline-step {{#if deployment.converting}}completed{{/if}}">
<i class="bi bi-arrow-left-right"></i>
<span>Converting TFC → TFT</span>
</div>
<div class="timeline-step {{#if deployment.deploying}}completed{{/if}}">
<i class="bi bi-cloud-upload"></i>
<span>Deploying on Grid</span>
</div>
<div class="timeline-step {{#if deployment.active}}completed{{/if}}">
<i class="bi bi-check-circle"></i>
<span>Service Active</span>
</div>
</div>
{{#if deployment.active}}
<div class="connection-details">
<h6>Connection Details</h6>
<div class="detail-item">
<label>IP Address:</label>
<span class="copyable">{{deployment.ip_address}}</span>
</div>
<div class="detail-item">
<label>SSH Access:</label>
<code class="copyable">ssh root@{{deployment.ip_address}}</code>
</div>
{{#if deployment.web_url}}
<div class="detail-item">
<label>Web Interface:</label>
<a href="{{deployment.web_url}}" target="_blank">{{deployment.web_url}}</a>
</div>
{{/if}}
</div>
{{/if}}
<div class="deployment-metrics">
<div class="metric">
<label>TFC Spent:</label>
<span>{{deployment.tfc_amount}} TFC</span>
</div>
<div class="metric">
<label>TFT Converted:</label>
<span>{{deployment.tft_amount}} TFT</span>
</div>
<div class="metric">
<label>Grid Node:</label>
<span>{{deployment.node_id}}</span>
</div>
</div>
</div>
</div>
```
### 2. Service Management Controls
```javascript
// Service Management Functions
class ServiceManager {
constructor(deploymentId) {
this.deploymentId = deploymentId;
this.statusPolling = null;
}
startStatusPolling() {
this.statusPolling = setInterval(async () => {
await this.updateDeploymentStatus();
}, 5000); // Poll every 5 seconds
}
async updateDeploymentStatus() {
try {
const response = await fetch(`/api/deployments/${this.deploymentId}/status`);
const deployment = await response.json();
this.updateStatusDisplay(deployment);
// Stop polling if deployment is complete or failed
if (deployment.status === 'Active' || deployment.status === 'Failed') {
this.stopStatusPolling();
}
} catch (error) {
console.error('Failed to update deployment status:', error);
}
}
updateStatusDisplay(deployment) {
// Update progress timeline
document.querySelectorAll('.timeline-step').forEach((step, index) => {
const stepStates = ['payment_completed', 'queued', 'converting', 'deploying', 'active'];
if (deployment[stepStates[index]]) {
step.classList.add('completed');
}
});
// Update status badge
const statusBadge = document.querySelector('.status-badge');
statusBadge.className = `status-badge status-${deployment.status.toLowerCase()}`;
statusBadge.textContent = deployment.status;
// Update connection details if active
if (deployment.status === 'Active' && deployment.connection_info) {
this.showConnectionDetails(deployment.connection_info);
}
}
async restartService() {
try {
const response = await fetch(`/api/deployments/${this.deploymentId}/restart`, {
method: 'POST'
});
if (response.ok) {
showSuccessToast('Service restart initiated');
this.startStatusPolling();
} else {
showErrorToast('Failed to restart service');
}
} catch (error) {
showErrorToast('Failed to restart service');
}
}
async stopService() {
if (confirm('Are you sure you want to stop this service? This action cannot be undone.')) {
try {
const response = await fetch(`/api/deployments/${this.deploymentId}/stop`, {
method: 'POST'
});
if (response.ok) {
showSuccessToast('Service stopped successfully');
window.location.href = '/dashboard/deployments';
} else {
showErrorToast('Failed to stop service');
}
} catch (error) {
showErrorToast('Failed to stop service');
}
}
}
}
```
---
## Configuration & Environment
```toml
# config/deployment.toml
[deployment_queue]
worker_count = 4
max_queue_size = 1000
retry_attempts = 3
retry_delay_seconds = 30
[threefold_grid]
grid_proxy_url = "https://gridproxy.grid.tf"
substrate_url = "wss://tfchain.grid.tf/ws"
relay_url = "wss://relay.grid.tf"
[tft_conversion]
price_sources = ["dex", "coingecko", "grid_stats"]
conversion_buffer_percent = 2.0
max_slippage_percent = 5.0
[notifications]
email_enabled = true
webhook_enabled = true
slack_enabled = false
```
---
This deployment automation specification provides the complete technical foundation for seamless TFC-to-Grid deployment automation, ensuring reliable and scalable service provisioning on the ThreeFold Grid.

View File

@@ -0,0 +1,550 @@
# Dual UX Specification: Modern App + E-commerce Flows
**Document Purpose**: Comprehensive UX specification supporting both modern app-style instant purchase and traditional e-commerce cart workflows.
**Last Updated**: 2025-08-04
**Status**: Implementation Ready
---
## Overview
The Project Mycelium supports **two distinct user experience patterns** to accommodate different user preferences and use cases:
1. **Modern App Flow** (OpenRouter-style): Instant purchase with wallet top-up
2. **Traditional E-commerce Flow**: Cart-based shopping with checkout
---
## UX Flow Comparison
### **Flow 1: Modern App Style (OpenRouter-inspired)**
```mermaid
graph TD
A[Browse Marketplace] --> B[Register/Login]
B --> C[View Service]
C --> D[Buy Now Button]
D --> E{TFC Balance Check}
E -->|Sufficient| F[Instant Deploy]
E -->|Insufficient| G[Auto Top-up Modal]
G --> H[Stripe Payment]
H --> I[TFC Added]
I --> F
F --> J[Deployment Status]
J --> K[Service Active]
```
**Key Features:**
- **Instant Purchase**: Single-click buying
- **Auto Top-up**: Seamless balance management
- **Wallet-centric**: Balance always visible
- **Minimal Friction**: No cart, no checkout process
### **Flow 2: Traditional E-commerce Style**
```mermaid
graph TD
A[Browse Marketplace] --> B[View Service]
B --> C[Add to Cart]
C --> D[Continue Shopping]
D --> E[Review Cart]
E --> F[Checkout]
F --> G{Payment Method}
G -->|TFC Balance| H[Pay with TFC]
G -->|Credit Card| I[Stripe Checkout]
I --> J[TFC Purchase]
J --> H
H --> K[Batch Deployment]
K --> L[Order Confirmation]
```
**Key Features:**
- **Bulk Shopping**: Multiple items in cart
- **Price Comparison**: Review before purchase
- **Batch Deployment**: Deploy multiple services together
- **Familiar UX**: Traditional e-commerce experience
---
## Detailed UX Specifications
### **Modern App Flow Implementation**
#### **1. Wallet-First Interface**
```html
<!-- Wallet Status Component (Always Visible) -->
<div class="wallet-status-bar">
<div class="balance-display">
<span class="balance-amount">{{user.tfc_balance}} TFC</span>
<span class="usd-equivalent">${{user.tfc_balance}} USD</span>
</div>
<div class="wallet-actions">
<button class="btn btn-sm btn-outline-primary" onclick="showTopUpModal()">
<i class="bi bi-plus-circle"></i> Top Up
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="toggleAutoTopUp()">
<i class="bi bi-arrow-repeat"></i> Auto Top-up: {{#if user.auto_topup}}ON{{else}}OFF{{/if}}
</button>
</div>
</div>
```
#### **2. Buy Now Button (Primary Action)**
```html
<!-- Service Card with Buy Now -->
<div class="service-card modern-style">
<div class="service-header">
<h4>{{service.name}}</h4>
<div class="price-tag">
<span class="tfc-price">{{service.price_tfc}} TFC</span>
<span class="deployment-time">~2 min deploy</span>
</div>
</div>
<div class="service-actions">
<button class="btn btn-primary btn-lg buy-now-btn"
onclick="buyNowInstant('{{service.id}}')">
<i class="bi bi-lightning-fill"></i>
Buy Now & Deploy
</button>
<button class="btn btn-outline-secondary add-to-cart-btn"
onclick="addToCart('{{service.id}}')">
<i class="bi bi-cart-plus"></i>
Add to Cart
</button>
</div>
<!-- Balance Check Indicator -->
<div class="balance-check">
{{#if (gte user.tfc_balance service.price_tfc)}}
<span class="text-success">
<i class="bi bi-check-circle"></i> Ready to deploy
</span>
{{else}}
<span class="text-warning">
<i class="bi bi-exclamation-triangle"></i>
Need {{subtract service.price_tfc user.tfc_balance}} more TFC
</span>
{{/if}}
</div>
</div>
```
#### **3. Auto Top-up Configuration**
```html
<!-- Auto Top-up Settings Modal -->
<div class="modal fade" id="autoTopUpModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5>Auto Top-up Settings</h5>
</div>
<div class="modal-body">
<div class="form-group">
<label>Enable Auto Top-up</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="enableAutoTopUp">
<label class="form-check-label">Automatically add TFC when balance is low</label>
</div>
</div>
<div class="form-group">
<label>Trigger Threshold</label>
<select class="form-select" id="topUpThreshold">
<option value="10">When balance < 10 TFC</option>
<option value="25">When balance < 25 TFC</option>
<option value="50" selected>When balance < 50 TFC</option>
<option value="100">When balance < 100 TFC</option>
</select>
</div>
<div class="form-group">
<label>Top-up Amount</label>
<select class="form-select" id="topUpAmount">
<option value="50">Add 50 TFC ($50)</option>
<option value="100" selected>Add 100 TFC ($100)</option>
<option value="200">Add 200 TFC ($200)</option>
<option value="500">Add 500 TFC ($500)</option>
</select>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
Auto top-up uses your saved payment method. You'll receive an email confirmation for each transaction.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="saveAutoTopUpSettings()">Save Settings</button>
</div>
</div>
</div>
</div>
```
### **Traditional E-commerce Flow Implementation**
#### **1. Shopping Cart Component**
```html
<!-- Shopping Cart (Sidebar or Page) -->
<div class="shopping-cart">
<div class="cart-header">
<h4>Shopping Cart</h4>
<span class="item-count">{{cart.items.length}} items</span>
</div>
<div class="cart-items">
{{#each cart.items}}
<div class="cart-item">
<div class="item-info">
<h6>{{this.service_name}}</h6>
<p class="item-specs">{{this.cpu}}CPU • {{this.memory}}GB RAM • {{this.storage}}GB</p>
</div>
<div class="item-price">
<span class="tfc-price">{{this.price_tfc}} TFC</span>
<button class="btn btn-sm btn-outline-danger" onclick="removeFromCart('{{this.id}}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
{{/each}}
</div>
<div class="cart-summary">
<div class="summary-line">
<span>Subtotal:</span>
<span>{{cart.subtotal}} TFC</span>
</div>
<div class="summary-line">
<span>Estimated Deploy Time:</span>
<span>~{{cart.estimated_deploy_time}} minutes</span>
</div>
<div class="summary-line total">
<span><strong>Total:</strong></span>
<span><strong>{{cart.total}} TFC</strong></span>
</div>
<button class="btn btn-primary btn-lg w-100" onclick="proceedToCheckout()">
<i class="bi bi-credit-card"></i>
Proceed to Checkout
</button>
</div>
</div>
```
#### **2. Checkout Process**
```html
<!-- Checkout Page -->
<div class="checkout-container">
<div class="checkout-steps">
<div class="step active">1. Review Order</div>
<div class="step">2. Payment</div>
<div class="step">3. Deployment</div>
</div>
<div class="checkout-content">
<div class="order-review">
<h5>Order Summary</h5>
<!-- Cart items review -->
<div class="deployment-options">
<h6>Deployment Options</h6>
<div class="form-check">
<input class="form-check-input" type="radio" name="deploymentTiming" value="immediate" checked>
<label class="form-check-label">Deploy immediately after payment</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="deploymentTiming" value="scheduled">
<label class="form-check-label">Schedule deployment for later</label>
</div>
</div>
</div>
<div class="payment-section">
<h5>Payment Method</h5>
<div class="payment-options">
<div class="payment-option" onclick="selectPaymentMethod('tfc')">
<div class="option-header">
<i class="bi bi-wallet2"></i>
<span>Pay with TFC Balance</span>
<span class="balance-info">{{user.tfc_balance}} TFC available</span>
</div>
{{#if (lt user.tfc_balance cart.total)}}
<div class="insufficient-notice">
<span class="text-warning">Insufficient balance. Need {{subtract cart.total user.tfc_balance}} more TFC.</span>
</div>
{{/if}}
</div>
<div class="payment-option" onclick="selectPaymentMethod('stripe')">
<div class="option-header">
<i class="bi bi-credit-card"></i>
<span>Credit/Debit Card</span>
<span class="amount-info">${{cart.total}} USD</span>
</div>
</div>
<div class="payment-option" onclick="selectPaymentMethod('mixed')">
<div class="option-header">
<i class="bi bi-shuffle"></i>
<span>Use TFC + Credit Card</span>
<span class="mixed-info">{{user.tfc_balance}} TFC + ${{subtract cart.total user.tfc_balance}} USD</span>
</div>
</div>
</div>
</div>
</div>
</div>
```
---
## JavaScript Implementation
### **Modern App Flow Functions**
```javascript
// Modern App Style - Instant Purchase
async function buyNowInstant(serviceId) {
try {
// Check balance first
const balance = await getUserTFCBalance();
const service = await getServiceDetails(serviceId);
if (balance >= service.price_tfc) {
// Sufficient balance - instant deploy
showLoadingToast('Starting deployment...');
const result = await initiateDeployment(serviceId, 'tfc');
if (result.success) {
showSuccessToast('Deployment started! Redirecting...');
window.location.href = `/dashboard/deployments/${result.deployment_id}`;
}
} else {
// Insufficient balance - auto top-up flow
const needed = service.price_tfc - balance;
if (await isAutoTopUpEnabled()) {
showAutoTopUpModal(needed, serviceId);
} else {
showManualTopUpModal(needed, serviceId);
}
}
} catch (error) {
showErrorToast('Failed to process purchase');
}
}
// Auto Top-up Flow
async function handleAutoTopUp(amount, serviceId) {
try {
showLoadingToast('Processing auto top-up...');
const topUpResult = await processAutoTopUp(amount);
if (topUpResult.success) {
showSuccessToast('Balance updated! Starting deployment...');
// Proceed with deployment
const deployResult = await initiateDeployment(serviceId, 'tfc');
if (deployResult.success) {
window.location.href = `/dashboard/deployments/${deployResult.deployment_id}`;
}
}
} catch (error) {
showErrorToast('Auto top-up failed');
}
}
// Real-time Balance Updates
function startBalancePolling() {
setInterval(async () => {
const balance = await getUserTFCBalance();
updateBalanceDisplay(balance);
}, 10000); // Update every 10 seconds
}
function updateBalanceDisplay(balance) {
document.querySelector('.balance-amount').textContent = `${balance} TFC`;
document.querySelector('.usd-equivalent').textContent = `$${balance} USD`;
// Update buy buttons state
document.querySelectorAll('.buy-now-btn').forEach(btn => {
const servicePrice = parseFloat(btn.dataset.price);
if (balance >= servicePrice) {
btn.classList.remove('insufficient-balance');
btn.disabled = false;
} else {
btn.classList.add('insufficient-balance');
btn.disabled = true;
}
});
}
```
### **Traditional E-commerce Functions**
```javascript
// Traditional E-commerce - Cart Management
class ShoppingCart {
constructor() {
this.items = JSON.parse(localStorage.getItem('cart_items') || '[]');
this.updateCartDisplay();
}
addItem(serviceId, serviceName, price, specs) {
const existingItem = this.items.find(item => item.service_id === serviceId);
if (existingItem) {
showInfoToast('Item already in cart');
return;
}
this.items.push({
id: generateId(),
service_id: serviceId,
service_name: serviceName,
price_tfc: price,
specs: specs,
added_at: new Date().toISOString()
});
this.saveCart();
this.updateCartDisplay();
showSuccessToast('Added to cart');
}
removeItem(itemId) {
this.items = this.items.filter(item => item.id !== itemId);
this.saveCart();
this.updateCartDisplay();
showSuccessToast('Removed from cart');
}
getTotal() {
return this.items.reduce((total, item) => total + item.price_tfc, 0);
}
async proceedToCheckout() {
if (this.items.length === 0) {
showErrorToast('Cart is empty');
return;
}
// Navigate to checkout page with cart data
const cartData = encodeURIComponent(JSON.stringify(this.items));
window.location.href = `/checkout?cart=${cartData}`;
}
saveCart() {
localStorage.setItem('cart_items', JSON.stringify(this.items));
}
updateCartDisplay() {
const cartCount = document.querySelector('.cart-count');
const cartTotal = document.querySelector('.cart-total');
if (cartCount) cartCount.textContent = this.items.length;
if (cartTotal) cartTotal.textContent = `${this.getTotal()} TFC`;
}
}
// Checkout Process
async function processCheckout(paymentMethod, cartItems) {
try {
showLoadingToast('Processing order...');
const orderData = {
items: cartItems,
payment_method: paymentMethod,
total_tfc: cartItems.reduce((sum, item) => sum + item.price_tfc, 0)
};
const result = await fetch('/api/checkout/process', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderData)
});
const response = await result.json();
if (response.success) {
// Clear cart
localStorage.removeItem('cart_items');
// Redirect to order confirmation
window.location.href = `/orders/${response.order_id}`;
} else {
showErrorToast(response.message);
}
} catch (error) {
showErrorToast('Checkout failed');
}
}
```
---
## User Preference System
### **UX Mode Selection**
```html
<!-- User Preferences - UX Mode -->
<div class="ux-preference-setting">
<h6>Shopping Experience</h6>
<div class="form-check">
<input class="form-check-input" type="radio" name="uxMode" value="modern" id="modernUX">
<label class="form-check-label" for="modernUX">
<strong>Modern App Style</strong>
<br><small>Instant purchases with wallet top-up (like OpenRouter)</small>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="uxMode" value="ecommerce" id="ecommerceUX">
<label class="form-check-label" for="ecommerceUX">
<strong>Traditional E-commerce</strong>
<br><small>Shopping cart with checkout process</small>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="uxMode" value="both" id="bothUX" checked>
<label class="form-check-label" for="bothUX">
<strong>Both Options</strong>
<br><small>Show both "Buy Now" and "Add to Cart" buttons</small>
</label>
</div>
</div>
```
---
## Benefits of Dual UX Approach
### **Modern App Flow Benefits:**
-**Speed**: Instant purchases
-**Simplicity**: Minimal clicks
-**Mobile-friendly**: Touch-optimized
-**Auto-management**: Set-and-forget top-ups
### **Traditional E-commerce Benefits:**
-**Bulk Shopping**: Multiple services at once
-**Price Comparison**: Review before buying
-**Familiar**: Standard shopping experience
-**Planning**: Schedule deployments
### **Combined Advantages:**
- 🎯 **User Choice**: Accommodate different preferences
- 🎯 **Use Case Flexibility**: Quick single purchases OR planned bulk orders
- 🎯 **Market Coverage**: Appeal to both app users and traditional shoppers
- 🎯 **Conversion Optimization**: Multiple paths to purchase
---
This dual UX specification ensures the Project Mycelium appeals to both modern app users (who want instant, frictionless purchases) and traditional e-commerce users (who prefer to review and plan their purchases).

View File

@@ -0,0 +1,411 @@
# Project Mycelium: Complete Roadmap & UX/UI Analysis
**Document Purpose**: Comprehensive roadmap for the complete marketplace ecosystem including payment integration, deployment automation, and full user experience flow.
**Last Updated**: 2025-08-04
**Status**: Strategic Planning Phase
---
## Table of Contents
1. [Current State & Next Phase](#current-state--next-phase)
2. [Complete UX/UI Flow Analysis](#complete-uxui-flow-analysis)
3. [Payment System Integration](#payment-system-integration)
4. [ThreeFold Grid Deployment Automation](#threefold-grid-deployment-automation)
5. [Revenue Model & Commission Structure](#revenue-model--commission-structure)
6. [Technical Architecture](#technical-architecture)
7. [Implementation Phases](#implementation-phases)
8. [Success Metrics](#success-metrics)
---
## Current State & Next Phase
### ✅ **Phase 1 Complete: Foundation**
- **TFC Credits System**: Single currency (1 TFC = 1 USD)
- **Database Migration**: Supabase PostgreSQL ready
- **HA Architecture**: k3s cluster deployment documented
- **Clean Codebase**: All TFP → TFC refactor complete
### 🎯 **Phase 2: Complete Marketplace Ecosystem**
The next critical phase involves creating a **complete end-to-end marketplace** that bridges:
- **Web2 UX**: Easy fiat payments via Stripe
- **Web3 Deployment**: Decentralized ThreeFold Grid infrastructure
- **Seamless Integration**: TFC → TFT conversion for grid deployments
---
## Complete UX/UI Flow Analysis
### **User Journey: From Discovery to Deployment**
```mermaid
graph TD
A[User Browses Marketplace] --> B[Selects App/VM/Service]
B --> C[Add to Cart]
C --> D[Review Cart]
D --> E{Payment Method}
E -->|Has TFC Credits| F[Pay with TFC]
E -->|Needs Credits| G[Buy TFC with Stripe]
G --> H[Stripe Payment Processing]
H --> I[TFC Credits Added]
I --> F
F --> J[TFC Balance Deducted]
J --> K[Deployment Queue]
K --> L[TFC → TFT Conversion]
L --> M[ThreeFold Grid Deployment]
M --> N[Service Active]
N --> O[User Dashboard Access]
```
### **Critical UX/UI Components**
#### **1. Shopping Cart & Checkout**
- **Add to Cart**: ✅ Working
- **Buy Now**: ❌ **MISSING** - Direct purchase flow
- **Cart Review**: Price breakdown, TFC cost, estimated deployment time
- **Payment Options**: TFC balance vs. Stripe top-up
#### **2. TFC Balance Management**
- **Real-time Balance**: Display current TFC credits
- **Deduction Logic**: `1000 TFC - 50 TFC product = 950 TFC`
- **Low Balance Alerts**: Prompt for Stripe top-up
- **Transaction History**: All TFC purchases and usage
#### **3. Payment Integration**
- **Stripe Integration**: Credit card payments → TFC credits
- **Currency Bridge**: USD → TFC (1:1) → TFT (market rate)
- **Commission Handling**: Marketplace cut before grid deployment
#### **4. Deployment Status**
- **Queue Position**: Real-time deployment progress
- **Grid Status**: ThreeFold node selection and deployment
- **Service Access**: Connection details and management
---
## Payment System Integration
### **Stripe Payment Flow**
```rust
// Proposed Stripe Integration Architecture
pub struct StripePaymentService {
stripe_client: stripe::Client,
webhook_secret: String,
}
impl StripePaymentService {
pub async fn create_payment_intent(
&self,
amount_usd: Decimal,
user_id: String,
) -> Result<PaymentIntent> {
// Create Stripe payment intent
// Amount in cents (USD)
let amount_cents = (amount_usd * 100).to_u64().unwrap();
stripe::PaymentIntent::create(&self.stripe_client, CreatePaymentIntent {
amount: amount_cents,
currency: Currency::USD,
metadata: [("user_id", user_id)].iter().cloned().collect(),
..Default::default()
}).await
}
pub async fn handle_webhook(&self, payload: &str, signature: &str) -> Result<()> {
// Verify webhook signature
// Process payment success
// Add TFC credits to user balance
// Trigger any pending deployments
}
}
```
### **TFC ↔ Fiat Bridge Logic**
| User Action | USD Amount | TFC Credits | Marketplace Cut | Grid Deployment |
|-------------|------------|-------------|-----------------|-----------------|
| Buy 100 TFC | $100.00 | +100 TFC | $0 (top-up) | N/A |
| Deploy VM (50 TFC) | N/A | -50 TFC | $5 (10%) | $45 → TFT |
| Deploy App (25 TFC) | N/A | -25 TFC | $2.50 (10%) | $22.50 → TFT |
### **Commission Structure**
- **Marketplace Fee**: 10% of each deployment
- **Grid Payment**: 90% converted to TFT for actual deployment
- **Revenue Split**: Transparent fee structure for users
---
## ThreeFold Grid Deployment Automation
### **Deployment Pipeline Architecture**
```mermaid
graph TD
A[User Purchases Service] --> B[TFC Deducted]
B --> C[Deployment Queue]
C --> D[Commission Calculation]
D --> E[Remaining Amount → TFT]
E --> F[ThreeFold Grid API]
F --> G[Node Selection]
G --> H[Resource Allocation]
H --> I[Service Deployment]
I --> J[Connection Details]
J --> K[User Notification]
```
### **Grid Integration Service**
```rust
pub struct ThreeFoldGridService {
grid_client: TFGridClient,
tft_wallet: TFTWallet,
}
impl ThreeFoldGridService {
pub async fn deploy_vm(
&self,
deployment_spec: VMDeploymentSpec,
tfc_amount: Decimal,
marketplace_cut: Decimal,
) -> Result<DeploymentResult> {
// Calculate actual deployment cost
let deployment_amount = tfc_amount - marketplace_cut;
// Convert TFC to TFT at current market rate
let tft_amount = self.convert_usd_to_tft(deployment_amount).await?;
// Deploy on ThreeFold Grid
let deployment = self.grid_client.deploy_vm(VMDeployment {
cpu: deployment_spec.cpu,
memory: deployment_spec.memory,
storage: deployment_spec.storage,
payment: tft_amount,
..Default::default()
}).await?;
Ok(DeploymentResult {
deployment_id: deployment.id,
grid_node: deployment.node_id,
access_details: deployment.connection_info,
tft_spent: tft_amount,
})
}
async fn convert_usd_to_tft(&self, usd_amount: Decimal) -> Result<Decimal> {
// Get current TFT/USD rate from ThreeFold Grid
let rate = self.grid_client.get_tft_usd_rate().await?;
Ok(usd_amount / rate)
}
}
```
### **Deployment Types & Automation**
| Service Type | TFC Cost | Deployment Method | Grid Resource |
|--------------|----------|-------------------|---------------|
| **VM Instance** | 50-200 TFC | Automated VM deployment | Compute nodes |
| **Kubernetes Cluster** | 100-500 TFC | k3s cluster provisioning | Multiple nodes |
| **Storage Service** | 25-100 TFC | Distributed storage | Storage nodes |
| **Application** | 10-50 TFC | Container deployment | App-specific nodes |
---
## Revenue Model & Commission Structure
### **Marketplace Economics**
```
User Payment Flow:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ User Pays │ │ Marketplace │ │ ThreeFold Grid │
│ $100 USD │───▶│ Takes 10% │───▶│ Receives 90% │
│ via Stripe │ │ ($10 USD) │ │ ($90 → TFT) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### **Commission Breakdown**
- **Platform Fee**: 10% of each deployment
- **Payment Processing**: 2.9% + $0.30 (Stripe fees)
- **Net Marketplace Revenue**: ~7% per transaction
- **Grid Deployment**: 90% converted to TFT for actual resources
### **Revenue Projections**
| Monthly Deployments | Avg. Deployment | Gross Revenue | Net Revenue |
|---------------------|-----------------|---------------|-------------|
| 1,000 | $75 | $7,500 | $5,250 |
| 5,000 | $75 | $37,500 | $26,250 |
| 10,000 | $75 | $75,000 | $52,500 |
---
## Technical Architecture
### **Complete System Architecture**
```
┌─────────────────────────────────────────────────────────────┐
│ Project Mycelium │
├─────────────────────────────────────────────────────────────┤
│ Frontend (React/HTML) │ Backend (Rust) │ Database │
│ - Shopping Cart │ - Payment API │ - PostgreSQL │
│ - TFC Balance │ - Grid API │ - Supabase │
│ - Deployment Status │ - Auth Service │ - Real-time │
├─────────────────────────────────────────────────────────────┤
│ External Integrations │
├─────────────────────────────────────────────────────────────┤
│ Stripe Payments │ ThreeFold Grid │ TFT Blockchain │
│ - Credit Cards │ - VM Deployment │ - Token Conversion│
│ - Webhooks │ - K8s Clusters │ - Market Rates │
│ - Subscriptions │ - Storage │ - Wallet Mgmt │
└─────────────────────────────────────────────────────────────┘
```
### **Data Flow Architecture**
```rust
// Complete marketplace data flow
pub struct MarketplaceTransaction {
pub id: String,
pub user_id: String,
pub service_type: ServiceType,
pub tfc_amount: Decimal,
pub usd_amount: Decimal,
pub marketplace_fee: Decimal,
pub grid_payment: Decimal,
pub tft_amount: Decimal,
pub deployment_id: Option<String>,
pub status: TransactionStatus,
pub created_at: DateTime<Utc>,
}
pub enum TransactionStatus {
PaymentPending,
PaymentCompleted,
DeploymentQueued,
DeploymentInProgress,
DeploymentCompleted,
DeploymentFailed,
}
```
---
## Implementation Phases
### **Phase 2A: Payment Integration (Weeks 1-3)**
- [ ] Stripe integration setup
- [ ] TFC purchase flow
- [ ] Webhook handling
- [ ] Balance management
- [ ] Commission calculation
### **Phase 2B: Grid Deployment (Weeks 4-6)**
- [ ] ThreeFold Grid API integration
- [ ] TFC → TFT conversion service
- [ ] Automated deployment pipeline
- [ ] Status tracking and notifications
- [ ] Error handling and rollback
### **Phase 2C: Complete UX/UI (Weeks 7-9)**
- [ ] Buy Now button implementation
- [ ] Real-time balance updates
- [ ] Deployment progress tracking
- [ ] Service management dashboard
- [ ] Mobile-responsive design
### **Phase 2D: Production & Scaling (Weeks 10-12)**
- [ ] Load testing
- [ ] Security audit
- [ ] Performance optimization
- [ ] Monitoring and alerting
- [ ] Documentation completion
---
## Success Metrics
### **Technical Metrics**
- **Payment Success Rate**: >99.5%
- **Deployment Success Rate**: >95%
- **Average Deployment Time**: <5 minutes
- **System Uptime**: >99.9%
- **API Response Time**: <200ms
### **Business Metrics**
- **Monthly Active Users**: Target 1,000+
- **Average Revenue Per User**: $50/month
- **Customer Acquisition Cost**: <$25
- **Customer Lifetime Value**: >$500
- **Marketplace Commission**: 10% of deployments
### **User Experience Metrics**
- **Cart Abandonment Rate**: <20%
- **Payment Completion Rate**: >90%
- **User Satisfaction Score**: >4.5/5
- **Support Ticket Volume**: <5% of transactions
- **Feature Adoption Rate**: >70%
---
## Critical Questions & Analysis
### **Is This a Complete UX/UI for the Marketplace?**
**Current State Analysis**:
**Strengths**:
- Clean TFC credits system (1 TFC = 1 USD)
- Add to cart functionality working
- Clear pricing and service catalog
- User authentication and dashboard
**Missing Critical Components**:
- **Buy Now button** - Direct purchase flow
- **Real-time TFC deduction** - Balance updates on purchase
- **Stripe payment integration** - Fiat → TFC conversion
- **Grid deployment automation** - TFC → TFT → Deployment
- **Deployment status tracking** - Real-time progress
- **Service management** - Post-deployment controls
### **Recommended UX/UI Enhancements**
1. **Immediate Purchase Flow**
```
[Service Page] → [Buy Now] → [Payment Method] → [Stripe/TFC] → [Deployment]
```
2. **Balance Management**
```
TFC Balance: 1000 → Purchase 50 TFC service → New Balance: 950
```
3. **Payment Options**
```
┌─ Pay with TFC Credits (if sufficient balance)
└─ Buy TFC with Credit Card (Stripe integration)
```
4. **Deployment Pipeline**
```
Payment → Queue → TFC→TFT → Grid API → Service Active → User Access
```
---
## Conclusion
The proposed marketplace architecture creates a **seamless Web2-to-Web3 bridge** that:
- **Simplifies User Experience**: Familiar credit card payments
- **Maintains Decentralization**: ThreeFold Grid deployment
- **Generates Revenue**: 10% marketplace commission
- **Scales Efficiently**: Cloud-native architecture
- **Ensures Reliability**: HA k3s deployment ready
The next implementation phase will transform this from a catalog into a **complete e-commerce platform** with automated deployment capabilities.
---
**Next Steps**: Begin Phase 2A implementation with Stripe integration and TFC purchase flow.

View File

@@ -0,0 +1,582 @@
# Payment Integration Technical Specification
**Document Purpose**: Detailed technical specification for Stripe payment integration and TFC credits system.
**Last Updated**: 2025-08-04
**Status**: Implementation Ready
---
## Overview
This document outlines the technical implementation for integrating Stripe payments with the Project Mycelium TFC credits system, enabling seamless fiat-to-credits conversion and automated grid deployments.
---
## Architecture Components
### 1. Stripe Payment Service
```rust
// src/services/stripe_service.rs
use stripe::{Client, PaymentIntent, CreatePaymentIntent, Currency, Webhook, EventType};
use serde::{Deserialize, Serialize};
use rust_decimal::Decimal;
#[derive(Debug, Clone)]
pub struct StripeService {
client: Client,
webhook_secret: String,
publishable_key: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct TFCPurchaseRequest {
pub amount_usd: Decimal,
pub user_id: String,
pub return_url: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaymentIntentResponse {
pub client_secret: String,
pub payment_intent_id: String,
pub amount_cents: u64,
pub tfc_amount: Decimal,
}
impl StripeService {
pub fn new(secret_key: String, webhook_secret: String, publishable_key: String) -> Self {
Self {
client: Client::new(secret_key),
webhook_secret,
publishable_key,
}
}
pub async fn create_tfc_purchase_intent(
&self,
request: TFCPurchaseRequest,
) -> Result<PaymentIntentResponse, StripeError> {
let amount_cents = (request.amount_usd * Decimal::from(100)).to_u64().unwrap();
let payment_intent = PaymentIntent::create(&self.client, CreatePaymentIntent {
amount: amount_cents,
currency: Currency::USD,
metadata: [
("user_id".to_string(), request.user_id.clone()),
("tfc_amount".to_string(), request.amount_usd.to_string()),
("purpose".to_string(), "tfc_purchase".to_string()),
].iter().cloned().collect(),
..Default::default()
}).await?;
Ok(PaymentIntentResponse {
client_secret: payment_intent.client_secret.unwrap(),
payment_intent_id: payment_intent.id.to_string(),
amount_cents,
tfc_amount: request.amount_usd,
})
}
pub async fn handle_webhook(
&self,
payload: &str,
signature: &str,
) -> Result<WebhookResult, StripeError> {
let event = Webhook::construct_event(payload, signature, &self.webhook_secret)?;
match event.type_ {
EventType::PaymentIntentSucceeded => {
if let Ok(payment_intent) = serde_json::from_value::<PaymentIntent>(event.data.object) {
return Ok(WebhookResult::PaymentSucceeded {
user_id: payment_intent.metadata.get("user_id").cloned().unwrap_or_default(),
tfc_amount: payment_intent.metadata.get("tfc_amount")
.and_then(|s| s.parse().ok())
.unwrap_or_default(),
payment_intent_id: payment_intent.id.to_string(),
});
}
}
EventType::PaymentIntentPaymentFailed => {
// Handle failed payments
return Ok(WebhookResult::PaymentFailed);
}
_ => {}
}
Ok(WebhookResult::Ignored)
}
}
#[derive(Debug)]
pub enum WebhookResult {
PaymentSucceeded {
user_id: String,
tfc_amount: Decimal,
payment_intent_id: String,
},
PaymentFailed,
Ignored,
}
```
### 2. TFC Credits Management
```rust
// src/services/tfc_service.rs
use rust_decimal::Decimal;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TFCTransaction {
pub id: String,
pub user_id: String,
pub transaction_type: TFCTransactionType,
pub amount: Decimal,
pub balance_before: Decimal,
pub balance_after: Decimal,
pub description: String,
pub metadata: serde_json::Value,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TFCTransactionType {
Purchase, // Stripe payment → TFC
Deployment, // TFC → Grid deployment
Refund, // Failed deployment refund
Transfer, // User-to-user transfer
AdminAdjustment, // Manual balance adjustment
}
pub struct TFCService {
db: Arc<dyn Database>,
}
impl TFCService {
pub async fn add_tfc_credits(
&self,
user_id: &str,
amount: Decimal,
payment_intent_id: &str,
) -> Result<TFCTransaction, TFCError> {
let current_balance = self.get_user_balance(user_id).await?;
let new_balance = current_balance + amount;
let transaction = TFCTransaction {
id: generate_transaction_id(),
user_id: user_id.to_string(),
transaction_type: TFCTransactionType::Purchase,
amount,
balance_before: current_balance,
balance_after: new_balance,
description: format!("TFC purchase via Stripe ({})", payment_intent_id),
metadata: json!({
"payment_intent_id": payment_intent_id,
"stripe_amount_cents": (amount * Decimal::from(100)).to_u64(),
}),
created_at: Utc::now(),
};
// Atomic transaction: update balance + record transaction
self.db.execute_transaction(|tx| {
tx.update_user_balance(user_id, new_balance)?;
tx.insert_tfc_transaction(&transaction)?;
Ok(())
}).await?;
Ok(transaction)
}
pub async fn deduct_tfc_for_deployment(
&self,
user_id: &str,
amount: Decimal,
deployment_id: &str,
service_name: &str,
) -> Result<TFCTransaction, TFCError> {
let current_balance = self.get_user_balance(user_id).await?;
if current_balance < amount {
return Err(TFCError::InsufficientBalance {
required: amount,
available: current_balance,
});
}
let new_balance = current_balance - amount;
let transaction = TFCTransaction {
id: generate_transaction_id(),
user_id: user_id.to_string(),
transaction_type: TFCTransactionType::Deployment,
amount: -amount, // Negative for deduction
balance_before: current_balance,
balance_after: new_balance,
description: format!("Deployment: {} ({})", service_name, deployment_id),
metadata: json!({
"deployment_id": deployment_id,
"service_name": service_name,
}),
created_at: Utc::now(),
};
self.db.execute_transaction(|tx| {
tx.update_user_balance(user_id, new_balance)?;
tx.insert_tfc_transaction(&transaction)?;
Ok(())
}).await?;
Ok(transaction)
}
pub async fn get_user_balance(&self, user_id: &str) -> Result<Decimal, TFCError> {
self.db.get_user_tfc_balance(user_id).await
}
pub async fn get_transaction_history(
&self,
user_id: &str,
limit: Option<u32>,
) -> Result<Vec<TFCTransaction>, TFCError> {
self.db.get_user_tfc_transactions(user_id, limit.unwrap_or(50)).await
}
}
```
### 3. Buy Now Implementation
```rust
// src/controllers/marketplace.rs
use actix_web::{web, HttpResponse, Result};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct BuyNowRequest {
pub service_id: String,
pub payment_method: PaymentMethod,
}
#[derive(Debug, Deserialize)]
pub enum PaymentMethod {
TFC, // Use existing TFC balance
Stripe, // Purchase TFC with credit card
}
#[derive(Debug, Serialize)]
pub struct BuyNowResponse {
pub success: bool,
pub action: BuyNowAction,
pub message: String,
}
#[derive(Debug, Serialize)]
pub enum BuyNowAction {
DeploymentStarted { deployment_id: String },
PaymentRequired { payment_intent: PaymentIntentResponse },
InsufficientFunds { required: Decimal, available: Decimal },
}
impl MarketplaceController {
pub async fn buy_now(
&self,
request: web::Json<BuyNowRequest>,
session: Session,
) -> Result<HttpResponse> {
let user_id = self.get_user_id_from_session(&session)?;
let service = self.get_service(&request.service_id).await?;
match request.payment_method {
PaymentMethod::TFC => {
// Check TFC balance and proceed with deployment
let balance = self.tfc_service.get_user_balance(&user_id).await?;
if balance >= service.price_tfc {
// Sufficient balance - start deployment
let deployment_id = self.start_deployment(&user_id, &service).await?;
Ok(HttpResponse::Ok().json(BuyNowResponse {
success: true,
action: BuyNowAction::DeploymentStarted { deployment_id },
message: "Deployment started successfully".to_string(),
}))
} else {
// Insufficient balance
Ok(HttpResponse::Ok().json(BuyNowResponse {
success: false,
action: BuyNowAction::InsufficientFunds {
required: service.price_tfc,
available: balance,
},
message: "Insufficient TFC balance".to_string(),
}))
}
}
PaymentMethod::Stripe => {
// Create Stripe payment intent for TFC purchase
let payment_intent = self.stripe_service.create_tfc_purchase_intent(
TFCPurchaseRequest {
amount_usd: service.price_tfc, // 1 TFC = 1 USD
user_id: user_id.clone(),
return_url: format!("/marketplace/service/{}/deploy", service.id),
}
).await?;
Ok(HttpResponse::Ok().json(BuyNowResponse {
success: true,
action: BuyNowAction::PaymentRequired { payment_intent },
message: "Payment required to complete purchase".to_string(),
}))
}
}
}
async fn start_deployment(
&self,
user_id: &str,
service: &MarketplaceService,
) -> Result<String, MarketplaceError> {
// 1. Deduct TFC from user balance
let transaction = self.tfc_service.deduct_tfc_for_deployment(
user_id,
service.price_tfc,
&generate_deployment_id(),
&service.name,
).await?;
// 2. Calculate marketplace commission
let commission = service.price_tfc * Decimal::from_str("0.10")?; // 10%
let grid_payment = service.price_tfc - commission;
// 3. Queue deployment
let deployment = Deployment {
id: transaction.metadata["deployment_id"].as_str().unwrap().to_string(),
user_id: user_id.to_string(),
service_id: service.id.clone(),
tfc_amount: service.price_tfc,
commission_amount: commission,
grid_payment_amount: grid_payment,
status: DeploymentStatus::Queued,
created_at: Utc::now(),
};
self.deployment_service.queue_deployment(deployment).await?;
Ok(transaction.metadata["deployment_id"].as_str().unwrap().to_string())
}
}
```
---
## Frontend Integration
### 1. Buy Now Button Component
```html
<!-- Buy Now Button with Payment Options -->
<div class="buy-now-section">
<div class="price-display">
<span class="price">{{service.price_tfc}} TFC</span>
<span class="usd-equivalent">${{service.price_tfc}} USD</span>
</div>
<div class="payment-options">
<button id="buyNowTFC" class="btn btn-primary btn-lg"
onclick="buyNowWithTFC('{{service.id}}')">
<i class="bi bi-lightning-fill"></i>
Buy Now with TFC
</button>
<button id="buyNowStripe" class="btn btn-outline-primary btn-lg"
onclick="buyNowWithStripe('{{service.id}}')">
<i class="bi bi-credit-card"></i>
Buy with Credit Card
</button>
</div>
<div class="balance-info">
<small>Your TFC Balance: <span id="userTFCBalance">{{user.tfc_balance}}</span> TFC</small>
</div>
</div>
```
### 2. JavaScript Payment Handling
```javascript
// Buy Now with TFC Credits
async function buyNowWithTFC(serviceId) {
try {
const response = await fetch('/api/marketplace/buy-now', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
service_id: serviceId,
payment_method: 'TFC'
})
});
const result = await response.json();
if (result.success) {
switch (result.action.type) {
case 'DeploymentStarted':
showSuccessToast('Deployment started! Redirecting to dashboard...');
setTimeout(() => {
window.location.href = `/dashboard/deployments/${result.action.deployment_id}`;
}, 2000);
break;
}
} else {
switch (result.action.type) {
case 'InsufficientFunds':
showInsufficientFundsModal(result.action.required, result.action.available);
break;
}
}
} catch (error) {
showErrorToast('Failed to process purchase');
}
}
// Buy Now with Stripe
async function buyNowWithStripe(serviceId) {
try {
const response = await fetch('/api/marketplace/buy-now', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
service_id: serviceId,
payment_method: 'Stripe'
})
});
const result = await response.json();
if (result.success && result.action.type === 'PaymentRequired') {
// Initialize Stripe Elements
const stripe = Stripe('pk_test_your_publishable_key');
const elements = stripe.elements();
// Show Stripe payment modal
showStripePaymentModal(stripe, result.action.payment_intent.client_secret);
}
} catch (error) {
showErrorToast('Failed to initialize payment');
}
}
// Stripe Payment Modal
function showStripePaymentModal(stripe, clientSecret) {
const modal = document.getElementById('stripePaymentModal');
const elements = stripe.elements();
const cardElement = elements.create('card');
cardElement.mount('#card-element');
document.getElementById('confirmPayment').onclick = async () => {
const {error, paymentIntent} = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
}
});
if (error) {
showErrorToast(error.message);
} else {
showSuccessToast('Payment successful! TFC credits added to your account.');
// Refresh balance and redirect
updateTFCBalance();
modal.hide();
}
};
new bootstrap.Modal(modal).show();
}
// Real-time TFC Balance Updates
function updateTFCBalance() {
fetch('/api/user/tfc-balance')
.then(response => response.json())
.then(data => {
document.getElementById('userTFCBalance').textContent = data.balance;
});
}
```
---
## Database Schema Updates
```sql
-- TFC Transactions Table
CREATE TABLE tfc_transactions (
id VARCHAR(255) PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
transaction_type VARCHAR(50) NOT NULL,
amount DECIMAL(15,2) NOT NULL,
balance_before DECIMAL(15,2) NOT NULL,
balance_after DECIMAL(15,2) NOT NULL,
description TEXT,
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
INDEX idx_user_id (user_id),
INDEX idx_created_at (created_at),
INDEX idx_transaction_type (transaction_type)
);
-- Deployments Table
CREATE TABLE deployments (
id VARCHAR(255) PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
service_id VARCHAR(255) NOT NULL,
tfc_amount DECIMAL(15,2) NOT NULL,
commission_amount DECIMAL(15,2) NOT NULL,
grid_payment_amount DECIMAL(15,2) NOT NULL,
tft_amount DECIMAL(15,8),
grid_deployment_id VARCHAR(255),
status VARCHAR(50) NOT NULL,
error_message TEXT,
connection_details JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
INDEX idx_user_id (user_id),
INDEX idx_status (status),
INDEX idx_created_at (created_at)
);
-- Update Users Table
ALTER TABLE users ADD COLUMN tfc_balance DECIMAL(15,2) DEFAULT 0.00;
```
---
## Configuration
```toml
# config/marketplace.toml
[stripe]
secret_key = "sk_test_..."
publishable_key = "pk_test_..."
webhook_secret = "whsec_..."
[marketplace]
commission_rate = 0.10 # 10%
tfc_usd_rate = 1.00 # 1 TFC = 1 USD
[threefold_grid]
api_endpoint = "https://gridproxy.grid.tf"
tft_wallet_mnemonic = "your wallet mnemonic"
```
---
This technical specification provides the complete implementation details for integrating Stripe payments with the TFC credits system, enabling seamless fiat-to-deployment automation in the Project Mycelium.