reservation = $reservation; } /** * Create a WC order from a completed agent session. * * @param string $session_id The agent session UUID. * * @return int WC order ID. * * @throws \InvalidArgumentException If session not found. * @throws \RuntimeException If session is not in a valid state. */ public function create_from_session( string $session_id ): int { global $wpdb; // Safe: internal $wpdb->prefix table names; user values use placeholders. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $session = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}aicommerce_agent_sessions WHERE session_id = %s", $session_id ), ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter if ( ! $session ) { throw new \InvalidArgumentException( esc_html( "Session not found: {$session_id}" ) ); } if ( 'pending' !== $session['status'] && 'processing' !== $session['status'] ) { throw new \RuntimeException( esc_html( "Session {$session_id} is not in a valid state: {$session['status']}" ) ); } // Mark as processing to prevent duplicate order creation. // Safe: internal $wpdb->prefix table names; user values use placeholders. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->update( $wpdb->prefix . 'aicommerce_agent_sessions', array( 'status' => 'processing' ), array( 'session_id' => $session_id ), array( '%s' ), array( '%s' ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $cart_items = json_decode( $session['cart_items'], true ); if ( ! is_array( $cart_items ) || empty( $cart_items ) ) { throw new \RuntimeException( esc_html( "Session {$session_id} has no cart items" ) ); } $order = wc_create_order( array( 'status' => 'pending', 'customer_id' => 0, ) ); if ( is_wp_error( $order ) ) { throw new \RuntimeException( esc_html( 'Failed to create WC order: ' . $order->get_error_message() ) ); } foreach ( $cart_items as $item ) { $product_id = ! empty( $item['variation_id'] ) ? $item['variation_id'] : $item['product_id']; $product = wc_get_product( $product_id ); if ( ! $product ) { continue; } $order->add_product( $product, (int) $item['quantity'] ); } $order->set_payment_method( 'aicommerce_agent' ); $order->set_payment_method_title( 'AI Agent (Agentic Commerce)' ); $order->set_currency( $session['currency'] ?? get_woocommerce_currency() ); $order->add_order_note( sprintf( /* translators: %s: agent identity hash */ __( 'Order created by AI agent via Tyralix AI Discoverability MCP endpoint. Agent: %s', 'ai-discoverability-kit' ), $session['agent_identity'] ?? 'unknown' ) ); $order->update_meta_data( '_aicommerce_agent_session', $session_id ); $order->update_meta_data( '_aicommerce_agent_identity', $session['agent_identity'] ?? '' ); $order->calculate_totals(); $order->payment_complete(); $order->save(); // Update session with order ID and completed status. // Safe: internal $wpdb->prefix table names; user values use placeholders. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->update( $wpdb->prefix . 'aicommerce_agent_sessions', array( 'status' => 'completed', 'wc_order_id' => $order->get_id(), ), array( 'session_id' => $session_id ), array( '%s', '%d' ), array( '%s' ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter // Mark reservation as completed — stock stays reduced (items sold). if ( $this->reservation ) { $this->reservation->mark_completed( $session_id ); } return $order->get_id(); } }