Initializing
Back to Projects
Year2024
DomainFullstack
AccessOpen Source
Complexity0 / 10
PHPWordPressWordPress PluginClient PortalWooCommerce
FullstackArchived

Pay As You Grow Portal (WordPress Plugin)

A complete WordPress plugin for price calculation, client portal, quote storage, and access management with dynamic service management and payment tracking.

# 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:

  1. Dynamic Price Calculator — Real-time quote generation based on selected services
  2. Client Portal — Secure access for clients to view their quotes and history
  3. Quote Management — Custom post type for storing and managing quotations
  4. Access Tokens — Secure, token-based access without requiring user accounts
  5. GST Handling — Configurable inclusive/exclusive tax calculations
  6. Addon System — Allow clients to request additional services after initial quote

Architecture

Parsing system architecture diagram...

Core Features

1. Custom Post Types

php
// 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:

php
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:

php
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

php
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:

php
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:

php
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 SlugShortcodePurpose
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-conditionsManualLegal terms
privacy-policyManualPrivacy policy
admin-dashboard[bps_admin_dashboard]Admin management

Database Schema

Custom Tables

sql
-- 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/rejected

Post Meta Fields

Meta KeyPost TypeDescription
_bps_service_pricebps_serviceService price
_bps_service_durationbps_serviceDuration in hours
_bps_access_tokenbps_quotationUnique access token
_bps_client_namebps_quotationClient name
_bps_client_emailbps_quotationClient email
_bps_total_amountbps_quotationTotal with GST
_bps_quote_statusbps_quotationpending/accepted/rejected

Configuration Options

php
// 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

php
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

ShortcodeDescription
[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

  1. Token-based access — No WordPress user accounts needed
  2. Nonced forms — All forms use wp_nonce verification
  3. Input sanitization — All inputs sanitized before processing
  4. Access validation — Tokens validated before displaying data
  5. URL hashing — Access tokens are randomly generated

Email Notifications

php
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

Parsing system architecture diagram...

Installation

  1. Create required pages with specified slugs
  2. Add shortcodes to respective pages
  3. Activate plugin
  4. Add services in the Services CPT
  5. Configure GST setting in admin

Version History

VersionDateChanges
11.3.1FinalComplete business logic
11.0EarlierAdded addon system
10.0EarlierInitial 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.

Submit a Technical Suggestion