This is the project I'm most proud of in this portfolio — not because it's the most complex thing I've ever built, but because it's the most complete. It's a full digital platform: a public-facing website, a payment pipeline, an AI assistant, a support operation with SLA tracking, and an admin command centre. All on Salesforce. All in one org.

The idea came from real professional experience. I've worked on Experience Cloud implementations, Stripe-style payment integrations, and external system connectors in production environments. I wanted to build something that demonstrated those same patterns properly — end to end, with the full architecture visible — since the original client work lives behind NDAs. So I built Sai Dance Academy: a fictional dance academy, a real platform.

Part 1 covers the architecture, data model, and the six core user journeys. Part 2 is where it gets honest — 14 real issues, how each was diagnosed and fixed, and what they taught me about the platform.


The brief

Build a complete digital platform for a dance academy. A public website where prospective students can discover classes, enroll and pay, register for events, get AI-assisted support, and make enquiries — backed by an internal operation for managing the business and serving customers. Do it entirely on Salesforce, demonstrating breadth across the platform.

💡 Why this scope

I deliberately made the brief ambitious. A simple "display some records on an Experience Cloud page" doesn't demonstrate much. The brief needed a payment pipeline, an AI layer, a support operation, and an admin view — because that's what real platform delivery looks like. Each piece forced a different set of platform decisions.

Sai Dance Academy public website homepage

The public-facing Experience Cloud site — no login required


The solution at a glance

A single Salesforce org delivering six capabilities across two surfaces — a public website and an internal admin app:

Public Site
Experience Cloud (LWR)
Class discovery, enrollment + payment, event registration, AI assistant, Web-to-Case support, Web-to-Lead enquiries. No login required.
Payments
Stripe Checkout + Webhooks
Stripe Checkout for classes and events. A public Apex REST webhook confirms records automatically on payment success or failure.
AI Layer
Agentforce Assistant
An AI agent grounded in live org data. Recommends classes, answers questions, captures leads, logs support cases, and hands off responsibly.
Support
Service Cloud with SLA
Web-to-Case intake, automatic priority assignment, Entitlement/Milestone SLA tracking, and auto-response acknowledgment emails.
Admin
Custom App + Dashboards
One app, two live dashboards: Business Performance and Service SLA Command Centre. Full record visibility across all objects.
Catalog
Fully Data-Driven
Classes are records, not code. An admin adds a new class and it flows through the entire site and payment pipeline with zero deployment.

1

How it connects

Architecture Overview

The platform has three distinct layers. Understanding how they connect is the foundation for everything else.

Front end — Experience Cloud (LWR)

The public site is built on Salesforce Experience Cloud using the LWR (Lightning Web Runtime) framework with custom Lightning Web Components. The hero, the data-driven class catalogue with detail modals and live availability, the enrollment form, the events page, and the support/contact pages are all custom LWC. A test-data notice banner appears on data-entry pages — it's a demo site, and I wanted that visible and honest.

Backend — Apex controllers

The LWC components talk to @AuraEnabled Apex controllers for the catalogue, enrollment, and Stripe checkout. A public @RestResource webhook handles payment confirmation from Stripe. @InvocableMethod actions power the Agentforce agent's data access.

Automation — Flows

Record-triggered Flows handle confirmation emails for enrollments, RSVPs, and cases. A before-save Flow assigns case priority based on reason. Auto-Response Rules handle case acknowledgment. The automation layer is deliberately declarative where possible — Apex only where the platform requires it.

Architecture diagram — front end, backend, automation, and integration layers

Architecture diagram — front end, backend, automation, and integration layers

🏗️ Key architectural principle

The payment pipeline is designed as a single confirmation trigger. When an enrollment or RSVP "becomes Confirmed" — whether instantly (free) or via webhook (paid) — the same downstream automation fires. One trigger, two paths, consistent behaviour.


2

The foundation

Data Model — Five Custom Objects

Getting the data model right before writing a line of Apex or touching Experience Builder was non-negotiable. The integration is only as good as the structure it writes into.

