Document Version: 1.1 (Corrected) Last Updated: 2025-07-11
This document provides a comprehensive, end-to-end architectural overview of the ZenServer payment platform. It merges the client-side implementation details of the React Single-Page Application (SPA) with the formal backend API specifications, creating a single source of truth for developers.
The architecture consists of two primary components:
- Frontend (React SPA): A client-side application built with React, TypeScript, and Tailwind CSS. It is responsible for rendering the user interface, managing the multi-step checkout flow, and interacting with the backend to fetch data and process payments. It is served as a static asset by a Hono server.
- Backend (ZenServer API): A secure, JSON-only API that handles business logic, data persistence, and all security operations. It is responsible for managing tours, generating security tokens (CSRF, Nonces), calculating payment fingerprints, and interfacing with the ZenPay gateway.
The system employs a progressive security model where security controls are layered on at each stage of the user's journey, culminating in a nonce-gated payment flow.
The application supports two primary user journeys for booking a tour: the standard website flow and a direct booking link flow for emails or administrative use.
This flow describes the journey for a typical user visiting the website. After filling out their details, a booking record is created to persist the information before proceeding to the secure payment stage.
graph TD A[User visits website] --> B[Landing Page - Step 0] B --> B1[Call: GET /api/v1/tour/tour-list] B1 --> C[Display 6 tour cards] C --> D[User selects tour] D --> E[Call: GET /api/v1/tour/{id}] E --> E1[Server sets SESSION + XSRF cookies] 1 --> F[Form Page - Step 1] F --> G[User fills traveler info] G --> G1[Call: POST /api/v1/booking] G1 --> H[User checks terms & clicks Continue] H --> I[Payment Page - Step 2] I --> J[Turnstile CAPTCHA loads] J --> K[User completes verification] K --> L[Call: POST /api/v1/payment/create-nonce] L --> M[Call: POST /api/v1/payment/exchange-nonce] M --> N[ZenPay.init() with fingerprint] N --> O[Payment form iframe/modal]
This flow is initiated from a secure, single-use link typically sent via email. The booking is pre-created by an admin.
graph TD A[Admin generates pay token] --> A1[Call: POST /api/v1/s/booking/generate-token] A1 --> B[Email sent with booking link] B --> C[User clicks email link] C --> D[Call: GET /api/v1/s/booking/{token}] D --> E[Pre-filled booking page loads] E --> F[Same payment flow as website] F --> G[Turnstile β Nonce β Fingerprint β Payment]
The core of the system's security is the two-step, nonce-gated fingerprint generation process. This ensures that a valid, human-verified, and time-limited token is required before the server will generate the credentials needed to initialize a payment.
sequenceDiagram participant F as Frontend (React App) participant S as Server (Backend API) participant T as Turnstile (Cloudflare) participant Z as ZenPay (Payment Gateway) Note over F: Step 0: Landing Page F->>S: GET /api/v1/tour/tour-list S-->>F: 200 OK { tours[], pagination } Note over F: Step 0β1: Tour Selection F->>S: GET /api/v1/tour/{id} Note over S: Sets SESSION + XSRF-TOKEN cookies S-->>F: 200 OK { tourDetails } + cookies Note over F: Step 1: Form Page F->>S: POST /api/v1/booking<br/>{ tourId, customer: {...} } S-->>F: 200 OK { bookingId, ... } Note over F: Step 2: Payment Page F->>T: Load Turnstile widget T-->>F: cfToken (CAPTCHA verification) F->>S: POST /api/v1/payment/create-nonce<br/>{ bookingId, cfToken, email_verify: "π―" } Note over S: Validate CSRF + Turnstile + Honeypot S-->>F: 200 OK { nonce, expiresIn: 60 } F->>S: POST /api/v1/payment/exchange-nonce<br/>X-Payment-Nonce: {nonce} Note over S: Consume nonce, generate fingerprint S-->>F: 200 OK { fingerprint, paymentParams } F->>Z: ZenPay.init({ fingerprint, ...paymentParams }) Z-->>F: Payment form (iframe/modal)
-
Database Interaction (Anti-Injection):
- Parameterized Queries: To prevent SQL injection into the Val Town SQLite database, all database queries that include external input are executed using parameterized queries (also known as prepared statements). User-provided data is never directly formatted into SQL strings.
-
/api/v1/payment/create-nonce Validations:
- Session & CSRF Token: Must match the userβs established session.
- Turnstile CAPTCHA: Verifies human interaction via
cfToken. - Rate Limiting: Max 3 requests per minute per IP.
- Honeypot Field: The
email_verifyfield must be present and contain the correct value ("π―") to trap bots. - Logging: Records IP, user agent, and timestamp for auditing.
-
/api/v1/payment/exchange-nonce Validations:
- Nonce Check: Verifies the nonce from the
X-Payment-Nonceheader is valid, unused, and unexpired (60s TTL). The server also ensures it was created by the same user session (by matching theSESSIONcookie ID) and optionally the same IP address. - Header Validation: Checks
Origin,Referer, andX-Requested-Withheaders for legitimacy. - Audit Logging: Records nonce consumption and associated details.
- Nonce Check: Verifies the nonce from the
This matrix provides a detailed overview of the security controls applied to each API endpoint.
| Endpoint | Security Controls Applied |
|---|---|
GET /api/v1/tour/tour-list | Public access; optional rate limiting to mitigate scraping. |
GET /api/v1/tour/{id} | Session cookie seeding (SESSION + XSRF-TOKEN); CSRF protection setup. |
POST /api/v1/booking | Session validation; CSRF protection. |
POST /api/v1/payment/create-nonce | CSRF protection; session validation; CAPTCHA/Turnstile; honeypot detection; rate limiting (3 req/min/IP); logging. |
POST /api/v1/payment/exchange-nonce | Nonce validation (single-use, TTL); header checks (Origin, Referer, X-Requested-With); rate limiting; audit logging. |
POST /api/v1/payment/callback | ValidationCode verification (SHA3-512 hash); payload authentication. |
GET /api/v1/s/booking/{token} | Token validation (single-use, TTL); IP tracking; session establishment. |
POST /api/v1/s/booking/generate-token | API key authentication (Bearer token); booking validation. |
The following table defines the official, implemented API endpoints that the frontend application consumes, aligned with the openapi.json specification.
| Endpoint | Method | Security Controls | Used By | Handler File | Purpose & Notes |
|---|---|---|---|---|---|
/api/v1/tour/tour-list | GET | Public access, optional rate limiting. | Frontend | tourHandler.ts | Step 0: Fetches the initial list of tours for the landing page. |
/api/v1/tour/{id} | GET | Sets SESSION + XSRF-TOKEN cookies, HSTS, CSP. | Frontend | tourHandler.ts | Step 0β1: Gets details for a selected tour and seeds the session for CSRF protection. |
/api/v1/booking | POST | Session, CSRF. | Frontend | adminHandler.ts | Step 1β2: Creates a booking record with traveler details before proceeding to payment. |
/api/v1/payment/create-nonce | POST | CSRF, Session, Turnstile, Honeypot, Rate limit (3/min/IP). | Frontend | adminHandler.ts | Step 2a: Generates a single-use payment nonce after human verification. |
/api/v1/payment/exchange-nonce | POST | CSRF, Nonce validation (single-use, TTL), Header checks. | Frontend | adminHandler.ts | Step 2b: Exchanges a valid nonce for a ZenPay payment fingerprint. |
/api/v1/payment/callback | POST | ValidationCode verification (SHA3-512). | Backend | adminHandler.ts | Post-Payment: Webhook handler for receiving payment status updates from ZenPay. |
/api/v1/s/booking/{token} | GET | Token validation (single-use, TTL). | Frontend | adminHandler.ts | Email Flow: Handles the initial click from a shareable booking link. |
/api/v1/s/booking/generate-token | POST | API key authentication (Bearer token). | Admin | adminHandler.ts | Admin Flow: Generates a secure, shareable booking link. |
Request from Frontend to Server:
POST /api/v1/payment/create-nonce HTTP/1.1 Content-Type: application/json Cookie: SESSION=abc123; XSRF-TOKEN=xyz456 X-XSRF-TOKEN: xyz456 { "bookingId": "b1234567", "cfToken": "turnstile-token-here", "email_verify": "π―" }
Response from Server to Frontend:
{ "nonce": "e7b8f3a14c5d6789abcdef1234567890abcdef1234567890abcdef1234567890", "expiresIn": 60 }
Request from Frontend to Server:
POST /api/v1/payment/exchange-nonce HTTP/1.1 Content-Type: application/json Cookie: SESSION=abc123; XSRF-TOKEN=xyz456 X-Payment-Nonce: e7b8f3a14c5d6789abcdef1234567890abcdef1234567890abcdef1234567890 { "bookingId": "b1234567" }
Response from Server to Frontend:
{ "fingerprint": "4fa501b4c7e123def456789abcdef1234567890abcdef1234567890abcdef1234", "paymentParams": { "url": "https://pay.travelpay.com.au/online/v5", "merchantCode": "MERCHANT123", "apiKey": "r1-ghKhwM0u8_at8ePhKtA", "redirectUrl": "https://myapp.com/payment/return", "customerName": "John Smith", "paymentAmount": 25900, "timestamp": "2025-01-18T14:31:55Z" } }
The frontend is a React 18.2.0 SPA written in TypeScript with Tailwind CSS for styling.
| File | Lines | Purpose |
|---|---|---|
checkout-flow.ts | 2,030 | Main application logic - 3-step wizard component. |
api-client.ts | 326 | API communication layer with error handling and data normalization. |
app-core.ts | 377 | React foundation, shared UI components, and core utilities. |
utils.ts | 290 | Helper functions for storage, validation, cookies, keyboard shortcuts. |
config.ts | 64 | Application configuration, endpoints, and environment variables. |
app.tsx | 25 | React entry point and component mounting. |
index.ts | 25 | Hono static file server for serving the index.html and assets. |
types.ts | 136 | Centralized TypeScript type definitions. |
This is the monolithic component that manages the entire user journey.
- State Management: Uses over 15
useStatehooks to manage the current step, tour data, form inputs, tokens, and processing states. - Step Components:
- Step 0 (
TourCard): Renders tour cards with animations and handles tour selection. - Step 1 (
TravelerFormStep): Manages the traveler information form, including validation and the auto-fill system. - Step 2 (
PaymentReviewStep): Integrates the Turnstile widget and orchestrates the nonce/fingerprint flow before initializing ZenPay.
- Step 0 (
- Auto-Fill System: A demo-purposed feature that uses a predefined list of 24 names from Blizzard games to populate form fields, facilitating rapid testing.
- CSRF Protection: The
getXSRFTokenutility reads theXSRF-TOKENcookie and thegetDefaultHeadersfunction automatically attaches it as anX-XSRF-TOKENheader to all state-changing API requests. - Storage Management: Uses Base64 encoded
sessionStoragefor transient data andlocalStoragefor image caching (5-min TTL). ACTRL+SHIFT+ALT+Ckeyboard shortcut exists to clear all storage for debugging. - Lazy Script Loading: Third-party payment scripts (jQuery, Bootstrap, ZenPay) are loaded dynamically and only when the user reaches Step 2 of the checkout, optimizing initial page load.
- Error Handling: The
api-client.tscontains a comprehensivehandleApiErrorfunction to gracefully manage non-200 responses from the API, preventing crashes and providing fallback data where appropriate.
- Performance: Key optimizations include lazy script loading, image caching, staggered animations for UI elements, and robust error boundaries in React components.
- Deployment: The application is deployed as a set of static files (
index.html, JS/CSS bundles) served by a lightweight Hono server. The server's only role is to serve the frontend application; it does not perform any server-side rendering or API logic.
| File | Lines | Purpose |
|---|---|---|
checkout-flow.ts | 2,030 | Main application logic - 3-step wizard component |
api-client.ts | 326 | API communication layer with error handling |
app-core.ts | 377 | React foundation, UI components, utilities |
utils.ts | 290 | Storage, validation, cookies, keyboard shortcuts |
config.ts | 64 | Application configuration and endpoints |
app.tsx | 25 | React entry point and mounting |
index.ts | 25 | Hono static file server |
types.ts | 136 | TypeScript type definitions |
State Management (in checkout-flow.ts):
const [step, setStep] = useState(0); // 0=tours, 1=form, 2=payment
const [tours, setTours] = useState([]);
const [selectedTour, setSelectedTour] = useState(null);
const [formData, setFormData] = useState({...});
const [turnstileToken, setTurnstileToken] = useState(null);
const [isProcessing, setIsProcessing] = useState(false);
// ... 15+ state variables for complete flow management
Step Components:
- Step 0:
TourCardcomponents with animation, hover effects - Step 1:
TravelerFormStepwith auto-fill system and validation - Step 2:
PaymentReviewStepwith Turnstile integration and ZenPay
// In checkout-flow.ts
function generateSmartFormDefaults() {
const fullName = generateRandomName();
const [firstName, lastName] = fullName.split(" ");
return {
firstName,
lastName,
email: `${firstName.toLowerCase()}@zenpay.com.au`,
phone: "0400000000",
emergencyContact: generateRandomName(),
emergencyPhone: "0400000000",
};
}
Character Names: 24 character names from Blizzard games for demo auto-fill
CSRF Protection (in utils.ts):
export function getXSRFToken(): string | null {
return getCookie("XSRF-TOKEN");
}
export function getDefaultHeaders(): Record<string, string> {
const headers = { "Content-Type": "application/json" };
const xsrfToken = getXSRFToken();
if (xsrfToken) headers["X-XSRF-TOKEN"] = xsrfToken;
return headers;
}
Honeypot Protection:
// email_verify field with emoji honeypot
const updatedData = { ...filledData, email_verify: "π―" };
Turnstile Integration:
// Cloudflare Turnstile widget in Step 2
const turnstileElement = document.querySelector(".cf-turnstile");
window.turnstile.render(turnstileElement, {
sitekey: config.turnstileSiteKey,
callback: window.onTurnstileSuccess,
});
Storage Management (in utils.ts):
- Base64 encoded sessionStorage
- Image caching with TTL
- CTRL+SHIFT+ALT+C: Clear all storage with modal notification
Script Loading (in checkout-flow.ts):
- Dynamic loading of payment scripts only in Step 2
- jQuery, Bootstrap, ZenPay plugin loaded on-demand
// Comprehensive error handling in api-client.ts
export async function getAllTours(): Promise<{ tours: TourData[]; pagination: any }> {
try {
const res = await fetch(url, { credentials: "include", mode: "cors" });
if (!res.ok) throw await utils.handleApiError(res);
// ... success handling
} catch (err) {
console.error("[api-client] Failed to load tours:", err);
return { tours: [], pagination: {...} }; // Graceful fallback
}
}
// Handles API inconsistencies in tour data
function normalizeApiTourData(tour: any): TourData {
return {
id: tour.tourId ?? tour.id,
name: tour.name ?? tour.title,
heroImageUrl: tour.heroImageUrl ?? tour.image_url,
price: tour.price,
currency: tour.currency ?? "AUD",
// ... comprehensive field mapping
};
}
export const config = {
apiUrl: "https://server.zenithpayments.support",
turnstileSiteKey: "0x4AAAAAABjPDL4GDo6BXFza",
endpoints: {
tours: "/api/v1/tour/tour-list",
tourDetails: "/api/v1/tour",
createNonce: "/api/v1/payment/create-nonce",
exchangeNonce: "/api/v1/payment/exchange-nonce",
},
defaults: {
currency: "AUD",
nonceTimeout: 60000, // 60 seconds
},
};
// Hono server for static files only
const app = new Hono();
app.get("/", async (c) => {
const html = await readFile("/index.html", import.meta.url);
return c.html(html);
});
app.get("/*", (c) => serveFile(c.req.path, import.meta.url));
- Lazy Script Loading: Payment scripts only loaded in Step 2
- Image Caching: 5-minute TTL with localStorage
- Animation Staggering: 0.1s delays for smooth card reveals
- Error Boundaries: Graceful fallbacks for API failures
- Script Ordering: Sequential loading to prevent race conditions
Current Architecture: Mature React SPA with comprehensive security, UX features, and error handling. The application successfully implements the nonce-gated payment flow with progressive security controls and excellent user experience features like auto-fill, animations, and graceful error handling.
Key Strengths:
- β Comprehensive security implementation (CSRF, Turnstile, honeypots)
- β Excellent error handling with fallback mechanisms
- β Performance optimizations (lazy loading, caching)
- β Rich UX features (auto-fill, animations, keyboard shortcuts)
- β Type-safe implementation with comprehensive TypeScript coverage
Architecture Pattern: The implementation follows a progressive enhancement model where security controls are applied at each step, scripts are loaded only when needed, and user experience is prioritized while maintaining robust security.