From 5b8882e5950d677f4603bafddafe2884a93c3108 Mon Sep 17 00:00:00 2001 From: Dwindi Ramadhana Date: Mon, 1 Jun 2026 00:58:10 +0700 Subject: [PATCH] fix(affiliate): persist referral code through SPA checkout and process after save --- customer-spa/src/main.tsx | 42 +++++++++++++++++++++++ customer-spa/src/pages/Checkout/index.tsx | 23 ++++++++++++- includes/Api/CheckoutController.php | 11 ++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/customer-spa/src/main.tsx b/customer-spa/src/main.tsx index 4bf6c9d..03c6f9d 100644 --- a/customer-spa/src/main.tsx +++ b/customer-spa/src/main.tsx @@ -5,6 +5,48 @@ import './styles/fonts.css'; import './styles/theme.css'; import App from './App'; +// Capture referral code from URL on app load +// This ensures referral tracking works even for block/AJAX checkout flows +(function captureReferralCode() { + const params = new URLSearchParams(window.location.search); + const ref = params.get('ref'); + console.log('[WooNooW] captureReferralCode - URL search:', window.location.search, ', ref:', ref); + + if (ref && ref.trim() !== '') { + // Store in localStorage as backup + try { + localStorage.setItem('woonoow_ref', ref); + console.log('[WooNooW] Stored ref in localStorage:', ref); + } catch (e) { + // localStorage may be blocked + console.log('[WooNooW] localStorage blocked:', e); + } + + // Also set cookie for PHP backend to read + // Cookie expires in 30 days, path is root + const expires = new Date(); + expires.setDate(expires.getDate() + 30); + const cookieStr = `woonoow_ref=${encodeURIComponent(ref)}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`; + document.cookie = cookieStr; + console.log('[WooNooW] Set cookie:', cookieStr); + } else { + // Check if ref exists in localStorage from previous visit + try { + const storedRef = localStorage.getItem('woonoow_ref'); + if (storedRef) { + console.log('[WooNooW] Found stored ref in localStorage:', storedRef); + // Re-set the cookie from localStorage + const expires = new Date(); + expires.setDate(expires.getDate() + 30); + document.cookie = `woonoow_ref=${encodeURIComponent(storedRef)}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`; + console.log('[WooNooW] Re-set cookie from localStorage:', storedRef); + } + } catch (e) { + console.log('[WooNooW] Could not read localStorage:', e); + } + } +})(); + const el = document.getElementById('woonoow-customer-app'); if (el) { createRoot(el).render( diff --git a/customer-spa/src/pages/Checkout/index.tsx b/customer-spa/src/pages/Checkout/index.tsx index 76c9804..689baad 100644 --- a/customer-spa/src/pages/Checkout/index.tsx +++ b/customer-spa/src/pages/Checkout/index.tsx @@ -599,6 +599,18 @@ export default function Checkout() { setIsProcessing(true); try { + const referralCode = (() => { + try { + const stored = localStorage.getItem('woonoow_ref')?.trim(); + if (stored) return stored; + } catch { + // Ignore storage access errors. + } + + const match = document.cookie.match(/(?:^|;\s*)woonoow_ref=([^;]+)/); + return match ? decodeURIComponent(match[1]).trim() : ''; + })(); + // Prepare order data const orderData = { items: cart.items.map(item => ({ @@ -652,6 +664,7 @@ export default function Checkout() { custom_fields: customFieldData, // CAPTCHA token for security validation captcha_token: captchaToken, + referral_code: referralCode || undefined, }; // Submit order @@ -670,8 +683,16 @@ export default function Checkout() { // If user was logged in during this request (guest auto-register), // we need a full page reload to recognize the auth cookie if (data.user_logged_in) { + // Get basePath from window + const basePath = (window as any).woonoowCustomer?.basePath ?? '/store'; + + // Ensure thankYouUrl starts with / if basePath doesn't end with / + const sep = basePath.endsWith('/') || thankYouUrl.startsWith('/') ? '' : '/'; + const prefix = thankYouUrl.startsWith('/') && basePath.endsWith('/') ? basePath.slice(0, -1) : basePath; + // Full page reload so browser recognizes the new auth cookie - window.location.href = thankYouUrl; + const path = thankYouUrl.startsWith('/') ? thankYouUrl.slice(1) : thankYouUrl; + window.location.href = `${prefix}${sep}${path}`; } else { // Already logged in or no login happened - SPA navigate is fine navigate(thankYouUrl, { replace: true }); diff --git a/includes/Api/CheckoutController.php b/includes/Api/CheckoutController.php index 3189c81..79376d5 100644 --- a/includes/Api/CheckoutController.php +++ b/includes/Api/CheckoutController.php @@ -329,6 +329,11 @@ class CheckoutController return ['error' => $order->get_error_message()]; } + if (!empty($payload['referral_code'])) { + $order->update_meta_data('_woonoow_referral_code', $payload['referral_code']); + $_COOKIE['woonoow_ref'] = $payload['referral_code']; + } + // Track if user was logged in during this request (for frontend page reload) $user_logged_in = false; @@ -516,6 +521,11 @@ class CheckoutController $order->save(); + // wc_create_order() fires woocommerce_new_order before SPA checkout line + // items exist. Fire the processed hook after saving so integrations that + // calculate from order items, like affiliate tracking, see a complete order. + do_action('woocommerce_checkout_order_processed', $order->get_id(), $payload, $order); + if (!headers_sent()) { header('Server-Timing: app;dur=' . round((microtime(true) - $__t0) * 1000, 1)); } @@ -842,6 +852,7 @@ class CheckoutController 'shipping_title' => isset($json['shipping_title']) ? sanitize_text_field($json['shipping_title']) : null, 'custom_fields' => $custom_fields, 'customer_note' => isset($json['customer_note']) ? sanitize_textarea_field($json['customer_note']) : '', + 'referral_code' => isset($json['referral_code']) ? sanitize_text_field($json['referral_code']) : '', ]; }