• 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 Rewrite API custom endpoints endpoints and role overrides

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

Leveraging WordPress REST API for Dynamic B2B Pricing Grids

Building a robust B2B pricing system within WordPress often necessitates dynamic adjustments based on user roles, custom capabilities, or even specific customer groups. While standard WordPress roles offer a baseline, true B2B scenarios demand granular control. This post details how to construct a secure and flexible pricing grid system by extending the WordPress REST API with custom endpoints and implementing sophisticated role overrides.

Custom REST API Endpoints for Pricing Data

The WordPress REST API provides a powerful foundation for exposing data. We’ll create a custom endpoint to serve pricing information, ensuring it’s accessible programmatically and can be consumed by front-end JavaScript or external applications. This endpoint will be designed to accept parameters for filtering and customization.

First, we register a new namespace and route within our custom plugin. This keeps our API extensions organized and avoids conflicts with core WordPress routes or other plugins.

Registering the Pricing Endpoint

In your plugin’s main PHP file or an included API-specific file, add the following code:

/**
 * Register custom REST API routes.
 */
function my_b2b_pricing_register_routes() {
    // Register a new route for pricing data.
    register_rest_route( 'my-b2b-pricing/v1', '/products/(?P<id>\d+)', array(
        'methods'             => WP_REST_Server::READABLE, // GET request
        'callback'            => 'my_b2b_pricing_get_product_price',
        'permission_callback' => 'my_b2b_pricing_permissions_check',
        'args'                => array(
            'id' => array(
                'validate_callback' => function($param, $request, $key) {
                    return is_numeric( $param );
                },
                'required'          => true,
            ),
            'currency' => array(
                'description'       => __( 'Optional currency code (e.g., USD, EUR). Defaults to site default.', 'my-b2b-pricing' ),
                'type'              => 'string',
                'default'           => get_option( 'woocommerce_currency' ),
                'sanitize_callback' => 'sanitize_text_field',
                'validate_callback' => 'rest_validate_request_arg',
            ),
            'quantity' => array(
                'description'       => __( 'Optional quantity for tiered pricing.', 'my-b2b-pricing' ),
                'type'              => 'integer',
                'default'           => 1,
                'sanitize_callback' => 'absint',
                'validate_callback' => 'rest_validate_request_arg',
            ),
        ),
    ) );
}
add_action( 'rest_api_init', 'my_b2b_pricing_register_routes' );

The Callback Function: Fetching and Formatting Price Data

The `my_b2b_pricing_get_product_price` function will be responsible for retrieving the actual pricing data. This is where the core logic for role-based pricing will reside. For this example, we’ll assume product data is stored in post meta, possibly managed by WooCommerce or a custom product post type.

/**
 * Callback function to retrieve product pricing.
 *
 * @param WP_REST_Request $request Full data.
 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
 */
function my_b2b_pricing_get_product_price( WP_REST_Request $request ) {
    $product_id = $request['id'];
    $currency   = $request['currency'];
    $quantity   = $request['quantity'];

    // Basic validation: Ensure product exists.
    $product = wc_get_product( $product_id ); // Assuming WooCommerce for product object
    if ( ! $product ) {
        return new WP_Error( 'rest_not_found', __( 'Product not found.', 'my-b2b-pricing' ), array( 'status' => 404 ) );
    }

    // --- Core Logic: Determine Price based on User Role and Overrides ---
    $price = my_b2b_pricing_determine_price( $product_id, $currency, $quantity, $request->get_user() );

    if ( is_wp_error( $price ) ) {
        return $price; // Return error if price determination failed.
    }

    $data = array(
        'product_id' => $product_id,
        'currency'   => $currency,
        'quantity'   => $quantity,
        'price'      => $price,
        'formatted_price' => wc_price( $price, array( 'currency' => $currency ) ), // Format price using WooCommerce function
    );

    return new WP_REST_Response( $data, 200 );
}

