• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
ianmenethil

ianmenethil

ZenServer

Unlisted
Like
ZenServer
Home
Code
20
.claude
.client
.cursor
.git
.vscode
.vt
scripts
5
src
10
tasks
.gitignore
.vtignore
AGENTS.md
CLAUDE.md
PLAN.md
PROJECT_MAPPING.MD
ZenApp.md
deno.json
env.example
H
main.ts
map.md
Branches
1
Pull requests
Remixes
History
Environment variables
22
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
ZenApp.md
Code
/
ZenApp.md
Search
7/11/2025
Viewing readonly version of main branch: v664
View latest version
ZenApp.md

ZenServer: Full-Stack Payment Flow Architecture

Document Version: 1.1 (Corrected) Last Updated: 2025-07-11

1. Introduction & System Overview

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.

2. User & Application 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.

2.1 Standard Website Booking Flow

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]

2.2 Email/Admin Booking Flow (Direct Link)

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]

3. Security Implementation & Token Sequence

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.

3.1 End-to-End Security Sequence

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)

3.2 Server-Side Security Controls

  • Database Interaction (Anti-Injection):

    1. 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:

    1. Session & CSRF Token: Must match the user’s established session.
    2. Turnstile CAPTCHA: Verifies human interaction via cfToken.
    3. Rate Limiting: Max 3 requests per minute per IP.
    4. Honeypot Field: The email_verify field must be present and contain the correct value ("🍯") to trap bots.
    5. Logging: Records IP, user agent, and timestamp for auditing.
  • /api/v1/payment/exchange-nonce Validations:

    1. Nonce Check: Verifies the nonce from the X-Payment-Nonce header is valid, unused, and unexpired (60s TTL). The server also ensures it was created by the same user session (by matching the SESSION cookie ID) and optionally the same IP address.
    2. Header Validation: Checks Origin, Referer, and X-Requested-With headers for legitimacy.
    3. Audit Logging: Records nonce consumption and associated details.

3.3 Security Control Matrix

This matrix provides a detailed overview of the security controls applied to each API endpoint.

EndpointSecurity Controls Applied
GET /api/v1/tour/tour-listPublic access; optional rate limiting to mitigate scraping.
GET /api/v1/tour/{id}Session cookie seeding (SESSION + XSRF-TOKEN); CSRF protection setup.
POST /api/v1/bookingSession validation; CSRF protection.
POST /api/v1/payment/create-nonceCSRF protection; session validation; CAPTCHA/Turnstile; honeypot detection; rate limiting (3 req/min/IP); logging.
POST /api/v1/payment/exchange-nonceNonce validation (single-use, TTL); header checks (Origin, Referer, X-Requested-With); rate limiting; audit logging.
POST /api/v1/payment/callbackValidationCode 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-tokenAPI key authentication (Bearer token); booking validation.

4. Canonical API Endpoints & Data Contracts

The following table defines the official, implemented API endpoints that the frontend application consumes, aligned with the openapi.json specification.

EndpointMethodSecurity ControlsUsed ByHandler FilePurpose & Notes
/api/v1/tour/tour-listGETPublic access, optional rate limiting.FrontendtourHandler.tsStep 0: Fetches the initial list of tours for the landing page.
/api/v1/tour/{id}GETSets SESSION + XSRF-TOKEN cookies, HSTS, CSP.FrontendtourHandler.tsStep 0→1: Gets details for a selected tour and seeds the session for CSRF protection.
/api/v1/bookingPOSTSession, CSRF.FrontendadminHandler.tsStep 1→2: Creates a booking record with traveler details before proceeding to payment.
/api/v1/payment/create-noncePOSTCSRF, Session, Turnstile, Honeypot, Rate limit (3/min/IP).FrontendadminHandler.tsStep 2a: Generates a single-use payment nonce after human verification.
/api/v1/payment/exchange-noncePOSTCSRF, Nonce validation (single-use, TTL), Header checks.FrontendadminHandler.tsStep 2b: Exchanges a valid nonce for a ZenPay payment fingerprint.
/api/v1/payment/callbackPOSTValidationCode verification (SHA3-512).BackendadminHandler.tsPost-Payment: Webhook handler for receiving payment status updates from ZenPay.
/api/v1/s/booking/{token}GETToken validation (single-use, TTL).FrontendadminHandler.tsEmail Flow: Handles the initial click from a shareable booking link.
/api/v1/s/booking/generate-tokenPOSTAPI key authentication (Bearer token).AdminadminHandler.tsAdmin Flow: Generates a secure, shareable booking link.

4.1 Data Flow Examples

Nonce Request

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 }

Fingerprint Exchange Request

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" } }

5. Client-Side Architecture (React SPA)

The frontend is a React 18.2.0 SPA written in TypeScript with Tailwind CSS for styling.

5.1 File Architecture

FileLinesPurpose
checkout-flow.ts2,030Main application logic - 3-step wizard component.
api-client.ts326API communication layer with error handling and data normalization.
app-core.ts377React foundation, shared UI components, and core utilities.
utils.ts290Helper functions for storage, validation, cookies, keyboard shortcuts.
config.ts64Application configuration, endpoints, and environment variables.
app.tsx25React entry point and component mounting.
index.ts25Hono static file server for serving the index.html and assets.
types.ts136Centralized TypeScript type definitions.

5.2 CheckoutFlow Component

This is the monolithic component that manages the entire user journey.

  • State Management: Uses over 15 useState hooks 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.

5.3 Key Client-Side Features

  • 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 getXSRFToken utility reads the XSRF-TOKEN cookie and the getDefaultHeaders function automatically attaches it as an X-XSRF-TOKEN header to all state-changing API requests.
  • Storage Management: Uses Base64 encoded sessionStorage for transient data and localStorage for image caching (5-min TTL). A CTRL+SHIFT+ALT+C keyboard 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.ts contains a comprehensive handleApiError function to gracefully manage non-200 responses from the API, preventing crashes and providing fallback data where appropriate.

6. Performance & Deployment

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

Frontend

File Architecture

Core Application Files

FileLinesPurpose
checkout-flow.ts2,030Main application logic - 3-step wizard component
api-client.ts326API communication layer with error handling
app-core.ts377React foundation, UI components, utilities
utils.ts290Storage, validation, cookies, keyboard shortcuts
config.ts64Application configuration and endpoints
app.tsx25React entry point and mounting
index.ts25Hono static file server
types.ts136TypeScript type definitions

CheckoutFlow Component Structure

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: TourCard components with animation, hover effects
  • Step 1: TravelerFormStep with auto-fill system and validation
  • Step 2: PaymentReviewStep with Turnstile integration and ZenPay

Key Features Implementation

Auto-Fill System

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

Security Features

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 & Utilities

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

API Integration Patterns

Error Handling

// 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 } }

Data Normalization

// 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 }; }

Configuration & Deployment

Environment Configuration (in config.ts)

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 }, };

Static File Serving (in index.ts)

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

Performance Optimizations

  1. Lazy Script Loading: Payment scripts only loaded in Step 2
  2. Image Caching: 5-minute TTL with localStorage
  3. Animation Staggering: 0.1s delays for smooth card reveals
  4. Error Boundaries: Graceful fallbacks for API failures
  5. Script Ordering: Sequential loading to prevent race conditions

Summary

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.

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.