Skip to content

spavlovic77/v0-nop-web-test-client

Repository files navigation

QR Terminal - Certificate-Based Payment Application

Secure payment QR code generation and real-time transaction monitoring with mutual TLS authentication

Next.js React TypeScript Supabase License


πŸ“‹ Table of Contents


🎯 Overview

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.

Why QR Terminal?

  • βœ… 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

✨ Features

Core Functionality

  • 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

Security Features

  • βœ… 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

User Experience

  • πŸ“± 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

πŸ”’ Security Architecture

Threat Model

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

Security Implementations

1. Mutual TLS (mTLS) Authentication ⭐⭐⭐⭐⭐

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%)

2. Certificate Management ⭐⭐⭐⭐⭐

Implementation:

  • Temporary files with 0o600 permissions (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

3. Input Validation ⭐⭐⭐⭐

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

4. Data Integrity Verification ⭐⭐⭐⭐⭐

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

5. Database Security ⭐⭐⭐⭐

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

6. API Security ⭐⭐⭐⭐

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

Security Scorecard

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


πŸ“¦ Prerequisites

Required Software

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

Required Services

Service Purpose Sign Up
Supabase PostgreSQL database with real-time supabase.com
Vercel Deployment platform (optional) vercel.com

Banking Requirements

  • βœ… 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

πŸš€ Installation Guide

Step 1: Clone Repository

```bash git clone https://github.com/your-username/qr-terminal.git cd qr-terminal ```

Step 2: Install Dependencies

```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

Step 3: Database Setup

3.1 Create Supabase Project

  1. Visit Supabase Dashboard
  2. Click "New Project"
  3. Configure:
    • Name: qr-terminal (or preferred name)
    • Database Password: Strong password (save securely!)
    • Region: Europe West (Frankfurt - recommended for Slovakia)
  4. Click "Create new project" (takes 2-3 minutes)

3.2 Run Database Migrations

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

Open: scripts/001_drop_all_tables.sql

Copy entire content

Paste in SQL Editor

Click "Run"

```

Script 2: Create transaction_generations Table ```bash

Open: scripts/002_create_transaction_generations_table.sql

Copy entire content

Paste in SQL Editor

Click "Run"

```

Script 3: Create mqtt_notifications Table ```bash

Open: scripts/003_create_mqtt_notifications_table.sql

Copy entire content

Paste in SQL Editor

Click "Run"

```

Script 4: Create mqtt_subscriptions Table ```bash

Open: scripts/004_create_mqtt_subscriptions_table.sql

Copy entire content

Paste in SQL Editor

Click "Run"

```

Script 5: Create get_transactions_by_date Function ```bash

Open: scripts/005_create_get_transactions_by_date_function.sql

Copy entire content

Paste in SQL Editor

Click "Run"

```

Script 6: Create get_transaction_by_id Function ```bash

Open: scripts/006_create_get_transaction_by_id_function.sql

Copy entire content

Paste in SQL Editor

Click "Run"

```

3.3 Verify Database Setup

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)

Step 4: Environment Configuration

Create .env.local file in project root:

```env

===========================================

Supabase Configuration

===========================================

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

===========================================

CA Certificate Bundle - TEST Environment

===========================================

NEXT_PUBLIC_EMBEDDED_CA_BUNDLE=-----BEGIN CERTIFICATE----- MIIFazCCBFOgAwIBAgIQBN... (Complete CA certificate chain for api-erp-i.kverkom.sk) ... -----END CERTIFICATE-----

===========================================

CA Certificate Bundle - PRODUCTION Environment

===========================================

NEXT_PUBLIC_EMBEDDED_CA_BUNDLE_PROD=-----BEGIN CERTIFICATE----- MIIFazCCBFOgAwIBAgIQBN... (Complete CA certificate chain for api-erp.kverkom.sk) ... -----END CERTIFICATE-----

===========================================

Optional: Development Settings

===========================================

NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL=http://localhost:3000 ```

4.1 Obtaining Supabase Credentials

  1. In Supabase Dashboard β†’ Settings β†’ API
  2. 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

4.2 Obtaining CA Certificates

CA certificates enable TLS verification with banking APIs.

For TEST Environment (api-erp-i.kverkom.sk): ```bash

Method 1: OpenSSL (Linux/Mac)

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

Method 2: Web Browser

1. Visit https://api-erp-i.kverkom.sk in Chrome

2. Click padlock β†’ Connection is secure β†’ Certificate is valid

3. Export certificate chain (exclude server cert, include intermediate + root)

```

