init projectmycelium
This commit is contained in:
@@ -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.
|
@@ -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).
|
@@ -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.
|
@@ -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.
|
Reference in New Issue
Block a user