Work/White-Label/Booking & Intake Engine
WL
Dev
White-Label Engagementhome services · confidential client

A booking-fee funnel with a full job lifecycle — built where no plugin existed.

A home services company needed customers to qualify their project, pay a booking fee, and enter a managed job pipeline. There was no off-the-shelf plugin that owned a booking as a first-class entity. We built one.

What we built
Custom WordPress plugin
Industry
Home services & contracting
Delivery
White-labelconfidential
Plugin version
0.4.0active
Book a Service
What are we painting?
// Start with scope. We size the crew and prep accordingly.
Interior
Rooms, ceilings, trim, doors, cabinetry inside.
Exterior
Siding, brick, stucco, shutters, trim — outside.
Interior + Exterior
Full repaint — combined indoor and outdoor.
Name
Alex R.
ZIP
48226
Email
alex@example.com
Phone
(313) 555-0147
Booking fee — server-resolved $150
Confirm & Pay →
Booking Confirmed
Reference: Booking #1042 · Interior Painting
// the problem

WooCommerce can take a payment. It cannot own a booking or run a job lifecycle.

The client needed customers to move through a guided qualifier, pay a booking fee, and enter a structured work pipeline — from initial intake all the way through site visit, quote, scheduling, and completion. That's a lifecycle problem, not a payment problem. Off-the-shelf plugins don't see the distinction.

🗂️

No booking entity

WooCommerce orders are payment records, not job records. There was no place to hang a job lifecycle, a qualifier answer set, or a status that meant anything to the business.

📋

Static service pages

Every service needed its own qualifier flow — different questions, different options, different logic. That content had to be editable in the admin without a code deploy every time something changed.

💳

Payment tightly coupled

If the payment processor ever changed, every piece of booking logic would break. The business needed payment to be one swappable component inside a larger system — not the system itself.

// plugin architecture

Six layers. One plugin.

Each layer owns one responsibility. None of them know more than they need to.

layer 01

Service CPT

Each service is a native WordPress custom post type. Fee, lead time, site-visit requirements, and the entire qualifier flow are configured per-service in the admin — no code deploy to add a new service or change a price.

layer 02

Qualifier flow builder

A dashboard-configurable step builder defines any number of qualifier questions with multiple-choice options per service. Code owns the funnel behavior; the client owns every word of its content. Flow is stored in post meta, never hardcoded.

layer 03

Booking entity + status machine

The booking is a first-class custom database table record — not a WooCommerce order. It owns a 10-state lifecycle from intake to cleared, with validated status transitions, action hooks on every change, and full audit trail.

layer 04

Payment adapter interface

An interface contract means WooCommerce is one implementation, not a dependency. The booking controller never calls WooCommerce directly — only the adapter. Switching processors is an adapter swap with zero booking-layer changes.

layer 05

Dual contact record

Contact data lives in two layers: the original funnel-session capture (never overwritten — the source-of-intent record) and the final WooCommerce billing details (editable at checkout). Both link to one booking for the CRM layer.

layer 06

Notification system

Lifecycle-driven transactional emails for every event. Every subject, body, and recipient is filterable via WordPress hooks — no code deploy to change messaging. Qualifier answers are mapped to human-readable labels in admin emails.

// booking lifecycle · full status vocabulary

From the moment a customer submits to the moment the job is cleared.

pending_payment fee_paid site_visit_scheduled assessed quoted work_scheduled completed cleared  ·  cancelled  ·  refunded

The funnel creates a pending_payment booking and the payment layer advances it to fee_paid. The full vocabulary is defined now — every downstream phase of the job is already modeled so later additions never need to redefine it. The next layer — CRM and post-booking management — picks up exactly where this lifecycle leaves off.

// the booking flow · step by step

From first click to confirmed booking.

Visitor
Lands on a service page and clicks "Book." The [service_booking] shortcode renders the funnel for that specific service — reading its qualifier steps live from the CPT.
Qualifier
Steps through the dashboard-configured qualifier flow — each question with branded multiple-choice options. Answers are stored in the session for the booking record.
Contact
Enters name, email, phone, ZIP, and optional company. This is the funnel-session capture layer — stored as-is on the booking, never overwritten even if the customer edits details at checkout.
Fee
The booking fee is shown — but the browser never sets the price. The REST controller recomputes it server-side from the service config on every submission. The client cannot manipulate the fee.
Payment
The controller creates the booking record, fires the booking_created action, then hands off to the payment adapter. WooCommerce processes the fee as an invisible subprocessor.
Confirmed
On successful payment, the booking advances to fee_paid. The customer receives a confirmation email with their booking reference and next steps. The admin receives a full booking detail with qualifier answers.
🔒

Server-side fee enforcement

The booking controller is the trust boundary. Fee is always recomputed from the service config — the browser has no ability to set or manipulate the price.

🎛️

Admin-configurable per-service flows

Three launch services seeded with real qualifier flows (lead remediation, painting, concrete). Each fully editable in the WP admin — new services, new questions, new options with no code changes.

🏠

Homepage funnel router

A [home_funnel] shortcode surfaces up to 4 featured services with their own taglines, page URLs, and display order — managed entirely from the admin dashboard.

📊

Full admin bookings dashboard

Bookings table in WP admin — searchable, filterable by status, showing both the original funnel capture and the confirmed billing layer side-by-side.

// built to the right standards

Security-first at the trust boundary.

Nonce-verified REST endpoint

Every booking submission requires a valid wp_rest nonce as a CSRF guard. No anonymous request without a verified nonce reaches the booking logic.

Server-side answer validation

Qualifier answers are validated against the service's step schema on the server. Invalid or missing answers return a clean error — the booking is never created with unvalidated data.

Pluggable payment interface

An interface contract enforces what a payment adapter must do. A null adapter supports dev/staging environments. Switching processors never touches booking logic.

Preserve-by-default uninstall

Deactivation only flushes rewrite rules. Deleting the plugin preserves all booking data and settings. An optional purge block is present but disabled — for operators who ever want true full removal.

Full input sanitization

Every field sanitized at the controller before the booking record is created — sanitize_text_field(), sanitize_email(), sanitize_title(). No raw user input touches the database.

Action hook architecture

booking_created, booking_status_changed, booking_fee — all filterable. The business can customize behavior without ever modifying core plugin code.

// what happens after fee_paid

The funnel is the beginning. The CRM is what comes next.

The booking lifecycle was designed with the full job pipeline in mind from day one. Every status from site_visit_scheduled through cleared is already modeled — waiting for the management layer that sits on top. That layer — a custom CRM built for this exact lifecycle — is in active development. The dual contact record, the qualifier answers, the status vocabulary: all of it was built to feed what comes next.

Coming SoonPost-booking CRM
& lifecycle management

Need a booking system that owns the full job lifecycle?

We build what the plugins don't. Tell us the problem.