For PRODUCTION Environment (api-erp.kverkom.sk): ```bash

Same process as TEST, but with production domain

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 ```

⚠️ Important: CA bundle should contain ONLY intermediate and root CA certificates, NOT the server certificate.

Step 5: Start Development Server

```bash npm run dev ```

Application runs at: http://localhost:3000

πŸŽ‰ Installation Complete! Proceed to Usage section.


βš™οΈ Configuration

Environment Variables Reference

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

Security Best Practices

  1. Never commit .env.local to Git ```bash

    Already in .gitignore

    echo ".env.local" >> .gitignore ```

  2. Use Vercel environment variables for production

    • Encrypted at rest
    • Separate environments (preview/production)
    • Automatic regeneration on rotation
  3. Rotate service role key quarterly

    • Supabase Dashboard β†’ Settings β†’ API β†’ Reset service_role key
  4. Use different Supabase projects for TEST/PROD

    • Isolates data
    • Separate billing
    • Independent scaling

πŸ“– Usage

1. Upload Certificates

Production Environment Requirements

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.

Certificate Upload Steps

  1. XML Authentication Data (PKCS#12 Certificate)

    • Click "Choose File" or drag-and-drop
    • Select your .p12 or .pfx file from bank
    • File contains: Client certificate + Private key + VATSK + POKLADNICA
  2. Certificate Password

    • Enter password provided by your bank
    • Password is used only for decryption, never stored
  3. IBAN (Optional)

    • Default recipient IBAN
    • Auto-formatted with spaces (e.g., SK31 1200 0000 1987 4263 7541)
  4. 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)

2. Generate Payment QR Code

Enter Payment Details

  1. IBAN (if not pre-filled)

    • Format: SK31 1200 0000 1987 4263 7541
    • Auto-formatted as you type
    • Validated with Mod-97 checksum
  2. Amount

    • Format: 123,45 € (Slovak formatting)
    • Minimum: 0,01 €
    • Maximum: 999999,99 €
  3. Environment Toggle

    • TEST (green): api-erp-i.kverkom.sk / mqtt-i.kverkom.sk
    • PRODUCTION (yellow): api-erp.kverkom.sk / mqtt.kverkom.sk

Generate QR Code

Click "VygenerovaΕ₯ QR kΓ³d" (Generate QR Code)

The application will:

  1. βœ… Call banking API with mTLS authentication
  2. βœ… Receive unique transaction_id (e.g., QR-abc123...)
  3. βœ… Generate SHA-256 integrity hash
  4. βœ… Create Slovak Payment Link Standard v1.3 compliant URL
  5. βœ… Generate QR code image
  6. βœ… Automatically subscribe to MQTT for payment confirmation
  7. βœ… Save transaction to database for audit

QR Code Example: ``` https://scantopay.sk/?iban=SK3112000000198742637541 &amount=123.45&currency=EUR&vs=&ss=&ks=&message= &transactionId=QR-abc123&integrityHash=9a7f8b3c... ```

3. Customer Payment Flow

  1. Customer scans QR code with mobile banking app
  2. Banking app opens with pre-filled payment details
  3. Customer reviews and authorizes payment
  4. Bank processes payment instantly
  5. Bank sends MQTT notification to application
  6. Application receives real-time confirmation (typically 2-5 seconds)
  7. Integrity hash verified to ensure payment authenticity
  8. Confirmation modal displayed with payment details

4. View Transaction History

Zoznam platieb (Transaction List)

  1. Click "Zoznam platieb" button
  2. Select date using date picker
  3. View transactions for selected day:
    • Transaction ID
    • IBAN
    • Amount in EUR
    • Timestamp (from banking system)
    • Dispute flag (if flagged)

Filter Options

  • Date Range: Select specific day
  • Timezone Aware: Correctly converts UTC to local time
  • Export: Print transaction summary

Doklady o nepotvrdenΓ½ch platbΓ‘ch (Unconfirmed Payments)

  1. Click "Doklady o nepotvrdenΓ½ch platbΓ‘ch"
  2. Select date
  3. View transactions without payment confirmation:
    • Generated QR codes
    • No received MQTT notification
    • Potential disputes

5. Payment Simulator (TEST Mode Only)

Available only in TEST environment - hidden in PRODUCTION.

  1. After generating QR code, click "SimulΓ‘tor ΓΊhrady"
  2. Reveals QR code link: https://scantopay.vercel.app/...
  3. Open link on mobile device
  4. Simulates payment without real banking app
  5. Sends test MQTT notification
  6. Useful for development and testing

πŸ”Œ API Documentation

