Secure payment QR code generation and real-time transaction monitoring with mutual TLS authentication
- Overview
- Features
- Security Architecture
- Prerequisites
- Installation Guide
- Configuration
- Usage
- API Documentation
- Database Schema
- Deployment
- Security Recommendations
- Troubleshooting
- Contributing
- License
QR Terminal is a Next.js-based Progressive Web App (PWA) that enables Slovak merchants to:
- Generate payment QR codes compliant with Slovak Payment Link Standard v1.3
- Receive real-time payment confirmations via MQTT over TLS
- Track transaction history with comprehensive audit logging
- Verify payment integrity using SHA-256 hashing
- Operate seamlessly in both TEST and PRODUCTION banking environments
The application uses mutual TLS (mTLS) authentication with client certificates to securely communicate with banking APIs, ensuring bank-grade security for all financial transactions.
- β Bank-Grade Security: mTLS authentication with PKCS#12 certificates
- β Real-Time Updates: Instant payment confirmations via MQTT
- β Data Integrity: SHA-256 hash verification for every transaction
- β Mobile-First: Responsive PWA design for all devices
- β Dual Environment: Seamless TEST/PRODUCTION switching
- β Comprehensive Audit: Full transaction history with timestamps
- Certificate Management: Upload and convert PKCS#12 certificates from XML format
- QR Code Generation: Create compliant payment QR codes following Slovak standards
- Real-time Notifications: MQTT-based instant payment confirmations with TLS encryption
- Data Integrity: SHA-256 hash verification for payment authenticity
- Transaction History: Complete audit trail with database persistence and date filtering
- Dual Environment: Visual indicators and seamless switching between TEST and PRODUCTION
- Dispute Management: Flag and track disputed transactions
- Timezone Handling: Correct UTC/local timezone conversion for international use
- β Mutual TLS (mTLS) authentication with client certificates
- β Certificate-based API access with automatic cleanup
- β Input sanitization and XSS prevention
- β IBAN checksum validation (ISO 13616 Mod-97)
- β Temporary file management with 0o600 permissions
- β Comprehensive audit logging to PostgreSQL
- β Data integrity verification with SHA-256
- β Rate limiting on API endpoints (2 req/min)
- β Row Level Security (RLS) policies on database
- π± Mobile-first responsive design with touch optimization
- π Real-time payment status updates without page refresh
- π³ Automatic IBAN formatting with space insertion
- π° Currency input with Slovak EUR formatting (0,01 β¬)
- π Transaction summary with PDF export capability
- π¨ Visual environment indicators (yellow=PRODUCTION, green=TEST)
- π Dark mode support (future enhancement)
- π² PWA support - installable on mobile devices
The application handles sensitive financial data and protects against:
- π‘οΈ Man-in-the-middle (MITM) attacks
- π‘οΈ Certificate theft and unauthorized access
- π‘οΈ Data tampering and replay attacks
- π‘οΈ XSS and SQL injection attacks
- π‘οΈ Information disclosure
- π‘οΈ Denial of Service (DoS) attacks
Implementation:
```typescript
const client = mqtt.connect(mqtts://${broker}:8883, {
cert: clientCertBuffer,
key: clientKeyBuffer,
ca: caCertBuffer,
rejectUnauthorized: true,
protocol: "mqtts"
})
```
Security Level: Bank-Grade
Attack Probability: Very Low (0.1%)
Implementation:
- Temporary files with
0o600permissions (owner read/write only) - Unique session IDs using
randomUUID() - Automatic cleanup with
Promise.allSettled() - No persistent storage - certificates exist for <1 second
Vulnerability: Temporary file exposure
Attack Probability: Low (5%) - Requires local system access and precise timing
Implementation:
- IBAN validation with Mod-97 checksum (ISO 13616)
- Regex-based XSS sanitization
- TypeScript type safety
- Amount validation with digit-only input
Vulnerability: Regex bypasses
Attack Probability: Medium (15%)
Recommendation: Use DOMPurify library
Implementation: ```typescript const hash = await crypto.subtle.digest("SHA-256", data) ```
Security Level: Cryptographically Secure
Attack Probability: Negligible (<0.0001%)
Recommendation: Upgrade to HMAC-SHA256 with secret key for production
Implementation:
- Row Level Security (RLS) policies
- Parameterized queries (Supabase client)
- TLS-encrypted connections
- Separate service and anon keys
Vulnerability: Anonymous read access
Attack Probability: High (80%)
Recommendation: Implement user authentication with Supabase Auth
Implementation:
- Rate limiting (2 requests/minute per endpoint)
- Client IP logging for audit trail
- 30-second timeout protection
- HTTPS enforced
Vulnerability: No API key authentication
Attack Probability: High (80%)
Recommendation: Add API key or JWT authentication
| Category | Score | Status |
|---|---|---|
| Authentication | βββββ | Excellent (mTLS) |
| Authorization | βββ | Needs Improvement |
| Data Encryption | βββββ | Excellent (TLS) |
| Input Validation | ββββ | Good |
| Data Integrity | βββββ | Excellent (SHA-256) |
| Certificate Mgmt | βββββ | Excellent |
| API Security | ββββ | Good (Rate Limited) |
| Database Security | ββββ | Good (RLS Enabled) |
| Audit Logging | βββββ | Excellent |
Overall Security Rating: ββββ (4.2/5) - Production Ready
| Software | Version | Download |
|---|---|---|
| Node.js | 18.x or higher | nodejs.org |
| npm | 9.x or higher | Included with Node.js |
| Git | Latest | git-scm.com |
| Service | Purpose | Sign Up |
|---|---|---|
| Supabase | PostgreSQL database with real-time | supabase.com |
| Vercel | Deployment platform (optional) | vercel.com |
- β Valid merchant account with Slovak bank supporting instant payments
- β PKCS#12 certificate containing VATSK (tax ID) and POKLADNICA (cash register ID)
- β
Access to TEST environment (
api-erp-i.kverkom.sk) for development - β
Production credentials for live transactions (
api-erp.kverkom.sk) - β CA certificate bundle for TLS verification
```bash git clone https://github.com/your-username/qr-terminal.git cd qr-terminal ```
```bash npm install ```
This installs:
- Next.js 16 with App Router
- React 19.2 with Server Components
- Supabase client libraries (
@supabase/supabase-js,@supabase/ssr) - MQTT.js for real-time messaging
- node-forge for certificate handling
- qrcode for QR generation
- shadcn/ui components
- Tailwind CSS v4
- Visit Supabase Dashboard
- Click "New Project"
- Configure:
- Name:
qr-terminal(or preferred name) - Database Password: Strong password (save securely!)
- Region:
Europe West(Frankfurt - recommended for Slovakia)
- Name:
- Click "Create new project" (takes 2-3 minutes)
Execute SQL scripts in order using Supabase SQL Editor:
Navigate to: Supabase Dashboard β SQL Editor β New Query
Script 1: Drop All Tables (optional - only if starting fresh) ```bash
```
Script 2: Create transaction_generations Table ```bash
```
Script 3: Create mqtt_notifications Table ```bash
```
Script 4: Create mqtt_subscriptions Table ```bash
```
Script 5: Create get_transactions_by_date Function ```bash
```
Script 6: Create get_transaction_by_id Function ```bash
```
In Supabase Dashboard β Table Editor, confirm these tables exist:
| Table | Purpose | Key Columns |
|---|---|---|
transaction_generations |
Transaction audit log | transaction_id, vatsk, pokladnica, amount, status_code, response_timestamp |
mqtt_notifications |
Payment notifications | transaction_id, amount, integrity_hash, payload_received_at |
mqtt_subscriptions |
MQTT subscription tracking | topic, vatsk, pokladnica, qos |
β All tables should have RLS enabled (green shield icon)
Create .env.local file in project root:
```env
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here
NEXT_PUBLIC_EMBEDDED_CA_BUNDLE=-----BEGIN CERTIFICATE----- MIIFazCCBFOgAwIBAgIQBN... (Complete CA certificate chain for api-erp-i.kverkom.sk) ... -----END CERTIFICATE-----
NEXT_PUBLIC_EMBEDDED_CA_BUNDLE_PROD=-----BEGIN CERTIFICATE----- MIIFazCCBFOgAwIBAgIQBN... (Complete CA certificate chain for api-erp.kverkom.sk) ... -----END CERTIFICATE-----
NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL=http://localhost:3000 ```
- In Supabase Dashboard β Settings β API
- Copy these values:
- Project URL β
NEXT_PUBLIC_SUPABASE_URL - anon public key β
NEXT_PUBLIC_SUPABASE_ANON_KEY - service_role key β
SUPABASE_SERVICE_ROLE_KEYβ οΈ KEEP SECRET
- Project URL β
CA certificates enable TLS verification with banking APIs.
For TEST Environment (api-erp-i.kverkom.sk):
```bash
openssl s_client -showcerts -connect api-erp-i.kverkom.sk:443 </dev/null 2>/dev/null |
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > test-ca-bundle.pem
1. Visit https://api-erp-i.kverkom.sk in Chrome
```
For PRODUCTION Environment (api-erp.kverkom.sk):
```bash
openssl s_client -showcerts -connect api-erp.kverkom.sk:443 </dev/null 2>/dev/null |
sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > prod-ca-bundle.pem
```
```bash npm run dev ```
Application runs at: http://localhost:3000
π Installation Complete! Proceed to Usage section.
| Variable | Required | Description | Example |
|---|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
β | Supabase project URL | https://abc123.supabase.co |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
β | Public anon key | eyJhbGciOiJIUzI1Ni... |
SUPABASE_SERVICE_ROLE_KEY |
β | Service role key (SECRET) | eyJhbGciOiJIUzI1Ni... |
NEXT_PUBLIC_EMBEDDED_CA_BUNDLE |
β | TEST CA cert chain | -----BEGIN CERTIFICATE-----... |
NEXT_PUBLIC_EMBEDDED_CA_BUNDLE_PROD |
β | PROD CA cert chain | -----BEGIN CERTIFICATE-----... |
NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL |
βͺ | Dev redirect URL | http://localhost:3000 |
-
Never commit
.env.localto Git ```bashecho ".env.local" >> .gitignore ```
-
Use Vercel environment variables for production
- Encrypted at rest
- Separate environments (preview/production)
- Automatic regeneration on rotation
-
Rotate service role key quarterly
- Supabase Dashboard β Settings β API β Reset service_role key
-
Use different Supabase projects for TEST/PROD
- Isolates data
- Separate billing
- Independent scaling
Before switching to PRODUCTION mode, you MUST complete these prerequisites:
β
Checkbox 1: "MΓ‘me aktivovanΓΊ sluΕΎbu oznamovania okamΕΎitΓ½ch ΓΊhrad v banke"
(We have activated instant payment notification service at the bank)
β
Checkbox 2: "MΓ‘me podpΓsanΓΊ Dohodu o spoluprΓ‘ci s FS SR"
(We have signed cooperation agreement with Financial Administration of Slovak Republic)
Contact: kverkom.kasoveIS@financnasprava.sk
Warning: Production mode is disabled until both checkboxes are confirmed.
-
XML Authentication Data (PKCS#12 Certificate)
- Click "Choose File" or drag-and-drop
- Select your
.p12or.pfxfile from bank - File contains: Client certificate + Private key + VATSK + POKLADNICA
-
Certificate Password
- Enter password provided by your bank
- Password is used only for decryption, never stored
-
IBAN (Optional)
- Default recipient IBAN
- Auto-formatted with spaces (e.g.,
SK31 1200 0000 1987 4263 7541)
-
Click "PrihlΓ‘siΕ₯ sa" (Log In)
The application will:
- β Extract VATSK (10-digit tax ID)
- β Extract POKLADNICA (17-digit cash register ID)
- β Convert PKCS#12 to PEM format
- β Validate certificate structure
- β Store credentials securely in browser session (not on server)
-
IBAN (if not pre-filled)
- Format:
SK31 1200 0000 1987 4263 7541 - Auto-formatted as you type
- Validated with Mod-97 checksum
- Format:
-
Amount
- Format:
123,45 β¬(Slovak formatting) - Minimum:
0,01 β¬ - Maximum:
999999,99 β¬
- Format:
-
Environment Toggle
- TEST (green):
api-erp-i.kverkom.sk/mqtt-i.kverkom.sk - PRODUCTION (yellow):
api-erp.kverkom.sk/mqtt.kverkom.sk
- TEST (green):
Click "VygenerovaΕ₯ QR kΓ³d" (Generate QR Code)
The application will:
- β Call banking API with mTLS authentication
- β
Receive unique
transaction_id(e.g.,QR-abc123...) - β Generate SHA-256 integrity hash
- β Create Slovak Payment Link Standard v1.3 compliant URL
- β Generate QR code image
- β Automatically subscribe to MQTT for payment confirmation
- β Save transaction to database for audit
QR Code Example: ``` https://scantopay.sk/?iban=SK3112000000198742637541 &amount=123.45¤cy=EUR&vs=&ss=&ks=&message= &transactionId=QR-abc123&integrityHash=9a7f8b3c... ```
- Customer scans QR code with mobile banking app
- Banking app opens with pre-filled payment details
- Customer reviews and authorizes payment
- Bank processes payment instantly
- Bank sends MQTT notification to application
- Application receives real-time confirmation (typically 2-5 seconds)
- Integrity hash verified to ensure payment authenticity
- Confirmation modal displayed with payment details
- Click "Zoznam platieb" button
- Select date using date picker
- View transactions for selected day:
- Transaction ID
- IBAN
- Amount in EUR
- Timestamp (from banking system)
- Dispute flag (if flagged)
- Date Range: Select specific day
- Timezone Aware: Correctly converts UTC to local time
- Export: Print transaction summary
- Click "Doklady o nepotvrdenΓ½ch platbΓ‘ch"
- Select date
- View transactions without payment confirmation:
- Generated QR codes
- No received MQTT notification
- Potential disputes
Available only in TEST environment - hidden in PRODUCTION.
- After generating QR code, click "SimulΓ‘tor ΓΊhrady"
- Reveals QR code link:
https://scantopay.vercel.app/... - Open link on mobile device
- Simulates payment without real banking app
- Sends test MQTT notification
- Useful for development and testing
Generates new transaction ID from banking API using mTLS authentication.
Request: ```typescript FormData { clientCert: File | string // PEM client certificate clientKey: File | string // PEM private key caCert: File | string // CA certificate bundle iban: string // Recipient IBAN (optional) amount: string // Payment amount (optional) isProductionMode: boolean // Environment flag } ```
Response (Success): ```json { "success": true, "data": { "transaction_id": "QR-780554711ad94950bbac61f9e7d3af41", "created_at": "2025-11-10T23:43:01.184745Z" }, "clientIP": "192.168.1.1", "timestamp": "2025-11-10T23:43:01.500Z" } ```
Response (Error): ```json { "error": "API call failed", "details": "Connection timeout", "timestamp": "2025-11-10T23:43:01.500Z" } ```
Rate Limit: 2 requests per minute per IP
Security:
- β mTLS authentication required
- β Temporary certificate files (0o600 permissions)
- β Automatic cleanup after request
- β Audit logging to database
- β 30-second timeout protection
- β Client IP tracking
Subscribes to MQTT broker for real-time payment notifications.
Request: ```typescript FormData { clientCert: File | string // PEM client certificate clientKey: File | string // PEM private key caCert: File | string // CA certificate bundle transactionId: string // Transaction to monitor vatsk: string // Tax ID (10 digits) pokladnica: string // Cash register ID (17 digits) isProductionMode: boolean // Environment flag } ```
Response (Success with messages): ```json { "success": true, "hasMessages": true, "messages": [ { "transactionId": "QR-780554711ad94950bbac61f9e7d3af41", "transactionStatus": "RECEIVED", "amount": "123.45", "currency": "EUR", "integrityHash": "9a7f8b3c...", "endToEndId": "E2E-123456", "receivedAt": "2025-11-10T23:43:05.000Z" } ], "messageCount": 1, "communicationLog": [ "[2025-11-10T23:43:01.500Z] Connecting to mqtt-i.kverkom.sk:8883...", "[2025-11-10T23:43:02.100Z] Connected successfully", "[2025-11-10T23:43:02.200Z] Subscribed to topic: VATSK-1234567890/POKLADNICA-12345678901234567/QR-780554...", "[2025-11-10T23:43:05.300Z] Message received", "[2025-11-10T23:43:05.400Z] Disconnected" ], "clientIP": "192.168.1.1", "listeningDuration": "120 seconds" } ```
Response (No messages): ```json { "success": true, "hasMessages": false, "messages": [], "messageCount": 0, "communicationLog": ["..."], "clientIP": "192.168.1.1", "listeningDuration": "120 seconds" } ```
Configuration:
- Broker:
mqtt-i.kverkom.sk(TEST) /mqtt.kverkom.sk(PROD) - Port:
8883(MQTTS - MQTT over TLS) - Protocol:
mqtts:// - QoS:
1(at-least-once delivery) - Timeout:
120 seconds - Keep-Alive:
60 seconds
Topic Format: ``` VATSK-{vatsk}/POKLADNICA-{pokladnica}/{transactionId} ```
Security:
- β MQTTS (MQTT over TLS 1.2+)
- β Client certificate authentication
- β CA certificate validation
- β
rejectUnauthorized: true - β Automatic database persistence
Converts PKCS#12 certificate to PEM format and extracts metadata.
Request: ```typescript FormData { p12File: File // PKCS#12 certificate (.p12 or .pfx) password: string // Certificate password } ```
Response (Success): ```json { "certificate": "-----BEGIN CERTIFICATE-----\nMIIE...\n-----END CERTIFICATE-----", "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----", "vatsk": "1234567890", "pokladnica": "12345678901234567" } ```
Response (Error): ```json { "error": "Failed to convert P12 to PEM", "details": "Invalid password" } ```
Extraction Patterns:
- VATSK:
O=VATSK-(\d{10})orVATSK-(\d{10}) - POKLADNICA:
OU=POKLADNICA (\d{17})orPOKLADNICA-(\d{17})
Security:
- β Password-protected certificate parsing
- β No persistent storage (processed in memory)
- β Automatic memory cleanup
- β node-forge library for secure parsing
Retrieves transactions for a specific date with timezone handling.
Request: ```json { "date": "2025-11-10", "pokladnica": "88821225390010001", "timezoneOffset": -60 } ```
Response: ```json { "success": true, "data": [ { "transaction_id": "QR-780554711ad94950bbac61f9e7d3af41", "vatsk": "1234567890", "pokladnica": "88821225390010001", "iban": "SK3112000000198742637541", "amount": "123.45", "currency": "EUR", "status_code": 200, "response_timestamp": "2025-11-10T23:43:01.184745Z", "dispute": false, "payload_received_at": "2025-11-10T23:43:05.000Z" } ] } ```
Timezone Handling:
- Converts local date to UTC range
- Example:
2025-11-10in CET (UTC+1) β2025-11-09T23:00:00Zto2025-11-10T22:59:59Z - Uses
payload_received_atfrom banking system (not database insertion time)
Toggles dispute flag for a transaction.
Request: ```json { "transactionId": "QR-780554711ad94950bbac61f9e7d3af41", "dispute": true } ```
Response: ```json { "success": true, "message": "Dispute flag updated successfully" } ```
Behavior:
- Creates
transaction_generationsrecord if doesn't exist - Copies data from
mqtt_notificationstable - Sets default
status_code: 0for dispute-created records
Retrieves transaction details for confirmation page.
Request: ``` GET /api/view-confirmation/QR-780554711ad94950bbac61f9e7d3af41 ```
Response: ```json { "success": true, "data": { "transaction_id": "QR-780554711ad94950bbac61f9e7d3af41", "vatsk": "1234567890", "pokladnica": "88821225390010001", "iban": "SK3112000000198742637541", "amount": "123.45", "currency": "EUR", "response_timestamp": "2025-11-10T23:43:01.184745Z", "created_at": "2025-11-10T23:43:01.500Z" } } ```
Note: Displays response_timestamp (external system time) instead of created_at (database insertion time).
Audit log for all transaction generation requests.
```sql CREATE TABLE transaction_generations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), transaction_id TEXT, vatsk TEXT, pokladnica TEXT, iban TEXT, amount NUMERIC(10,2), status_code INTEGER NOT NULL, duration_ms INTEGER NOT NULL, client_ip TEXT NOT NULL, response_timestamp TIMESTAMPTZ, dispute BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW() );
CREATE INDEX idx_transaction_id ON transaction_generations(transaction_id); CREATE INDEX idx_response_timestamp ON transaction_generations(response_timestamp); CREATE INDEX idx_vatsk ON transaction_generations(vatsk); CREATE INDEX idx_pokladnica ON transaction_generations(pokladnica); ```
Columns:
transaction_id: Banking system transaction ID (e.g.,QR-abc123...)response_timestamp: Timestamp from banking API response (used for filtering)status_code: HTTP status code (200=success, 500=error, 0=dispute-created)duration_ms: API call duration in millisecondsdispute: Flag for disputed transactions
Storage for received payment notifications from MQTT broker.
```sql CREATE TABLE mqtt_notifications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), topic TEXT NOT NULL, raw_payload TEXT NOT NULL, vatsk TEXT, pokladnica TEXT, transaction_id TEXT, transaction_status TEXT, amount NUMERIC(10,2), currency TEXT, integrity_hash TEXT, end_to_end_id TEXT, payload_received_at TIMESTAMPTZ, integrity_validation BOOLEAN, created_at TIMESTAMPTZ DEFAULT NOW() );
CREATE INDEX idx_transaction_id_notif ON mqtt_notifications(transaction_id); CREATE INDEX idx_payload_received_at ON mqtt_notifications(payload_received_at); CREATE INDEX idx_vatsk_notif ON mqtt_notifications(vatsk); CREATE INDEX idx_pokladnica_notif ON mqtt_notifications(pokladnica); ```
Columns:
payload_received_at: Timestamp from banking system (used for filtering, NOTcreated_at)integrity_hash: SHA-256 hash for payment verificationintegrity_validation: Boolean indicating if hash matchesraw_payload: Complete MQTT message JSON
Tracking for MQTT subscription attempts.
```sql CREATE TABLE mqtt_subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), topic TEXT NOT NULL, vatsk TEXT, pokladnica TEXT, end_to_end_id TEXT, qos INTEGER NOT NULL, timestamp TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW() );
CREATE INDEX idx_topic ON mqtt_subscriptions(topic); CREATE INDEX idx_created_at_sub ON mqtt_subscriptions(created_at); ```
Retrieves transactions for date range with dispute flags.
```sql SELECT mn.transaction_id, mn.vatsk, mn.pokladnica, mn.iban, mn.amount, mn.currency, COALESCE(tg.response_timestamp, mn.payload_received_at) as response_timestamp, COALESCE(tg.dispute, false) as dispute FROM mqtt_notifications mn LEFT JOIN transaction_generations tg ON mn.transaction_id = tg.transaction_id WHERE mn.payload_received_at >= p_start_date AND mn.payload_received_at <= p_end_date AND mn.pokladnica = p_pokladnica ORDER BY mn.payload_received_at DESC; ```
Usage: ```sql SELECT * FROM get_transactions_by_date( '2025-11-09T23:00:00Z'::timestamptz, '2025-11-10T22:59:59Z'::timestamptz, '88821225390010001' ); ```
Retrieves single transaction details.
```sql SELECT tg.transaction_id, tg.vatsk, tg.pokladnica, tg.iban, tg.amount, tg.response_timestamp, tg.created_at FROM transaction_generations tg WHERE tg.transaction_id = p_transaction_id LIMIT 1; ```
```bash git add . git commit -m "Initial commit" git push origin main ```
- Visit Vercel Dashboard
- Click "Add New..." β "Project"
- Import your GitHub repository
- Vercel auto-detects Next.js configuration
- Click "Deploy"
In Vercel Project β Settings β Environment Variables, add:
| Variable | Value | Environment |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
https://your-project.supabase.co |
All |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
eyJhbGciOiJIUzI1Ni... |
All |
SUPABASE_SERVICE_ROLE_KEY |
eyJhbGciOiJIUzI1Ni... |
All (Secret β ) |
NEXT_PUBLIC_EMBEDDED_CA_BUNDLE |
-----BEGIN CERTIFICATE-----... |
All |
NEXT_PUBLIC_EMBEDDED_CA_BUNDLE_PROD |
-----BEGIN CERTIFICATE-----... |
All |
For long CA bundles: Use Vercel CLI: ```bash vercel env add NEXT_PUBLIC_EMBEDDED_CA_BUNDLE < test-ca-bundle.pem ```
```bash
vercel --prod
vercel ```
Deployment URL: https://your-project.vercel.app
- Vercel Dashboard β Project β Settings β Domains
- Click "Add Domain"
- Enter domain:
qrterminal.yourdomain.com - Configure DNS records as instructed: ``` Type: CNAME Name: qrterminal Value: cname.vercel-dns.com ```
- SSL certificate automatically provisioned by Vercel
```bash
NODE_ENV=production npm run build
NODE_ENV=development npm run build ```
-
β DONE: Implement Rate Limiting
- Current: 2 req/min per IP on all API routes
- Uses in-memory store with automatic cleanup
-
β οΈ TODO: Add API Key Authentication ```typescript // Add to all API routes const apiKey = request.headers.get("x-api-key") if (apiKey !== process.env.API_SECRET_KEY) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) } ``` -
β οΈ TODO: Implement User Authentication ```bashnpm install @supabase/auth-helpers-nextjs ```
-
β οΈ TODO: Update RLS Policies for User-Specific Access ```sql -- Update transaction_generations RLS ALTER TABLE transaction_generations ENABLE ROW LEVEL SECURITY;CREATE POLICY "Users can only see their own transactions" ON transaction_generations FOR SELECT USING (auth.uid() = user_id); ```
-
β DONE: Secure Service Role Key
- Stored in Vercel environment variables (encrypted)
- Never committed to Git
-
β οΈ TODO: Add Timestamp Validation for MQTT ```typescript const messageAge = Date.now() - new Date(payload.receivedAt).getTime() if (messageAge > 300000) { // 5 minutes throw new Error("Message too old, possible replay attack") } ```
-
β οΈ TODO: Replace Regex Sanitization with DOMPurify ```bash npm install isomorphic-dompurify ``` ```typescript import DOMPurify from 'isomorphic-dompurify' const sanitize = (input: string) => DOMPurify.sanitize(input) ``` -
β οΈ TODO: Implement Nonce Tracking ```typescript const processedNonces = new Set() if (processedNonces.has(payload.nonce)) { throw new Error("Duplicate message detected") } processedNonces.add(payload.nonce) ``` -
β οΈ TODO: Upgrade to HMAC-SHA256 ```typescript const secret = process.env.INTEGRITY_SECRET_KEY const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"] ) const signature = await crypto.subtle.sign("HMAC", key, data) ```
-
β οΈ TODO: Add Security Headers ```typescript // next.config.mjs const securityHeaders = [ { key: 'X-Frame-Options', value: 'DENY' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, { key: 'Content-Security-Policy', value: "default-src 'self'" } ] ``` -
β οΈ TODO: Generic Error Messages for Production ```typescript if (process.env.NODE_ENV === "production") { return NextResponse.json({ error: "An error occurred" }, { status: 500 }) } ``` -
β οΈ TODO: Add Monitoring- Vercel Analytics (already included:
@vercel/analytics) - Sentry for error tracking
- Uptime monitoring (UptimeRobot, Pingdom)
- Vercel Analytics (already included:
Problem: "Failed to convert P12 to PEM"
Causes:
- Incorrect password
- Corrupted certificate file
- Unsupported P12 format
Solutions:
- Verify password with your bank
- Re-export certificate from bank portal
- Ensure file is
.p12or.pfxformat - Check file size (should be >1KB)
Problem: "No certificate found in P12 file"
Causes:
- Invalid P12 structure
- Missing private key
- Certificate not yet activated
Solutions:
- Ensure P12 contains both certificate AND private key
- Use
opensslto verify: ```bash openssl pkcs12 -info -in certificate.p12 ``` - Contact bank if certificate is incomplete
Problem: "Forbidden" (403) response from banking API
Causes:
- Invalid CA certificate bundle
- Wrong environment (TEST vs PRODUCTION)
- Expired client certificate
Solutions:
- Verify CA bundle contains only intermediate + root CA (not server cert)
- Ensure using correct environment toggle
- Check certificate expiration date: ```bash openssl x509 -in certificate.pem -noout -dates ```
- Confirm VATSK and POKLADNICA are correct
Problem: "Connection timeout" (30 seconds)
Causes:
- Network issues
- Firewall blocking port 443
- Banking API downtime
Solutions:
- Verify internet connection
- Check firewall settings (allow outbound HTTPS)
- Test banking API availability: ```bash curl -I https://api-erp-i.kverkom.sk/api/v1/generateNewTransactionId ```
- Try again during off-peak hours
Problem: "MQTT connection failed"
Causes:
- Invalid certificates
- Broker unreachable
- Port 8883 blocked
Solutions:
- Verify certificates are valid (same as API)
- Check MQTT broker hostname:
- TEST:
mqtt-i.kverkom.sk - PRODUCTION:
mqtt.kverkom.sk
- TEST:
- Ensure port 8883 is not blocked: ```bash telnet mqtt-i.kverkom.sk 8883 ```
- Check VATSK and POKLADNICA match certificate
Problem: "No messages received" (120-second timeout)
Causes:
- Payment not completed by customer
- Wrong MQTT topic
- MQTT broker issue
Solutions:
- Verify customer authorized payment in banking app
- Check VATSK and POKLADNICA are correct (extracted from certificate)
- Wait full 120-second timeout period
- Check database
mqtt_subscriptionstable for subscription confirmation: ```sql SELECT * FROM mqtt_subscriptions ORDER BY created_at DESC LIMIT 10; ```
Problem: "Missing Supabase configuration"
Causes:
- Environment variables not set
- Typo in variable names
Solutions:
- Verify
.env.localexists in project root - Check variable names match exactly:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY
- Restart development server after adding variables
Problem: "Database insert failed" or "RLS policy violation"
Causes:
- RLS policies not created
- Service role key incorrect
- Schema mismatch
Solutions:
- Verify RLS policies are enabled (green shield in Supabase Table Editor)
- Re-run migration scripts in order
- Confirm service role key is correct (not anon key)
- Check table schema matches migration files
Problem: "Transactions from today not showing in list"
Causes:
- Timezone offset not passed to API
- Server in different timezone
- UTC/local time confusion
Solutions:
- β
Already fixed:
timezoneOffsetparameter now sent from frontend - Verify browser timezone is correct
- Check server logs for date range being queried
- Use
payload_received_atfrom banking system (notcreated_at)
Problem: "Module not found" errors
Causes:
- Missing dependencies
- Corrupted
node_modules
Solutions: ```bash
rm -rf node_modules package-lock.json npm install
npm ci ```
Problem: "Type errors" during build
Causes:
- TypeScript configuration
- Missing type definitions
Solutions: ```bash
npm run type-check
npm install typescript@latest --save-dev ```
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open Pull Request
- TypeScript: Strict mode enabled
- Linting: ESLint with Next.js config
- Formatting: Prettier (if configured)
- Commits: Conventional Commits format
```bash
npm run type-check
npm run lint
npm run build ```
This project is licensed under the MIT License - see LICENSE file for details.
- Slovak Banking Association for Payment Link Standard v1.3
- Vercel for Next.js framework and hosting platform
- Supabase for PostgreSQL database infrastructure
- shadcn/ui for beautiful component library
- Financial Administration of Slovak Republic for .kverkom instant payment infrastructure
- Documentation: functional-documentation.md
- Issues: GitHub Issues
- Banking API Support: Contact your bank's technical support
- Supabase Documentation: supabase.com/docs
- Next.js Documentation: nextjs.org/docs
Built with β€οΈ using Vercel v0
Last Updated: November 2025
Version: 1.0.0
Maintainer: Your Name your.email@example.com