Implementing Role-Based Pricing Logic

The heart of our B2B pricing system lies in the `my_b2b_pricing_determine_price` function. This function needs to inspect the current user’s roles and capabilities, and potentially apply custom pricing tiers or discounts. We’ll use WordPress’s built-in user and role management functions.

The `my_b2b_pricing_determine_price` Function

This function will fetch the base price and then apply modifications based on user context. We’ll store custom pricing rules in post meta for flexibility.

/**
 * Determines the price for a product based on user role, quantity, and custom rules.
 *
 * @param int $product_id The ID of the product.
 * @param string $currency The desired currency.
 * @param int $quantity The quantity of the product.
 * @param WP_User|null $user The current WP_User object, or null if not logged in.
 * @return float|WP_Error The determined price, or a WP_Error object on failure.
 */
function my_b2b_pricing_determine_price( $product_id, $currency, $quantity, $user = null ) {
    // 1. Get the base price.
    // This could be from post meta, WooCommerce price, or another source.
    // For simplicity, let's assume a meta key '_base_price'.
    $base_price = get_post_meta( $product_id, '_base_price', true );

    if ( ! is_numeric( $base_price ) || $base_price <= 0 ) {
        // Fallback or error if no base price is set.
        // You might want to return a default price or an error.
        return new WP_Error( 'price_error', __( 'Base price not set for this product.', 'my-b2b-pricing' ), array( 'status' => 500 ) );
    }

    $final_price = $base_price;

    // 2. Apply currency conversion if necessary.
    // This is a simplified example. Real-world scenarios might involve exchange rates.
    if ( $currency !== get_option( 'woocommerce_currency' ) ) {
        // Placeholder for currency conversion logic.
        // For now, we'll assume a fixed markup or simply return an error if not supported.
        // Example: return new WP_Error( 'currency_unsupported', __( 'Currency conversion not supported.', 'my-b2b-pricing' ) );
        // Or, apply a conversion rate:
        // $exchange_rate = get_option( 'my_b2b_pricing_exchange_rate_' . $currency );
        // $final_price = $final_price * $exchange_rate;
    }

    // 3. Apply quantity-based pricing (tiered pricing).
    // Assume meta keys like '_tier_qty_1', '_tier_price_1', etc.
    for ( $i = 1; $i <= 5; $i++ ) { // Example: up to 5 tiers
        $tier_qty = get_post_meta( $product_id, '_tier_qty_' . $i, true );
        $tier_price = get_post_meta( $product_id, '_tier_price_' . $i, true );

        if ( ! empty( $tier_qty ) && is_numeric( $tier_qty ) && ! empty( $tier_price ) && is_numeric( $tier_price ) ) {
            if ( $quantity >= intval( $tier_qty ) ) {
                $final_price = floatval( $tier_price );
            } else {
                // If quantity is less than current tier, we've found our price bracket.
                break;
            }
        }
    }

    // 4. Apply role-based overrides.
    if ( $user && $user->exists() ) {
        $user_roles = (array) $user->roles;

        // Check for specific role overrides (e.g., 'wholesale_customer', 'premium_partner').
        // Assume meta keys like '_role_price_wholesale_customer', '_role_discount_premium_partner'.
        foreach ( $user_roles as $role ) {
            $role_price_meta = '_role_price_' . $role;
            $role_discount_meta = '_role_discount_' . $role; // Discount as percentage

            if ( metadata_exists( $product_id, $role_price_meta ) ) {
                $role_specific_price = get_post_meta( $product_id, $role_price_meta, true );
                if ( is_numeric( $role_specific_price ) && $role_specific_price >= 0 ) {
                    $final_price = floatval( $role_specific_price );
                    // If a specific price is set for a role, it usually overrides other discounts.
                    // You might want to break here or continue if multiple roles can apply.
                    break;
                }
            } elseif ( metadata_exists( $product_id, $role_discount_meta ) ) {
                $role_discount_percentage = get_post_meta( $product_id, $role_discount_meta, true );
                if ( is_numeric( $role_discount_percentage ) && $role_discount_percentage > 0 ) {
                    $discount_amount = $final_price * ( floatval( $role_discount_percentage ) / 100 );
                    $final_price = $final_price - $discount_amount;
                }
            }
        }
    }

    // Ensure price doesn't go below zero.
    $final_price = max( 0, $final_price );

    return $final_price;
}

