JezK
Edit File: advaxe-incomplete-order.php
<?php /** * Plugin Name: Incomplete Order Status (No Duplicate Fix) * Plugin URI: https://advaxe.com * Description: Captures abandoned checkout data accurately using Browser Fingerprinting to prevent duplicates. * Version: 3.0.0 * Author: advaxe * Author URI: https://advaxe.com * Text Domain: advaxe-incomplete */ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } /** * 1. Register Order Status */ add_action( 'init', 'advaxe_register_status' ); function advaxe_register_status() { register_post_status( 'wc-incomplete-order', array( 'label' => 'Incomplete Order', 'public' => true, 'exclude_from_search' => false, 'show_in_admin_all_list' => true, 'show_in_admin_status_list' => true, 'label_count' => _n_noop( 'Incomplete Order (%s)', 'Incomplete Orders (%s)' ) ) ); } add_filter( 'wc_order_statuses', 'advaxe_add_status_list' ); function advaxe_add_status_list( $order_statuses ) { $new = array(); foreach ( $order_statuses as $key => $status ) { $new[ $key ] = $status; if ( 'wc-pending' === $key ) { $new['wc-incomplete-order'] = 'Incomplete Order'; } } return $new; } /** * 2. Javascript Logic (Generates Unique ID per Session) */ add_action( 'wp_enqueue_scripts', 'advaxe_checkout_tracker_script' ); function advaxe_checkout_tracker_script() { if ( ! is_checkout() || is_order_received_page() ) { return; } wp_enqueue_script( 'jquery' ); $script = " jQuery(document).ready(function($) { // 1. Generate or Retrieve Unique Browser Key for this checkout session let sessionKey = sessionStorage.getItem('advaxe_checkout_session_key'); if (!sessionKey) { sessionKey = 'sess_' + Date.now() + '_' + Math.floor(Math.random() * 10000); sessionStorage.setItem('advaxe_checkout_session_key', sessionKey); } let isPlaceOrderClicked = false; let isRequestSentRecently = false; // Debounce flag // 2. Detect Place Order Clicks $('form.checkout').on('submit', function() { isPlaceOrderClicked = true; }); $('#place_order').on('click', function() { isPlaceOrderClicked = true; }); // 3. Main Capture Function function captureIncomplete() { if (isPlaceOrderClicked) return; // User is buying properly // Basic Debounce: Prevent double firing (visibility + unload same time) if (isRequestSentRecently) return; isRequestSentRecently = true; setTimeout(() => { isRequestSentRecently = false; }, 1000); var email = $('#billing_email').val(); var phone = $('#billing_phone').val(); if (!email && !phone) return; var formData = new FormData(); formData.append('action', 'advaxe_capture_incomplete_order'); formData.append('security', '" . wp_create_nonce('advaxe_incomplete_nonce') . "'); formData.append('form_data', $('form.checkout').serialize()); formData.append('browser_session_key', sessionKey); // <--- THE KEY // Send reliable beacon navigator.sendBeacon('" . admin_url( 'admin-ajax.php' ) . "', formData); } // 4. Triggers document.addEventListener('visibilitychange', function() { if (document.visibilityState === 'hidden') { captureIncomplete(); } }); window.addEventListener('beforeunload', function() { captureIncomplete(); }); }); "; wp_add_inline_script( 'jquery-core', $script ); } /** * 3. Server Side Processing (Checks for Duplicate Key) */ add_action( 'wp_ajax_advaxe_capture_incomplete_order', 'advaxe_process_ajax' ); add_action( 'wp_ajax_nopriv_advaxe_capture_incomplete_order', 'advaxe_process_ajax' ); function advaxe_process_ajax() { check_ajax_referer( 'advaxe_incomplete_nonce', 'security' ); $session_key = isset($_POST['browser_session_key']) ? sanitize_text_field($_POST['browser_session_key']) : ''; if ( empty($session_key) ) wp_die(); // No key, no process // Parse Form Data $params = array(); parse_str( $_POST['form_data'], $params ); $email = isset($params['billing_email']) ? sanitize_email($params['billing_email']) : ''; $phone = isset($params['billing_phone']) ? sanitize_text_field($params['billing_phone']) : ''; if ( empty($email) && empty($phone) ) wp_die(); // --- CRITICAL: Find existing order with this Session Key --- // We query the database to see if we already made an incomplete order for this browser session $existing_orders = wc_get_orders(array( 'limit' => 1, 'status' => 'wc-incomplete-order', 'meta_key' => '_advaxe_session_key', 'meta_value' => $session_key, 'return' => 'ids' )); $order = false; if ( !empty($existing_orders) ) { $order_id = $existing_orders[0]; $order = wc_get_order($order_id); } if ( !$order ) { // Create NEW $order = wc_create_order(); $order->set_status( 'wc-incomplete-order' ); // Save the Key so we find it next time $order->update_meta_data( '_advaxe_session_key', $session_key ); } // --- Standard Data Saving --- $address_billing = array( 'first_name' => isset($params['billing_first_name']) ? sanitize_text_field($params['billing_first_name']) : '', 'last_name' => isset($params['billing_last_name']) ? sanitize_text_field($params['billing_last_name']) : '', 'email' => $email, 'phone' => $phone, 'address_1' => isset($params['billing_address_1']) ? sanitize_text_field($params['billing_address_1']) : '', 'city' => isset($params['billing_city']) ? sanitize_text_field($params['billing_city']) : '', ); $order->set_address( $address_billing, 'billing' ); // Handle Shipping Address if ( isset($params['ship_to_different_address']) && $params['ship_to_different_address'] ) { $address_shipping = array( 'first_name' => isset($params['shipping_first_name']) ? sanitize_text_field($params['shipping_first_name']) : '', 'last_name' => isset($params['shipping_last_name']) ? sanitize_text_field($params['shipping_last_name']) : '', 'address_1' => isset($params['shipping_address_1']) ? sanitize_text_field($params['shipping_address_1']) : '', 'city' => isset($params['shipping_city']) ? sanitize_text_field($params['shipping_city']) : '', ); $order->set_address( $address_shipping, 'shipping' ); } else { $order->set_address( $address_billing, 'shipping' ); } // Cart Items $order->remove_order_items(); if ( WC()->cart ) { foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) { $item = new WC_Order_Item_Product(); $item->set_product( $values['data'] ); $item->set_quantity( $values['quantity'] ); $item->set_variation_id( $values['variation_id'] ); $item->set_subtotal( $values['line_subtotal'] ); $item->set_total( $values['line_subtotal_tax'] ); $order->add_item( $item ); } } $order->calculate_totals(); $order->save(); wp_die(); } /** * 4. Clean up on Success * If user completes order, clear the browser storage so next time they are fresh. */ add_action( 'woocommerce_thankyou', 'advaxe_clear_browser_storage' ); function advaxe_clear_browser_storage() { ?> <script type="text/javascript"> if(sessionStorage) { sessionStorage.removeItem('advaxe_checkout_session_key'); } </script> <?php }