POST /api/generate-transaction

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

POST /api/mqtt-subscribe

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

POST /api/convert-p12-to-pem

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}) or VATSK-(\d{10})
  • POKLADNICA: OU=POKLADNICA (\d{17}) or POKLADNICA-(\d{17})

Security:

  • βœ… Password-protected certificate parsing
  • βœ… No persistent storage (processed in memory)
  • βœ… Automatic memory cleanup
  • βœ… node-forge library for secure parsing

POST /api/get-transactions-by-date

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-10 in CET (UTC+1) β†’ 2025-11-09T23:00:00Z to 2025-11-10T22:59:59Z
  • Uses payload_received_at from banking system (not database insertion time)

PATCH /api/update-dispute

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_generations record if doesn't exist
  • Copies data from mqtt_notifications table
  • Sets default status_code: 0 for dispute-created records

GET /api/view-confirmation/[transactionId]

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).


πŸ—„οΈ Database Schema

transaction_generations

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 milliseconds
  • dispute: Flag for disputed transactions

mqtt_notifications

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, NOT created_at)
  • integrity_hash: SHA-256 hash for payment verification
  • integrity_validation: Boolean indicating if hash matches
  • raw_payload: Complete MQTT message JSON

mqtt_subscriptions

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); ```


SQL Functions

get_transactions_by_date(p_start_date, p_end_date, p_pokladnica)

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' ); ```

get_transaction_by_id(p_transaction_id)

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; ```


🚒 Deployment

Vercel Deployment (Recommended)

Step 1: Push to GitHub

```bash git add . git commit -m "Initial commit" git push origin main ```

Step 2: Import to Vercel

  1. Visit Vercel Dashboard
  2. Click "Add New..." β†’ "Project"
  3. Import your GitHub repository
  4. Vercel auto-detects Next.js configuration
  5. Click "Deploy"

Step 3: Configure Environment Variables

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 ```

Step 4: Deploy

```bash

Production deployment

vercel --prod

Preview deployment

vercel ```

Deployment URL: https://your-project.vercel.app


Custom Domain Setup

  1. Vercel Dashboard β†’ Project β†’ Settings β†’ Domains
  2. Click "Add Domain"
  3. Enter domain: qrterminal.yourdomain.com
  4. Configure DNS records as instructed: ``` Type: CNAME Name: qrterminal Value: cname.vercel-dns.com ```
  5. SSL certificate automatically provisioned by Vercel

Environment-Specific Builds

```bash

Production (uses NEXT_PUBLIC_EMBEDDED_CA_BUNDLE_PROD)

NODE_ENV=production npm run build

Development (uses NEXT_PUBLIC_EMBEDDED_CA_BUNDLE)

NODE_ENV=development npm run build ```


πŸ” Security Recommendations

Priority 0: Immediate (Before Production Launch)

  1. βœ… DONE: Implement Rate Limiting

    • Current: 2 req/min per IP on all API routes
    • Uses in-memory store with automatic cleanup
  2. ⚠️ 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 }) } ```

  3. ⚠️ TODO: Implement User Authentication ```bash

    Enable Supabase Auth

    npm install @supabase/auth-helpers-nextjs ```


Priority 1: High Priority (Within 1 Week)

  1. ⚠️ 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); ```

  2. βœ… DONE: Secure Service Role Key

    • Stored in Vercel environment variables (encrypted)
    • Never committed to Git
  3. ⚠️ 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") } ```


Priority 2: Medium Priority (Within 1 Month)

  1. ⚠️ TODO: Replace Regex Sanitization with DOMPurify ```bash npm install isomorphic-dompurify ``` ```typescript import DOMPurify from 'isomorphic-dompurify' const sanitize = (input: string) => DOMPurify.sanitize(input) ```

  2. ⚠️ TODO: Implement Nonce Tracking ```typescript const processedNonces = new Set() if (processedNonces.has(payload.nonce)) { throw new Error("Duplicate message detected") } processedNonces.add(payload.nonce) ```

  3. ⚠️ 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) ```


Priority 3: Low Priority (Within 3 Months)

  1. ⚠️ 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'" } ] ```

  2. ⚠️ TODO: Generic Error Messages for Production ```typescript if (process.env.NODE_ENV === "production") { return NextResponse.json({ error: "An error occurred" }, { status: 500 }) } ```

  3. ⚠️ TODO: Add Monitoring

    • Vercel Analytics (already included: @vercel/analytics)
    • Sentry for error tracking
    • Uptime monitoring (UptimeRobot, Pingdom)

πŸ› Troubleshooting

Certificate Issues

