Multi-Tenant Architecture Documentation
A comprehensive guide to implementing secure, scalable payment processing with multi-tenant support, VATU currency conversion, and seamless application integration.
The Merchant Warrior Payment Gateway Integration is a modern, secure payment processing application that provides comprehensive multi-tenant support, enabling organizations to deploy a single application that serves multiple clients with completely separate configurations.
This application is built with enterprise-grade architecture, supporting unlimited tenants through a single deployment while maintaining complete isolation between tenant configurations and data.
The application is deployed and available for testing:
| Tenant | URL | Features |
|---|---|---|
| Default | /?amount=50.00 | Test credentials, basic features |
| Altima Education | /?tenant=altima-education&vatu=5000 | VATU converter, education branding |
| Charity Network | /?tenant=charity-donations&amount=25.00 | Charity branding, no VATU converter |
Single deployment supporting unlimited organizations with separate configurations, merchant accounts, and branding.
Full integration with Merchant Warrior PayFrame for PCI DSS compliant card processing with no card data touching your servers.
Real-time VATU to AUD conversion using live exchange rates, configurable per tenant for specific use cases.
Automated email confirmations for customers and admin notifications with tenant-specific templates and branding.
Pre-populate forms via URL parameters for seamless integration with existing applications and workflows.
Automatic return to spawning application with payment status, enabling complex multi-application workflows.
Custom branding per tenant including company names, colors, logos, and feature toggles.
Works perfectly on desktop and mobile devices with modern React, TypeScript, and Tailwind CSS.
The application supports multiple tenant configurations without requiring separate builds, enabling both hosted multi-tenancy and self-hosting scenarios with complete configuration isolation.
The system detects tenants using multiple methods in priority order:
| Priority | Method | Example | Use Case |
|---|---|---|---|
| 1 | URL Parameter | ?tenant=altima-education |
Dynamic switching, testing |
| 2 | URL Path | /tenant/charity-donations/ |
RESTful routing |
| 3 | Subdomain | altima.gateway.com |
Multi-tenant hosting |
| 4 | Environment Variable | VITE_TENANT_ID=altima |
Self-hosting |
| 5 | Local Storage | Persisted selection | User preference |
| 6 | Default | Test configuration | Fallback |
# URL Parameter Method (Highest Priority)
https://paymentgateway.com/?tenant=altima-education&amount=50.00
# Path Method
https://paymentgateway.com/tenant/charity-donations/?amount=25.00
# Subdomain Method
https://altima.paymentgateway.com/?amount=100.00
# Self-Hosting (Environment Variable)
https://payments.altima-education.org/?amount=75.00
Each tenant can have completely different configurations:
Different merchant UUIDs, API keys, passphrases, and gateway URLs per tenant.
Separate EmailJS configurations, templates, and admin email addresses.
Custom company names, colors, logos, and visual identity per tenant.
Enable/disable VATU converter, email notifications, and other features per tenant.
'altima-education': {
tenantId: 'altima-education',
tenantName: 'Altima Education Foundation',
merchantWarrior: {
merchantUUID: import.meta.env.VITE_ALTIMA_MW_MERCHANT_UUID,
apiKey: import.meta.env.VITE_ALTIMA_MW_API_KEY,
passphrase: import.meta.env.VITE_ALTIMA_MW_PASSPHRASE,
// ... other config
},
email: {
serviceId: import.meta.env.VITE_ALTIMA_EMAILJS_SERVICE_ID,
adminEmail: 'admin@altima-education.org',
// ... email templates
},
branding: {
companyName: 'Altima Education Foundation',
primaryColor: '#059669',
secondaryColor: '#047857',
// ... branding options
},
features: {
vatuConverter: true,
emailNotifications: true,
streamlinedFlow: true,
customReturnUrls: true
}
}
Adding a new tenant requires no code changes to the core application - just configuration and environment variables.
Add the new tenant configuration in src/config/tenantConfig.ts:
'your-organization': {
tenantId: 'your-organization',
tenantName: 'Your Organization Name',
merchantWarrior: {
merchantUUID: import.meta.env.VITE_YOUR_MW_MERCHANT_UUID || 'test_fallback',
apiKey: import.meta.env.VITE_YOUR_MW_API_KEY || 'test_fallback',
passphrase: import.meta.env.VITE_YOUR_MW_PASSPHRASE || 'test_fallback',
// ... other merchant warrior config
},
branding: {
companyName: 'Your Organization',
primaryColor: '#your-primary-color',
secondaryColor: '#your-secondary-color',
supportEmail: 'support@yourorganization.com'
},
features: {
vatuConverter: false, // Enable/disable as needed
emailNotifications: true,
streamlinedFlow: true,
customReturnUrls: true
}
}
Configure production credentials in your deployment environment:
# Your Organization Configuration
VITE_YOUR_MW_MERCHANT_UUID=live_merchant_uuid
VITE_YOUR_MW_API_KEY=live_api_key
VITE_YOUR_MW_PASSPHRASE=live_passphrase
VITE_YOUR_EMAILJS_SERVICE_ID=your_service_id
VITE_YOUR_EMAILJS_PUBLIC_KEY=your_public_key
VITE_YOUR_ADMIN_EMAIL=admin@yourorganization.com
# ... other variables
Deploy the application once - all tenants (existing and new) will use the same build with their respective configurations loaded dynamically.
Adding new tenants requires no downtime for existing tenants. The application dynamically loads configurations based on the detected tenant.
The application supports comprehensive URL parameters for seamless integration with existing applications and workflows.
| Parameter | Description | Example |
|---|---|---|
tenant |
Select specific tenant configuration | ?tenant=altima-education |
t |
Short form tenant parameter | ?t=charity-donations |
| Parameter | Description | Example |
|---|---|---|
amount |
Pre-fills AUD amount directly (disables VATU converter) | ?amount=50.00 |
vatu |
Pre-fills VATU amount and shows real-time AUD conversion | ?vatu=5000 |
| Parameter | Description | Example |
|---|---|---|
name |
Pre-fills customer name | ?name=John%20Doe |
email |
Pre-fills email address | ?email=john@example.com |
phone |
Pre-fills phone number | ?phone=+61400000000 |
address |
Pre-fills street address | ?address=123%20Main%20St |
city |
Pre-fills city | ?city=Sydney |
state |
Pre-fills state/province | ?state=NSW |
postcode |
Pre-fills postal code | ?postcode=2000 |
country |
Pre-fills country (default: AU) | ?country=AU |
product |
Pre-fills product/service description | ?product=Computer%20Donation |
| Parameter | Description | Example |
|---|---|---|
return_url |
URL to return to after payment completion | ?return_url=https://myapp.com/dashboard |
returnUrl |
Alternative parameter name for return URL | ?returnUrl=https://myapp.com/dashboard |
// From your application - redirect to payment gateway
function initiatePayment(customerData, amount, tenant = 'default') {
const baseUrl = 'https://altimapay.com/demo';
const returnUrl = encodeURIComponent(window.location.origin + '/payment-complete');
const params = new URLSearchParams({
tenant: tenant,
amount: amount.toString(),
name: customerData.name,
email: customerData.email,
phone: customerData.phone,
return_url: returnUrl
});
window.location.href = baseUrl + '?' + params.toString();
}
// Handle return from payment gateway
function handlePaymentReturn() {
const urlParams = new URLSearchParams(window.location.search);
const paymentStatus = urlParams.get('payment_status'); // 'success' or 'failed'
const transactionId = urlParams.get('transaction_id');
const amount = urlParams.get('amount');
if (paymentStatus === 'success') {
console.log(`Payment successful! Transaction: ${transactionId}, Amount: $${amount}`);
// Update UI, redirect to success page, etc.
} else {
console.log('Payment failed or was cancelled');
// Handle failure case
}
}
The application supports two distinct payment flows depending on how much information is provided via URL parameters.
When all required parameters are provided via URL, the application automatically enters streamlined mode:
Activated when URL contains: name, email, phone, address, city, postcode, and (amount OR vatu)
The application implements enterprise-grade security practices with comprehensive environment variable support for sensitive configuration data.
Each tenant uses prefixed environment variables to maintain complete isolation:
# Altima Education Tenant
VITE_ALTIMA_MW_MERCHANT_UUID=altima_live_uuid
VITE_ALTIMA_MW_API_KEY=altima_live_key
VITE_ALTIMA_MW_PASSPHRASE=altima_live_passphrase
VITE_ALTIMA_MW_PAYFRAME_JS_URL=https://secure.merchantwarrior.com/payframe/payframe.js
VITE_ALTIMA_MW_PAYFRAME_URL=https://secure.merchantwarrior.com/payframe/
VITE_ALTIMA_MW_SUBMIT_URL=https://api.merchantwarrior.com/payframe/
VITE_ALTIMA_EMAILJS_SERVICE_ID=service_altima
VITE_ALTIMA_EMAILJS_PUBLIC_KEY=altima_public_key
VITE_ALTIMA_EMAILJS_USER_SUCCESS_TEMPLATE_ID=template_altima_success
VITE_ALTIMA_EMAILJS_USER_FAILURE_TEMPLATE_ID=template_altima_failure
VITE_ALTIMA_EMAILJS_ADMIN_NOTIFICATION_TEMPLATE_ID=template_altima_admin
VITE_ALTIMA_ADMIN_EMAIL=admin@altima-education.org
VITE_ALTIMA_EXCHANGE_RATE_API_KEY=altima_fx_api_key
# Charity Network Tenant
VITE_CHARITY_MW_MERCHANT_UUID=charity_live_uuid
VITE_CHARITY_MW_API_KEY=charity_live_key
VITE_CHARITY_MW_PASSPHRASE=charity_live_passphrase
VITE_CHARITY_EMAILJS_SERVICE_ID=service_charity
VITE_CHARITY_EMAILJS_PUBLIC_KEY=charity_public_key
VITE_CHARITY_ADMIN_EMAIL=donations@charity.org
# ... additional charity configuration
For self-hosting scenarios, set a tenant ID and configure your organization's settings:
# Set tenant ID for self-hosting
VITE_TENANT_ID=your-organization
# Configure your organization's settings
VITE_YOUR_MW_MERCHANT_UUID=your_merchant_uuid
VITE_YOUR_MW_API_KEY=your_api_key
VITE_YOUR_MW_PASSPHRASE=your_passphrase
VITE_YOUR_MW_PAYFRAME_JS_URL=https://secure.merchantwarrior.com/payframe/payframe.js
VITE_YOUR_MW_PAYFRAME_URL=https://secure.merchantwarrior.com/payframe/
VITE_YOUR_MW_SUBMIT_URL=https://api.merchantwarrior.com/payframe/
VITE_YOUR_EMAILJS_SERVICE_ID=service_your_org
VITE_YOUR_EMAILJS_PUBLIC_KEY=your_public_key
VITE_YOUR_ADMIN_EMAIL=admin@yourcompany.com
| Environment | PayFrame JS URL | PayFrame URL | Submit URL |
|---|---|---|---|
| Test | securetest.merchantwarrior.com/payframe/payframe.js |
securetest.merchantwarrior.com/payframe/ |
base.merchantwarrior.com/payframe/ |
| Production | secure.merchantwarrior.com/payframe/payframe.js |
secure.merchantwarrior.com/payframe/ |
api.merchantwarrior.com/payframe/ |
Test credentials are safely hardcoded as fallbacks, all URLs point to Merchant Warrior's test environment, and no real payments can be processed.
Never commit .env files containing production credentials. Always use environment variables for sensitive data in production deployments.
| Security Layer | Implementation | Benefit |
|---|---|---|
| Payment Processing | Merchant Warrior PayFrame | PCI DSS compliant, no card data touches application |
| API Authentication | MD5 hash generation with multiple elements | Secure API communication with payment gateway |
| Data Transmission | HTTPS/SSL encryption | All data encrypted in transit |
| Configuration | Environment variables | Sensitive data never exposed in code |
| Input Validation | Client and server-side validation | Prevents malicious input and data corruption |
| Tenant Isolation | Dynamic configuration loading | Complete separation between tenant data |
# Production .env file (NEVER commit this!)
# Altima Education tenant
VITE_ALTIMA_MW_MERCHANT_UUID=your_live_merchant_uuid
VITE_ALTIMA_MW_API_KEY=your_live_api_key
VITE_ALTIMA_MW_PASSPHRASE=your_live_passphrase
# Charity tenant
VITE_CHARITY_MW_MERCHANT_UUID=charity_live_uuid
VITE_CHARITY_MW_API_KEY=charity_live_key
VITE_CHARITY_MW_PASSPHRASE=charity_live_passphrase
# Add to .gitignore
.env
.env.local
.env.production
The application supports multiple deployment scenarios, from multi-tenant SaaS platforms to self-hosted single-tenant installations.
Host multiple organizations on a single deployment with complete configuration isolation.
# DNS CNAME records for subdomain-based tenant detection
altima.yourgateway.com -> yourgateway.com
charity.yourgateway.com -> yourgateway.com
education.yourgateway.com -> yourgateway.com
*.yourgateway.com -> yourgateway.com # Wildcard for all subdomains
For hosting platforms like Netlify, Vercel, or Heroku:
# Set all tenant environment variables in deployment platform
VITE_ALTIMA_MW_MERCHANT_UUID=altima_live_uuid
VITE_ALTIMA_MW_API_KEY=altima_live_key
VITE_ALTIMA_EMAILJS_SERVICE_ID=service_altima
VITE_ALTIMA_ADMIN_EMAIL=admin@altima-education.org
VITE_CHARITY_MW_MERCHANT_UUID=charity_live_uuid
VITE_CHARITY_MW_API_KEY=charity_live_key
VITE_CHARITY_EMAILJS_SERVICE_ID=service_charity
VITE_CHARITY_ADMIN_EMAIL=donations@charity.org
# Add more tenants as needed...
Single deployment and maintenance for unlimited tenants reduces operational costs.
Deploy once to update all tenants simultaneously with new features and security patches.
Add new tenants without additional infrastructure or deployment complexity.
Complete configuration and data isolation between tenants for security and compliance.
Organizations can self-host the application with their own branding and custom domain.
Self-hosting requires only setting a tenant ID environment variable and configuring your organization's credentials.
# Force specific tenant configuration
VITE_TENANT_ID=your-organization
# Your organization's Merchant Warrior credentials
VITE_YOUR_MW_MERCHANT_UUID=your_live_uuid
VITE_YOUR_MW_API_KEY=your_live_key
VITE_YOUR_MW_PASSPHRASE=your_live_passphrase
# Your organization's email configuration
VITE_YOUR_EMAILJS_SERVICE_ID=your_service_id
VITE_YOUR_EMAILJS_PUBLIC_KEY=your_public_key
VITE_YOUR_ADMIN_EMAIL=admin@yourcompany.com
# Production URLs
VITE_YOUR_MW_PAYFRAME_JS_URL=https://secure.merchantwarrior.com/payframe/payframe.js
VITE_YOUR_MW_PAYFRAME_URL=https://secure.merchantwarrior.com/payframe/
VITE_YOUR_MW_SUBMIT_URL=https://api.merchantwarrior.com/payframe/
# Deploy to your organization's domain
payments.yourcompany.com
pay.yourorganization.org
gateway.yourbusiness.net
| Layer | Technology | Purpose |
|---|---|---|
| Frontend Framework | React 18 + TypeScript | Modern, type-safe user interface |
| Build Tool | Vite | Fast development and optimized builds |
| Styling | Tailwind CSS | Utility-first CSS framework |
| Payment Processing | Merchant Warrior PayFrame | PCI DSS compliant card processing |
| Email Service | EmailJS | Client-side email notifications |
| Currency API | exchangeratesapi.io | Real-time exchange rates |
| Icons | Lucide React | Modern icon library |
| State Management | React Hooks | Component state and lifecycle |
src/
âââ components/
â âââ CustomerForm.tsx # Customer information form
â âââ VatuConverter.tsx # VATU to AUD conversion
â âââ PayFrameContainer.tsx # Merchant Warrior PayFrame integration
â âââ PaymentResponse.tsx # Payment result display
â âââ TenantSelector.tsx # Development tenant switching
âââ services/
â âââ emailService.ts # Tenant-aware EmailJS integration
â âââ fxService.ts # Tenant-aware exchange rate API
âââ config/
â âââ tenantConfig.ts # Multi-tenant configuration system
â âââ settings.ts # Legacy settings (backward compatibility)
â âââ merchantWarrior.ts # Dynamic payment gateway config
âââ hooks/
â âââ useUrlParams.ts # URL parameter handling
âââ types/
âââ payment.ts # TypeScript interfaces
For server-side integration, monitor return URLs to detect payment completion:
// Express.js example
app.get('/payment-complete', (req, res) => {
const { payment_status, transaction_id, amount, tenant } = req.query;
if (payment_status === 'success') {
// Update database, send confirmations, etc.
console.log(`Payment received: ${transaction_id} for $${amount} (${tenant})`);
// Tenant-specific processing
switch(tenant) {
case 'altima-education':
await processEducationDonation(transaction_id, amount);
break;
case 'charity-donations':
await processCharityDonation(transaction_id, amount);
break;
}
}
// Redirect user to appropriate page
res.redirect('/dashboard');
});
// React component example
function PaymentButton({ customerData, amount, tenant }) {
const initiatePayment = () => {
const baseUrl = 'https://altimapay.com/demo';
const returnUrl = encodeURIComponent(window.location.origin + '/payment-complete');
const params = new URLSearchParams({
tenant: tenant,
amount: amount.toString(),
name: customerData.name,
email: customerData.email,
phone: customerData.phone,
address: customerData.address,
city: customerData.city,
state: customerData.state,
postcode: customerData.postcode,
country: customerData.country || 'AU',
product: customerData.product || 'Purchase',
return_url: returnUrl
});
// Redirect to payment gateway
window.location.href = baseUrl + '?' + params.toString();
};
return (
<button onClick={initiatePayment} className="payment-button">
Pay ${amount} AUD
</button>
);
}
When users return from the payment gateway, these parameters are added to the return URL:
| Parameter | Description | Example Values |
|---|---|---|
payment_status |
Payment result status | success, failed |
transaction_id |
Merchant Warrior transaction ID | 12345678 |
amount |
Payment amount in AUD | 50.00 |
tenant |
Tenant that processed the payment | altima-education |
| Issue | Cause | Solution |
|---|---|---|
| Wrong tenant detected | Multiple detection methods conflicting | Check detection priority order, clear localStorage |
| Default configuration loading | Tenant ID not found in configuration | Verify tenant exists in tenantConfig.ts |
| Subdomain not working | DNS configuration incorrect | Check CNAME records and wildcard DNS setup |
| Environment variable not loading | Variable name mismatch | Verify tenant prefix matches configuration |
| Issue | Cause | Solution |
|---|---|---|
| PayFrame not loading | Incorrect Merchant Warrior credentials | Verify tenant's MW UUID, API key, and passphrase |
| Payment fails with hash error | Incorrect hash generation | Check passphrase and hash algorithm implementation |
| Test payments not working | Using production URLs with test credentials | Ensure test URLs are used with test credentials |
| Live payments not working | Using test URLs with live credentials | Update to production URLs for live processing |
| Issue | Cause | Solution |
|---|---|---|
| No emails sent | EmailJS not configured for tenant | Verify tenant's EmailJS service ID and public key |
| Customer emails not sending | Template ID not configured | Check tenant's success/failure template IDs |
| Admin emails not sending | Admin email or template not configured | Verify admin email and notification template ID |
| Email template errors | Template variables mismatch | Ensure template variables match application parameters |
Use browser developer tools to inspect tenant detection and configuration loading. Check console logs for detailed tenant information.
// Add to browser console for debugging
console.log('Current tenant:', TenantConfigService.detectTenant());
console.log('Tenant config:', TenantConfigService.getConfig());
console.log('Available tenants:', TenantConfigService.getAvailableTenants());
// Check environment variables
console.log('Environment variables:', import.meta.env);
# Test tenant detection methods
# URL Parameter
/?tenant=altima-education&amount=10.00
# Subdomain (requires DNS setup)
https://altima.yourgateway.com/?amount=10.00
# Path-based
/tenant/charity-donations/?amount=25.00
# Environment variable (self-hosting)
VITE_TENANT_ID=altima-education
# Local storage (browser console)
localStorage.setItem('tenantId', 'charity-donations');
This application is designed for secure payment processing with seamless integration capabilities and enterprise-grade multi-tenant architecture. It's particularly well-suited for organizations requiring scalable payment solutions with complete tenant isolation.
Merchant Warrior Payment Gateway - Multi-Tenant Documentation
Generated on
Version 2.0 - Multi-Tenant Architecture