• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Building secure B2B pricing grids with custom Shortcode API endpoints and role overrides

Building secure B2B pricing grids with custom Shortcode API endpoints and role overrides

Leveraging WordPress’s Shortcode API for Secure B2B Pricing Grids

Building dynamic, role-specific pricing grids within WordPress for B2B clients presents a unique set of challenges. Standard e-commerce solutions often fall short when dealing with complex, tiered pricing structures or per-customer agreements. This post details a robust approach using WordPress’s Shortcode API, coupled with custom role management and endpoint security, to deliver a flexible and secure pricing solution.

Designing the Shortcode Endpoint

The core of our solution is a custom shortcode that acts as an API endpoint. This shortcode will dynamically fetch and render pricing data based on the logged-in user’s role and potentially custom meta data associated with their account. We’ll use WordPress’s built-in AJAX handling to make this an asynchronous, non-page-reload experience.

First, we define the shortcode itself. This will be registered in your theme’s `functions.php` file or, preferably, within a custom plugin.

Registering the Shortcode

<?php
/**
 * Plugin Name: B2B Pricing Grid
 * Description: Provides a secure shortcode for B2B pricing grids.
 * Version: 1.0
 * Author: Antigravity
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

// Register the shortcode.
add_shortcode( 'b2b_pricing_grid', 'b2b_pricing_grid_shortcode_handler' );

/**
 * Shortcode handler function.
 *
 * @param array $atts Shortcode attributes.
 * @return string HTML output.
 */
function b2b_pricing_grid_shortcode_handler( $atts ) {
    // Enqueue scripts and styles for AJAX.
    wp_enqueue_script( 'b2b-pricing-ajax', plugin_dir_url( __FILE__ ) . 'js/b2b-pricing.js', array( 'jquery' ), '1.0', true );

    // Localize script with AJAX URL and nonce.
    wp_localize_script( 'b2b-pricing-ajax', 'b2bPricingAjax', array(
        'ajax_url' => admin_url( 'admin-ajax.php' ),
        'nonce'    => wp_create_nonce( 'b2b_pricing_nonce' ),
    ) );

    // Basic HTML structure for the pricing grid container.
    // The actual data will be loaded via AJAX.
    return '<div id="b2b-pricing-grid-container" data-user-id="' . get_current_user_id() . '">Loading pricing...</div>';
}

// Hook into WordPress AJAX actions.
add_action( 'wp_ajax_get_b2b_pricing', 'handle_get_b2b_pricing_request' );
add_action( 'wp_ajax_nopriv_get_b2b_pricing', 'handle_get_b2b_pricing_request' ); // For logged-out users, though typically not needed for B2B.

/**
 * AJAX handler for fetching pricing data.
 */
function handle_get_b2b_pricing_request() {
    // Verify nonce for security.
    check_ajax_referer( 'b2b_pricing_nonce', 'nonce' );

    // Ensure user is logged in.
    if ( ! is_user_logged_in() ) {
        wp_send_json_error( array( 'message' => 'You must be logged in to view pricing.' ), 403 );
    }

    $user_id = get_current_user_id();
    $user_roles = wp_get_current_user()->roles;

    // --- Pricing Logic Goes Here ---
    // This is where you'd fetch pricing based on user role or custom meta.
    // For demonstration, we'll return a simple structure.
    $pricing_data = get_user_specific_pricing( $user_id, $user_roles );

    if ( $pricing_data ) {
        wp_send_json_success( $pricing_data );
    } else {
        wp_send_json_error( array( 'message' => 'Could not retrieve pricing information.' ), 500 );
    }
}

/**
 * Placeholder function to fetch user-specific pricing.
 * In a real-world scenario, this would query a custom database table,
 * user meta, or a dedicated pricing service.
 *
 * @param int   $user_id    The current user's ID.
 * @param array $user_roles The current user's roles.
 * @return array|false Pricing data or false if not found.
 */
function get_user_specific_pricing( $user_id, $user_roles ) {
    // Example: Fetch pricing based on a specific role, e.g., 'wholesale_customer'.
    if ( in_array( 'wholesale_customer', $user_roles, true ) ) {
        // Simulate fetching data for wholesale customers.
        // This could come from user meta: get_user_meta( $user_id, 'wholesale_pricing_tier', true );
        // Or a custom table lookup.
        return array(
            'tier' => 'Wholesale',
            'items' => array(
                array( 'sku' => 'PROD-A', 'name' => 'Product Alpha', 'price' => 10.50, 'discount' => '5%' ),
                array( 'sku' => 'PROD-B', 'name' => 'Product Beta', 'price' => 25.00, 'discount' => '10%' ),
            ),
        );
    } elseif ( in_array( 'premium_partner', $user_roles, true ) ) {
        // Simulate data for premium partners.
        return array(
            'tier' => 'Premium Partner',
            'items' => array(
                array( 'sku' => 'PROD-A', 'name' => 'Product Alpha', 'price' => 9.00, 'discount' => '15%' ),
                array( 'sku' => 'PROD-B', 'name' => 'Product Beta', 'price' => 22.00, 'discount' => '12%' ),
                array( 'sku' => 'PROD-C', 'name' => 'Product Gamma', 'price' => 50.00, 'discount' => '20%' ),
            ),
        );
    } else {
        // Default or standard pricing for other logged-in users.
        // This could be a general price list.
        return array(
            'tier' => 'Standard',
            'items' => array(
                array( 'sku' => 'PROD-A', 'name' => 'Product Alpha', 'price' => 12.00 ),
                array( 'sku' => 'PROD-B', 'name' => 'Product Beta', 'price' => 30.00 ),
            ),
        );
    }
    return false; // No specific pricing found.
}
?>

The `b2b_pricing_grid_shortcode_handler` function does the heavy lifting:

  • It enqueues a JavaScript file (`js/b2b-pricing.js`) responsible for making the AJAX request.
  • It uses `wp_localize_script` to pass the AJAX URL and a security nonce to the JavaScript. This nonce is crucial for verifying the request’s origin on the server.
  • It renders a placeholder `div` with an ID `b2b-pricing-grid-container` and a `data-user-id` attribute. This `div` will be populated by JavaScript once the pricing data is fetched.
  • It hooks into WordPress’s AJAX actions (`wp_ajax_get_b2b_pricing` and `wp_ajax_nopriv_get_b2b_pricing`). The `nopriv` hook is generally not needed for B2B scenarios where users must be logged in, but it’s included for completeness.
  • The `handle_get_b2b_pricing_request` function is the AJAX endpoint handler. It verifies the nonce, checks if the user is logged in, retrieves the user’s roles, and then calls a (placeholder) function `get_user_specific_pricing` to fetch the relevant data.
  • Finally, it sends the pricing data back to the client as a JSON response using `wp_send_json_success` or an error message with `wp_send_json_error`.

Client-Side JavaScript for AJAX Interaction

The accompanying JavaScript file (`js/b2b-pricing.js`) will handle the AJAX call and update the DOM.

jQuery(document).ready(function($) {
    var pricingContainer = $('#b2b-pricing-grid-container');

    if (pricingContainer.length && typeof b2bPricingAjax !== 'undefined') {
        // Prepare data for AJAX request.
        var data = {
            action: 'get_b2b_pricing', // Corresponds to wp_ajax_get_b2b_pricing hook.
            nonce: b2bPricingAjax.nonce,
            user_id: pricingContainer.data('user-id') // Optional, if needed server-side.
        };

        // Make the AJAX request.
        $.post(b2bPricingAjax.ajax_url, data, function(response) {
            if (response.success) {
                // Render the pricing grid.
                renderPricingGrid(response.data);
            } else {
                // Display error message.
                pricingContainer.html('<p class="error">' + (response.data.message || 'An unknown error occurred.') + '</p>');
            }
        }).fail(function(xhr, status, error) {
            // Handle network or server errors.
            console.error("AJAX Error: ", status, error);
            pricingContainer.html('<p class="error">Failed to load pricing. Please try again later.</p>');
        });
    }

    /**
     * Renders the pricing grid HTML.
     * @param {object} pricingData - The pricing data object received from the server.
     */
    function renderPricingGrid(pricingData) {
        var html = '<h3>Pricing Tier: ' + (pricingData.tier || 'Standard') + '</h3>';
        html += '<table class="b2b-pricing-table">';
        html += '<thead><tr><th>SKU</th><th>Product Name</th><th>Price</th><th>Discount</th></tr></thead>';
        html += '<tbody>';

        if (pricingData.items && pricingData.items.length > 0) {
            pricingData.items.forEach(function(item) {
                html += '<tr>';
                html += '<td>' + item.sku + '</td>';
                html += '<td>' + item.name + '</td>';
                html += '<td>' + formatCurrency(item.price) + '</td>'; // Assuming a formatCurrency function exists or will be added.
                html += '<td>' + (item.discount || '-') + '</td>';
                html += '</tr>';
            });
        } else {
            html += '<tr><td colspan="4">No pricing details available for this tier.</td></tr>';
        }

        html += '</tbody></table>';

        pricingContainer.html(html);
    }

    /**
     * Basic currency formatting function (can be expanded).
     * @param {number} amount - The amount to format.
     * @returns {string} Formatted currency string.
     */
    function formatCurrency(amount) {
        // Example: Use Intl.NumberFormat for robust formatting.
        return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
    }
});

