false, 'cart_assistance' => false, 'checkout_handoff' => false, 'coupon_assistance' => false, 'order_status' => false, 'purchase_evidence' => false, 'revision' => 1], (array) get_option(self::OPTIONS, [])); } public static function enabled(string $capability): bool { $policy=get_option('chat4u_commerce_policy',[]);$state=Chat4U_Protocol::connection(); $allowed=($state['state'] ?? '')==='connected' && self::compatible() && self::public_store() && !empty(self::settings()[$capability]); $allowed=$allowed && ($policy['generation'] ?? null)===($state['generation'] ?? null) && !empty($policy['capabilities'][$capability]); return $allowed; } public static function manifest(): array { $options = self::settings(); return ['adapter' => 'woocommerce', 'protocol' => 1, 'available' => self::compatible() && self::public_store(), 'unavailable_reason'=>self::available() && !self::public_store() ? 'store_not_public' : (self::available() && !self::compatible() ? 'woocommerce_version_unqualified' : null), 'version' => defined('WC_VERSION') ? WC_VERSION : null, 'revision' => $options['revision'], 'catalog_supported'=>self::available() && Chat4U_Commerce_Catalog::supported_store(), 'authorized' => array_intersect_key($options, array_flip(['catalog', 'cart_assistance', 'checkout_handoff', 'coupon_assistance', 'order_status', 'purchase_evidence'])), 'measurement_supported'=>self::available() && Chat4U_Commerce_Measurement::supported() && Chat4U_Commerce_Payments::available(), 'payment_evidence_adapters'=>Chat4U_Commerce_Payments::available() ? ['woo-stripe-charge-v1'] : [], 'order_mail_health'=>get_option('chat4u_order_mail_health',[]), 'account_url'=>self::available() ? wc_get_page_permalink('myaccount') : null, 'product_types' => ['simple', 'variable'], 'cart_url'=>self::available() ? wc_get_cart_url() : null, 'checkout_url'=>self::available() ? wc_get_checkout_url() : null, 'native_checkout' => true]; } public static function save(): void { if (!current_user_can('manage_options') || !current_user_can('manage_woocommerce')) { wp_die(esc_html__('You cannot manage this store connection.', 'chat4u')); } check_admin_referer('chat4u_commerce'); $prior = self::settings(); $next = []; foreach (['catalog', 'cart_assistance', 'checkout_handoff', 'coupon_assistance', 'order_status', 'purchase_evidence'] as $key) { $next[$key] = !empty($_POST[$key]); } if ($next['coupon_assistance'] && !$next['cart_assistance']) { wp_die(esc_html__('Enable cart assistance before coupon assistance.', 'chat4u')); } $next['revision'] = $prior['revision'] + 1; update_option(self::OPTIONS, $next, false); if (($prior['cart_assistance'] && !$next['cart_assistance']) || ($prior['order_status'] && !$next['order_status']) || ($prior['purchase_evidence'] && !$next['purchase_evidence'])) { do_action('chat4u_commerce_revoke', $prior, $next); } wp_safe_redirect(admin_url('options-general.php?page=chat4u')); exit; } public static function admin(): void { if (!self::available() || !current_user_can("manage_woocommerce")) { return; } echo '

' . esc_html__('WooCommerce assistance', 'chat4u') . '

' . esc_html__('Your public store products share your WordPress knowledge source. Enable only the store access you want to allow. Shoppers complete payment in your store.', 'chat4u') . '

'; echo '
'; wp_nonce_field('chat4u_commerce'); $labels = ['catalog' => 'Include public store products', 'cart_assistance' => 'Allow confirmed native cart changes', 'checkout_handoff' => 'Offer native checkout links', 'coupon_assistance' => 'Apply or remove shopper-provided coupon codes', 'order_status' => 'Allow signed-in and verified guest order assistance', 'purchase_evidence' => 'Measure purchases linked to chat with visitor permission']; foreach ($labels as $key => $label) { echo ''; } echo '

' . esc_html__('Order help reads only the authorized shopper’s order. Optional purchase measurement needs a supported consent setup. Chat4U’s privacy information and your store’s notices must describe the enabled processing.', 'chat4u') . '

'; echo '
'; } public static function changed(int $id): void { if (!self::available()) { return; } $product = wc_get_product($id); if (!$product) { return; } $parent = $product->is_type('variation') ? $product->get_parent_id() : $id; update_post_meta($parent, '_chat4u_commerce_revision', wp_generate_uuid4()); Chat4U_Changes::commerce($parent, !Chat4U_Commerce_Catalog::public_product(wc_get_product($parent))); } public static function deleted(int $id, $post): void { if (!self::available() || !$post instanceof WP_Post || !in_array($post->post_type, ['product','product_variation'], true)) { return; } if ($post->post_type === 'product') { Chat4U_Changes::commerce($id, true); } elseif ($post->post_parent) { self::changed((int) $post->post_parent); } } public static function policy(WP_REST_Request $request) { global $wpdb;$state=Chat4U_Protocol::connection();$body=$request->get_json_params(); if (!is_int($body['revision'] ?? null) || $body['revision']<0 || !is_array($body['capabilities'] ?? null)) { return new WP_Error('chat4u_policy_invalid','Invalid store policy.',['status'=>422]); } $lock='c4u-policy:'.substr(hash('sha256',$state['scope']),0,48); if ((int)$wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s,1)',$lock))!==1) { return new WP_Error('chat4u_policy_busy','Store policy is updating.',['status'=>409]); } try { wp_cache_delete('chat4u_commerce_policy','options');$prior=get_option('chat4u_commerce_policy',[]); if (($prior['generation'] ?? null)===($state['generation'] ?? null) && ($prior['revision'] ?? -1)>$body['revision']) { return Chat4U_Transfer::response($request,['current'=>true,'revision'=>$prior['revision']]); } $next=['generation'=>$state['generation'],'revision'=>$body['revision'],'capabilities'=>[]]; foreach (['catalog','cart_assistance','checkout_handoff','coupon_assistance','order_status','purchase_evidence'] as $key) { $next['capabilities'][$key]=($body['capabilities'][$key] ?? false)===true; } if ($prior && $prior['generation']===$next['generation'] && $prior['revision']===$next['revision'] && $prior['capabilities']!==$next['capabilities']) { return new WP_Error('chat4u_policy_changed','Refresh the current store policy.',['status'=>409]); } Chat4U_Commerce_Storage::revoke($prior['capabilities'] ?? [],$next['capabilities']); update_option('chat4u_commerce_policy',$next,false);return Chat4U_Transfer::response($request,['current'=>true,'revision'=>$next['revision']]); } finally { $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)',$lock)); } } public static function register(): void { register_rest_route('chat4u/v1','/commerce/context',['methods'=>'POST','permission_callback'=>'__return_true', 'callback'=>['Chat4U_Commerce_Context','bootstrap']]); register_rest_route('chat4u/v1','/commerce/policy',['methods'=>'POST','permission_callback'=>['Chat4U_Protocol','authorize'],'callback'=>[self::class,'policy']]); register_rest_route('chat4u/v1','/commerce/measurement-withdraw',['methods'=>'POST','permission_callback'=>'__return_true','callback'=>['Chat4U_Commerce_Measurement','withdraw']]); foreach (['measurement-revoke'=>'revoke','measurement'=>'marker','outcomes'=>'events','outcomes-ack'=>'ack'] as $route=>$method) { register_rest_route('chat4u/v1','/commerce/'.$route,['methods'=>'POST','permission_callback'=>['Chat4U_Protocol','authorize'],'callback'=>['Chat4U_Commerce_Measurement',$method]]); } register_rest_route('chat4u/v1','/commerce/order-code',['methods'=>'POST','permission_callback'=>'__return_true','callback'=>['Chat4U_Commerce_Orders','code']]); foreach (['orders'=>'read','order-proof'=>'proof'] as $route=>$method) { register_rest_route('chat4u/v1','/commerce/'.$route,['methods'=>'POST','permission_callback'=>['Chat4U_Protocol','authorize'],'callback'=>['Chat4U_Commerce_Orders',$method]]); } foreach (['cart','action','product','review','recommendations','delivery'] as $operation) { register_rest_route('chat4u/v1','/commerce/'.$operation,['methods'=>'POST','permission_callback'=>['Chat4U_Protocol','authorize'], 'callback'=>static function($request) use ($operation) { return Chat4U_Commerce_Cart::read($request,$operation); }]); } foreach (['catalog' => 'page', 'family' => 'family'] as $route => $callback) { register_rest_route('chat4u/v1', '/commerce/' . $route, ['methods' => 'GET', 'permission_callback' => ['Chat4U_Protocol', 'authorize'], 'callback' => static function ($request) use ($callback) { if (!self::enabled('catalog')) { return new WP_Error('chat4u_commerce_unavailable', __('Store product access is disabled.', 'chat4u'), ['status' => 403]); } try { return Chat4U_Transfer::response($request, Chat4U_Commerce_Catalog::$callback($request)); } catch (LengthException $error) { return new WP_Error("chat4u_record_too_large","The complete product family is too large to publish.",["status"=>413]); } }]); } } }