• 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 WP HTTP API endpoints and role overrides

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

Securing B2B Pricing Grids: Custom WP HTTP API Endpoints and Role Overrides

Implementing dynamic, role-based pricing grids for B2B clients within WordPress requires a robust and secure approach. Relying solely on frontend JavaScript to hide/show prices is a significant security vulnerability. This post details a production-ready strategy using custom WordPress HTTP API endpoints and advanced user role management to serve pricing data securely.

Leveraging the WP HTTP API for Secure Data Retrieval

The WordPress REST API, particularly when extended with custom endpoints, provides a powerful mechanism for serving data. For pricing grids, we’ll create a dedicated endpoint that validates user permissions before returning sensitive pricing information. This ensures that only authenticated and authorized users can access B2B-specific pricing.

Registering a Custom REST API Endpoint

We’ll use the `register_rest_route` function within a custom plugin or theme’s `functions.php` file. This function allows us to define a new endpoint, specify its callback function, and set access permissions.

Example: `b2b-pricing/v1/products` Endpoint

This endpoint will fetch product pricing data, filtered by the current user’s role or assigned customer group.

<?php
/**
 * Register custom REST API endpoint for B2B pricing.
 */
add_action( 'rest_api_init', function () {
    register_rest_route( 'b2b-pricing/v1', '/products', array(
        'methods'  => WP_REST_Server::READABLE, // GET method
        'callback' => 'get_b2b_pricing_data',
        'permission_callback' => 'get_b2b_pricing_permissions',
    ) );
} );

/**
 * Callback function to retrieve B2B pricing data.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 */
function get_b2b_pricing_data( WP_REST_Request $request ) {
    // In a real-world scenario, fetch this data from a custom table,
    // WooCommerce products, or a dedicated pricing plugin's data store.
    // For demonstration, we'll use a mock array.

    $all_products_pricing = array(
        'SKU001' => array(
            'name' => 'Premium Widget',
            'base_price' => 100.00,
            'b2b_tier_1' => 90.00, // e.g., for customers in group A
            'b2b_tier_2' => 85.00, // e.g., for customers in group B
        ),
        'SKU002' => array(
            'name' => 'Standard Gadget',
            'base_price' => 50.00,
            'b2b_tier_1' => 45.00,
            'b2b_tier_2' => 40.00,
        ),
        // ... more products
    );

    $current_user_id = get_current_user_id();
    $user_pricing_data = array();

    // Determine user's pricing tier or specific pricing.
    // This logic needs to be robust and based on your B2B customer structure.
    if ( user_can( $current_user_id, 'manage_options' ) ) { // Example: Admin role gets highest tier
        foreach ( $all_products_pricing as $sku => $product_data ) {
            $user_pricing_data[$sku] = array(
                'name' => $product_data['name'],
                'price' => $product_data['b2b_tier_2'], // Highest discount tier
            );
        }
    } elseif ( is_user_logged_in() ) {
        // Example: Check for custom meta or group association
        $customer_group = get_user_meta( $current_user_id, 'b2b_customer_group', true );

        if ( 'group_a' === $customer_group ) {
            foreach ( $all_products_pricing as $sku => $product_data ) {
                $user_pricing_data[$sku] = array(
                    'name' => $product_data['name'],
                    'price' => $product_data['b2b_tier_1'],
                );
            }
        } else {
            // Default B2B pricing or fallback to public pricing if applicable
            foreach ( $all_products_pricing as $sku => $product_data ) {
                $user_pricing_data[$sku] = array(
                    'name' => $product_data['name'],
                    'price' => $product_data['base_price'], // Fallback to base price
                );
            }
        }
    } else {
        // Not logged in, return public pricing or an empty array
        // For this example, we'll return an empty array to enforce login for pricing.
        return new WP_Error( 'rest_not_logged_in', 'You must be logged in to view pricing.', array( 'status' => 401 ) );
    }

    return new WP_REST_Response( $user_pricing_data, 200 );
}

/**
 * Permission callback for the B2B pricing endpoint.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return bool|WP_Error True if the request has access, WP_Error object otherwise.
 */
function get_b2b_pricing_permissions( WP_REST_Request $request ) {
    // Check if the user is logged in.
    if ( ! is_user_logged_in() ) {
        return new WP_Error( 'rest_not_logged_in', 'You must be logged in to view pricing.', array( 'status' => 401 ) );
    }

    // Further role-based checks can be added here.
    // For example, if you have a specific 'B2B Customer' role:
    // if ( ! current_user_can( 'b2b_customer_role' ) ) {
    //     return new WP_Error( 'rest_forbidden', 'You do not have permission to view B2B pricing.', array( 'status' => 403 ) );
    // }

    // For this example, we'll allow any logged-in user to access,
    // but the callback function will determine the specific pricing tier.
    // More granular control is recommended for production.
    return true;
}
?>

Understanding the Code

  • `add_action( ‘rest_api_init’, … )`: Hooks into WordPress to register our custom route.
  • `register_rest_route( ‘b2b-pricing/v1’, ‘/products’, … )`: Defines the endpoint namespace (`b2b-pricing/v1`) and path (`/products`).
  • `’methods’ => WP_REST_Server::READABLE`: Specifies that this endpoint responds to GET requests.
  • `’callback’ => ‘get_b2b_pricing_data’`: The function that will execute when the endpoint is hit.
  • `’permission_callback’ => ‘get_b2b_pricing_permissions’`: A crucial function to determine if the current user is allowed to access this endpoint at all.
  • `get_b2b_pricing_data( WP_REST_Request $request )`: This function contains the core logic for fetching and filtering pricing data based on user roles, meta, or custom group assignments. It returns a `WP_REST_Response` object or a `WP_Error`.
  • `get_b2b_pricing_permissions( WP_REST_Request $request )`: This function performs initial checks. If it returns `true`, the callback is executed. If it returns a `WP_Error`, the request is terminated with that error.

Implementing Role and Capability Overrides

WordPress’s built-in role and capability system is fundamental. For B2B scenarios, you might need more granular control than standard roles like ‘Administrator’ or ‘Editor’ offer. This can involve creating custom roles or leveraging user meta to define customer groups, each with specific pricing tiers.

Custom Roles and Capabilities

You can define custom roles and capabilities using the `add_role()` function. This is typically done once during plugin activation.

<?php
/**
 * Plugin activation hook.
 * Registers custom roles.
 */
register_activation_hook( __FILE__, 'my_b2b_plugin_activate' );

function my_b2b_plugin_activate() {
    // Add a custom role for B2B customers with specific pricing access.
    add_role(
        'b2b_customer_tier_1',
        __( 'B2B Customer (Tier 1)', 'my-b2b-plugin' ),
        array(
            'read' => true,  // Basic read access
            'edit_posts' => false, // Prevent editing posts
            'upload_files' => false, // Prevent uploading files
            'view_b2b_pricing' => true, // Custom capability
        )
    );

    // Add another tier
    add_role(
        'b2b_customer_tier_2',
        __( 'B2B Customer (Tier 2)', 'my-b2b-plugin' ),
        array(
            'read' => true,
            'edit_posts' => false,
            'upload_files' => false,
            'view_b2b_pricing' => true, // Custom capability
        )
    );

    // Add the custom capability to existing roles if needed, e.g., Administrators
    $admin_role = get_role( 'administrator' );
    if ( $admin_role ) {
        $admin_role->add_cap( 'view_b2b_pricing' );
    }
}

/**
 * Plugin deactivation hook.
 * Removes custom roles.
 */
register_deactivation_hook( __FILE__, 'my_b2b_plugin_deactivate' );

function my_b2b_plugin_deactivate() {
    remove_role( 'b2b_customer_tier_1' );
    remove_role( 'b2b_customer_tier_2' );

    // Remove custom capability from roles if necessary
    $admin_role = get_role( 'administrator' );
    if ( $admin_role ) {
        $admin_role->remove_cap( 'view_b2b_pricing' );
    }
}
?>

Using User Meta for Customer Groups

For more dynamic assignments or when you don’t want to create a multitude of roles, user meta is an excellent alternative. You can store a ‘customer_group’ or ‘pricing_tier’ value in the user’s profile.

In the `get_b2b_pricing_data` callback, we already demonstrated fetching this meta: $customer_group = get_user_meta( $current_user_id, 'b2b_customer_group', true );. You would typically manage this meta data via the user profile edit screen (using `show_user_profile` and `edit_user_profile` actions) or through an admin interface for managing B2B clients.

Updating the Permission Callback

The `get_b2b_pricing_permissions` function can be enhanced to check these custom roles or meta values:

/**
 * Enhanced permission callback for the B2B pricing endpoint.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return bool|WP_Error True if the request has access, WP_Error object otherwise.
 */
function get_b2b_pricing_permissions( WP_REST_Request $request ) {
    if ( ! is_user_logged_in() ) {
        return new WP_Error( 'rest_not_logged_in', 'You must be logged in to view pricing.', array( 'status' => 401 ) );
    }

    $current_user_id = get_current_user_id();

    // Option 1: Check for custom capability
    if ( ! user_can( $current_user_id, 'view_b2b_pricing' ) ) {
        return new WP_Error( 'rest_forbidden', 'You do not have permission to view B2B pricing.', array( 'status' => 403 ) );
    }

    // Option 2: Check for specific roles (if you created them)
    // $user = wp_get_current_user();
    // $allowed_roles = array( 'b2b_customer_tier_1', 'b2b_customer_tier_2', 'administrator' );
    // if ( array_intersect( $allowed_roles, $user->roles ) ) {
    //     return true; // User has one of the allowed roles
    // } else {
    //     return new WP_Error( 'rest_forbidden', 'You do not have permission to view B2B pricing.', array( 'status' => 403 ) );
    // }

    // Option 3: Check user meta for customer group
    // $customer_group = get_user_meta( $current_user_id, 'b2b_customer_group', true );
    // if ( ! empty( $customer_group ) && in_array( $customer_group, array( 'group_a', 'group_b' ) ) ) {
    //     return true; // User belongs to an allowed group
    // } else {
    //     return new WP_Error( 'rest_forbidden', 'You do not have permission to view B2B pricing.', array( 'status' => 403 ) );
    // }

    // If any of the above checks pass, return true.
    // For this example, we'll assume the 'view_b2b_pricing' capability is sufficient.
    return true;
}