This JavaScript:

  • Waits for the DOM to be ready.
  • Finds the pricing container.
  • Constructs the AJAX data payload, including the `action` (which maps to the `wp_ajax_` hook) and the `nonce`.
  • Uses jQuery’s `$.post` to send the request to `admin-ajax.php`.
  • On success, it calls `renderPricingGrid` to populate the container with the fetched data.
  • On failure, it displays an appropriate error message.
  • The `renderPricingGrid` function dynamically builds an HTML table from the JSON data.
  • A basic `formatCurrency` function is included for presentation.

Advanced Role and Capability Management

For more granular control, especially in complex B2B scenarios, relying solely on default WordPress roles might be insufficient. You might need to create custom roles and assign specific capabilities.

Creating Custom Roles and Capabilities

You can use a plugin like “Members” or “User Role Editor,” or programmatically add roles and capabilities. Here’s a programmatic example:

<?php
// Add custom roles on plugin activation.
register_activation_hook( __FILE__, 'b2b_pricing_activate' );
function b2b_pricing_activate() {
    // Add 'Wholesale Customer' role.
    add_role(
        'wholesale_customer',
        __( 'Wholesale Customer' ),
        array(
            'read' => true, // Basic read access.
            'view_b2b_pricing' => true, // Custom capability.
        )
    );

    // Add 'Premium Partner' role.
    add_role(
        'premium_partner',
        __( 'Premium Partner' ),
        array(
            'read' => true,
            'view_b2b_pricing' => true,
            'access_special_offers' => true, // Another custom capability.
        )
    );
}

// Remove custom roles on plugin deactivation.
register_deactivation_hook( __FILE__, 'b2b_pricing_deactivate' );
function b2b_pricing_deactivate() {
    remove_role( 'wholesale_customer' );
    remove_role( 'premium_partner' );
}

// Add custom capabilities to existing roles if needed.
function add_custom_capabilities_to_roles() {
    $admin_role = get_role( 'administrator' );
    if ( $admin_role && ! $admin_role->has_cap( 'view_b2b_pricing' ) ) {
        $admin_role->add_cap( 'view_b2b_pricing' );
    }
    // Add other capabilities to other roles as needed.
}
add_action( 'admin_init', 'add_custom_capabilities_to_roles' );
?>

With these custom capabilities, you can refine the `get_user_specific_pricing` function:

/**
 * Fetches user-specific pricing based on custom capabilities.
 *
 * @param int $user_id The current user's ID.
 * @return array|false Pricing data or false if not found.
 */
