12 KiB
Payfrit Business Platform
Main CFML/JS repository: API + Portal + KDS + HUD + Receipt. Git repo on Forgejo; local clone at C:\dev\payfrit-biz.
Build & Deploy
# All edits in local clone
cd C:\dev\payfrit-biz
# Commit and push
git add <files>
git commit -m "message"
git push origin main
# Auto-deploy runs within ~1 minute to dev.payfrit.com
Git remote: git.payfrit.com (Forgejo) Dev server: dev.payfrit.com
HARD RULE: Always edit in C:\dev\payfrit-biz, never directly on the server or in C:\lucee\...
Project Structure
payfrit-biz/
├── Application.cfm # Root app config: sessions (30min), datasource, layout wrapper
├── index.cfm # Legacy entry point, mode-based routing
├── onrequestend.cfm # Footer/cleanup
├── register.cfm # Account registration
├── confirm_email.cfm # Email verification
├── confirm_mobile.cfm # Mobile verification
├── reset.cfm # Password reset
├── show_order.cfm # Order detail (redirects to /receipt/)
├── privacy.html # Privacy policy
│
├── api/ # REST API (~135 endpoints, 23 domains)
│ ├── Application.cfm # Token auth gate (X-User-Token, X-Business-ID headers)
│ ├── config/
│ │ ├── stripe.cfm # Stripe API keys
│ │ └── environment.cfm # Dev vs prod settings
│ ├── addresses/ # CRUD delivery addresses (add, delete, list, setDefault, states, types)
│ ├── admin/ # Admin utilities
│ │ ├── _scripts/ # One-off migration scripts (git-ignored)
│ │ ├── quickTasks/ # Task type/category management
│ │ └── scheduledTasks/ # Cron coordination
│ ├── app/ # App info (about.cfm)
│ ├── assignments/ # Employee assignments (delete, list, save)
│ ├── auth/ # Authentication (login, loginOTP, sendOTP, verifyOTP, profile, avatar)
│ ├── beacons/ # Beacon management (CRUD, lookup, reassign)
│ ├── businesses/ # Business profiles (get, list, update, hours, hiring, brandColor)
│ ├── chat/ # Order chat (send, get, markRead, close)
│ ├── debug/ # Debug endpoints (localhost-protected)
│ ├── dev/ # Dev tools (seedData, timeTravel)
│ ├── import/ # Menu import (crimson_menu.cfm)
│ ├── menu/ # Menu management (items, categories, menus, builder, photos)
│ ├── orders/ # Order lifecycle (cart, submit, status, KDS list, history)
│ ├── portal/ # Portal-specific (team, stats, settings, searchUser)
│ ├── ratings/ # Customer ratings (create, list, submit)
│ ├── servicepoints/ # Service point CRUD
│ ├── setup/ # Business onboarding wizard (analyze, import, save)
│ ├── stations/ # Kitchen stations (list)
│ ├── stripe/ # Payments (createPaymentIntent, onboard, status, webhook)
│ ├── tasks/ # Task system (accept, complete, create, list, types, categories)
│ ├── users/ # User search
│ └── workers/ # Worker system (account, onboarding, tier, ledger, earlyUnlock)
│
├── portal/ # Business management SPA
│ ├── index.html # Main dashboard
│ ├── login.html # Portal login
│ ├── signup.html # Business registration
│ ├── menu-builder.html # Interactive menu builder
│ ├── setup-wizard.html # Onboarding wizard
│ ├── station-assignment.html # Station management
│ ├── quick-tasks.html # Quick task interface
│ ├── portal.js # Core JS (~169KB, vanilla JS, no framework)
│ └── portal.css # Portal styles (~22KB)
│
├── kds/ # Kitchen Display System
│ ├── index.html # Full-screen kitchen display
│ ├── admin.html # KDS admin
│ ├── debug.html # Debug tools
│ └── kds.js # KDS logic (~17KB, 5-second polling)
│
├── hud/ # Task HUD (Heads Up Display)
│ ├── index.html # Full-screen task bars
│ └── hud.js # HUD logic (~12KB, 60s target times)
│
├── receipt/ # Public receipt page (isolated app)
│ ├── Application.cfm # Minimal config (no parent layout)
│ └── index.cfm # Order receipt by UUID
│
├── admin/ # Admin tools
│ ├── beacons.cfm # Beacon management
│ ├── beacon_servicepoint.cfm # Beacon-SP mapping
│ ├── email_users.cfm # Bulk email
│ ├── god_mode.cfm # Super-admin panel
│ └── servicepoints.cfm # Service point management
│
├── library/cfc/ # ColdFusion components
│ ├── businessMaster.cfc # Business data abstraction
│ └── twilio.cfc # SMS integration
│
├── includes/ # Shared includes
│ ├── menu.cfm # Menu rendering
│ └── track_visitors.cfm # Analytics
│
├── uploads/ # User-generated content
│ ├── categories/ # Category images
│ ├── headers/ # Business header images
│ ├── items/ # Menu item photos
│ ├── logos/ # Business logos
│ └── users/ # User profile images
│
├── cron/
│ └── expireStaleChats.cfm # Cleanup old chats
│
├── css/ # Bootstrap 5.3.0 + custom
├── js/ # jQuery 1.11/2.1, Bootstrap JS
├── fonts/ # Glyphicons
├── images/ # Logo assets
├── cfpayment/ # Payment gateway library
└── twilio/ # Twilio SMS
Key Files
| File | Purpose |
|---|---|
api/Application.cfm |
Token auth, public endpoint allowlist, perf tracking, datasource config |
Application.cfm |
Root session management (30min timeout), layout wrapper, business/twilio objects |
portal/portal.js |
Main portal SPA logic (169KB, vanilla JS, Fetch API) |
kds/kds.js |
Kitchen display polling & order status management |
hud/hud.js |
Task visualization with time tracking & beacon alerts |
API Authentication
- Header:
X-User-Token(64-char hex, SHA-256 based) - Business Context:
X-Business-IDheader - Token Table:
UserTokens(Token, UserID) - Public Endpoints: ~50+ whitelisted in
api/Application.cfm(no token required) - OTP Flow: sendOTP → verifyOTP → token issued
- Magic OTP:
123456bypass for App Store review
Environment Detection
Localhost (127.0.0.1):
wwwrootprefix = /biz.payfrit.com/
image_display_prefix = http://127.0.0.1:8888/biz.payfrit.com/uploads/
uploads_dir = C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\uploads\
Production:
wwwrootprefix = /
image_display_prefix = https://biz.payfrit.com/uploads/
uploads_dir = /var/www/biz.payfrit.com/uploads/
Tech Stack
- Backend: CFML (Lucee), Application.cfm (not .cfc)
- Portal/KDS/HUD: Vanilla JavaScript (no framework)
- Root pages: jQuery 1.12.4 (CDN) + Bootstrap 5.3.0 (CDN)
- Payments: Stripe (webhook at
/api/stripe/webhook.cfm) - SMS: Twilio via
twilio.cfc - Database:
payfritdatasource
Database
Both payfrit (production) and payfrit_dev (development) use clean, unprefixed column names. PKs are always ID. FKs reference their parent table (e.g., BusinessID, UserID).
Key tables:
| Table | PK | Key Columns |
|---|---|---|
| Businesses | ID |
Name, UserID, Phone, BrandColor, TaxRate, OrderTypes, StripeAccountID, ParentBusinessID |
| Users | ID |
FirstName, LastName, ContactNumber, EmailAddress, StripeCustomerId, UUID |
| Orders | ID |
UUID, UserID, BusinessID, StatusID, OrderTypeID, ServicePointID, PaymentStatus |
| Items | ID |
BusinessID, Name, Price, CategoryID, ParentItemID, IsActive, SortOrder, ImageExtension |
| Categories | ID |
BusinessID, MenuID, ParentCategoryID, Name, OrderTypes, SortOrder |
| OrderLineItems | ID |
ParentOrderLineItemID, OrderID, ItemID, StatusID, Price, Quantity, Remark |
| Tasks | ID |
BusinessID, TaskTypeID, Title, Details, ClaimedByUserID, OrderID |
| ServicePoints | ID |
BusinessID, Name, TypeID, Code, SortOrder, IsActive, BeaconID |
| Beacons | ID |
BusinessID, UUID, Name, IsActive |
| Menus | ID |
BusinessID, Name, IsActive |
| Employees | ID |
BusinessID, UserID, StatusID, RoleID, IsActive |
| tt_StaffRoles | ID |
Name (1=Staff, 2=Manager, 3=Admin) |
| lt_ItemID_TemplateItemID | — | ItemID, TemplateItemID, SortOrder |
Modifier Template System (IMPORTANT)
Modifier groups can be shared across multiple menu items via the template linking table lt_ItemID_TemplateItemID. A template is a top-level Item (ParentItemID=0, CategoryID=0) with child options. Menu items link to templates via the linking table, not via ParentItemID.
Example: "Cheeseburger Mods" (template) → children: "SIDE AVOCADO", "NO CHEESE", etc. Both CHEESEBURGER and DOUBLE CHEESEBURGER link to the same template.
Virtual IDs in items.cfm
items.cfm returns template-linked modifiers as virtual children of menu items with synthetic IDs:
- Template group:
virtualID = menuItemID * 100000 + templateItemID - Template option:
virtualID = menuItemID * 100000 + optionItemID - ParentItemID is set to
menuItemID(for template groups) orvirtualTemplateID(for options)
This makes templates appear as regular children in the item tree. Mobile apps see them in itemsByParent[menuItemId] alongside any direct children.
Virtual ID Decoding in setLineItem.cfm
When receiving virtual IDs from clients: realItemID = virtualID MOD 100000 (if ItemID > 100000).
CRITICAL: API responses always return real ItemIDs, not virtual IDs. Clients must account for this round-trip: virtual IDs go out, real IDs come back.
API Response Key Names
All API endpoints use clean key names matching the database columns (e.g., ItemID, ParentOrderLineItemID, Price). Do NOT use old prefixed key names like OrderLineItemItemID — those are legacy and will cause key mismatches in client parsers.
Order Status Flow
- Cart (StatusID 1) → 2. Submitted (2) → 3. Preparing (3) → 4. Ready (4) → 5. Completed (5)
Receipt Page
- URL:
/receipt/index.cfm?UUID={orderUuid} - Separate
Application.cfm— bypasses root layout - Public (no auth) — secured by v4 UUID unguessability
- No internal data exposed (no platform fees, commissions, Stripe IDs)
Performance Tracking
API requests track:
request._perf_start(getTickCount)request._perf_queryCountrequest._perf_queryTimeMs
Git Configuration
Ignored: config/claude.json, *.tmp, *.bak, api/admin/_scripts/
Common Tasks
Add new API endpoint
- Create
.cfmfile in appropriateapi/subdirectory - Public endpoints: add to allowlist in
api/Application.cfm - Authenticated endpoints work automatically via
X-User-Token
Add new portal page
- Create HTML file in
portal/ - Add routing/navigation in
portal.js
KDS customization
- Refresh interval:
config.refreshIntervalinkds.js - Station filtering via URL params