Building secure B2B pricing grids with custom Cron API (wp_schedule_event) endpoints and role overrides
Securing B2B Pricing Grids: Custom Cron Endpoints and Role Overrides
Developing robust B2B pricing solutions within WordPress often necessitates dynamic pricing structures that are not directly exposed to the public. This requires a secure mechanism for updating prices, potentially triggered by external events or scheduled intervals, while ensuring only authorized personnel or systems can initiate these changes. This post details a production-ready approach using custom WordPress cron events (`wp_schedule_event`) coupled with API endpoints and granular role-based access control.
Designing the API Endpoint for Price Updates
We’ll create a custom REST API endpoint that will be responsible for triggering the price update process. This endpoint will be protected, requiring specific authentication and authorization. For simplicity in this example, we’ll use a nonce and a custom capability check, but in a real-world scenario, consider OAuth2 or JWT for more sophisticated authentication.
First, let’s define the endpoint registration within your plugin’s main file or an included setup file. We’ll hook into the rest_api_init action.
Registering the REST API Endpoint
add_action( 'rest_api_init', function () {
register_rest_route( 'my-pricing/v1', '/update-prices', array(
'methods' => 'POST',
'callback' => 'my_pricing_update_prices_callback',
'permission_callback' => function ( WP_REST_Request $request ) {
// 1. Verify nonce for basic security
if ( ! isset( $request['_wpnonce'] ) || ! wp_verify_nonce( $request['_wpnonce'], 'wp_rest' ) ) {
return new WP_Error( 'rest_nonce_invalid', 'Nonce is invalid.', array( 'status' => 401 ) );
}
// 2. Check for a custom capability
// We'll define 'manage_b2b_pricing' later
if ( ! current_user_can( 'manage_b2b_pricing' ) ) {
return new WP_Error( 'rest_forbidden', 'You do not have permission to update prices.', array( 'status' => 403 ) );
}
return true; // Permission granted
},
'args' => array(
'_wpnonce' => array(
'required' => true,
'type' => 'string',
'description' => 'WordPress nonce for security.',
),
// Add any other parameters needed for price updates, e.g., product IDs, new prices
),
) );
} );
The Callback Function for Price Updates
The callback function will be responsible for initiating the price update process. Instead of performing the update directly, it will schedule a WordPress cron event. This decouples the API request from the potentially long-running update task, preventing timeouts and allowing for more robust error handling and retries.
function my_pricing_update_prices_callback( WP_REST_Request $request ) {
// Optional: Extract parameters from the request if needed for the cron job
// $params = $request->get_params();
// Schedule the cron event to run as soon as possible
// 'my_pricing_run_updates' is the hook name
// time() + (60 * 5) schedules it for 5 minutes from now, adjust as needed
// 'daily' is the interval, can be 'hourly', 'twicedaily', or a custom interval
// The third parameter is the hook name, which must be unique.
$timestamp = time() + ( 60 * 5 ); // Schedule for 5 minutes from now
wp_schedule_single_event( $timestamp, 'my_pricing_run_updates' );
return new WP_REST_Response( array(
'message' => 'Price update scheduled. Check logs for progress.',
'scheduled_at' => date( 'Y-m-d H:i:s', $timestamp ),
), 202 ); // 202 Accepted
}
Implementing the Cron Event Handler
Now, we need to hook into the scheduled event and perform the actual price updates. This is where your B2B pricing logic will reside. It’s crucial to make this process idempotent and handle potential errors gracefully.
Hooking into the Scheduled Event
add_action( 'my_pricing_run_updates', 'my_pricing_perform_price_updates' );
function my_pricing_perform_price_updates() {
// Ensure this function is only executed by the cron job, not directly.
// A simple check can be to see if the current request is a WP-Cron request.
if ( ! ( defined( 'DOING_CRON' ) && DOING_CRON ) ) {
// Log this as an attempted direct execution if necessary
error_log( 'Attempted direct execution of my_pricing_perform_price_updates.' );
return;
}
// --- Your B2B Pricing Update Logic Here ---
// This could involve:
// 1. Fetching new pricing data from an external API.
// 2. Iterating through products and updating their prices in the database (e.g., WooCommerce products).
// 3. Applying complex B2B pricing rules (e.g., tiered pricing, customer-specific discounts).
// 4. Logging the process, successes, and failures.
// Example: Simulate an update process
error_log( 'Starting B2B price update process...' );
// Simulate fetching data
$pricing_data = array(
'SKU123' => array( 'price' => 99.99, 'tier' => 'gold' ),
'SKU456' => array( 'price' => 149.50, 'tier' => 'platinum' ),
);
foreach ( $pricing_data as $sku => $data ) {
// Find product by SKU
$product_id = wc_get_product_id_by_sku( $sku );
if ( $product_id ) {
$product = wc_get_product( $product_id );
if ( $product ) {
// For simplicity, updating regular price. Real-world might involve custom meta for B2B.
$product->set_regular_price( $data['price'] );
$product->save();
error_log( "Updated price for SKU {$sku} (Product ID: {$product_id}) to {$data['price']}." );
} else {
error_log( "Could not retrieve product object for SKU {$sku} (Product ID: {$product_id})." );
}
} else {
error_log( "Product not found for SKU {$sku}." );
}
}
error_log( 'B2B price update process finished.' );
// --- End of Your Logic ---
// Optional: Clear the scheduled event if it's a one-off task
// wp_clear_scheduled_hook( 'my_pricing_run_updates' );
}
Implementing Role-Based Access Control
To restrict access to the price update API endpoint, we need to define a custom capability and assign it to specific user roles. This ensures that only authorized administrators or specific user types can trigger price updates.
Adding a Custom Capability
This should be done when your plugin is activated. We’ll use the register_activation_hook.
register_activation_hook( __FILE__, 'my_pricing_plugin_activate' );
function my_pricing_plugin_activate() {
// Add the custom capability
$role = get_role( 'administrator' ); // Target the administrator role
if ( $role && ! $role->has_cap( 'manage_b2b_pricing' ) ) {
$role->add_cap( 'manage_b2b_pricing' );
}
// Optionally, add it to other roles or create a new role
// $editor_role = get_role( 'editor' );
// if ( $editor_role && ! $editor_role->has_cap( 'manage_b2b_pricing' ) ) {
// $editor_role->add_cap( 'manage_b2b_pricing' );
// }
// Schedule the initial cron job if needed on activation
if ( ! wp_next_scheduled( 'my_pricing_run_updates' ) ) {
wp_schedule_event( time(), 'daily', 'my_pricing_run_updates' ); // Example: schedule daily
}
}
Removing Capability on Deactivation
It’s good practice to clean up capabilities when the plugin is deactivated.
register_deactivation_hook( __FILE__, 'my_pricing_plugin_deactivate' );
function my_pricing_plugin_deactivate() {
// Remove the custom capability from the administrator role
$role = get_role( 'administrator' );
if ( $role && $role->has_cap( 'manage_b2b_pricing' ) ) {
$role->remove_cap( 'manage_b2b_pricing' );
}
// Clear any scheduled events
wp_clear_scheduled_hook( 'my_pricing_run_updates' );
}
Triggering the API Endpoint (Example)
To trigger the API endpoint securely from an external system or a privileged internal script, you would typically use a tool like curl. You’ll need to obtain a nonce for the current user and include it in the request.
Using curl to Trigger the Update
First, you need to authenticate as a user with the manage_b2b_pricing capability. Then, you can generate a nonce and make the POST request.
To get a nonce for a specific user (e.g., user ID 1), you can use PHP:
// In a WordPress context, e.g., a custom script or admin page $user_id = 1; // The ID of the user with 'manage_b2b_pricing' capability $nonce = wp_create_nonce( 'wp_rest' ); $api_url = rest_url( 'my-pricing/v1/update-prices' ); echo "Nonce: " . $nonce . "\n"; echo "API URL: " . $api_url . "\n";
Then, use curl:
# Replace YOUR_WORDPRESS_URL, YOUR_USERNAME, YOUR_PASSWORD with actual values
# This example uses basic auth, which is NOT recommended for production.
# For production, use application passwords or a more secure auth method.
# Get nonce and API URL (you'd typically get these dynamically)
# For demonstration, let's assume you have them:
NONCE="your_generated_nonce_here"
API_URL="https://your-wordpress-site.com/wp-json/my-pricing/v1/update-prices"
curl -X POST \
-H "Content-Type: application/json" \
-d "{\"_wpnonce\": \"$NONCE\"}" \
"$API_URL"
Important Security Note: Using basic authentication with curl as shown above is insecure for production environments. For real-world applications, consider using WordPress Application Passwords, OAuth2, or JWT for API authentication. The nonce check is a good start for internal WP REST API usage but might not be sufficient for external systems.
Advanced Considerations and Best Practices
- Error Handling and Logging: Implement comprehensive logging within
my_pricing_perform_price_updates. Log successes, failures, and any exceptions. Use WordPress’s built-in error logging or a dedicated logging library. - Idempotency: Ensure your price update logic can be run multiple times without unintended side effects. This is crucial if the cron job fails and is rescheduled.
- External Data Sources: If fetching pricing from an external API, implement robust error handling, timeouts, and retry mechanisms for the external API calls. Cache external data where appropriate.
- Concurrency: For very large sites or frequent updates, consider mechanisms to prevent multiple cron jobs from running concurrently if the previous one hasn’t finished. You can use transient API or file locking.
- User Feedback: Provide feedback to the user who triggered the update via the REST API. This could involve updating a status in the database that can be queried via another API endpoint, or sending email notifications.
- Custom Intervals: If you need more granular scheduling than ‘daily’, ‘hourly’, or ‘twicedaily’, you can define custom intervals using
add_filter( 'cron_schedules', 'my_pricing_add_custom_cron_interval' );. - Security Hardening: Beyond nonces and capabilities, consider IP whitelisting for the API endpoint if it’s only ever called from specific servers.
By combining custom REST API endpoints with scheduled WordPress cron events and robust role-based access control, you can build a secure and scalable system for managing dynamic B2B pricing grids within your WordPress multisite or single-site installation.