Customizing User Roles and Capabilities

For advanced B2B scenarios, you might need custom roles beyond the default WordPress roles. The User Role Editor plugin is excellent for this, or you can manage roles programmatically.

Example of programmatically adding a custom role:

/**
 * Add custom roles on plugin activation.
 */
function my_b2b_pricing_add_custom_roles() {
    // Add 'Wholesale Customer' role.
    add_role(
        'wholesale_customer',
        __( 'Wholesale Customer', 'my-b2b-pricing' ),
        array(
            'read'         => true,  // basic WordPress capability
            'edit_posts'   => false, // prevent editing posts
            'upload_files' => true,  // allow uploading files (e.g., for order forms)
            // Add other capabilities as needed
        )
    );

    // Add 'Premium Partner' role.
    add_role(
        'premium_partner',
        __( 'Premium Partner', 'my-b2b-pricing' ),
        array(
            'read' => true,
            // Potentially more capabilities for partners
        )
    );
}
register_activation_hook( __FILE__, 'my_b2b_pricing_add_custom_roles' );

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

Securing the API Endpoint with Permissions

It’s crucial to control who can access your pricing data. The `permission_callback` argument in `register_rest_route` is essential for this. We’ll create a function that checks if the current user has the necessary permissions.

The `my_b2b_pricing_permissions_check` Function

This function determines if the requesting user is allowed to access the endpoint. For B2B pricing, typically only logged-in users with specific roles should have access.

/**
 * Permission callback for the pricing endpoint.
 *
 * @param WP_REST_Request $request Full data.
 * @return bool|WP_Error True if the request has permission, WP_Error object otherwise.
 */
function my_b2b_pricing_permissions_check( WP_REST_Request $request ) {
    // Allow access only to logged-in users.
    if ( ! is_user_logged_in() ) {
        return new WP_Error( 'rest_forbidden', __( 'You must be logged in to view pricing.', 'my-b2b-pricing' ), array( 'status' => 401 ) );
    }

    $user = $request->get_user();
    $allowed_roles = array( 'administrator', 'editor', 'wholesale_customer', 'premium_partner' ); // Define roles that can access pricing

    // Check if the user has any of the allowed roles.
    $has_permission = false;
    foreach ( $user->roles as $role ) {
        if ( in_array( $role, $allowed_roles ) ) {
            $has_permission = true;
            break;
        }
    }

    if ( ! $has_permission ) {
        return new WP_Error( 'rest_forbidden', __( 'You do not have permission to view pricing.', 'my-b2b-pricing' ), array( 'status' => 403 ) );
    }

    // If all checks pass, return true.
    return true;
}

Integrating with the Frontend

Once the API is set up, you’ll want to consume it from your WordPress theme or a dedicated frontend JavaScript file. This typically involves using `fetch` or jQuery’s `$.ajax` to make requests to your custom endpoint.

Example JavaScript Fetch Request

This JavaScript snippet demonstrates how to fetch pricing data for a product. Ensure you’re making this request within a context where the user is logged in and has the necessary permissions.

