• 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 Block Patterns API endpoints and role overrides

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

Leveraging WordPress REST API for Dynamic B2B Pricing Grids

Building a robust B2B pricing system within WordPress often necessitates dynamic, role-based pricing displays. While standard WordPress roles and capabilities offer a foundation, complex tiered pricing or client-specific discounts demand a more granular approach. This post details how to construct secure, custom pricing grids using the WordPress REST API, specifically by registering custom endpoints and implementing role-based access control overrides.

Registering Custom REST API Endpoints for Pricing Data

The WordPress REST API provides a powerful mechanism for exposing data and functionality. We’ll register a custom endpoint to serve pricing information, ensuring it’s only accessible under specific conditions. This involves hooking into the rest_api_init action.

Endpoint Registration Logic

The core of our endpoint registration will reside within a custom plugin. This keeps our logic organized and maintainable. We’ll define a route that accepts a product ID and potentially a customer ID (or relies on the current user’s role).

Example Plugin Structure

Assume a plugin file named b2b-pricing-api.php in wp-content/plugins/.

b2b-pricing-api.php – Endpoint Registration

<?php
/**
 * Plugin Name: B2B Pricing API
 * Description: Provides custom REST API endpoints for B2B pricing grids.
 * Version: 1.0
 * Author: Antigravity
 */

add_action( 'rest_api_init', function () {
    register_rest_route( 'b2b-pricing/v1', '/product/(?P<id>\d+)', array(
        'methods' => WP_REST_Server::READABLE,
        'callback' => 'get_b2b_product_pricing',
        'permission_callback' => 'get_b2b_product_pricing_permissions_check',
        'args' => array(
            'id' => array(
                'validate_callback' => function( $param, $request, $key ) {
                    return is_numeric( $param );
                }
            ),
        ),
    ) );
} );

/**
 * Callback function to retrieve B2B pricing for a product.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
 */
function get_b2b_product_pricing( WP_REST_Request $request ) {
    $product_id = $request['id'];
    $current_user = wp_get_current_user();

    // In a real-world scenario, you'd fetch pricing based on user roles,
    // custom user meta, or specific customer IDs associated with pricing tiers.
    // For this example, we'll simulate tiered pricing based on a hypothetical 'customer_tier' role.

    $pricing_data = array();

    if ( user_can( $current_user->ID, 'wholesale_customer' ) ) {
        // Pricing for wholesale customers
        $pricing_data['base_price'] = get_post_meta( $product_id, '_wholesale_price', true );
        $pricing_data['discount_percentage'] = get_post_meta( $product_id, '_wholesale_discount', true );
        $pricing_data['display_price'] = ! empty( $pricing_data['base_price'] ) ?
            $pricing_data['base_price'] * ( 1 - ( floatval( $pricing_data['discount_percentage'] ?? 0 ) / 100 ) ) :
            __( 'Contact for Price', 'b2b-pricing-api' );
    } elseif ( user_can( $current_user->ID, 'vip_customer' ) ) {
        // Pricing for VIP customers
        $pricing_data['base_price'] = get_post_meta( $product_id, '_vip_price', true );
        $pricing_data['special_offer'] = get_post_meta( $product_id, '_vip_offer', true );
        $pricing_data['display_price'] = ! empty( $pricing_data['base_price'] ) ?
            $pricing_data['base_price'] :
            __( 'Contact for Price', 'b2b-pricing-api' );
    } else {
        // Default public pricing or fallback
        $pricing_data['base_price'] = get_post_meta( $product_id, '_regular_price', true ); // Assuming WooCommerce or similar
        $pricing_data['display_price'] = ! empty( $pricing_data['base_price'] ) ?
            $pricing_data['base_price'] :
            __( 'Contact for Price', 'b2b-pricing-api' );
    }

    // Ensure we have some data to return
    if ( empty( $pricing_data ) ) {
        return new WP_Error( 'no_pricing_data', __( 'No pricing data found for this product and user role.', 'b2b-pricing-api' ), array( 'status' => 404 ) );
    }

    // Add product title for context
    $product_title = get_the_title( $product_id );
    if ( ! $product_title ) {
        return new WP_Error( 'invalid_product', __( 'Invalid product ID.', 'b2b-pricing-api' ), array( 'status' => 404 ) );
    }
    $pricing_data['product_name'] = $product_title;

    $response = new WP_REST_Response( $pricing_data );
    $response->set_status( 200 );

    return $response;
}

/**
 * Permission callback for the B2B pricing endpoint.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_Error|bool False on failure, true on success.
 */
