session ? WC()->session->get('refref_handle') : ''; return is_string($handle) ? $handle : ''; } public static function consent(): bool { if (!WC()->session || !WC()->session->get('refref_consent')) { return false; } return !function_exists('wp_has_consent') || wp_has_consent('marketing'); } public static function register(): void { add_action('wc_ajax_refref_bootstrap', [self::class, 'bootstrap']); add_action('wc_ajax_refref', [self::class, 'request']); add_action('wp_enqueue_scripts', static function () { if (!RefRef_Config::connection()) { return; } wp_enqueue_script('refref', plugins_url('storefront.js', REFREF_PLUGIN_FILE), ['wp-i18n'], '0.1.0', true); wp_set_script_translations('refref', 'refref'); wp_add_inline_script('refref', 'window.refrefStore=' . wp_json_encode(['endpoint' => WC_AJAX::get_endpoint('refref'), 'bootstrap' => WC_AJAX::get_endpoint('refref_bootstrap')]) . ';', 'before'); }); add_shortcode('refref', [self::class, 'surface']); add_action('woocommerce_account_dashboard', static function () { echo wp_kses_post(self::surface()); }); add_action('woocommerce_thankyou', static function () { echo wp_kses_post(self::surface()); }); add_filter('wp_privacy_personal_data_exporters', static function ($exporters) { $exporters['refref'] = ['exporter_friendly_name' => 'RefRef', 'callback' => [self::class, 'export']]; return $exporters; }); add_filter('wp_privacy_personal_data_erasers', static function ($erasers) { $erasers['refref'] = ['eraser_friendly_name' => 'RefRef', 'callback' => [self::class, 'erase']]; return $erasers; }); } public static function bootstrap(): void { nocache_headers(); WC()->session->set_customer_session_cookie(true); $csrf = WC()->session->get('refref_csrf'); if (!$csrf) { $csrf = bin2hex(random_bytes(32)); WC()->session->set('refref_csrf', $csrf); } wp_send_json(['csrf' => $csrf, 'consent' => self::consent(), 'signedIn' => is_user_logged_in(), 'nonces' => ['woocommerce-login-nonce' => wp_create_nonce('woocommerce-login'), 'woocommerce-register-nonce' => wp_create_nonce('woocommerce-register'), 'woocommerce-process-checkout-nonce' => wp_create_nonce('woocommerce-process_checkout')]]); } public static function request(): void { nocache_headers(); $input = json_decode(file_get_contents('php://input'), true); $csrf = WC()->session ? WC()->session->get('refref_csrf') : null; // Compare the unslashed Origin exactly; sanitizing it could turn an invalid origin into an allowed one. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Strict origin equality below validates the complete value. $origin = isset($_SERVER['HTTP_ORIGIN']) ? wp_unslash($_SERVER['HTTP_ORIGIN']) : ''; $expected = wp_parse_url(home_url(), PHP_URL_SCHEME) . '://' . wp_parse_url(home_url(), PHP_URL_HOST); $port = wp_parse_url(home_url(), PHP_URL_PORT); if ($port) { $expected .= ':' . $port; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Exact method comparison validates the complete value. $method = isset($_SERVER['REQUEST_METHOD']) ? wp_unslash($_SERVER['REQUEST_METHOD']) : ''; if ($method !== 'POST' || $origin !== $expected || !$csrf || !is_array($input) || !is_string($input['csrf'] ?? null) || !hash_equals($csrf, $input['csrf'])) { wp_send_json(['error' => __('Refresh this page and try again.', 'refref')], 403); } try { $action = $input['action'] ?? ''; if ($action === 'withdraw') { if (WC()->cart) { foreach (WC()->cart->get_applied_coupons() as $code) { $coupon = new WC_Coupon($code); if ($coupon->get_meta('_refref_handle')) { WC()->cart->remove_coupon($code); } } } foreach (['refref_handle', 'refref_guest', 'refref_consent'] as $key) { WC()->session->__unset($key); } wp_send_json(['status' => 'cleared']); } if ($action === 'capture') { if (($input['consent'] ?? false) === true) { WC()->session->set('refref_consent', true); } if (!self::consent()) { wp_send_json(['error' => __('Allow referral tracking before applying this offer.', 'refref')], 403); } $payload = ['handoffToken' => (string) ($input['handoffToken'] ?? '')]; $handle = self::handle_id(); if ($handle !== '') { $payload['handle'] = $handle; } $result = RefRef_Transport::shopper('capture', $payload); WC()->session->set('refref_handle', $result['handle']); wp_send_json(['status' => 'captured']); } if ($action === 'offer') { if (!self::consent() || self::handle_id() === '') { wp_send_json(['status' => 'unavailable']); } $result = RefRef_Transport::shopper('offer', ['handle' => self::handle_id()]); $result['cartChanged'] = false; if (($result['status'] ?? '') === 'ready' && WC()->cart && !WC()->cart->has_discount($result['code'])) { if (!WC()->cart->apply_coupon($result['code'])) { throw new RuntimeException(esc_html__('The offer does not apply to this cart.', 'refref')); } $result['cartChanged'] = true; } wp_send_json($result); } if (!in_array($action, ['share', 'account'], true)) { throw new RuntimeException(esc_html__('Unknown referral action.', 'refref')); } if (!is_user_logged_in()) { wp_send_json(['status' => 'sign_in_required']); } $pagination = []; foreach (['referralsCursor', 'rewardsCursor'] as $key) { if (is_string($input[$key] ?? null) && strlen($input[$key]) <= 2048) { $pagination[$key] = $input[$key]; } } wp_send_json(RefRef_Transport::shopper($action, ['customerId' => (string) get_current_user_id()], $pagination)); } catch (Throwable $error) { wp_send_json(['error' => $error->getMessage()], 503); } } public static function surface(): string { if (!RefRef_Config::connection()) { return ''; } if (!is_user_logged_in()) { return '
' . esc_html__('Sign in to get your referral link', 'refref') . '
'; } return '