document.addEventListener('DOMContentLoaded', function() {
    const productId = 123; // Replace with actual product ID
    const apiUrl = `/wp-json/my-b2b-pricing/v1/products/${productId}`;

    fetch(apiUrl, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            // WordPress REST API often requires nonce for authenticated requests
            // You'll need to pass a nonce generated server-side.
            // 'X-WP-Nonce': wpApiSettings.nonce // Example if using wp_localize_script
        }
    })
    .then(response => {
        if (!response.ok) {
            // Handle errors (e.g., 401, 403, 404)
            return response.json().then(err => { throw err; });
        }
        return response.json();
    })
    .then(data => {
        console.log('Pricing data:', data);
        // Update your pricing grid UI with data.price or data.formatted_price
        const priceElement = document.getElementById('product-price-' + productId);
        if (priceElement) {
            priceElement.textContent = data.formatted_price;
        }
    })
    .catch(error => {
        console.error('Error fetching pricing:', error);
        // Display an error message to the user
        const priceElement = document.getElementById('product-price-' + productId);
        if (priceElement) {
            priceElement.textContent = 'Error loading price';
        }
    });
});

Passing Nonces for Authentication

For authenticated requests from the frontend, you must include a WordPress nonce to verify the request’s origin and prevent CSRF attacks. You can pass this nonce to your JavaScript using `wp_localize_script`.

/**
 * Enqueue scripts and pass localized data.
 */
function my_b2b_pricing_enqueue_scripts() {
    // Enqueue your custom JavaScript file.
    wp_enqueue_script( 'my-b2b-pricing-script', plugin_dir_url( __FILE__ ) . 'js/frontend.js', array( 'jquery' ), '1.0', true );

    // Localize the script to pass data to JavaScript.
    wp_localize_script( 'my-b2b-pricing-script', 'wpApiSettings', array(
        'root' => esc_url_raw( rest_url() ), // REST API root URL
        'nonce' => wp_create_nonce( 'wp_rest' ), // Nonce for REST API requests
    ) );
}
add_action( 'wp_enqueue_scripts', 'my_b2b_pricing_enqueue_scripts' );

Then, in your JavaScript, you would access `wpApiSettings.nonce` and include it in your `fetch` headers.

// ... inside your fetch call ...
    fetch(apiUrl, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': wpApiSettings.nonce // Use the localized nonce
        }
    })
// ... rest of the fetch logic ...

Advanced Considerations and Best Practices

  • Caching: Implement server-side caching (e.g., Redis, object cache) for pricing data to reduce database load, especially for high-traffic sites. Be mindful of cache invalidation when prices change.
  • Data Storage: For very complex pricing structures, consider a dedicated database table or a more structured meta-data approach rather than relying solely on simple post meta.
  • Error Handling: Robust error handling on both the server (PHP) and client (JavaScript) is critical. Provide informative messages to users when pricing cannot be retrieved.
  • Security: Always sanitize and validate all incoming request parameters. Ensure your `permission_callback` is strict and only allows authorized users.
  • Performance: Optimize database queries within your callback functions. Avoid N+1 query problems.
  • User Experience: Clearly indicate to users when special pricing is applied due to their role or customer group.

By extending the WordPress REST API with custom endpoints and implementing granular permission checks and role-based logic, you can build a sophisticated and secure B2B pricing system tailored to your specific business needs.

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

  • How to build custom Timber Twig templating engines extensions utilizing modern Heartbeat API schemas
  • Building custom automated PDF financial reports and invoices for WooCommerce using FPDF customized scripts
  • Debugging Guide: Diagnosing database connection pool timeouts in multi-site network environments with modern tools
  • Reducing database query bloat in FSE Block Themes layouts using custom lazy loaders
  • Advanced Diagnostics: Locating slow Domain-driven architecture (DDD) blocks query bottlenecks in WooCommerce custom checkout pipelines

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 (38)
  • 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 (7)
  • WordPress Plugin Development (8)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • How to build custom Timber Twig templating engines extensions utilizing modern Heartbeat API schemas
  • Building custom automated PDF financial reports and invoices for WooCommerce using FPDF customized scripts
  • Debugging Guide: Diagnosing database connection pool timeouts in multi-site network environments with modern tools

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