A secure, full-stack Virtual POS system built with React, Node.js/Express, and PostgreSQL, featuring Dynamic Currency Conversion (DCC), Idempotency protection, Stripe integration, Webhook support, and enterprise-grade network architecture including SOCKS5 tunneling, transparent proxy, and mobile VPOS reverse proxy authentication.
| Layer | Technology |
|---|---|
| Frontend | React 18, React Router v6 |
| Backend | Node.js 20, Express 4 |
| Database | PostgreSQL 16 |
| Payments | Stripe |
| BIN Lookup | binlist.net (free) / bintable.com (paid) |
| FX Rates | frankfurter.app (free) / exchangeratesapi.io (paid) |
| Proxy | HTTP / HTTPS / SOCKS5 |
| Reverse Proxy | Nginx |
| Containerization | Docker + Docker Compose |
- Node.js 20+
- PostgreSQL 16+
- npm
cd vpos
# Backend
cp backend/.env.example backend/.env
# Edit backend/.env with your values
# Frontend
cp frontend/.env.example frontend/.env# Backend
cd backend
npm install
# Frontend
cd ../frontend
npm installcd backend
# Run migrations (creates all tables)
npm run migrate
# Seed default admin user
npm run seed
# Or do both at once
npm run setupcd backend
npm run dev
# Runs on http://localhost:5000cd frontend
npm start
# Runs on http://localhost:3000# Check backend health (includes proxy and tunnel status)
curl http://localhost:5000/api/healthThe VPOS supports four enterprise network modes that can be combined:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VPOS Terminals β
β (Desktops / Tablets / Mobile) β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β
ββββββββββββΌβββββββββββ
β VPOS Backend β
β (Node.js/Express) β
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β Proxy Layer β
β HTTP/HTTPS/SOCKS5 β
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β Payment Gateway β
β (Stripe API) β
βββββββββββββββββββββββ
Used when VPOS is on an air-gapped network with no direct internet access. The proxy acts as the bridge between the internal network and Stripe.
Setup in backend/.env:
OUTBOUND_PROXY_URL=http://your-proxy-server:8080
# Or with authentication:
OUTBOUND_PROXY_URL=http://username:password@your-proxy-server:8080What it does:
- All Stripe API calls route through your proxy
- Your proxy's static IP is what Stripe sees (use for IP whitelisting)
- Full packet inspection by your security stack
- Centralized logging of all payment traffic
Best for organizations needing a VPN-like tunnel without a full VPN. SOCKS5 handles any protocol (TCP/UDP) on any port β not just HTTP.
Setup in backend/.env:
# Without authentication
OUTBOUND_PROXY_URL=socks5://your-proxy-server:1080
# With authentication
OUTBOUND_PROXY_URL=socks5://username:password@your-proxy-server:1080What it does:
- All payment traffic tunneled through SOCKS5
- Protocol agnostic β works with any payment processor
- All terminals appear as one static IP to Stripe
- Identity masking for multi-terminal deployments
Setting up a SOCKS5 server (Linux):
# Install Dante SOCKS5 server
apt-get install dante-server
# Edit /etc/danted.conf
logoutput: /var/log/danted.log
internal: eth0 port = 1080
external: eth0
method: username none
user.privileged: root
user.unprivileged: nobody
# Start service
systemctl start danted
systemctl enable dantedThe most VPN-like setup. VPOS terminals require zero configuration. Your router intercepts all traffic on port 443 and redirects to the proxy.
Setup in backend/.env:
TRANSPARENT_PROXY_MODE=trueRouter configuration (iptables on Linux gateway):
# Redirect all HTTPS traffic to proxy (port 3128)
iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 3128
# Save rules
iptables-save > /etc/iptables/rules.v4Squid proxy config (/etc/squid/squid.conf):
http_port 3128 intercept
https_port 3129 intercept ssl-bump cert=/etc/squid/ssl_cert/myCA.pem
ssl_bump server-first all
acl payment_gateways dstdomain .stripe.com
http_access allow payment_gateways
http_access deny all
What it does:
- Zero config on VPOS terminals
- Router handles all interception
- Employees cannot bypass or tamper with settings
- All traffic logged at network level
For mobile units like delivery agents or field sales reps. Each device must authenticate with a unique token before processing payments.
Step 1 β Enable mobile mode in backend/.env:
MOBILE_VPOS_MODE=trueStep 2 β Register each device in backend/.env:
# Format: DEVICE_TOKEN_<DEVICEID>=<secret-token>
# Device IDs must be uppercase, no spaces
DEVICE_TOKEN_TABLET001=aB3xK9mP2qR7sT1uV4wY6z
DEVICE_TOKEN_TABLET002=cD5eF8gH0iJ2kL4mN6oP8q
DEVICE_TOKEN_MOBILE001=rS1tU3vW5xY7zA9bC2dE4fStep 3 β Configure each mobile device to send these headers with every request: X-Device-Id: TABLET001 X-Device-Token: aB3xK9mP2qR7sT1uV4wY6z
Step 4 β Revoke a stolen device instantly:
If a tablet is stolen, remove its token from .env and restart the backend.
That device can no longer process any payments immediately.
Generating secure device tokens:
# Linux/Mac
openssl rand -hex 32
# PowerShell (Windows)
[System.Web.Security.Membership]::GeneratePassword(32, 8)
# Or simpler:
-join ((65..90) + (97..122) + (48..57) | Get-Random -Count 32 | % {[char]$_})Only approved domains can receive outbound connections. All other connections are blocked and logged.
Setup in backend/.env:
ALLOWED_OUTBOUND_DOMAINS=api.stripe.com,lookup.binlist.net,api.frankfurter.app,api.exchangeratesapi.io,api.bintable.comOnce your proxy is set up with a static IP:
- Go to dashboard.stripe.com
- Settings β Security β Allowed IP addresses
- Add your proxy server's public IP
- All terminals now share one trusted identity
- Manual entry: Card number, Expiry, CVV
- Real-time Luhn algorithm validation
- Card brand detection (Visa, Mastercard, Amex, Discover, JCB)
- Animated card preview
- CVV masking with correct length per brand
- BIN lookup detects card issuing country
- Offers conversion to customer's home currency
- Live exchange rates (1-hour DB cache)
- 2.5% DCC markup (industry standard)
- Full audit trail per transaction
- Auto-generated per charge session
- Duplicate clicks never double-charge
- 24-hour TTL with automatic cleanup
- Signature verification on every event
- Handles: succeeded, failed, refunded, disputed
- Duplicate event protection
- Full event log in database
usersβ merchant accountstransactionsβ full charge historyidempotency_keysβ deduplicationexchange_rate_cacheβ FX cacheaudit_logβ all actions with IPwebhook_eventsβ delivery log
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/login |
Login |
| GET | /api/auth/profile |
Get profile |
| PUT | /api/auth/change-password |
Change password |
| Method | Endpoint | Headers | Description |
|---|---|---|---|
| POST | /api/payments/dcc-quote |
Bearer | DCC quote |
| POST | /api/payments/charge |
Bearer + Idempotency-Key | Process charge |
| GET | /api/payments/transactions |
Bearer | List transactions |
| GET | /api/payments/transactions/:id |
Bearer | Single transaction |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Health + proxy + tunnel status |
| POST | /api/webhooks/stripe |
Stripe events |
vpos/
βββ backend/
β βββ src/
β β βββ controllers/
β β β βββ authController.js
β β β βββ paymentController.js
β β βββ middleware/
β β β βββ auth.js
β β β βββ domainWhitelist.js
β β β βββ idempotency.js
β β β βββ sanitize.js
β β βββ migrations/
β β β βββ run.js
β β β βββ seed.js
β β βββ models/
β β β βββ db.js
β β βββ routes/
β β β βββ auth.js
β β β βββ payment.js
β β βββ services/
β β β βββ currencyService.js
β β β βββ proxyConfig.js
β β β βββ tunnelService.js
β β βββ utils/
β β β βββ logger.js
β β βββ webhooks/
β β β βββ stripeWebhook.js
β β βββ index.js
β βββ Dockerfile
β βββ package.json
β βββ .env.example
β
βββ frontend/
β βββ public/
β β βββ index.html
β βββ src/
β β βββ components/
β β β βββ Layout.js
β β βββ pages/
β β β βββ Charge.js
β β β βββ Transactions.js
β β β βββ Settings.js
β β βββ styles/
β β β βββ global.css
β β βββ utils/
β β β βββ api.js
β β β βββ cardUtils.js
β β βββ App.js
β β βββ index.js
β βββ Dockerfile
β βββ package.json
β βββ .env.example
β
βββ nginx/
β βββ nginx.conf
βββ docker-compose.yml
βββ .env.example
βββ README.md
- Set strong
JWT_SECRET(openssl rand -hex 64) - Set strong
DB_PASSWORD - Add real Stripe live secret key
- Configure Stripe webhook endpoint and secret
- Add SSL certificate to
nginx/ssl/ - Update
ALLOWED_ORIGINSwith your domain - Configure outbound proxy (
OUTBOUND_PROXY_URL) - Add proxy static IP to Stripe IP whitelist
- Set
ALLOWED_OUTBOUND_DOMAINSfor PCI-DSS - Register all mobile devices with unique tokens
- Enable
TRANSPARENT_PROXY_MODEif using router interception - Set up PostgreSQL backups
- Configure log rotation for
backend/logs/ - Set
NODE_ENV=production
Proprietary β for internal organisational use only.