A production-grade WordPress booking plugin with Google Calendar sync, Stripe payments, multi-provider support, and a fully configurable email notification system.
- Requirements
- Installation
- Configuration
- Getting Started
- Shortcodes
- Booking Flow
- Payment Modes
- Admin Features
- Email Templates
- REST API
- File Structure
- Database Schema
- Changelog
| Requirement | Minimum |
|---|---|
| WordPress | 6.0 |
| PHP | 8.0 |
| MySQL / MariaDB | 5.7 / 10.3 |
| SSL | Required (Stripe, Google OAuth) |
Optional but recommended:
- Google Cloud project with Calendar API and Maps Geocoding API enabled
- Stripe account (test or live)
- Copy the
native-pro-schedulerfolder intowp-content/plugins/. - Activate the plugin via Plugins → Installed Plugins.
- Two database tables are created automatically on activation:
{prefix}nps_bookings{prefix}nps_availability
- A Scheduler top-level menu appears in the WordPress admin.
To deactivate cleanly, use Plugins → Deactivate. The plugin's scheduled cron event is removed on deactivation; database tables are retained.
Navigate to Scheduler → Settings to enter your API credentials.
| Field | Description |
|---|---|
| Currency Symbol | Symbol prepended to all prices (e.g. $, £, €, AU$). Default: $ |
| Field | Where to find it |
|---|---|
| Google Client ID | Google Cloud Console → APIs & Services → Credentials |
| Google Client Secret | Same credential entry |
| Google Maps API Key | Google Cloud Console → Maps Geocoding API |
Required OAuth scopes: https://www.googleapis.com/auth/calendar
After saving the credentials, open any Provider post and click Connect Google Calendar. Complete the OAuth flow; the access and refresh tokens are stored encrypted in provider post meta.
| Field | Where to find it |
|---|---|
| Publishable Key | Stripe Dashboard → Developers → API Keys |
| Secret Key | Same page (reveal once) |
Use pk_test_… / sk_test_… keys during development; swap to live keys for production.
Keys are stored AES-256-GCM encrypted using a key derived from WordPress's AUTH_KEY.
Enter dates in YYYY-MM-DD format, one per line. No slots are returned for these dates
regardless of provider availability.
- Go to Scheduler → Providers → Add New (or find Providers under the custom post type menu).
- Fill in the provider's display name and any post content you want shown publicly.
- In the Provider Details meta box:
- Timezone — provider's local timezone (used for display only; all storage is UTC).
- Assigned Services — tick every service this provider can deliver.
- Google Calendar — click Connect Google Calendar to authorise the OAuth flow.
- In the Weekly Availability meta box, set the working hours for each day of the week. Toggle days on/off and set start/end times. These are stored in UTC — enter times in UTC or adjust for the provider's timezone at the UI layer.
- Save the post. Availability rows are written to
{prefix}nps_availability.
Date overrides: The availability table supports override_date rows that take precedence
over the recurring schedule. These are not yet exposed in the UI but can be inserted directly
into the database for one-off exceptions.
- Go to Scheduler → Services → Add New.
- In the Service Details meta box:
| Field | Description |
|---|---|
| Duration (minutes) | Length of the appointment |
| Buffer (minutes) | Blocked time after each appointment (e.g. clean-up); not shown to clients |
| Price | Full service price in your Stripe account's default currency |
| Payment Mode | See Payment Modes |
| Deposit Amount | Fixed deposit amount (deposit_fixed mode only) |
| Deposit % | Percentage of price collected upfront (deposit_percent mode only) |
| Address | Physical location; geocoded to lat/lng on save |
- Assign one or more Service Categories using the taxonomy metabox in the right sidebar.
- Save the post. The booking form is automatically appended to this service's single page.
Manage categories via Scheduler → Categories in the admin menu.
- Categories are hierarchical (like WordPress post categories).
- Assign one or more categories to each service from the service edit screen.
- The public
/services/archive automatically renders a category filter strip. - Taxonomy archive pages (
/service-category/{slug}/) show only services in that category.
Automatic: The form is appended to any single nps_services page via the the_content
filter. No shortcode needed.
Manual / embed anywhere:
[nps_booking service_id="42"]
Replace 42 with the service post ID. Multiple shortcodes can appear on the same page (each
initialises its own isolated booking flow).
The plugin registers /services/ as the post-type archive for nps_services. Visiting this
URL displays all published services as Bootstrap-style cards (3 per row on desktop), each
showing the featured image, title, excerpt, price, and a Book Now link. A category filter
strip at the top links to the individual taxonomy archive pages.
Theme override: Place archive-nps_services.php or
taxonomy-nps_service_category.php in your active theme directory to replace the plugin's
default template.
Embeds the multi-step booking form for a specific service.
| Attribute | Default | Description |
|---|---|---|
service_id |
(required) | Post ID of the service to book |
label |
service title | Override the displayed service name |
[nps_booking service_id="42"]
[nps_booking service_id="42" label="Book a Massage"]
Renders a Bootstrap-style card grid listing published services.
| Attribute | Default | Description |
|---|---|---|
category |
(none) | Slug of a nps_service_category term; omit for all services |
limit |
-1 (all) |
Maximum number of cards to render |
[nps_services]
[nps_services category="massage" limit="6"]
When no category attribute is provided, a category filter strip is rendered above the grid.
Clicking a category tag reloads the page with ?nps_cat=slug to filter the results.
The public multi-step form works as follows:
Step 1 — Date picker
↓ (fetches /nps/v1/slots)
Step 2 — Available time slots grid
↓ (slot selected → POST /nps/v1/create-intent)
Step 3 — Client details (name, email, phone)
↓
Step 4 — Payment capture (Stripe card element) or direct confirm (no-payment mode)
↓ (POST /nps/v1/book)
Success — Confirmation message + emails sent
All times displayed to the client are formatted according to WordPress's Time Format setting (Settings → General). All internal storage and API communication use UTC.
Set per service in the Service Details meta box.
| Mode | Behaviour |
|---|---|
| None | No payment collected. Booking confirmed immediately on submit. |
| Full | Full service price charged at booking time. |
| Fixed Deposit | A specific dollar amount charged upfront; remainder collected separately. |
| Percentage Deposit | A percentage of the service price charged upfront. |
For all modes except None, Stripe's Authorize-then-Capture flow is used:
- A PaymentIntent is created and authorised (card held but not charged) before the booking is confirmed.
- Availability is re-validated server-side.
- The hold is captured only after the booking row is successfully inserted.
- If the slot is taken between step 1 and 3, the hold is released automatically.
- Total Bookings — all-time count.
- Confirmed — bookings with
confirmedstatus. - Today's Appointments — non-cancelled bookings for today.
- Recent bookings table (last 5).
- Filter by status (pending / confirmed / cancelled / completed).
- Search by client name or email.
- Paginated at 20 rows per page.
- Per-row actions for confirmed bookings:
- Reschedule — opens a date/slot picker; updates the DB, recreates the Google Calendar event, and emails the client a reschedule notification.
- Complete — marks the booking completed.
- Cancel — marks cancelled, deletes the Google Calendar event, and refunds/voids the Stripe charge. Prompts for confirmation.
General (currency symbol), Google API keys, Stripe keys, and blackout dates.
Hierarchical category management for the nps_services post type. Categories are used for
filtering on the services archive page and in the [nps_services] shortcode.
Edit subject line and HTML body for each of the five template types (see Email Templates).
Five template types are editable under Scheduler → Email Templates:
| Template | Sent when |
|---|---|
| Booking Confirmation | Client books; also sent to the provider |
| Appointment Reminder | 24 hours before the appointment (WP-Cron) |
| Cancellation Alert | Booking is cancelled |
| Admin New Booking Alert | Sent to site admin on every new booking |
| Reschedule Notification | Sent to client when admin reschedules their appointment |
Templates support a rich text editor (TinyMCE) for the body and a plain text subject line.
Use these placeholders anywhere in the subject or body; they are replaced at send time.
| Token | Value |
|---|---|
{{client_name}} |
Client's full name |
{{client_email}} |
Client's email address |
{{client_phone}} |
Client's phone number |
{{service_name}} |
Service title |
{{provider_name}} |
Provider display name |
{{booking_date}} |
Appointment date (WordPress date format) |
{{start_time}} |
Start time (WordPress time format) |
{{end_time}} |
End time (WordPress time format) |
{{booking_id}} |
Booking database ID |
{{site_name}} |
WordPress site name |
{{site_url}} |
WordPress site URL |
{{admin_url}} |
Direct URL to the Bookings admin page |
{{new_date}} |
New date after reschedule |
{{new_start_time}} |
New start time after reschedule |
{{new_end_time}} |
New end time after reschedule |
Base URL: /wp-json/nps/v1/
All POST endpoints require the X-WP-Nonce header set to a wp_rest nonce.
Returns available time slots for a service on a given date.
Query parameters
| Param | Type | Description |
|---|---|---|
service_id |
int | Service post ID |
date |
string | YYYY-MM-DD (UTC) |
Response
{
"success": true,
"slots": [
{
"start": "09:00",
"end": "10:00",
"provider_ids": [25, 31]
}
]
}Creates a Stripe PaymentIntent for the service. For none payment mode returns a signal to
skip payment.
Body (JSON)
| Param | Type | Required |
|---|---|---|
service_id |
int | yes |
nonce |
string | yes (nps_booking_nonce) |
Response
{
"success": true,
"no_payment": false,
"client_secret": "pi_…_secret_…",
"amount_cents": 5000,
"payment_mode": "full"
}For none mode: { "success": true, "no_payment": true }
Atomic booking endpoint. Validates the slot, inserts the booking, creates a Google Calendar event, captures payment, and sends confirmation emails.
Body (JSON)
| Param | Type | Required |
|---|---|---|
service_id |
int | yes |
provider_id |
int | yes |
date |
string | yes (YYYY-MM-DD) |
start_time |
string | yes (HH:MM) |
client_name |
string | yes |
client_email |
string | yes |
client_phone |
string | no |
payment_intent_id |
string | only for paid modes |
nonce |
string | yes (nps_booking_nonce) |
Response
{
"success": true,
"booking_id": 42,
"message": "Your booking is confirmed!"
}Cancels a booking. Requires manage_options capability.
Body (JSON)
| Param | Type | Required |
|---|---|---|
booking_id |
int | yes |
nonce |
string | yes (nps_cancel_nonce) |
native-pro-scheduler/
├── native-pro-scheduler.php # Plugin bootstrap, constants, autoload
│
├── includes/
│ ├── class-nps-plugin.php # Singleton service locator + NPS() helper
│ ├── class-nps-installer.php # Activation: dbDelta tables, cron scheduling
│ ├── class-nps-cpt.php # Provider & Service custom post types + meta boxes
│ ├── class-nps-availability-manager.php # Slot engine (schedule + GCal + DB)
│ ├── class-nps-google-calendar.php # OAuth2, FreeBusy, event CRUD
│ ├── class-nps-stripe.php # PaymentIntent create/verify/capture/cancel/refund
│ ├── class-nps-email.php # HTML email sending + token substitution
│ ├── class-nps-encryption.php # AES-256-GCM encrypt/decrypt (API key storage)
│ ├── class-nps-geocoding.php # Address → lat/lng via Maps Geocoding API
│ ├── class-nps-cron.php # Daily reminder cron job
│ └── class-nps-rest-controller.php # WP REST API route handlers
│
├── admin/
│ ├── class-nps-admin.php # Admin menu, dashboard page
│ ├── class-nps-bookings-admin.php # Bookings table + reschedule flow
│ ├── class-nps-settings.php # Settings page (API keys, blackout dates)
│ └── class-nps-email-editor.php # Email template editor (wp_editor)
│
├── public/
│ ├── class-nps-public.php # Asset enqueue + script localisation + archive template
│ ├── class-nps-booking-shortcode.php # [nps_booking] shortcode
│ └── class-nps-services-shortcode.php # [nps_services] shortcode
│
├── templates/
│ ├── booking-form.php # Multi-step booking form HTML
│ ├── services-archive.php # Services archive / category taxonomy template
│ └── emails/
│ ├── confirmation.php
│ ├── reminder.php
│ └── cancellation.php
│
├── assets/
│ ├── css/
│ │ ├── public.css # Booking form styles
│ │ └── admin.css # Admin UI styles
│ └── js/
│ ├── booking.js # Multi-step form JS + Stripe Elements
│ └── admin.js # Admin UI JS
│
├── README.md
└── CHANGELOG.md
| Column | Type | Notes |
|---|---|---|
id |
bigint UNSIGNED PK | Auto-increment |
service_id |
bigint UNSIGNED | Service post ID |
provider_id |
bigint UNSIGNED | Provider post ID |
booking_date |
date | UTC |
start_time |
time | UTC |
end_time |
time | UTC (start + duration) |
client_name |
varchar(150) | |
client_email |
varchar(200) | |
client_phone |
varchar(30) | |
status |
varchar(20) | pending / confirmed / cancelled / completed |
stripe_payment_intent_id |
varchar(200) | |
stripe_charge_id |
varchar(200) | Set after capture |
google_event_id |
varchar(200) | Set after GCal event creation |
created_at |
datetime | Auto |
updated_at |
datetime | Auto-updated |
| Column | Type | Notes |
|---|---|---|
id |
bigint UNSIGNED PK | Auto-increment |
provider_id |
bigint UNSIGNED | Provider post ID |
day_of_week |
tinyint | 0 = Sunday … 6 = Saturday |
start_time |
time | UTC |
end_time |
time | UTC |
is_available |
tinyint(1) | 1 = working, 0 = day off |
override_date |
date NULL | NULL = recurring; date = one-off override |
Recurring rows have override_date = NULL. Date-specific overrides take precedence over
recurring rows for the same provider on a given date.
See CHANGELOG.md for the full version history.