customer;$user=wp_get_current_user(); $values=[get_woocommerce_currency(),get_locale(),get_option('woocommerce_tax_display_shop'),get_option('woocommerce_prices_include_tax'), $customer ? [$customer->get_billing_country(),$customer->get_billing_state(),$customer->get_billing_postcode(), $customer->get_shipping_country(),$customer->get_shipping_state(),$customer->get_shipping_postcode(),$customer->get_is_vat_exempt()] : [], $user->ID,array_values($user->roles)]; return hash_hmac('sha256',wp_json_encode($values),Chat4U_Protocol::connection()['secret']); } public static function origin(): bool { $origin=isset($_SERVER['HTTP_ORIGIN']) ? (string)$_SERVER['HTTP_ORIGIN'] : ''; return $origin!=='' && hash_equals(Chat4U_Protocol::origin(home_url('/')),untrailingslashit($origin)); } public static function nonce(): void { if (!self::origin()) { wp_send_json_error(['message'=>'Open this form on the store website.'],403); } nocache_headers();wp_send_json(['nonce'=>wp_create_nonce('wp_rest')]); } public static function bootstrap(WP_REST_Request $request) { if (!self::origin() || !Chat4U_Commerce::available()) { return new WP_Error('chat4u_context_invalid','Store context is unavailable.',['status'=>403]); } $challenge=$request->get_param('challenge');$state=Chat4U_Protocol::connection(); if (!is_array($challenge) || !is_string($challenge['payload'] ?? null) || strlen($challenge['payload'])>4096 || !is_string($challenge['signature'] ?? null) || empty($state['secret']) || !hash_equals(hash_hmac('sha256',"woo-bootstrap-v1\n".$challenge['payload'],$state['secret']),$challenge['signature'])) { return new WP_Error('chat4u_context_invalid','Refresh the store page to continue.',['status'=>403]); } $claims=json_decode(base64_decode($challenge['payload'],true) ?: '',true); if (!is_array($claims) || ($claims['installation_id'] ?? '')!==($state['scope'] ?? '') || ($claims['generation'] ?? null)!==($state['generation'] ?? null) || ($claims['origin'] ?? '')!==Chat4U_Protocol::origin(home_url('/')) || ($claims['exp'] ?? 0)time()+60 || !preg_match('/^[a-f0-9]{32}$/D',$claims['nonce'] ?? '')) { return new WP_Error('chat4u_context_invalid','Refresh the store page to continue.',['status'=>403]); } $user=(int)wp_validate_auth_cookie('','logged_in'); if ($user && !wp_verify_nonce($request->get_header('X-WP-Nonce'),'wp_rest')) { return new WP_Error('chat4u_context_invalid','Refresh your store sign-in to continue.',['status'=>403]); } $used='chat4u_woo_nonce_'.$claims['nonce']; if (!add_option($used,time()+120,'',false)) { return new WP_Error('chat4u_context_invalid','Refresh the store page to continue.',['status'=>409]); } $native=new WP_REST_Request('GET','/wc/store/v1/cart'); $response=rest_do_request($native); if (is_wp_error($response) || $response->get_status()!==200) { return new WP_Error('chat4u_cart_unavailable','Your native cart could not be checked.',['status'=>503]); } $headers=$response->get_headers();$token=$headers['Cart-Token'] ?? $headers['cart-token'] ?? null; if (!$token) { return new WP_Error('chat4u_cart_unavailable','Your native cart could not be checked.',['status'=>503]); } if (WC()->session instanceof WC_Session_Handler) { WC()->session->set_customer_session_cookie(true); } $handle=bin2hex(random_bytes(32));$data=json_decode(wp_json_encode($response->get_data()),true); $csrf=Chat4U_Commerce_Orders::csrf(); $recovery=hash_hmac('sha256',"woo-recovery-v1\n".$state['scope'].'|'.$state['generation'].'|'.$csrf,$state['secret']); $cart_identity=hash_hmac('sha256',$state['generation'].'|'.WC()->session->get_customer_id(),$state['secret']); $measurement=Chat4U_Commerce_Measurement::allowed(); $consent_epoch=Chat4U_Commerce_Measurement::observe($cart_identity,$measurement); Chat4U_Commerce_Storage::put('handle:'.hash('sha256',$handle),'handle',[ 'context_hash'=>self::fingerprint(),'measurement_allowed'=>$measurement,'measured_at'=>time(),'consent_epoch'=>$consent_epoch,'token'=>$token,'user'=>$user,'session_token'=>$user ? wp_get_session_token() : null, 'generation'=>$state['generation'],'subject'=>$claims['subject'],'recovery_ref'=>$recovery,'locale'=>get_locale(), 'language'=>function_exists('pll_current_language') ? pll_current_language('slug') : apply_filters('wpml_current_language',null), 'cart_identity'=>hash_hmac('sha256',$state['generation'].'|'.WC()->session->get_customer_id(),$state['secret'])],900); $proof=['context_hash'=>self::fingerprint(),'recovery_ref'=>$recovery,'measurement_allowed'=>Chat4U_Commerce_Measurement::allowed(),'browser_csrf'=>$csrf,'shopper_proof'=>Chat4U_Commerce_Orders::identity((string)($claims['identity_challenge'] ?? '')),'handle'=>$handle,'subject'=>$claims['subject'],'nonce'=>$claims['nonce'],'exp'=>time()+60, 'cart_identity'=>hash_hmac('sha256',$state['generation'].'|'.WC()->session->get_customer_id(),$state['secret']), 'currency'=>$data['totals']['currency_code'] ?? get_woocommerce_currency(), 'country'=>WC()->customer ? WC()->customer->get_billing_country() : wc_get_base_location()['country'], 'locale'=>get_locale(),'empty'=>empty($data['items'])]; $encoded=base64_encode(wp_json_encode($proof,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)); return new WP_REST_Response(['payload'=>$encoded,'signature'=>hash_hmac('sha256',"woo-native-proof-v1\n".$encoded,$state['secret'])],200,['Cache-Control'=>'no-store']); } public static function with_handle(string $handle,callable $callback) { if (!preg_match('/^[a-f0-9]{64}$/D',$handle)) { throw new RuntimeException('Native context expired'); } $value=Chat4U_Commerce_Storage::get('handle:'.hash('sha256',$handle));$state=Chat4U_Protocol::connection(); if (!$value || $value['generation']!==$state['generation'] || ($value['user'] && !WP_Session_Tokens::get_instance($value['user'])->verify($value['session_token']))) { throw new RuntimeException('Native context expired'); } $before=get_current_user_id();wp_set_current_user($value['user']); $switched=!empty($value['locale']) && switch_to_locale($value['locale']); $language=apply_filters('wpml_current_language',null); if (defined('ICL_SITEPRESS_VERSION') && !empty($value['language'])) { do_action('wpml_switch_language',$value['language']); } try { return $callback($value); } finally { if (defined('ICL_SITEPRESS_VERSION') && $language) { do_action('wpml_switch_language',$language); } if ($switched) { restore_previous_locale(); } wp_set_current_user($before); } } public static function native(array $context,string $method,string $route,array $params=[]): WP_REST_Response { // Store API deliberately reads the outer HTTP token, including on internal dispatch. $_SERVER['HTTP_CART_TOKEN']=$context['token']; $request=new WP_REST_Request($method,'/wc/store/v1/'.$route);$request->set_header('Cart-Token',$context['token']); foreach ($params as $key=>$value) { $request->set_param($key,$value); } $response=rest_do_request($request); if (is_wp_error($response) || ($response->get_status()<200 || $response->get_status()>=300)) { throw new RuntimeException('Native store request was not confirmed'); } if (isset($context['context_hash']) && !hash_equals($context['context_hash'],self::fingerprint())) { throw new RuntimeException('The store price context changed'); } return $response; } }