function get_user_specific_pricing( $user_id ) {
    $user = wp_get_current_user();

    // Check for custom capabilities first.
    if ( user_can( $user_id, 'view_b2b_pricing' ) ) {
        // Further refine based on specific roles or user meta if needed.
        if ( user_can( $user_id, 'premium_partner' ) ) { // Check if they have the 'premium_partner' role.
            return array(
                'tier' => 'Premium Partner',
                'items' => array(
                    array( 'sku' => 'PROD-A', 'name' => 'Product Alpha', 'price' => 9.00, 'discount' => '15%' ),
                    array( 'sku' => 'PROD-B', 'name' => 'Product Beta', 'price' => 22.00, 'discount' => '12%' ),
                    array( 'sku' => 'PROD-C', 'name' => 'Product Gamma', 'price' => 50.00, 'discount' => '20%' ),
                ),
            );
        } elseif ( user_can( $user_id, 'wholesale_customer' ) ) { // Check if they have the 'wholesale_customer' role.
            return array(
                'tier' => 'Wholesale',
                'items' => array(
                    array( 'sku' => 'PROD-A', 'name' => 'Product Alpha', 'price' => 10.50, 'discount' => '5%' ),
                    array( 'sku' => 'PROD-B', 'name' => 'Product Beta', 'price' => 25.00, 'discount' => '10%' ),
                ),
            );
        }
        // If they have 'view_b2b_pricing' but not a specific role, fall back to a general B2B price.
        // This could be a default B2B price list.
        return array(
            'tier' => 'General B2B',
            'items' => array(
                array( 'sku' => 'PROD-A', 'name' => 'Product Alpha', 'price' => 11.00 ),
                array( 'sku' => 'PROD-B', 'name' => 'Product Beta', 'price' => 28.00 ),
            ),
        );
    }

    // If the user doesn't have the capability, return false or a public price.
    // For this example, we'll return false, indicating no special pricing.
    return false;
}

By using `user_can()`, you decouple the pricing logic from specific role names, making it more adaptable. You can also store per-user pricing tiers in user meta and fetch that data within this function, providing even more customization.

Securing the Endpoint and Data

Security is paramount. The current implementation includes:

  • Nonce Verification: `check_ajax_referer()` ensures that the AJAX request originates from your WordPress site and hasn’t been tampered with.
  • User Authentication: `is_user_logged_in()` and `wp_get_current_user()` ensure that only authenticated users can access pricing data.
  • Capability Checks: Using `user_can()` within the pricing logic restricts access to sensitive pricing information based on defined capabilities.

For enhanced security, consider:

  • Rate Limiting: Implement rate limiting on `admin-ajax.php` to prevent brute-force attacks or excessive requests. This is typically done at the web server level (e.g., Nginx) or via a security plugin.
  • HTTPS: Always use HTTPS to encrypt data in transit.
  • Input Validation: While not heavily used in this specific example, always sanitize and validate any user-provided input if your shortcode or AJAX handler were to accept parameters.
  • Least Privilege: Ensure that the roles and capabilities assigned only grant the necessary permissions. Avoid giving broad capabilities like `edit_posts` if the user only needs to view pricing.

Data Storage Strategies

The `get_user_specific_pricing` function is a placeholder. In a production environment, you’ll need a robust way to store and retrieve pricing data. Options include:

  • User Meta: Store pricing tier IDs or specific pricing rules directly in the `wp_usermeta` table. This is suitable for a moderate number of users and pricing variations.
  • Custom Database Tables: For very large datasets or complex pricing matrices, create custom tables (e.g., `wp_b2b_pricing_rules`, `wp_b2b_customer_pricing`). This offers the best performance and scalability. You would use WordPress’s `$wpdb` object to interact with these tables.
  • External API/Service: If your pricing is managed by a separate system (e.g., an ERP or CRM), your `get_user_specific_pricing` function would make an API call to that service. Ensure secure authentication (e.g., API keys, OAuth) for this communication.
  • Product-Specific Pricing (WooCommerce Integration): If using WooCommerce, you can leverage its extensive product and pricing APIs, potentially storing custom pricing in product meta or using dedicated B2B plugins that extend WooCommerce’s functionality. Your `get_user_specific_pricing` function would then query WooCommerce data.

Conclusion

By combining the WordPress Shortcode API with AJAX, custom roles, and robust security practices, you can build highly customized and secure B2B pricing grids. This approach offers flexibility, allowing you to tailor pricing logic to specific customer segments and business requirements, all within the familiar WordPress ecosystem.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins
  • How to implement native Redis caching layers for high-volume custom taxonomy queries in Carbon Fields custom wrappers
  • Building secure B2B pricing grids with custom REST API Controllers endpoints and role overrides

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (48)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (182)
  • WordPress Plugin Development (197)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala