# Pay As You Grow Portal (WordPress Plugin)
A comprehensive WordPress business plugin that combines price calculator, client portal, quote management, and access control system. Designed for service-based businesses that need dynamic pricing and client self-service capabilities.
Purpose and Philosophy
The Problem
Service-based businesses face challenges with:
- Complex pricing: Multiple services with varying rates, add-ons, and GST calculations
- Manual quotes: Creating quotes via email is time-consuming
- Client access: No secure way for clients to view their quotes and history
- Payment tracking: Difficulty tracking partial payments and addon requests
The Solution
This plugin provides an all-in-one solution:
- Dynamic Price Calculator — Real-time quote generation based on selected services
- Client Portal — Secure access for clients to view their quotes and history
- Quote Management — Custom post type for storing and managing quotations
- Access Tokens — Secure, token-based access without requiring user accounts
- GST Handling — Configurable inclusive/exclusive tax calculations
- Addon System — Allow clients to request additional services after initial quote
Architecture
Core Features
1. Custom Post Types
// Register Quotation CPT
function bps_register_post_types_and_taxonomies() {
// Quotation CPT
register_post_type(BPS_CPT_QUOTATION_SLUG, [
'labels' => ['name' => 'Quotations', 'singular_name' => 'Quotation'],
'public' => false,
'show_ui' => true,
'supports' => ['title', 'editor', 'custom-fields'],
'menu_icon' => 'dashicons-clipboard',
]);
// Service CPT
register_post_type(BPS_CPT_SERVICE_SLUG, [
'labels' => ['name' => 'Services', 'singular_name' => 'Service'],
'public' => false,
'show_ui' => true,
'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
'menu_icon' => 'dashicons-cart',
]);
// Service Categories Taxonomy
register_taxonomy(BPS_TAXONOMY_SLUG, [BPS_CPT_SERVICE_SLUG], [
'labels' => ['name' => 'Service Categories'],
'hierarchical' => true,
]);
}2. Dynamic Price Calculator
The price calculator processes selected services in real-time:
function bps_calculate_total() {
$selected_services = $_POST['services'];
$base_total = 0;
$addon_total = 0;
$gst_rate = 0.20; // 20% GST
foreach ($selected_services as $service_id) {
$price = get_post_meta($service_id, '_bps_service_price', true);
$base_total += floatval($price);
}
// Calculate GST
$gst_inclusive = get_option(BPS_GST_OPTION_KEY) === '1';
if ($gst_inclusive) {
$gst_amount = $base_total - ($base_total / (1 + $gst_rate));
$subtotal = $base_total - $gst_amount;
} else {
$gst_amount = $base_total * $gst_rate;
$subtotal = $base_total;
}
return [
'subtotal' => $subtotal,
'gst' => $gst_amount,
'total' => $subtotal + $gst_amount
];
}3. Client Access System
Token-based authentication without requiring WordPress accounts:
function bps_validate_access_token($token) {
global $wpdb;
$table_name = $wpdb->prefix . 'postmeta';
$result = $wpdb->get_row($wpdb->prepare(
"SELECT post_id FROM $table_name
WHERE meta_key = %s AND meta_value = %s",
BPS_TOKEN_META_KEY, $token
));
if ($result) {
$quote = get_post($result->post_id);
return [
'valid' => true,
'quote_id' => $quote->ID,
'client_name' => get_post_meta($quote->ID, '_bps_client_name', true)
];
}
return ['valid' => false];
}4. Quote Generation
function bps_create_quotation($services, $client_data, $total) {
$quote_data = [
'post_type' => BPS_CPT_QUOTATION_SLUG,
'post_title' => 'Quote-' . date('Ymd') . '-' . uniqid(),
'post_status' => 'publish',
'post_content' => json_encode($services)
];
$quote_id = wp_insert_post($quote_data);
// Store client details
update_post_meta($quote_id, '_bps_client_name', $client_data['name']);
update_post_meta($quote_id, '_bps_client_email', $client_data['email']);
update_post_meta($quote_id, '_bps_client_company', $client_data['company']);
update_post_meta($quote_id, '_bps_total_amount', $total['total']);
update_post_meta($quote_id, '_bps_subtotal', $total['subtotal']);
update_post_meta($quote_id, '_bps_gst_amount', $total['gst']);
// Generate access token
$token = wp_generate_password(32, false);
update_post_meta($quote_id, BPS_TOKEN_META_KEY, $token);
return ['quote_id' => $quote_id, 'access_token' => $token];
}5. Admin Dashboard
Comprehensive admin interface for managing all aspects:
function bps_admin_dashboard_shortcode() {
ob_start();
?>
<div class="bps-admin-dashboard">
<h2>Quotation Dashboard</h2>
<div class="bps-stats-grid">
<div class="bps-stat-card">
<h3>Total Quotations</h3>
<p class="stat-number"><?php echo bps_count_quotes(); ?></p>
</div>
<div class="bps-stat-card">
<h3>Pending</h3>
<p class="stat-number"><?php echo bps_count_quotes('pending'); ?></p>
</div>
<div class="bps-stat-card">
<h3>Accepted</h3>
<p class="stat-number"><?php echo bps_count_quotes('accepted'); ?></p>
</div>
<div class="bps-stat-card">
<h3>Total Value</h3>
<p class="stat-number"><?php echo bps_total_quote_value(); ?></p>
</div>
</div>
<h3>Recent Quotations</h3>
<?php echo bps_render_quotes_table(); ?>
<h3>Services Management</h3>
<?php echo bps_render_services_table(); ?>
</div>
<?php
return ob_get_clean();
}6. Client Portal
Secure area for clients to view their quotes:
function bps_client_portal_shortcode() {
$token = $_GET['token'] ?? $_POST['token'] ?? '';
if (!$token) {
return bps_render_login_form();
}
$access = bps_validate_access_token($token);
if (!$access['valid']) {
return '<div class="bps-error">Invalid access token.</div>';
}
$quote = get_post($access['quote_id']);
$services = json_decode($quote->post_content, true);
ob_start();
?>
<div class="bps-client-portal">
<h2>Welcome, <?php echo esc_html($access['client_name']); ?></h2>
<div class="bps-quote-details">
<h3>Quote: <?php echo get_the_title($quote->ID); ?></h3>
<table class="bps-services-table">
<tr>
<th>Service</th>
<th>Description</th>
<th>Price</th>
</tr>
<?php foreach ($services as $service): ?>
<tr>
<td><?php echo esc_html($service['name']); ?></td>
<td><?php echo esc_html($service['description'] ?? '-'); ?></td>
<td>₹<?php echo number_format($service['price'], 2); ?></td>
</tr>
<?php endforeach; ?>
<tr class="bps-total-row">
<td colspan="2"><strong>Total</strong></td>
<td><strong>₹<?php echo number_format(get_post_meta($quote->ID, '_bps_total_amount', true), 2); ?></strong></td>
</tr>
</table>
<div class="bps-actions">
<a href="<?php echo add_query_arg('action', 'addon', bps_get_portal_url()); ?>" class="bps-btn">
Request Addon
</a>
</div>
</div>
</div>
<?php
return ob_get_clean();
}Required Pages
The plugin requires specific pages with shortcodes:
| Page Slug | Shortcode | Purpose |
|---|---|---|
quote-thank-you | (auto-created) | Shown after quote submission |
client-portal | [bps_client_portal] | Client quote viewing |
request-addon | [bps_client_portal action="addon"] | Addon request form |
terms-and-conditions | Manual | Legal terms |
privacy-policy | Manual | Privacy policy |
admin-dashboard | [bps_admin_dashboard] | Admin management |
Database Schema
Custom Tables
-- Service Categories stored in wp_terms and wp_term_taxonomy
-- Quotations stored in wp_posts with post_type 'bps_quotation'
-- Services stored in wp_posts with post_type 'bps_service'
-- Meta stored in wp_postmeta
-- _bps_service_price - Service price
-- _bps_service_duration - Estimated duration
-- _bps_access_token - Client access token
-- _bps_client_name - Client name
-- _bps_client_email - Client email
-- _bps_total_amount - Quote total
-- _bps_quote_status - pending/accepted/rejectedPost Meta Fields
| Meta Key | Post Type | Description |
|---|---|---|
_bps_service_price | bps_service | Service price |
_bps_service_duration | bps_service | Duration in hours |
_bps_access_token | bps_quotation | Unique access token |
_bps_client_name | bps_quotation | Client name |
_bps_client_email | bps_quotation | Client email |
_bps_total_amount | bps_quotation | Total with GST |
_bps_quote_status | bps_quotation | pending/accepted/rejected |
Configuration Options
// Configuration constants
define('BPS_CPT_QUOTATION_SLUG', 'bps_quotation');
define('BPS_CPT_SERVICE_SLUG', 'bps_service');
define('BPS_TAXONOMY_SLUG', 'bps_service_category');
define('BPS_GST_OPTION_KEY', 'bps_prices_include_gst');Settings stored in wp_options:
bps_prices_include_gst- '1' for inclusive, '0' for exclusive- Admin email (default BPS_ADMIN_EMAIL)
- Site name (default BPS_FROM_NAME)
Pricing Logic
GST Handling
function bps_format_price($amount, $include_gst = true) {
if ($include_gst) {
$gst_rate = 0.20; // 20%
$gst_amount = $amount - ($amount / (1 + $gst_rate));
return [
'subtotal' => $amount - $gst_amount,
'gst' => $gst_amount,
'total' => $amount
];
} else {
$gst_amount = $amount * $gst_rate;
return [
'subtotal' => $amount,
'gst' => $gst_amount,
'total' => $amount + $gst_amount
];
}
}Dynamic Calculations
- Base price from selected services
- Add-ons calculated separately
- GST applied based on setting
- Total updated in real-time via AJAX
Shortcodes
| Shortcode | Description |
|---|---|
[bps_price_calculator] | Interactive price calculator form |
[bps_client_login_form] | Token entry form for clients |
[bps_client_portal] | Client dashboard with quote details |
[bps_admin_dashboard] | Admin overview and management |
Security Features
- Token-based access — No WordPress user accounts needed
- Nonced forms — All forms use wp_nonce verification
- Input sanitization — All inputs sanitized before processing
- Access validation — Tokens validated before displaying data
- URL hashing — Access tokens are randomly generated
Email Notifications
function bps_send_quote_email($quote_id, $client_email) {
$quote = get_post($quote_id);
$token = get_post_meta($quote_id, BPS_TOKEN_META_KEY, true);
$portal_url = add_query_arg('token', $token, get_permalink_by_slug('client-portal'));
$subject = 'Your Quote from ' . get_bloginfo('name');
$message = "Your quote is ready!\n\n";
$message .= "Quote: " . get_the_title($quote_id) . "\n";
$message .= "Total: ₹" . number_format(get_post_meta($quote_id, '_bps_total_amount', true), 2) . "\n\n";
$message .= "View your quote: $portal_url";
wp_mail($client_email, $subject, $message);
}Workflow
Installation
- Create required pages with specified slugs
- Add shortcodes to respective pages
- Activate plugin
- Add services in the Services CPT
- Configure GST setting in admin
Version History
| Version | Date | Changes |
|---|---|---|
| 11.3.1 | Final | Complete business logic |
| 11.0 | Earlier | Added addon system |
| 10.0 | Earlier | Initial client portal |
Portfolio Context
This plugin demonstrates:
- Custom Post Types — Multiple CPTs with custom fields
- Database Design — Custom tables and meta handling
- Shortcode Development — Reusable UI components
- AJAX Integration — Real-time price calculations
- Email Automation — Notification system
- Security — Token-based authentication without user accounts
- Business Logic — Complete quote-to-payment workflow
Architecture Feedback
Spotted a potential optimization or antipattern? Let me know.