Frontend Integration and Security Considerations

On the frontend, JavaScript will be responsible for fetching data from your custom endpoint and rendering the pricing grid. Crucially, it should *never* contain the pricing logic itself.

Fetching Data with JavaScript

Use the `fetch` API or jQuery’s `$.ajax` to call your endpoint. Ensure you include appropriate authentication headers if using nonces or JWTs for more advanced security.

// Example using Fetch API
document.addEventListener('DOMContentLoaded', function() {
    const pricingGridContainer = document.getElementById('b2b-pricing-grid');
    if (!pricingGridContainer) {
        return; // Pricing grid element not found
    }

    // Get the nonce from the WordPress REST API settings
    // Ensure this is properly enqueued and localized in your theme/plugin
    const nonce = wpApiSettings.nonce; // Assuming wpApiSettings is localized

    fetch('/wp-json/b2b-pricing/v1/products', {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': nonce // Important for authentication
        }
    })
    .then(response => {
        if (!response.ok) {
            // Handle errors: unauthorized, forbidden, not found, etc.
            if (response.status === 401 || response.status === 403) {
                pricingGridContainer.innerHTML = '<p>Please log in or check your permissions to view pricing.</p>';
            } else {
                pricingGridContainer.innerHTML = '<p>Could not load pricing data. Please try again later.</p>';
            }
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        if (Object.keys(data).length === 0) {
            pricingGridContainer.innerHTML = '<p>No pricing information available for your account.</p>';
            return;
        }
        renderPricingGrid(data, pricingGridContainer);
    })
    .catch(error => {
        console.error('Error fetching B2B pricing:', error);
        // Error message already set in the .then block for !response.ok
    });
});

function renderPricingGrid(pricingData, container) {
    let html = '<table><thead><tr><th>Product</th><th>Price</th></tr></thead><tbody>';
    for (const sku in pricingData) {
        html += `<tr><td>${pricingData[sku].name} (${sku})</td><td>$${pricingData[sku].price.toFixed(2)}</td></tr>`;
    }
    html += '</tbody></table>';
    container.innerHTML = html;
}

Important: Ensure you enqueue your JavaScript file and localize the `wpApiSettings.nonce` correctly. This is typically done using `wp_localize_script`:

/**
 * Enqueue and localize script for B2B pricing frontend.
 */
add_action( 'wp_enqueue_scripts', function() {
    // Only enqueue on pages where B2B pricing is relevant
    if ( is_page('pricing') || is_user_logged_in() ) { // Adjust condition as needed
        wp_enqueue_script(
            'b2b-pricing-frontend',
            get_template_directory_uri() . '/js/b2b-pricing-frontend.js', // Path to your JS file
            array( 'jquery' ), // Dependencies
            '1.0.0',
            true // Load in footer
        );

        // Localize script to pass nonce and other data to JavaScript
        wp_localize_script(
            'b2b-pricing-frontend',
            'wpApiSettings',
            array(
                'nonce' => wp_create_nonce( 'wp_rest' ), // Standard nonce for REST API
                // Add other settings if needed, e.g., base API URL
            )
        );
    }
} );

Security Best Practices

  • Never expose pricing logic client-side. All price calculations and data retrieval must happen server-side via the API.
  • Use Nonces for Authentication. The `X-WP-Nonce` header is critical for verifying that the request originates from a legitimate, logged-in user.
  • Strict Permission Callbacks. The `permission_callback` should be as restrictive as possible. If a user doesn’t need to see pricing, they shouldn’t even be able to hit the callback function.
  • Sanitize and Validate. While less critical for GET requests returning data, always sanitize any input parameters if your endpoint were to accept them (e.g., for fetching specific product pricing).
  • Rate Limiting. For public-facing APIs, consider implementing rate limiting to prevent abuse. For internal B2B APIs, this might be less of a concern but still good practice.
  • HTTPS. Always serve your WordPress site over HTTPS to encrypt data in transit.

Conclusion

By combining custom WordPress REST API endpoints with robust role and capability management, you can build a secure and dynamic B2B pricing system. This approach centralizes pricing logic on the server, preventing unauthorized access and ensuring data integrity, which is paramount for sensitive business information.

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