ObjectTypePurposeKey relationships
Dance_Class__c Custom The catalogue. Every class is a record — name, instructor, schedule, fee, max capacity, age group, description, active flag. Parent of Enrollment__c
Enrollment__c Custom Records a student's enrollment in a class. Status progresses: Pending Payment → Awaiting Payment → Confirmed (or Payment Failed). Lookup to Dance_Class__c, Contact
Payment__c Custom Audit trail for every payment. Created by the webhook on success or failure. Stores Stripe payment ID, amount, currency, status, failure reason. Lookup to Enrollment__c
Event__c Custom Events — free or paid. Carries a free flag; the payment pipeline branches based on this. Same catalog-driven approach as classes. Parent of RSVP__c
RSVP__c Custom Records an attendee's registration for an event. Same status model as Enrollment — free RSVPs confirm instantly, paid RSVPs wait for the webhook. Lookup to Event__c, Contact
Contact Standard The student/attendee record. Created or matched by email on enrollment/RSVP. Deduplication is handled in Apex — no duplicate Contact for the same email. Related to Enrollment__c, RSVP__c, Case
Lead Standard Captured by Web-to-Lead (contact form) and by the Agentforce agent when someone makes an enquiry. Source tracked (Web, Agentforce Assistant)
Case Standard Support requests — via Web-to-Case or Agentforce. Priority assigned automatically. Entitlements and Milestones track SLA. Entitlement, Contact
Entity Relationship Diagram — all 8 objects and their relationships

Entity Relationship Diagram — all 8 objects and their relationships

💡 Capacity is computed, not stored

Available spots are calculated live from confirmed enrollments rather than stored as a counter field. This avoids counter drift — a confirmed enrollment at any point produces the correct available count automatically. Defense in depth: enforced in the UI and in Apex so it can't be bypassed.


3

What it does

The Six Core User Journeys

Six user journeys cover the full platform. Each one was built and tested end to end — including in incognito to exercise true guest permissions, not just the authenticated preview.

1
Enroll in a class and pay
Core revenue flow

A visitor browses the class catalogue, opens a detail modal showing instructor, schedule, age group, capacity, and fee, clicks Enroll, completes the form, and is redirected to Stripe Checkout. On payment, Stripe fires a webhook to a public Apex REST endpoint. The endpoint finds the enrollment by the Stripe session ID stored in metadata, flips the status to Confirmed, creates a Payment audit record, and the confirmation Flow sends an email.

The entire chain — browse → enroll → pay → confirm → email — runs with no human intervention. The enrollment only becomes Confirmed after the webhook fires. Before that it sits at Awaiting Payment.

2
Full class — capacity enforcement
Defense in depth

When confirmed enrollments reach max capacity, the class card shows "Class Full" and the Enroll button disappears — enforced in the LWC by a computed property. If someone attempts to bypass the UI and submit directly, the Apex controller checks capacity server-side and refuses with "Sorry, this class is full." No enrollment is created. Two layers — neither alone is sufficient.

3
Event registration — free and paid
One flow, two paths

Free events confirm instantly on form submit — the RSVP is created as Confirmed and the confirmation email fires immediately. Paid events follow the same Stripe pipeline as class enrollments — the RSVP is created as Pending Payment, the visitor pays on Stripe, and the webhook flips it to Confirmed. The same confirmation email Flow handles both cases, triggered by the status change.

4
Agentforce AI assistant — discovery and recommendation
Grounded in live data

The chat assistant on the site is a deployed Agentforce agent. It's grounded in real org data via @InvocableMethod actions — when a visitor asks "what classes do you have?", the agent calls the same Apex method that powers the catalogue and returns live data, not hardcoded responses.

When a visitor asks to enroll, the agent provides the enrollment link rather than attempting the transaction itself. This is a deliberate design decision: the agent informs and recommends; transactions happen on the proper paid path. More on this in the design decisions section.

5
Agentforce — lead capture and case creation
Record creation

When a visitor makes an enquiry in the chat, the agent captures their name and email and creates a Lead with Source = Agentforce Assistant. When a visitor raises a support issue, the agent creates a Case with the correct Reason and Priority. Both records appear in the org immediately — verifiable in real time.

6
Support with SLA and auto-response
Service Cloud

A visitor submits a support request via the Web-to-Case form. A before-save Flow assigns Priority automatically based on Reason — Payment Issues and Complaints become Urgent. The case gets an Entitlement and Milestones are set, so SLA is tracked from the moment of creation. An Auto-Response Rule sends an acknowledgment email immediately. The admin sees the case on the Service SLA dashboard with health status visible.

The full SLA architecture — Entitlement Processes, Milestones, the SLA Health formula field, and the real-time dashboard — is documented in detail in Project 02: SLA Command Centre, which was built as a dedicated deep-dive into exactly this pattern.

Classes page showing card grid with availability

Classes — live availability, spots remaining

Class detail modal

Class detail modal — full information before enrolling

Stripe Checkout page

Stripe Checkout — correct class name and fee in USD

Enrollment record showing Confirmed status

Enrollment confirmed automatically after webhook fires


4

Why it was built this way

Design Decisions Worth Calling Out

Every project involves tradeoffs. These are the decisions I made deliberately — and the reasoning behind each one.

Decision 01
Data-driven catalogue — classes are records, not code
Classes are Dance_Class__c records. An admin adds a new class, sets it active, and it flows through the entire site and payment pipeline automatically — no code change, no deployment. This is the right pattern for any content that changes more often than the code.
Decision 02
Responsible AI handoff — the agent doesn't process payments
The Agentforce agent recommends classes and provides enrollment links. It does not attempt to create enrollments or initiate payments. This is intentional: the agent operates responsibly within its appropriate scope. Transactions that involve payment happen on the proper paid path — not inside a chat window. The agent creates records only where there's no downstream page that should do it (leads and cases).
Decision 03
Defense in depth on capacity — UI and server
Capacity is enforced in both the LWC (computed property hides the button) and in Apex (controller rejects the request). A UI-only check can be bypassed. A server-only check provides no user feedback. Both layers are necessary in a public-facing system where anyone can attempt anything.
Decision 04
Live capacity computation — not a stored counter
Available spots are computed from a COUNT of confirmed enrollments in real time, not stored as a decremented counter field. Counter fields drift — a failed payment, a manually updated record, or a flow bug can leave the counter wrong with no way to detect it. Computed values are always correct by definition.
Decision 05
Static Resources over CMS for guest-visible images
Class and event images are stored as Static Resources (Cache Control = Public), referenced as /sfsites/c/resource/NAME. CMS-managed content requires workspace sharing and per-item publishing to be visible to guests — easy to misconfigure, invisible in preview, broken in incognito. Static Resources are public by default with no extra setup.
Decision 06
Dashboards over notification emails for admin awareness
Administrators monitor the business through real-time dashboards, not per-record email notifications. Email noise at scale is a known anti-pattern. The planned Admin Agentforce agent will handle proactive awareness further — surfacing what needs attention on demand rather than pushing emails.

Agentforce assistant recommending classes

Agentforce — live class data, responsible handoff

Admin home page with both dashboards

Admin home — Business Performance and SLA Command Centre


Walkthrough

Watch the full platform walkthrough

✦ Continue reading · Part 2 of 2

The Hard Parts — What Broke and How I Fixed It

Part 2 covers the real build: 14 issues documented with root causes and fixes, the Stripe webhook architecture in depth, the Agentforce configuration, and an honest section on using AI as a coding partner.

Stripe webhook — multi-site guest context Named Credentials gotcha Callout after DML CMS vs Static Resources 14 real issues documented On AI as a coding partner
Read Part 2 — The Hard Parts →

Try the live site

The platform is live on a Salesforce Developer org in test mode. Use Stripe test card 4242 4242 4242 4242 with any future date and any CVC. All emails go to real inboxes — use a real address to see the confirmation.

Experience Cloud (LWR) · Lightning Web Components · Apex · Flows · Agentforce · Service Cloud · Stripe · 5 Custom Objects