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

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

Leveraging WordPress’s Filesystem API for Secure B2B Pricing Grids

For businesses operating on a Business-to-Business (B2B) model, dynamic and secure pricing is paramount. Unlike B2C, B2B pricing often involves tiered structures, volume discounts, and customer-specific rates. WordPress, while primarily a content management system, offers robust APIs that can be extended to handle such complex requirements. This post details how to build a secure B2B pricing grid system by creating custom Filesystem API endpoints and implementing granular role-based access control (RBAC) with role overrides.

Designing the Data Structure for Pricing Grids

A flexible pricing grid requires a structured approach to data storage. We’ll opt for storing pricing data in JSON files, managed via custom endpoints. This approach offers several advantages:

  • Decoupling: Separates pricing logic from the WordPress database, simplifying updates and backups.
  • Performance: JSON files are generally faster to read for structured data than complex database queries, especially when cached.
  • Version Control: JSON files can be easily managed with Git for versioning and auditing.
  • Portability: Easy to migrate or integrate with external systems.

Each B2B customer group or tier will have its own JSON file. The file structure might look like this, representing pricing for different product SKUs and quantities:

{
  "customer_tier": "premium",
  "currency": "USD",
  "products": {
    "SKU001": {
      "base_price": 100.00,
      "tiers": [
        {"min_quantity": 1, "price_per_unit": 100.00},
        {"min_quantity": 10, "price_per_unit": 95.00},
        {"min_quantity": 50, "price_per_unit": 90.00}
      ]
    },
    "SKU002": {
      "base_price": 250.00,
      "tiers": [
        {"min_quantity": 1, "price_per_unit": 250.00},
        {"min_quantity": 5, "price_per_unit": 240.00}
      ]
    }
  }
}

Implementing Custom Filesystem API Endpoints

WordPress’s REST API provides a powerful framework for creating custom endpoints. We’ll leverage this to expose functionality for reading and potentially writing (with strict controls) pricing data. The core idea is to map specific API routes to functions that interact with the filesystem.

First, we need to register our custom REST API routes. This is typically done within a plugin’s main file or an included file. We’ll use the `register_rest_route` function.

<?php
/**
 * Plugin Name: B2B Pricing Grid
 * Description: Custom endpoints for B2B pricing grids.
 * Version: 1.0
 * Author: Antigravity
 */

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

// Define the base directory for pricing data.
define( 'B2B_PRICING_DIR', trailingslashit( WP_CONTENT_DIR ) . 'b2b-pricing-data/' );

// Ensure the directory exists.
if ( ! file_exists( B2B_PRICING_DIR ) ) {
    wp_mkdir_p( B2B_PRICING_DIR );
}

/**
 * Register REST API routes for pricing data.
 */
function b2b_pricing_register_routes() {
    register_rest_route( 'b2b-pricing/v1', '/grid/(?P<customer_id>[\w-]+)', array(
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => 'b2b_pricing_get_grid',
        'permission_callback' => 'b2b_pricing_permissions_check',
        'args'                => array(
            'customer_id' => array(
                'required'          => true,
                'validate_callback' => function( $param, $request, $key ) {
                    // Basic validation: ensure it's a safe string.
                    return is_string( $param ) && preg_match( '/^[\w-]+$/', $param );
                },
            ),
        ),
    ) );

    // Example: Endpoint for updating pricing (highly restricted).
    register_rest_route( 'b2b-pricing/v1', '/grid/(?P<customer_id>[\w-]+)', array(
        'methods'             => WP_REST_Server::EDITABLE, // POST, PUT, PATCH, DELETE
        'callback'            => 'b2b_pricing_update_grid',
        'permission_callback' => 'b2b_pricing_admin_permissions_check',
        'args'                => array(
            'customer_id' => array(
                'required'          => true,
                'validate_callback' => function( $param, $request, $key ) {
                    return is_string( $param ) && preg_match( '/^[\w-]+$/', $param );
                },
            ),
            'data' => array(
                'required' => true,
                'validate_callback' => function( $param, $request, $key ) {
                    // Ensure the data is valid JSON.
                    json_decode( $param );
                    return ( json_last_error() === JSON_ERROR_NONE );
                },
            ),
        ),
    ) );
}
add_action( 'rest_api_init', 'b2b_pricing_register_routes' );

/**
 * Callback to retrieve the pricing grid for a specific customer ID.
 */
