How to Display Shortcode in the correct location, currently displaying before content after Page Title?

Shortcode callbacks must return a string or nothing at all. They must not print anything directly, because that will create an immediate output as you have seen in your example. We have a few topics about that already. I took the liberty to reopen your question, because you said the other answers didn’t really help you. πŸ™‚

Almost every function that prints something directly (often called template tag in WordPress) has an equivalent that returns the string instead, or a parameter that changes the return behavior. the_date() is a notable exception.

You can find these functions either by looking at the source code in your local WordPress copy, or you go to the developer manual and search for the function that you want to replace. The string equivalent will be mentioned there.

Now, knowing that, how do you apply that to your own code? It is clear that you cannot use something like this anymore:

<?php 
// some code 
?>
some HTML
<?php
// more code
?>

… because that some HTML will be printed directly, and that’s exactly what we don’t want.

We have to write instead:

<?php 
// some code 

$output="some HTML";

// more code

return $output;
?>

And that means, we need the functions that return strings, insert these strings into the some HTML, and finally return the result as a string.

Here is how I would rewrite your example. Note that I didn’t test it. You might run into some errors that you have to fix later. πŸ™‚

function show_product_pages() {

    $post = get_post();

    // Just in case the shortcode has been used in the wrong context.
    if ( empty( $post ) ) {
        return;
    }

    $child_pages = new WP_Query([
        'post_type'   => 'page',
        'post_parent' => $post->ID,

    ]);

    // Always return as early as possible.
    if ( ! $child_pages->have_posts() ) {
        return;
    }

    $output="";

    while ( $child_pages->have_posts() ) {
        // Set up post data.
        $child_pages->the_post();

        // Keep the dynamic values and the HTML template a bit separated.
        // Makes both parts easier to read, and we don't have to call a 
        // function twice.
        $url     = apply_filters( 'the_permalink', get_permalink() );
        $thumb   = get_the_post_thumbnail( null, [240, 240]);
        $title   = the_title( '', '', FALSE );
        $content = apply_filters( 'the_content', get_the_content() );

        // Now we add the HTML to $output.
        $output .= '
        <div class="col col-md-4">
            <a href="' . $url . '">
                <div class="product_thumb">' . $thumb . '</div>
                <h3 class="product_title">' . $title . '</h3>
            </a>
            <div class="product-desc">' . $content . '</div>
            <a class="cta-link" href="' . $url . '">Learn More</a>
        </div>
        ';
    }

    // Clean up what we have done to the global context.
    wp_reset_postdata();

    // We add the container and return the whole string.
    return '<div class="columns product-archives">' . $output . '</div>';
}

And that’s it. There are ways to separate the dynamic logic and the HTML a bit more, but for such a simple case, it will do.