# 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 { 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 { let event = Webhook::construct_event(payload, signature, &self.webhook_secret)?; match event.type_ { EventType::PaymentIntentSucceeded => { if let Ok(payment_intent) = serde_json::from_value::(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, } #[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, } impl TFCService { pub async fn add_tfc_credits( &self, user_id: &str, amount: Decimal, payment_intent_id: &str, ) -> Result { 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 { 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 { self.db.get_user_tfc_balance(user_id).await } pub async fn get_transaction_history( &self, user_id: &str, limit: Option, ) -> Result, 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, session: Session, ) -> Result { 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 { // 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
{{service.price_tfc}} TFC ${{service.price_tfc}} USD
Your TFC Balance: {{user.tfc_balance}} TFC
``` ### 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.