Created
July 31, 2026 11:29
-
-
Save xlplugins/e47cce3b09753104ca7e65f975b491a8 to your computer and use it in GitHub Desktop.
WFACP — Cart / checkout-page mismatch guard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Plugin Name: WFACP — Cart / checkout-page mismatch guard | |
| * Description: Prevents wrong-product orders on FunnelKit (Aero) checkouts sharing one WooCommerce session. Stamps every Aero cart item with the checkout page that added it, detects foreign items during checkout render and self-corrects via one guarded reload. As a final safety net it blocks a mismatched order submission with a visible notice, then auto-reloads so the page loads its own product and the customer can place the order again. | |
| * Version: 2.0.0 | |
| */ | |
| defined( 'ABSPATH' ) || exit; | |
| if ( ! class_exists( 'WFACP_Cart_Mismatch_Guard' ) ) { | |
| class WFACP_Cart_Mismatch_Guard { | |
| public function __construct() { | |
| add_filter( 'woocommerce_add_cart_item', array( $this, 'stamp_cart_item' ), 10 ); | |
| add_filter( 'woocommerce_update_order_review_fragments', array( $this, 'flag_mismatch_fragment' ) ); | |
| add_action( 'woocommerce_after_checkout_validation', array( $this, 'block_mismatched_submission' ), 999, 2 ); | |
| add_action( 'wp_footer', array( $this, 'footer_script' ), 99 ); | |
| } | |
| /** | |
| * Remember which Aero checkout page added this item (gist idea, kept). | |
| */ | |
| public function stamp_cart_item( $cart_item ) { | |
| if ( is_array( $cart_item ) && ( isset( $cart_item['_wfacp_product'] ) || isset( $cart_item['_wfacp_product_key'] ) ) && class_exists( 'WFACP_Common' ) ) { | |
| $page_id = absint( WFACP_Common::get_id() ); | |
| if ( $page_id > 0 ) { | |
| $cart_item['_wfacp_checkout_page_id'] = $page_id; | |
| } | |
| } | |
| return $cart_item; | |
| } | |
| /** | |
| * True when the cart holds an Aero item belonging to another checkout page. | |
| * | |
| * Pages without configured products (global/store checkout) never mismatch — | |
| * they legitimately sell whatever cart arrives. Items without Aero markers | |
| * (order bumps, upsells, gifts, custom additions) are never judged. | |
| */ | |
| private function cart_has_foreign_items( $page_id ) { | |
| if ( ! class_exists( 'WFACP_Common' ) || ! function_exists( 'WC' ) || is_null( WC()->cart ) ) { | |
| return false; | |
| } | |
| $page_id = absint( $page_id ); | |
| if ( $page_id < 1 ) { | |
| return false; | |
| } | |
| $page_products = WFACP_Common::get_page_product( $page_id ); | |
| if ( empty( $page_products ) || ! is_array( $page_products ) ) { | |
| return false; | |
| } | |
| $page_keys = array_map( 'strval', array_keys( $page_products ) ); | |
| foreach ( WC()->cart->get_cart_contents() as $cart_item ) { | |
| if ( ! is_array( $cart_item ) ) { | |
| continue; | |
| } | |
| // Primary: provenance stamp written by this guard. | |
| if ( isset( $cart_item['_wfacp_checkout_page_id'] ) && absint( $cart_item['_wfacp_checkout_page_id'] ) > 0 ) { | |
| if ( absint( $cart_item['_wfacp_checkout_page_id'] ) !== $page_id ) { | |
| return true; | |
| } | |
| continue; | |
| } | |
| // Fallback for items added before this guard was deployed. | |
| if ( ! empty( $cart_item['_wfacp_product_key'] ) && ! in_array( (string) $cart_item['_wfacp_product_key'], $page_keys, true ) ) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Render-time detection: tell the page (via fragments) that its cart is foreign. | |
| */ | |
| public function flag_mismatch_fragment( $fragments ) { | |
| if ( ! wp_doing_ajax() || ! class_exists( 'WFACP_Common' ) ) { | |
| return $fragments; | |
| } | |
| if ( $this->cart_has_foreign_items( WFACP_Common::get_id() ) ) { | |
| $fragments['wfacp_checkout_mismatch'] = 'yes'; | |
| } | |
| return $fragments; | |
| } | |
| /** | |
| * Final safety net: never create an order from a foreign cart. | |
| */ | |
| public function block_mismatched_submission( $data, $errors ) { | |
| $page_id = isset( $_POST['_wfacp_post_id'] ) ? absint( wp_unslash( $_POST['_wfacp_post_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- WC checkout nonce verified upstream. | |
| if ( $page_id < 1 && class_exists( 'WFACP_Common' ) ) { | |
| $page_id = absint( WFACP_Common::get_id() ); | |
| } | |
| if ( ! $this->cart_has_foreign_items( $page_id ) ) { | |
| return; | |
| } | |
| if ( function_exists( 'wc_get_logger' ) ) { | |
| wc_get_logger()->warning( | |
| sprintf( 'Blocked mismatched order submission on checkout page %d.', $page_id ), | |
| array( 'source' => 'wfacp-mismatch-guard' ) | |
| ); | |
| } | |
| // Block with a visible reason; the footer JS reloads shortly after so | |
| // the page re-claims its own product and the customer can order again. | |
| $errors->add( | |
| 'wfacp_product_mismatch', | |
| apply_filters( | |
| 'wfacp_product_mismatch_notice', | |
| __( 'The product in your cart did not match this checkout, so the order was not placed. The page will refresh automatically to load the correct product — please place your order again.', 'woofunnels-aero-checkout' ) | |
| ), | |
| array( 'id' => 'wfacp_checkout_mismatch' ) | |
| ); | |
| } | |
| /** | |
| * Client side: self-correct with ONE guarded reload when a mismatch is | |
| * reported (render-time or after a blocked submission), and refresh state | |
| * when the page is restored from the back/forward cache. | |
| */ | |
| public function footer_script() { | |
| if ( ! function_exists( 'is_checkout' ) || ! is_checkout() ) { | |
| return; | |
| } | |
| ?> | |
| <script> | |
| (function ($) { | |
| var GUARD_KEY = 'wfacp_mm_reload_at'; | |
| function reloadOnce() { | |
| var now = Date.now(); | |
| var last = 0; | |
| try { last = parseInt(sessionStorage.getItem(GUARD_KEY), 10) || 0; } catch (e) {} | |
| if (now - last < 30000) { | |
| return; // never loop: at most one corrective reload per 30s | |
| } | |
| try { sessionStorage.setItem(GUARD_KEY, String(now)); } catch (e) {} | |
| window.location.reload(); | |
| } | |
| $(document.body).on('updated_checkout', function (e, data) { | |
| if (data && data.fragments && data.fragments.wfacp_checkout_mismatch === 'yes') { | |
| reloadOnce(); | |
| } | |
| }); | |
| // Blocked submission: the notice explains why; give the customer a | |
| // few seconds to read it, then reload so the page loads its own product. | |
| $(document.body).on('checkout_error', function () { | |
| setTimeout(function () { | |
| var $err = $('li[data-id="wfacp_checkout_mismatch"], .woocommerce-error [data-id="wfacp_checkout_mismatch"]'); | |
| if ($err.length === 0) { | |
| return; | |
| } | |
| setTimeout(reloadOnce, 4000); | |
| }, 0); | |
| }); | |
| // Restored from back/forward cache: state may be stale — resync. | |
| window.addEventListener('pageshow', function (e) { | |
| if (e.persisted) { | |
| $(document.body).trigger('update_checkout'); | |
| } | |
| }); | |
| })(jQuery); | |
| </script> | |
| <?php | |
| } | |
| } | |
| new WFACP_Cart_Mismatch_Guard(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment