π MyZubster Gateway: Deep Dive Guide
Complete technical guide to the Monero payment engine β from architecture to production
π What is MyZubster Gateway?
MyZubster Gateway is the payment engine of the MyZubster ecosystem. It handles all interactions with the Monero blockchain:
β
Generates unique subaddresses for each order
β
Monitors the blockchain for incoming payments (real-time)
β
Sends webhooks to the Marketplace when payments are confirmed
β
Manages transaction history and payment status
Think of it as the bridge between MyZubster and the Monero network.
π§© Architecture Overview
text
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MyZubster Gateway β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Express ββββββΆβ MongoDB ββββββΆβ Monero RPC Client β β
β β Server β β (Database) β β (monero-javascript)β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Routes β β Models β β Monero Wallet β β
β β /api/* β β (Mongo) β β (Stagenet/Mainnet)β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β
β βΌ βΌ β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β Webhooks βββββββΌβββΆβ Marketplace β β
β β (Payment Conf) β β (port 4000) β β
β ββββββββββββββββββββ ββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π οΈ Tech Stack
Layer Technology Purpose
Server Node.js + Express RESTful API server
Database MongoDB Store payments, transactions, and address mappings
Monero Client monero-javascript Interact with Monero blockchain
Webhooks Axios Notify Marketplace of payment confirmations
Security Helmet, CORS, JWT API authentication and security
π How the Gateway Works
The Payment Flow
text
ββββββββββββ ββββββββββββββ ββββββββββββββ ββββββββββββββ
β Buyer β β Marketplaceβ β Gateway β β Monero β
β β β β β β β Blockchain β
ββββββ¬ββββββ βββββββ¬βββββββ βββββββ¬βββββββ βββββββ¬βββββββ
β β β β
β 1. Creates β β β
β Order with β β β
β Skill ID β β β
βββββββββββββββββββΆβ β β
β β β β
β β 2. Request β β
β β Payment β β
β β (POST /initiate) β β
β ββββββββββββββββββββΆβ β
β β β β
β β β 3. Generate β
β β β Subaddress β
β β ββββββββββββββββββββΆβ
β β β β
β β β 4. Return β
β β β Subaddress β
β β 5. Return βββββββββββββββββββββ
β 6. Show β Subaddress β β
β Address βββββββββββββββββββββ β
ββββββββββββββββββββ β β
β β β β
β 7. Send β β β
β Monero to β β β
β Subaddress β β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΆβ
β β β β
β β β 8. Monitor β
β β β Blockchain β
β β β (every 60s) β
β β ββββββββββββββββββββΆβ
β β β β
β β β 9. Payment β
β β β Detected β
β β βββββββββββββββββββββ
β β β β
β β 10. Webhook β β
β β (Payment Conf) β β
β βββββββββββββββββββββ β
β β β β
β β 11. Update β β
β β Order Status β β
β 12. Order β to "paid" β β
β Completed βββββββββββββββββββββ β
ββββββββββββββββββββ β β
β β β β
π Complete File Structure
text
gateway/
βββ server.js # Entry point
βββ .env # Environment variables
βββ package.json # Dependencies
βββ models/
β βββ index.js # Database connection
β βββ User.js # User model
β βββ Order.js # Order model (tracks payments)
β βββ Skill.js # Skill model
β βββ Payment.js # Payment model (subaddress + status)
β βββ Transaction.js # Transaction history
βββ routes/
β βββ auth.js # Authentication routes
β βββ payments.js # Payment endpoints
β βββ webhook.js # Webhook handling
β βββ health.js # Health check
βββ services/
β βββ monero.js # Monero RPC client
β βββ paymentMonitor.js # Background payment monitoring
β βββ webhookSender.js # Webhook delivery
βββ middleware/
β βββ auth.js # JWT verification
β βββ rateLimiter.js # Rate limiting
βββ config/
β βββ database.js # Database configuration
βββ tests/
βββ payments.test.js
βββ webhook.test.js
βββ monero.test.js
π§ Complete Code Walkthrough
- Server Configuration (server.js)
File: gateway/server.js
javascript
// server.js - MyZubster Gateway
// Backend principale per la piattaforma MyZubster
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const dotenv = require('dotenv');
// Carica le variabili d'ambiente
dotenv.config();
// Inizializza Express
const app = express();
const PORT = process.env.PORT || 3000;
// ============================================
// MIDDLEWARE
// ============================================
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// ============================================
// IMPORT MODELLI
// ============================================
require('./models/User');
require('./models/Order');
require('./models/Skill');
require('./models/Offer');
require('./models/Request');
require('./models/Transaction');
require('./models/Review');
// ============================================
// IMPORT ROUTE
// ============================================
const authRoutes = require('./routes/auth');
const skillRoutes = require('./routes/skills');
const offerRoutes = require('./routes/offers');
const requestRoutes = require('./routes/requests');
const orderRoutes = require('./routes/orders');
const paymentRoutes = require('./routes/payments');
const transactionRoutes = require('./routes/transactions');
const reviewRoutes = require('./routes/reviews');
// ============================================
// ROTTE API
// ============================================
app.use('/api/auth', authRoutes);
app.use('/api/skills', skillRoutes);
app.use('/api/offers', offerRoutes);
app.use('/api/requests', requestRoutes);
app.use('/api/orders', orderRoutes);
app.use('/api/payments', paymentRoutes);
app.use('/api/transactions', transactionRoutes);
app.use('/api/reviews', reviewRoutes);
// ============================================
// ENDPOINT PER GENERARE UN INDIRIZZO MONERO (MOCK)
// ============================================
app.post('/api/payments/initiate', async (req, res) => {
try {
console.log('π Richiesta pagamento ricevuta:', req.body);
const { orderId, amount, currency } = req.body;
if (!orderId || !amount) {
return res.status(400).json({ error: 'orderId e amount obbligatori' });
}
// Mock: genera un indirizzo Monero fittizio per il test
const mockAddress = '8A1B2C3D4E5F6G7H8I9J0K' + Math.random().toString(36).substring(2, 8);
res.json({
success: true,
paymentId: 'pay_' + Date.now(),
moneroAddress: mockAddress,
moneroAmount: amount,
addressIndex: Math.floor(Math.random() * 1000),
network: 'testnet',
status: 'pending',
orderId: orderId
});
} catch (error) {
console.error('β Errore creazione pagamento:', error);
res.status(500).json({ error: 'Errore creazione pagamento' });
}
});
// ============================================
// ROTTA DI TEST
// ============================================
app.get('/api/health', (req, res) => {
res.json({
status: 'OK',
message: 'MyZubster Gateway is running!',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// ============================================
// CONNESSIONE AL DATABASE
// ============================================
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myzubster';
mongoose.connect(MONGODB_URI)
.then(() => {
console.log('β
Connesso a MongoDB');
console.log(π¦ Database: ${MONGODB_URI});
})
.catch((err) => {
console.error('β Errore connessione MongoDB:', err);
process.exit(1);
});
// ============================================
// AVVIO DEL SERVER
// ============================================
app.listen(PORT, () => {
console.log(π Server avviato sulla porta ${PORT});
console.log(π URL: http://localhost:${PORT});
console.log(π Health check: http://localhost:${PORT}/api/health);
});
// Gestione errori non catturati
process.on('unhandledRejection', (err) => {
console.error('β Unhandled Rejection:', err);
});
module.exports = app;
- Payment Model (models/Payment.js)
File: gateway/models/Payment.js
javascript
const mongoose = require('mongoose');
const PaymentSchema = new mongoose.Schema({
orderId: {
type: String,
required: true,
index: true
},
subaddress: {
type: String,
required: true,
unique: true,
index: true
},
amount: {
type: Number,
required: true
},
currency: {
type: String,
default: 'USD'
},
moneroAmount: {
type: Number,
required: true
},
addressIndex: {
type: Number,
required: true
},
txHash: {
type: String,
default: null
},
status: {
type: String,
enum: ['pending', 'confirmed', 'expired'],
default: 'pending'
},
confirmations: {
type: Number,
default: 0
},
amountReceived: {
type: Number,
default: 0
},
network: {
type: String,
enum: ['testnet', 'mainnet'],
default: 'testnet'
},
createdAt: {
type: Date,
default: Date.now
},
confirmedAt: {
type: Date,
default: null
},
webhookDelivered: {
type: Boolean,
default: false
}
});
module.exports = mongoose.model('Payment', PaymentSchema);
- Payment Routes (routes/payments.js)
File: gateway/routes/payments.js
javascript
const express = require('express');
const router = express.Router();
const Payment = require('../models/Payment');
const { createMoneroClient } = require('../services/monero');
const { sendWebhook } = require('../services/webhookSender');
// ============================================
// POST /api/payments/initiate
// Crea un nuovo pagamento
// ============================================
router.post('/initiate', async (req, res) => {
try {
const { orderId, amount, currency } = req.body;
if (!orderId || !amount) {
return res.status(400).json({ error: 'orderId e amount obbligatori' });
}
// 1. Connetti al wallet Monero
const wallet = await createMoneroClient();
// 2. Genera un nuovo subaddress
const subaddress = await wallet.createSubaddress();
// 3. Salva il pagamento nel database
const payment = await Payment.create({
orderId,
subaddress: subaddress.address,
amount,
moneroAmount: amount, // Convert in base al tasso di cambio (semplificato)
addressIndex: subaddress.index,
network: process.env.MONERO_NETWORK || 'testnet',
status: 'pending'
});
// 4. Rispondi al client
res.status(201).json({
success: true,
paymentId: payment._id,
moneroAddress: payment.subaddress,
moneroAmount: payment.moneroAmount,
addressIndex: payment.addressIndex,
network: payment.network,
status: payment.status
});
// 5. Inizia il monitoraggio del pagamento in background
startPaymentMonitoring(payment._id);
} catch (error) {
console.error('β Errore initiate payment:', error);
res.status(500).json({ error: 'Errore creazione pagamento' });
}
});
// ============================================
// GET /api/payments/status/:paymentId
// Verifica lo stato di un pagamento
// ============================================
router.get('/status/:paymentId', async (req, res) => {
try {
const payment = await Payment.findById(req.params.paymentId);
if (!payment) {
return res.status(404).json({ error: 'Pagamento non trovato' });
}
res.json({
paymentId: payment._id,
status: payment.status,
confirmations: payment.confirmations,
amountReceived: payment.amountReceived,
txHash: payment.txHash,
createdAt: payment.createdAt,
confirmedAt: payment.confirmedAt
});
} catch (error) {
console.error('β Errore status payment:', error);
res.status(500).json({ error: 'Errore recupero stato' });
}
});
// ============================================
// POST /api/payments/webhook (test)
// Simula un webhook di pagamento
// ============================================
router.post('/webhook', async (req, res) => {
try {
const { paymentId, txHash, amount } = req.body;
const payment = await Payment.findById(paymentId);
if (!payment) {
return res.status(404).json({ error: 'Pagamento non trovato' });
}
// Aggiorna il pagamento
payment.status = 'confirmed';
payment.txHash = txHash;
payment.amountReceived = amount || payment.amount;
payment.confirmations = 10;
payment.confirmedAt = new Date();
await payment.save();
// Invia webhook al Marketplace
await sendWebhook(payment.orderId, {
status: 'confirmed',
txHash: txHash,
amount: payment.amountReceived
});
res.json({
success: true,
payment: payment
});
} catch (error) {
console.error('β Errore webhook:', error);
res.status(500).json({ error: 'Errore webhook' });
}
});
module.exports = router;
- Monero Service (services/monero.js)
File: gateway/services/monero.js
javascript
const monerojs = require('monero-javascript');
let walletInstance = null;
const MONERO_RPC_URL = process.env.MONERO_RPC_URL || 'http://localhost:18081';
const MONERO_RPC_USERNAME = process.env.MONERO_RPC_USERNAME || '';
const MONERO_RPC_PASSWORD = process.env.MONERO_RPC_PASSWORD || '';
const MONERO_NETWORK = process.env.MONERO_NETWORK || 'testnet';
// ============================================
// Crea il client Monero
// ============================================
const createMoneroClient = async () => {
if (walletInstance) {
return walletInstance;
}
try {
// In produzione, usa un wallet RPC
// Per test, usa un wallet mock
const wallet = await monerojs.createWalletRpc({
url: MONERO_RPC_URL,
username: MONERO_RPC_USERNAME,
password: MONERO_RPC_PASSWORD,
networkType: MONERO_NETWORK
});
walletInstance = wallet;
return wallet;
} catch (error) {
console.error('β Errore connessione Monero RPC:', error);
throw error;
}
};
// ============================================
// Genera un subaddress
// ============================================
const generateSubaddress = async (accountIndex = 0) => {
const wallet = await createMoneroClient();
const subaddress = await wallet.createSubaddress(accountIndex);
return {
address: subaddress.getAddress(),
index: subaddress.getIndex()
};
};
// ============================================
// Verifica i pagamenti per un subaddress
// ============================================
const checkPayment = async (subaddress) => {
const wallet = await createMoneroClient();
const transfers = await wallet.getTransfers({
subaddress: subaddress,
isIncoming: true
});
// Trova l'ultimo trasferimento confermato
const confirmed = transfers
.filter(t => t.isConfirmed())
.sort((a, b) => b.getTimestamp() - a.getTimestamp());
if (confirmed.length === 0) {
return null;
}
const last = confirmed[0];
return {
txHash: last.getTxHash(),
amount: last.getAmount(),
confirmations: last.getNumConfirmations(),
timestamp: last.getTimestamp()
};
};
module.exports = {
createMoneroClient,
generateSubaddress,
checkPayment
};
- Payment Monitor (services/paymentMonitor.js)
File: gateway/services/paymentMonitor.js
javascript
const Payment = require('../models/Payment');
const { checkPayment } = require('./monero');
const { sendWebhook } = require('./webhookSender');
// ============================================
// Monitora i pagamenti in background
// ============================================
const paymentMonitors = new Map();
const startPaymentMonitoring = async (paymentId) => {
// Se il monitoraggio Γ¨ giΓ attivo, non far nulla
if (paymentMonitors.has(paymentId.toString())) {
return;
}
console.log(π Avvio monitoraggio pagamento ${paymentId});
const monitorInterval = setInterval(async () => {
try {
const payment = await Payment.findById(paymentId);
if (!payment) {
clearInterval(monitorInterval);
paymentMonitors.delete(paymentId.toString());
return;
}
if (payment.status === 'confirmed') {
clearInterval(monitorInterval);
paymentMonitors.delete(paymentId.toString());
return;
}
// Controlla se il pagamento Γ¨ stato ricevuto
const result = await checkPayment(payment.subaddress);
if (result && result.confirmations >= 1) {
// Pagamento confermato!
payment.status = 'confirmed';
payment.txHash = result.txHash;
payment.amountReceived = result.amount;
payment.confirmations = result.confirmations;
payment.confirmedAt = new Date();
await payment.save();
// Invia webhook al Marketplace
await sendWebhook(payment.orderId, {
status: 'confirmed',
txHash: result.txHash,
amount: result.amount
});
// Ferma il monitoraggio
clearInterval(monitorInterval);
paymentMonitors.delete(paymentId.toString());
console.log(`β
Pagamento ${paymentId} confermato!`);
}
} catch (error) {
console.error(`β Errore monitoraggio pagamento ${paymentId}:`, error);
}
}, 30000); // Controlla ogni 30 secondi
paymentMonitors.set(paymentId.toString(), monitorInterval);
};
// ============================================
// Ferma il monitoraggio di un pagamento
// ============================================
const stopPaymentMonitoring = (paymentId) => {
const interval = paymentMonitors.get(paymentId.toString());
if (interval) {
clearInterval(interval);
paymentMonitors.delete(paymentId.toString());
}
};
module.exports = {
startPaymentMonitoring,
stopPaymentMonitoring
};
- Webhook Sender (services/webhookSender.js)
File: gateway/services/webhookSender.js
javascript
const axios = require('axios');
const crypto = require('crypto');
const WEBHOOK_URL = process.env.WEBHOOK_URL || 'http://localhost:4000/webhook/order-update';
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || 'your_webhook_secret';
// ============================================
// Invia webhook al Marketplace
// ============================================
const sendWebhook = async (orderId, payload) => {
try {
const data = {
orderId,
...payload,
timestamp: new Date().toISOString()
};
// Firma HMAC per autenticazione
const signature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(data))
.digest('hex');
const response = await axios.post(WEBHOOK_URL, data, {
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': signature
},
timeout: 10000
});
console.log(`β
Webhook inviato per ordine ${orderId}:`, response.status);
return response.data;
} catch (error) {
console.error(β Errore webhook per ordine ${orderId}:, error.message);
throw error;
}
};
module.exports = {
sendWebhook
};
π§ Environment Variables
File: gateway/.env
env
Server
PORT=3000
NODE_ENV=development
MongoDB
MONGODB_URI=mongodb://localhost:27017/myzubster
Monero RPC
MONERO_RPC_URL=http://localhost:18081
MONERO_RPC_USERNAME=
MONERO_RPC_PASSWORD=
MONERO_NETWORK=testnet
JWT
JWT_SECRET=your_gateway_jwt_secret
Webhook
WEBHOOK_URL=http://localhost:4000/webhook/order-update
WEBHOOK_SECRET=your_webhook_secret
Logging
LOG_LEVEL=info
π How to Run the Gateway
1οΈβ£ Prerequisites
bash
Start MongoDB
docker run -d -p 27017:27017 --name mongodb mongo:6
Or use local MongoDB
mongod --dbpath ~/data/db
2οΈβ£ Install Dependencies
bash
cd gateway
npm install
3οΈβ£ Configure Environment
bash
cp .env.example .env
Edit .env with your values
4οΈβ£ Start Gateway
bash
npm run dev
Expected output:
text
π Server avviato sulla porta 3000
β
Connesso a MongoDB
π¦ Database: mongodb://localhost:27017/myzubster
5οΈβ£ Test Gateway
bash
Health check
curl http://localhost:3000/api/health
Initiate payment
curl -X POST http://localhost:3000/api/payments/initiate \
-H "Content-Type: application/json" \
-d '{"orderId":"123","amount":150,"currency":"USD"}'
π§ͺ Test Commands
Create a Payment
bash
curl -X POST http://localhost:3000/api/payments/initiate \
-H "Content-Type: application/json" \
-d '{"orderId":"test-123","amount":150,"currency":"USD"}'
Check Payment Status
bash
curl http://localhost:3000/api/payments/status/{paymentId}
Simulate Webhook
bash
curl -X POST http://localhost:3000/api/payments/webhook \
-H "Content-Type: application/json" \
-d '{"paymentId":"{paymentId}","txHash":"mock-tx-123","amount":150}'
π Database Schema
Payment Collection
json
{
"_id": "ObjectId(...)",
"orderId": "test-123",
"subaddress": "8A1B2C3D4E5F6G7H8I9J0K",
"amount": 150,
"currency": "USD",
"moneroAmount": 0.0125,
"addressIndex": 0,
"txHash": null,
"status": "pending",
"confirmations": 0,
"amountReceived": 0,
"network": "testnet",
"createdAt": "2026-07-20T10:00:00Z",
"confirmedAt": null,
"webhookDelivered": false
}
π Security Best Practices
Practice Implementation
JWT Authentication All API endpoints require valid JWT token
Webhook Signing HMAC-SHA256 signature verification
Rate Limiting Prevent DoS attacks
Environment Variables No hardcoded secrets
HTTPS Use TLS in production
Input Validation Validate all incoming data
CORS Configure allowed origins
π Troubleshooting
"Cannot connect to MongoDB"
bash
Check if MongoDB is running
docker ps | grep mongodb
Or
ps aux | grep mongod
Start MongoDB
docker start mongodb
"Cannot connect to Monero RPC"
bash
Check if Monero node is running
curl http://localhost:18081/get_info
Start Monero node (testnet)
monerod --testnet --rpc-bind-port 18081
"Webhook delivery failed"
bash
Check if Marketplace is running
curl http://localhost:4000/health
Check webhook endpoint
curl -X POST http://localhost:4000/webhook/order-update \
-H "Content-Type: application/json" \
-d '{"orderId":"test","status":"test"}'
π Resources
Monero RPC Documentation: monero-rpc
monero-javascript: npm
MongoDB Documentation: mongodb.com
MyZubster GitHub: github.com/DanielIoni-creator
β Next Steps
Add real Monero wallet β Connect to a real Monero daemon
Add rate limiting β Protect the API from abuse
Add monitoring β Payment monitoring and alerts
Add retry logic β For failed webhook deliveries
Write tests β Unit and integration tests
Built with β€οΈ for privacy, freedom, and decentralization.
Happy coding! π
Top comments (0)