function b2b_pricing_get_grid( WP_REST_Request $request ) {
    $customer_id = $request['customer_id'];
    $file_path   = B2B_PRICING_DIR . sanitize_file_name( $customer_id ) . '.json';

    if ( ! file_exists( $file_path ) ) {
        return new WP_Error( 'pricing_grid_not_found', 'Pricing grid not found for this customer.', array( 'status' => 404 ) );
    }

    $data = file_get_contents( $file_path );
    if ( $data === false ) {
        return new WP_Error( 'file_read_error', 'Could not read pricing grid file.', array( 'status' => 500 ) );
    }

    $pricing_data = json_decode( $data, true );
    if ( json_last_error() !== JSON_ERROR_NONE ) {
        return new WP_Error( 'json_decode_error', 'Invalid JSON format in pricing grid file.', array( 'status' => 500 ) );
    }

    // Implement caching here for performance.
    // Example: Using WordPress Transients API.
    $cache_key = 'b2b_pricing_grid_' . $customer_id;
    set_transient( $cache_key, $pricing_data, HOUR_IN_SECONDS ); // Cache for 1 hour.

    return new WP_REST_Response( $pricing_data, 200 );
}

/**
 * Callback to update the pricing grid (requires admin privileges).
 */
function b2b_pricing_update_grid( WP_REST_Request $request ) {
    $customer_id = $request['customer_id'];
    $data_json   = $request['data'];
    $file_path   = B2B_PRICING_DIR . sanitize_file_name( $customer_id ) . '.json';

    // Data is already validated as JSON in the 'args' section.
    $pricing_data = json_decode( $data_json, true );

    // Further validation of the pricing structure can be added here.

    $result = file_put_contents( $file_path, json_encode( $pricing_data, JSON_PRETTY_PRINT ) );

    if ( $result === false ) {
        return new WP_Error( 'file_write_error', 'Could not write to pricing grid file.', array( 'status' => 500 ) );
    }

    // Clear cache after update.
    delete_transient( 'b2b_pricing_grid_' . $customer_id );

    return new WP_REST_Response( array( 'message' => 'Pricing grid updated successfully.' ), 200 );
}

/**
 * Permission callback for reading pricing grids.
 * Checks if the user is logged in and has a role that can view pricing.
 */
function b2b_pricing_permissions_check( WP_REST_Request $request ) {
    if ( ! is_user_logged_in() ) {
        return new WP_Error( 'rest_not_logged_in', 'You must be logged in to access pricing data.', array( 'status' => 401 ) );
    }

    // Get the current user.
    $user = wp_get_current_user();

    // Define roles that are allowed to view pricing.
    $allowed_roles = array( 'customer', 'wholesale_customer', 'premium_customer', 'administrator' ); // Example roles.

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

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

    // Further checks can be implemented here, e.g., linking customer ID to user ID.
    // For simplicity, we assume the customer_id in the URL is implicitly accessible by the logged-in user.
    // A more robust system would map user IDs to specific customer_ids or tiers.

    return true; // Permission granted.
}

/**
 * Permission callback for updating pricing grids.
 * Only administrators should be able to update pricing.
 */
function b2b_pricing_admin_permissions_check( WP_REST_Request $request ) {
    if ( ! current_user_can( 'manage_options' ) ) { // 'manage_options' is typically assigned to Administrators.
        return new WP_Error( 'rest_forbidden', 'You do not have permission to update pricing data.', array( 'status' => 403 ) );
    }
    return true; // Permission granted.
}

// Add a custom role for B2B customers if it doesn't exist.
function b2b_pricing_add_custom_roles() {
    if ( ! get_role( 'wholesale_customer' ) ) {
        add_role(
            'wholesale_customer',
            __( 'Wholesale Customer', 'b2b-pricing-grid' ),
            array(
                'read'         => true,  // Basic read access
                'edit_posts'   => false, // Cannot edit posts
                'upload_files' => false, // Cannot upload files
            )
        );
    }
    if ( ! get_role( 'premium_customer' ) ) {
        add_role(
            'premium_customer',
            __( 'Premium Customer', 'b2b-pricing-grid' ),
            array(
                'read'         => true,
                'edit_posts'   => false,
                'upload_files' => false,
            )
        );
    }
}
register_activation_hook( __FILE__, 'b2b_pricing_add_custom_roles' );

// Remove custom roles on plugin deactivation.
function b2b_pricing_remove_custom_roles() {
    remove_role( 'wholesale_customer' );
    remove_role( 'premium_customer' );
}
register_deactivation_hook( __FILE__, 'b2b_pricing_remove_custom_roles' );
?>

In this code:

  • We define a constant `B2B_PRICING_DIR` pointing to a dedicated directory outside of the theme or plugin for storing JSON pricing files. This is crucial for security and maintainability.
  • `register_rest_route` sets up two endpoints: one for reading (`WP_REST_Server::READABLE`) and one for editing (`WP_REST_Server::EDITABLE`).
  • The `permission_callback` is key to security. `b2b_pricing_permissions_check` ensures only logged-in users with specific roles can access pricing data. `b2b_pricing_admin_permissions_check` restricts write access to administrators.
  • `b2b_pricing_get_grid` reads the JSON file, decodes it, and returns it. Basic caching using `set_transient` is included for performance.
  • `b2b_pricing_update_grid` handles writing data back to the file. It’s protected by the admin-only permission callback.
  • Custom roles like `wholesale_customer` and `premium_customer` are added upon plugin activation to facilitate granular access control.

Implementing Role Overrides for Granular Access

The `permission_callback` in the REST API is powerful, but sometimes you need more fine-grained control than just assigning roles. For instance, a single user might belong to multiple customer tiers or have specific overrides. WordPress’s user meta and role management can be extended for this.

Let’s say we want to associate a specific `customer_id` from our pricing grid directly with a WordPress user. This is a more secure and direct way to grant access than relying solely on generic roles.

Associating Users with Customer IDs

We can use user meta to store the `customer_id` for each user. This requires adding functionality to the user profile page in the WordPress admin.

// Add custom field to user profile page
function b2b_pricing_show_extra_profile_fields( $user ) {
    ?>
    

<?php _e( "B2B Pricing Information", "b2b-pricing-grid" ); ?>

<table class="form-table"> <tr> <th><label for="b2b_customer_id"><?php _e( "B2B Customer ID", "b2b-pricing-grid" ); ?></label></th> <td> <input type="text" name="b2b_customer_id" id="b2b_customer_id" value="<?php echo esc_attr( get_user_meta( $user->ID, 'b2b_customer_id', true ) ); ?>" class="regular-text" /> <span class="description"><?php _e( "Enter the customer ID for pricing grid access.", "b2b-pricing-grid" ); ?></span> </td> </tr> </table> <?php } add_action( 'show_user_profile', 'b2b_pricing_show_extra_profile_fields' ); add_action( 'edit_user_profile', 'b2b_pricing_show_extra_profile_fields' ); // Save custom field data function b2b_pricing_save_extra_profile_fields( $user_id ) { if ( ! current_user_can( 'edit_user', $user_id ) ) { return false; } if ( isset( $_POST['b2b_customer_id'] ) ) { $customer_id = sanitize_text_field( $_POST['b2b_customer_id'] ); update_user_meta( $user_id, 'b2b_customer_id', $customer_id ); } } add_action( 'personal_options_update', 'b2b_pricing_save_extra_profile_fields' ); add_action( 'edit_user_profile_update', 'b2b_pricing_save_extra_profile_fields' ); ?>

With this in place, administrators can assign a specific `customer_id` to each user. Now, we can modify our `b2b_pricing_permissions_check` to use this user meta.

// Modified permission callback for reading pricing grids
function b2b_pricing_permissions_check( WP_REST_Request $request ) {
    if ( ! is_user_logged_in() ) {
        return new WP_Error( 'rest_not_logged_in', 'You must be logged in to access pricing data.', array( 'status' => 401 ) );
    }

    $user_id = get_current_user_id();
    $requested_customer_id = $request['customer_id'];

    // Get the customer ID associated with the logged-in user.
    $user_customer_id = get_user_meta( $user_id, 'b2b_customer_id', true );

    // Check if the user has a customer ID assigned.
    if ( empty( $user_customer_id ) ) {
        return new WP_Error( 'rest_forbidden', 'Your user account is not configured for B2B pricing.', array( 'status' => 403 ) );
    }

    // Crucial check: Does the requested customer ID match the user's assigned customer ID?
    if ( $requested_customer_id !== $user_customer_id ) {
        return new WP_Error( 'rest_forbidden', 'You do not have permission to access this customer\'s pricing data.', array( 'status' => 403 ) );
    }

    // Optional: Further role-based checks can still be applied here if needed.
    // For example, ensure the user also has a 'customer' role.
    $user = wp_get_current_user();
    $allowed_roles = array( 'customer', 'wholesale_customer', 'premium_customer', 'administrator' );
    $has_allowed_role = false;
    foreach ( $user->roles as $role ) {
        if ( in_array( $role, $allowed_roles ) ) {
            $has_allowed_role = true;
            break;
        }
    }

    if ( ! $has_allowed_role ) {
        return new WP_Error( 'rest_forbidden', 'Your user role does not permit pricing access.', array( 'status' => 403 ) );
    }

    return true; // Permission granted.
}
?>

This revised permission check is much more robust:

  • It first verifies if the user is logged in.
  • It retrieves the `b2b_customer_id` meta for the current user.
  • It compares the `customer_id` requested in the API call with the user’s assigned `b2b_customer_id`. If they don’t match, access is denied.
  • It still includes the role check as a secondary layer of security.

Frontend Integration and Usage

On the frontend, you’ll need to make authenticated AJAX requests to your custom API endpoints. Ensure that the user is logged in and that their `b2b_customer_id` is available (e.g., passed via `wp_localize_script` or fetched separately).

// Example using jQuery for AJAX request
jQuery(document).ready(function($) {
    var userId = wp_user_params.user_id; // Assuming user ID is available via wp_localize_script
    var pricingApiUrl = '/wp-json/b2b-pricing/v1/grid/';

    // Fetch user's assigned customer ID (e.g., from wp_localize_script or another API call)
    // For this example, let's assume it's available as userCustomerData.customer_id
    var userCustomerData = { customer_id: 'customer-abc' }; // Replace with actual fetched data

    if (userId && userCustomerData.customer_id) {
        var requestUrl = pricingApiUrl + userCustomerData.customer_id;

        $.ajax({
            url: requestUrl,
            method: 'GET',
            beforeSend: function(xhr) {
                // Add nonce for authentication if needed, though REST API often uses cookies for logged-in users.
                // xhr.setRequestHeader('X-WP-Nonce', wp_rest_params.nonce);
            },
            success: function(data) {
                console.log('Pricing data:', data);
                // Render pricing grid using 'data'
                renderPricingGrid(data);
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.error('Error fetching pricing data:', textStatus, errorThrown);
                // Display error message to the user
                if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
                    alert('Error: ' + jqXHR.responseJSON.message);
                } else {
                    alert('An unexpected error occurred.');
                }
            }
        });
    } else {
        console.log('User not logged in or customer ID not available.');
        // Redirect to login or show a message
    }

    function renderPricingGrid(pricingData) {
        // Logic to dynamically build and display the pricing table
        var html = '<table><thead><tr><th>Product</th><th>Price</th></tr></thead><tbody>';
        for (var sku in pricingData.products) {
            var product = pricingData.products[sku];
            html += '<tr><td>' + sku + '</td><td>' + product.base_price.toFixed(2) + '</td></tr>';
            // Add logic for tiers if needed
        }
        html += '</tbody></table>';
        $('#pricing-grid-container').html(html); // Assuming a div with id="pricing-grid-container"
    }
});

For frontend scripts, you’ll typically enqueue them and pass necessary data using `wp_localize_script`. This includes the REST API URL, nonce (if required for specific authentication methods), and potentially the current user’s ID or assigned customer ID.

Security Considerations and Best Practices

When dealing with pricing data, security is paramount. Always adhere to these principles:

  • Never expose sensitive data directly. All access should be mediated by permission callbacks.
  • Sanitize all inputs. Use functions like `sanitize_file_name`, `sanitize_text_field`, and `json_decode` with error checking.
  • Validate data structures. Beyond basic JSON validation, ensure the pricing data itself conforms to expected formats and types.
  • Use HTTPS. Essential for all API communication.
  • Rate Limiting. Implement rate limiting on your API endpoints to prevent abuse. WordPress’s REST API has some built-in rate limiting, but you might need more aggressive measures.
  • File Permissions. Ensure the `b2b-pricing-data` directory has appropriate file permissions (e.g., `755` for directories, `644` for files) and is not web-writable by default.
  • Regular Audits. Periodically review user roles, meta data, and file access logs.

Conclusion

By combining WordPress’s REST API, the Filesystem API, and a robust role/user meta management strategy, you can build a secure and flexible B2B pricing grid system. Storing pricing data in JSON files managed by custom endpoints offers a performant and maintainable solution. The implementation of granular permission callbacks and user-specific customer ID assignments ensures that only authorized users can access the correct pricing information, fulfilling critical B2B e-commerce requirements.

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

  • Upgrading Apache HTTP Server from version 2.4.57 to the latest security patch on openSUSE Leap 15.5 without breaking virtual hosts
  • Upgrading a multi-node Redis replication cluster on RHEL 9: Pre-flight failover validation runbooks
  • Upgrading Nginx from mainline repository to the latest stable branch on Ubuntu 24.04 LTS: Zero-downtime configuration validations
  • Upgrading a high-traffic production PostgreSQL database cluster from version 15 to 16 using pg_upgrade link mode on Debian 12
  • Upgrading PHP 8.2 to 8.3 on Rocky Linux 9: Re-compiling APCu, Imagick, and Memcached extensions safely

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 (58)
  • 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

  • Upgrading Apache HTTP Server from version 2.4.57 to the latest security patch on openSUSE Leap 15.5 without breaking virtual hosts
  • Upgrading a multi-node Redis replication cluster on RHEL 9: Pre-flight failover validation runbooks
  • Upgrading Nginx from mainline repository to the latest stable branch on Ubuntu 24.04 LTS: Zero-downtime configuration validations

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