function get_b2b_product_pricing_permissions_check( WP_REST_Request $request ) {
    // Check if the user is logged in. For B2B, this is usually a prerequisite.
    if ( ! is_user_logged_in() ) {
        return new WP_Error( 'rest_forbidden', __( 'You must be logged in to view pricing.', 'b2b-pricing-api' ), array( 'status' => 401 ) );
    }

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

    // If the user is logged in and has passed any other checks, allow access.
    return true;
}

// Optional: Add custom capabilities for finer-grained control
function add_custom_capabilities() {
    $roles = array( 'wholesale_customer', 'vip_customer' ); // Example roles
    foreach ( $roles as $role_name ) {
        $role = get_role( $role_name );
        if ( $role ) {
            // Add capability if it doesn't exist
            if ( ! $role->has_cap( 'view_b2b_pricing' ) ) {
                $role->add_cap( 'view_b2b_pricing' );
            }
        } else {
            // If role doesn't exist, create it (e.g., for new user registration)
            add_role( $role_name, ucfirst( $role_name ), array( 'read' => true, 'view_b2b_pricing' => true ) );
        }
    }
}
register_activation_hook( __FILE__, 'add_custom_capabilities' );

// Optional: Remove custom capabilities on deactivation
function remove_custom_capabilities() {
    $roles = array( 'wholesale_customer', 'vip_customer' );
    foreach ( $roles as $role_name ) {
        $role = get_role( $role_name );
        if ( $role ) {
            $role->remove_cap( 'view_b2b_pricing' );
        }
    }
    // Optionally remove the roles themselves if they were created by the plugin
    // remove_role( 'wholesale_customer' );
    // remove_role( 'vip_customer' );
}
register_deactivation_hook( __FILE__, 'remove_custom_capabilities' );
?>

In this code:

  • We hook into rest_api_init to register our route.
  • The route /b2b-pricing/v1/product/(?P<id>\d+) is defined, accepting a product ID.
  • WP_REST_Server::READABLE specifies that this endpoint responds to GET requests.
  • get_b2b_product_pricing is the callback function that fetches and formats the pricing data.
  • get_b2b_product_pricing_permissions_check handles access control.
  • We use get_post_meta to retrieve custom pricing fields. In a real e-commerce setup (like WooCommerce), these would be product meta fields.
  • The example simulates different pricing tiers based on hypothetical roles like wholesale_customer and vip_customer.
  • The add_custom_capabilities and remove_custom_capabilities functions demonstrate how to programmatically add roles and capabilities upon plugin activation/deactivation, which is crucial for managing user permissions effectively.

Implementing Role-Based Access Control Overrides

The permission_callback is the gatekeeper for our API endpoint. It’s essential to implement robust checks here to ensure only authorized users can access sensitive pricing information. We’ll go beyond basic logged-in checks to incorporate role-specific permissions.

Advanced Permission Logic

The get_b2b_product_pricing_permissions_check function is where the magic happens. We can leverage WordPress’s built-in user roles and capabilities, or define custom ones.

Example Permission Check

/**
 * Permission callback for the B2B pricing endpoint.
 *
 * @param WP_REST_Request $request Full data about the request.
 * @return WP_Error|bool False on failure, true on success.
 */
function get_b2b_product_pricing_permissions_check( WP_REST_Request $request ) {
    // 1. Basic logged-in check
    if ( ! is_user_logged_in() ) {
        return new WP_Error( 'rest_forbidden', __( 'You must be logged in to view pricing.', 'b2b-pricing-api' ), array( 'status' => 401 ) );
    }

    $current_user_id = get_current_user_id();

    // 2. Role-based checks (using hypothetical roles)
    if ( user_can( $current_user_id, 'wholesale_customer' ) ) {
        // User is a wholesale customer, grant access.
        return true;
    }

    if ( user_can( $current_user_id, 'vip_customer' ) ) {
        // User is a VIP customer, grant access.
        return true;
    }

    // 3. Custom capability check (if you've defined a general B2B access capability)
    // if ( current_user_can( 'access_b2b_pricing' ) ) {
    //     return true;
    // }

    // 4. Fallback: If none of the above conditions are met, deny access.
    return new WP_Error( 'rest_forbidden', __( 'You do not have permission to view B2B pricing.', 'b2b-pricing-api' ), array( 'status' => 403 ) );
}

This enhanced permission check:

  • First, verifies if the user is logged in.
  • Then, it checks for specific roles (wholesale_customer, vip_customer). If the user possesses any of these roles, access is granted.
  • A commented-out section shows how to implement a more general capability like access_b2b_pricing, which could be assigned to multiple roles or specific users.
  • If none of the authorized conditions are met, a 403 Forbidden error is returned.