Problem: "Failed to convert P12 to PEM"

Causes:

  • Incorrect password
  • Corrupted certificate file
  • Unsupported P12 format

Solutions:

  1. Verify password with your bank
  2. Re-export certificate from bank portal
  3. Ensure file is .p12 or .pfx format
  4. 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:

  1. Ensure P12 contains both certificate AND private key
  2. Use openssl to verify: ```bash openssl pkcs12 -info -in certificate.p12 ```
  3. Contact bank if certificate is incomplete

API Connection Issues

Problem: "Forbidden" (403) response from banking API

Causes:

  • Invalid CA certificate bundle
  • Wrong environment (TEST vs PRODUCTION)
  • Expired client certificate

Solutions:

  1. Verify CA bundle contains only intermediate + root CA (not server cert)
  2. Ensure using correct environment toggle
  3. Check certificate expiration date: ```bash openssl x509 -in certificate.pem -noout -dates ```
  4. Confirm VATSK and POKLADNICA are correct

Problem: "Connection timeout" (30 seconds)

Causes:

  • Network issues
  • Firewall blocking port 443
  • Banking API downtime

Solutions:

  1. Verify internet connection
  2. Check firewall settings (allow outbound HTTPS)
  3. Test banking API availability: ```bash curl -I https://api-erp-i.kverkom.sk/api/v1/generateNewTransactionId ```
  4. Try again during off-peak hours

MQTT Issues

Problem: "MQTT connection failed"

Causes:

  • Invalid certificates
  • Broker unreachable
  • Port 8883 blocked

Solutions:

  1. Verify certificates are valid (same as API)
  2. Check MQTT broker hostname:
    • TEST: mqtt-i.kverkom.sk
    • PRODUCTION: mqtt.kverkom.sk
  3. Ensure port 8883 is not blocked: ```bash telnet mqtt-i.kverkom.sk 8883 ```
  4. 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:

  1. Verify customer authorized payment in banking app
  2. Check VATSK and POKLADNICA are correct (extracted from certificate)
  3. Wait full 120-second timeout period
  4. Check database mqtt_subscriptions table for subscription confirmation: ```sql SELECT * FROM mqtt_subscriptions ORDER BY created_at DESC LIMIT 10; ```

Database Issues

Problem: "Missing Supabase configuration"

Causes:

  • Environment variables not set
  • Typo in variable names

Solutions:

  1. Verify .env.local exists in project root
  2. Check variable names match exactly:
    • NEXT_PUBLIC_SUPABASE_URL
    • NEXT_PUBLIC_SUPABASE_ANON_KEY
    • SUPABASE_SERVICE_ROLE_KEY
  3. 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:

  1. Verify RLS policies are enabled (green shield in Supabase Table Editor)
  2. Re-run migration scripts in order
  3. Confirm service role key is correct (not anon key)
  4. Check table schema matches migration files

Timezone Issues

Problem: "Transactions from today not showing in list"

Causes:

  • Timezone offset not passed to API
  • Server in different timezone
  • UTC/local time confusion

Solutions:

  1. βœ… Already fixed: timezoneOffset parameter now sent from frontend
  2. Verify browser timezone is correct
  3. Check server logs for date range being queried
  4. Use payload_received_at from banking system (not created_at)

Build Issues

Problem: "Module not found" errors

Causes:

  • Missing dependencies
  • Corrupted node_modules

Solutions: ```bash

Clean install

rm -rf node_modules package-lock.json npm install

Or use npm ci for clean install

npm ci ```


Problem: "Type errors" during build

Causes:

  • TypeScript configuration
  • Missing type definitions

Solutions: ```bash

Check for type errors

npm run type-check

Update TypeScript

npm install typescript@latest --save-dev ```


🀝 Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create feature branch: git checkout -b feature/amazing-feature
  3. Commit changes: git commit -m 'Add amazing feature'
  4. Push to branch: git push origin feature/amazing-feature
  5. Open Pull Request

Code Standards

  • TypeScript: Strict mode enabled
  • Linting: ESLint with Next.js config
  • Formatting: Prettier (if configured)
  • Commits: Conventional Commits format

Testing

```bash

Run type checks

npm run type-check

Run linter

npm run lint

Build test

npm run build ```


πŸ“„ License

This project is licensed under the MIT License - see LICENSE file for details.


πŸ™ Acknowledgments

  • 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

πŸ“ž Support


Built with ❀️ using Vercel v0

Deploy with Vercel


Last Updated: November 2025
Version: 1.0.0
Maintainer: Your Name your.email@example.com

About

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •