WordPress Security for Restaurant Sites: Protecting Orders and Customer Data
Secure your WordPress restaurant website with strategies to protect online orders, payment data, and customer information.
Restaurant websites handle sensitive customer data including payment information, delivery addresses, and dietary preferences. Protecting this data is essential for maintaining customer trust and regulatory compliance.
Restaurant Site Security Challenges
Restaurant websites face unique threats:
- Order manipulation and fraud
- Payment card data theft
- Customer account hijacking
- Menu price manipulation
- Reservation system abuse
Sensitive Data Categories
Restaurant sites typically collect:
- Customer names and contact information
- Delivery addresses (home and work)
- Payment card details
- Order history and preferences
- Dietary restrictions and allergies
- Loyalty points and rewards
Secure Order Processing
Protect order submissions from manipulation:
function process_restaurant_order($order_data) {
// Verify nonce
if (!wp_verify_nonce($order_data['nonce'], 'restaurant_order')) {
return new WP_Error('invalid_nonce', 'Security verification failed');
}
// Validate and recalculate prices server-side
$order_items = array();
$total = 0;
foreach ($order_data['items'] as $item) {
$menu_item = get_menu_item($item['id']);
if (!$menu_item) {
continue;
}
// Use database price, not client-submitted price
$item_price = $menu_item['price'];
$quantity = max(1, intval($item['quantity']));
$order_items[] = array(
'id' => $menu_item['id'],
'name' => $menu_item['name'],
'price' => $item_price,
'quantity' => $quantity,
);
$total += $item_price * $quantity;
}
// Apply coupons server-side
if (!empty($order_data['coupon'])) {
$coupon = validate_coupon($order_data['coupon']);
if ($coupon) {
$total = apply_coupon_discount($total, $coupon);
}
}
return array(
'items' => $order_items,
'total' => $total,
);
}
Delivery Address Validation
Validate delivery addresses to prevent fraud:
function validate_delivery_address($address) {
// Sanitize address fields
$sanitized = array(
'street' => sanitize_text_field($address['street']),
'city' => sanitize_text_field($address['city']),
'postal_code' => preg_replace('/[^A-Z0-9 -]/i', '', $address['postal_code']),
'phone' => preg_replace('/[^0-9+() -]/', '', $address['phone']),
);
// Check delivery radius
$restaurant_location = get_restaurant_coordinates();
$delivery_coords = geocode_address($sanitized);
if ($delivery_coords) {
$distance = calculate_distance(
$restaurant_location['lat'],
$restaurant_location['lng'],
$delivery_coords['lat'],
$delivery_coords['lng']
);
if ($distance > get_option('max_delivery_radius', 10)) {
return new WP_Error('out_of_range', 'Address is outside delivery area');
}
}
// Verify phone number format
if (strlen(preg_replace('/[^0-9]/', '', $sanitized['phone'])) < 10) {
return new WP_Error('invalid_phone', 'Please enter a valid phone number');
}
return $sanitized;
}
Protecting Customer Accounts
Secure customer order history and saved data:
// Require re-authentication for sensitive actions
function require_customer_reauth() {
$last_auth = get_user_meta(get_current_user_id(), 'last_password_entry', true);
// Require password every 15 minutes for sensitive operations
if (!$last_auth || (time() - $last_auth) > 900) {
return false;
}
return true;
}
// Protect payment method changes
add_action('wp_ajax_update_payment_method', function() {
if (!require_customer_reauth()) {
wp_send_json_error(array('message' => 'Please confirm your password'));
}
// Process payment method update
});
// Protect address changes
add_action('wp_ajax_update_saved_address', function() {
// Log address changes
$old_address = get_user_meta(get_current_user_id(), 'delivery_address', true);
log_security_event(array(
'type' => 'address_change',
'user_id' => get_current_user_id(),
'old_value' => $old_address,
'ip' => $_SERVER['REMOTE_ADDR'],
));
});
Reservation System Security
Protect the reservation system from abuse:
function validate_reservation($reservation) {
// Rate limit reservations per IP
$ip = $_SERVER['REMOTE_ADDR'];
$key = 'reservations_' . md5($ip);
$recent = get_transient($key) ?: 0;
if ($recent > 3) { // Max 3 reservations per hour
return new WP_Error('rate_limit', 'Too many reservation attempts');
}
set_transient($key, $recent + 1, HOUR_IN_SECONDS);
// Validate party size
$party_size = intval($reservation['party_size']);
$max_party = get_option('max_party_size', 20);
if ($party_size < 1 || $party_size > $max_party) {
return new WP_Error('invalid_party', 'Invalid party size');
}
// Validate date/time
$reservation_time = strtotime($reservation['datetime']);
if ($reservation_time < time()) {
return new WP_Error('past_date', 'Cannot book in the past');
}
// Check if time slot is available
if (!is_slot_available($reservation_time, $party_size)) {
return new WP_Error('unavailable', 'This time slot is not available');
}
// Verify confirmation code for modifications
if (!empty($reservation['modification_code'])) {
if (!verify_reservation_code($reservation['modification_code'])) {
return new WP_Error('invalid_code', 'Invalid confirmation code');
}
}
return true;
}
Menu Price Protection
Prevent unauthorized menu modifications:
// Log all menu price changes
add_action('save_post_menu_item', function($post_id, $post, $update) {
if (!$update) {
return;
}
$old_price = get_post_meta($post_id, '_price', true);
$new_price = $_POST['menu_price'] ?? null;
if ($new_price && $old_price !== $new_price) {
log_menu_change(array(
'item_id' => $post_id,
'old_price' => $old_price,
'new_price' => $new_price,
'user' => get_current_user_id(),
'time' => current_time('mysql'),
));
// Alert for significant price changes
$change_percent = abs(($new_price - $old_price) / $old_price * 100);
if ($change_percent > 20) {
wp_mail(
get_option('admin_email'),
'Large menu price change',
sprintf(
'Item %s price changed by %.1f%% from %.2f to %.2f',
get_the_title($post_id), $change_percent, $old_price, $new_price
)
);
}
}
}, 10, 3);
Allergy Information Protection
Ensure dietary and allergy information is accurate:
// Track allergy information changes
add_action('update_post_meta', function($meta_id, $post_id, $meta_key, $meta_value) {
if ($meta_key !== '_allergens') {
return;
}
$old_allergens = get_post_meta($post_id, '_allergens', true);
// Log the change with full audit trail
global $wpdb;
$wpdb->insert('allergen_audit_log', array(
'menu_item_id' => $post_id,
'old_allergens' => maybe_serialize($old_allergens),
'new_allergens' => maybe_serialize($meta_value),
'changed_by' => get_current_user_id(),
'changed_at' => current_time('mysql'),
'ip_address' => $_SERVER['REMOTE_ADDR'],
));
}, 10, 4);
Order History Privacy
Protect customer order history:
function get_customer_orders($customer_id, $requesting_user) {
// Only allow customers to see their own orders
if ($customer_id !== $requesting_user && !current_user_can('manage_orders')) {
return new WP_Error('unauthorized', 'Cannot access other customer orders');
}
$orders = get_orders_by_customer($customer_id);
// Mask sensitive data for display
foreach ($orders as &$order) {
if (isset($order['card_last_four'])) {
$order['payment_display'] = '**** ' . $order['card_last_four'];
unset($order['card_last_four']);
}
}
return $orders;
}
Conclusion
Restaurant website security requires protecting order data, validating prices server-side, securing reservation systems, and maintaining accurate allergen information. Regular security audits help ensure ongoing customer protection.
Written by Sarah Chen
WP Folder Shield Team