Integrating with Frontend for Dynamic Pricing Display

Once the API endpoint is functional, the next step is to consume it from the frontend. This typically involves JavaScript to fetch data and dynamically update pricing on product pages or custom pricing grid pages.

JavaScript Fetch Example

This example uses the native fetch API. Ensure you enqueue this script properly within your WordPress theme or plugin.

document.addEventListener('DOMContentLoaded', function() {
    const productId = document.querySelector('[data-product-id]')?.dataset.productId; // Assuming product ID is in a data attribute

    if (!productId) {
        console.error('Product ID not found.');
        return;
    }

    // Construct the API URL. Ensure your site URL is correctly handled.
    const apiUrl = `${window.location.origin}/wp-json/b2b-pricing/v1/product/${productId}`;

    fetch(apiUrl, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': window.wpApiSettings ? window.wpApiSettings.nonce : '' // Important for authenticated requests if needed
        }
    })
    .then(response => {
        if (!response.ok) {
            // Handle non-2xx responses
            if (response.status === 401) {
                return Promise.reject('Authentication required. Please log in.');
            } else if (response.status === 403) {
                return Promise.reject('You do not have permission to view this pricing.');
            } else {
                return response.json().then(err => Promise.reject(err.message || `HTTP error! status: ${response.status}`));
            }
        }
        return response.json();
    })
    .then(data => {
        console.log('Pricing data received:', data);
        // Update the DOM with pricing information
        updatePricingDisplay(data);
    })
    .catch(error => {
        console.error('Error fetching pricing:', error);
        // Display an error message to the user
        displayPricingError(error);
    });
});

function updatePricingDisplay(pricingData) {
    const pricingContainer = document.getElementById('b2b-pricing-display'); // Assume an element with this ID exists
    if (!pricingContainer) return;

    let html = `

${pricingData.product_name || 'Pricing Information'}

`; if (pricingData.display_price) { html += `

Price: ${pricingData.display_price}

`; } if (pricingData.discount_percentage) { html += `

Wholesale Discount: ${pricingData.discount_percentage}%

`; } if (pricingData.special_offer) { html += `

Special Offer: ${pricingData.special_offer}

`; } // Add more fields as needed pricingContainer.innerHTML = html; } function displayPricingError(errorMessage) { const pricingContainer = document.getElementById('b2b-pricing-display'); if (!pricingContainer) return; pricingContainer.innerHTML = `

Error: ${errorMessage}

`; }

Key considerations for the frontend integration:

  • The script fetches data from the registered API endpoint using the product ID.
  • It includes basic error handling for different HTTP status codes (401, 403).
  • A placeholder updatePricingDisplay function shows where you would inject the fetched pricing data into your HTML.
  • The X-WP-Nonce header is crucial if your API endpoint requires authentication beyond just being logged in (e.g., if you were performing actions, not just reading data). For read operations on public endpoints, it might be optional but good practice.

Security Best Practices and Considerations

Securing B2B pricing is paramount. Beyond the role checks, consider these points:

Nonce Verification

While our current endpoint is read-only and relies on user roles, for any endpoint that modifies data (e.g., updating pricing rules), nonces are absolutely essential. WordPress automatically generates nonces for logged-in users, and they can be passed via JavaScript. The REST API validates these nonces automatically for authenticated requests.

HTTPS Enforcement

Always ensure your WordPress site is served over HTTPS. This encrypts data in transit, protecting pricing information from being intercepted, especially on public networks.

Rate Limiting

For public-facing APIs, implementing rate limiting (e.g., using Nginx or a WordPress plugin) can prevent abuse and denial-of-service attacks. While B2B pricing might be less of a target, it’s a good general security practice.

Data Sanitization and Validation

Always sanitize any data received from the client (even if it’s just the product ID in the URL) and validate it rigorously. The example uses is_numeric, but for more complex data, use functions like sanitize_text_field, absint, etc., and ensure your args array in register_rest_route is properly configured.

Custom Roles and Capabilities Management

For a production environment, consider a more robust system for managing custom roles and capabilities. Plugins like “Members” or “User Role Editor” can simplify this, or you can build your own management interface. The activation/deactivation hooks provide a programmatic way to set these up initially.

Conclusion

By leveraging the WordPress REST API with custom endpoints and carefully crafted permission callbacks, you can build sophisticated, secure, and dynamic B2B pricing grids. This approach offers flexibility to cater to complex business requirements while maintaining a secure and manageable WordPress environment. Remember to tailor the pricing logic and permission checks precisely to your specific B2B sales strategy.

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