Using Posts as Products for Shopping Cart and Sales in WooCommerce

Utilizing articles as products to add to the shopping cart and sell may not be a common practice on e-commerce sites, but it is a solution worth considering, especially when viewed from an SEO perspective. Articles often have better SEO potential than traditional product pages, offering various benefits for content marketing strategies.

Why Is This?

Google has its own ad ecosystem and significant revenue from it, so direct SEO for products may affect their ad revenue. Search algorithms can be balanced to optimize SEM ad revenue. In my opinion, this partially explains why many affiliate marketers, especially those on platforms like Amazon or AliExpress, often prefer SEO approaches through reviews and guides.

To integrate WordPress posts as sellable products via WooCommerce, this guide will show you how to add posts to the cart. Users can add posts to the cart and proceed with checkout like other WooCommerce products.

Integration Steps

Create a Virtual Product for the Post

First, create a “placeholder” product as a template for the posts to be sold:

  • Use the polydev_create_placeholder_product() function to check if the placeholder product exists. If not, this function will create a new product with hidden attributes in the WooCommerce catalog.
  • This virtual product serves as a storage frame for necessary information and values so WooCommerce can recognize relevant components when the post is added to the cart. This method effectively limits the need to hook into too many WooCommerce core functions, which can be challenging to control during Woo updates. Feel free to share alternative methods if you have any!

Function to Add Post to Cart

Use polydev_add_code_to_cart_with_placeholder_product() to:

  • Retrieve the sale_price value from the post’s meta information. This information can be customized using ACF or a custom metabox.
  • The function handles storing custom variables, such as custom_type, custom_price, and post_id, for processing within other hook functions. You can adjust this section if you want to integrate other custom post types.

Example: Integrate into a shortcode or block for easy insertion into any post, like the “Add code to cart” display below. Test it to see if adding the post to the cart works similarly to a product!

Recently Updated
Included
  • Lifetime support
  • Future updates
  • Free installation
  • Paid customization
Add code to cart$8.99$13.99
Last Update
Published

Display Post Information in Cart and Order

To correctly display the post in the cart and order, additional handling is needed based on saved variables. These functions replace the default virtual product information.

  • Post Title and Price: Use polydev_update_cart_item_data_with_post_info() to update the title and price in the cart.
  • Post Thumbnail: Use polydev_custom_cart_item_thumbnail() to display the post thumbnail instead of the default product image. This hook is unnecessary if the default image is sufficient.
  • Post Link: Replace the virtual product link with the post link using polydev_custom_cart_item_permalink().

Display Order Information in Admin

WooCommerce will store and display post information in the order:

  • Save Post Meta: Use polydev_save_post_data_to_order_item() to save the post ID and type for each product in the order.
  • Display Post Categories: Customize the order information to show post categories in WooCommerce Admin using polydev_modify_order_item_meta_data().

Customize BACS Instructions for Orders

Use custom_bacs_instructions_with_order_id() to display bank transfer instructions upon order completion, reminding customers to note the Order ID in their payment details.

By default, WooCommerce supports this function. However, if you want to include order-related information, such as the Order ID, for bank transfer payments, you can customize this section here. For added functionality, you might enhance this part when writing plugins for easy management.

Add a DOWNLOAD Button for Premium Content

If the post contains premium content, use this function for handling. For example, this blog shares codes, some of which are advanced and available for purchase before download. This function can compress premium content for download. You can integrate additional file uploads like zip, doc, media, or anything you wish to sell.

  • Use polydev_add_download_code_button_to_order() to add a download button for the post after the order is completed.
  • Premium content from the post will be downloadable as a file when the user clicks the “Download code” button.

Consider adding features like password-protected compression or API connections to Google Drive, OneDrive, or access to your premium folders.

Additional Customizations: Notifications and Cart Information

Finally, ensure consistent information for posts across features like deleting posts and adding posts already in the cart by hooking the following functions. Without these steps, WooCommerce’s placeholder product information could degrade user experience.

  • Error Messages: Customize messages when users add duplicate posts to the cart with polydev_custom_modify_cart_error_message().
  • Product Removal Notification: Remove WooCommerce’s default notice when the placeholder product is deleted and replace it with the actual post title using polydev_remove_cart_notices() and polydev_custom_cart_item_removed().

Advantages and Disadvantages

This integration allows WordPress posts to be sold directly via WooCommerce without creating separate products. Users can easily add posts to the cart, checkout, and download premium content. Additionally, you can use standard product features on the site. Independent hook handling ensures the cart accepts any combination of products, posts, or custom post types you integrate.

A downside is that sales reports won’t reflect individual post sales, as only the virtual product’s sales are recorded. However, you can extend reporting by hooking into these WooCommerce components if desired!

define('POLYDEV_PLACEHOLDER_PRODUCT_TITLE', 'PolyDev Product Template');

function polydev_create_placeholder_product() {
    $query = new WP_Query([
        'title'         => POLYDEV_PLACEHOLDER_PRODUCT_TITLE,
        'post_type'     => 'product',
        'post_status'   => 'any',
        'posts_per_page'=> 1
    ]);

    if ($query->have_posts()) {
        $existing_product = $query->post;
        wp_reset_postdata();
        return $existing_product->ID;
    }

    $product = new WC_Product();
    $product->set_name(POLYDEV_PLACEHOLDER_PRODUCT_TITLE);
    $product->set_status('private');
    $product->set_catalog_visibility('hidden');
    $product->set_price(0);
    $product->set_regular_price(0);
    $product->set_sold_individually(true);
    $product->save();

    return $product->get_id();
}

function polydev_add_code_to_cart_with_placeholder_product() {
    if (isset($_GET['add_code_to_cart']) && isset($_GET['post_id'])) {
        $post_id = intval($_GET['post_id']);
        $preview_settings = get_post_meta($post_id, '_polydev_preview_settings', true);
        $sale_price = isset($preview_settings['sale_price']) ? floatval($preview_settings['sale_price']) : 0;

        if ($sale_price > 0) {
            $placeholder_product_id = polydev_create_placeholder_product();
            WC()->cart->add_to_cart(
                $placeholder_product_id,
                1,
                '',
                '',
                array(
                    'custom_type' => 'post',
                    'post_id' => $post_id,
                    'custom_price' => $sale_price
                )
            );
            wp_redirect(wc_get_cart_url());
            exit();
        }
    }
}
add_action('template_redirect', 'polydev_add_code_to_cart_with_placeholder_product');

function polydev_handle_order_again_custom_items($cart_item_data, $item, $order) {
    $post_id = $item->get_meta('post_id');
    if ($post_id !== null) {
        $preview_settings = get_post_meta($post_id, '_polydev_preview_settings', true);
        $sale_price = isset($preview_settings['sale_price']) ? floatval($preview_settings['sale_price']) : 0;
        if ($sale_price > 0 && $post_id) {
            WC()->cart->empty_cart();
            $placeholder_product_id = polydev_create_placeholder_product();
            WC()->cart->add_to_cart(
                $placeholder_product_id,
                1,
                '',
                '',
                array(
                    'custom_type' => 'post',
                    'post_id' => $post_id,
                    'custom_price' => $sale_price
                )
            );
            wp_redirect(wc_get_cart_url());
            exit();
        }
    }
}
add_filter('woocommerce_order_again_cart_item_data', 'polydev_handle_order_again_custom_items', 10, 3);

function custom_bacs_instructions_with_order_id($order_id) {
    $order = wc_get_order($order_id);
    if ($order && $order->get_payment_method() === 'bacs') {
        echo '<h2>' . __('Payment Instructions', 'polydev') . '</h2>';
        echo '<p>' . sprintf(
            __('Please make the payment to our bank account and use Order ID <strong>#%s</strong> as the payment reference. Once we receive your payment, the download link for the product will be updated in the order details.', 'polydev'),
            $order_id
        ) . '</p>';
    }
}
add_action('woocommerce_thankyou_bacs', 'custom_bacs_instructions_with_order_id', 10, 1);

function polydev_add_cart_item_data($cart_item_data, $product_id) {
    if (isset($_GET['add_code_to_cart']) && isset($_GET['post_id'])) {
        $post_id = intval($_GET['post_id']);
        $cart_item_data['custom_type'] = 'post';
        $cart_item_data['post_id'] = $post_id;
        $cart_item_data['unique_key'] = md5($product_id . '_' . $post_id);
    }
    return $cart_item_data;
}
add_filter('woocommerce_add_cart_item_data', 'polydev_add_cart_item_data', 10, 2);

function polydev_save_post_data_to_order_item($item_id, $values, $cart_item_key) {
    if (isset($values['custom_type']) && $values['custom_type'] === 'post') {
        wc_add_order_item_meta($item_id, 'custom_type', 'post');
        wc_add_order_item_meta($item_id, 'post_id', $values['post_id']);
    }
}
add_action('woocommerce_add_order_item_meta', 'polydev_save_post_data_to_order_item', 10, 3);

function polydev_update_cart_item_data_with_post_info($cart_object) {
    foreach ($cart_object->get_cart() as $cart_item) {
        if (isset($cart_item['custom_type']) && $cart_item['custom_type'] === 'post') {
            $post_id = $cart_item['post_id'];
            $custom_price = floatval($cart_item['custom_price']);
            $cart_item['data']->set_name(get_the_title($post_id));
            $cart_item['data']->set_price($custom_price);
        }
    }
}
add_action('woocommerce_before_calculate_totals', 'polydev_update_cart_item_data_with_post_info', 10, 1);

function polydev_custom_cart_item_name_display($name, $cart_item, $cart_item_key) {
    if (isset($cart_item['custom_type']) && $cart_item['custom_type'] === 'post') {
        $post_id = $cart_item['post_id'];
        $name = get_the_title($post_id);
    }
    return $name;
}
add_filter('woocommerce_cart_item_name', 'polydev_custom_cart_item_name_display', 10, 3);

function polydev_custom_cart_item_price_display($price, $cart_item, $cart_item_key) {
    if (isset($cart_item['custom_type']) && $cart_item['custom_type'] === 'post') {
        $price = wc_price($cart_item['custom_price']);
    }
    return $price;
}
add_filter('woocommerce_cart_item_price', 'polydev_custom_cart_item_price_display', 10, 3);

function polydev_custom_cart_item_permalink($url, $cart_item, $cart_item_key) {
    if (isset($cart_item['custom_type']) && $cart_item['custom_type'] === 'post') {
        $post_id = $cart_item['post_id'];
        $url = get_permalink($post_id);
    }
    return $url;
}
add_filter('woocommerce_cart_item_permalink', 'polydev_custom_cart_item_permalink', 10, 3);

function polydev_custom_order_item_permalink($url, $item, $order) {
    if (isset($item['custom_type']) && $item['custom_type'] === 'post') {
        $post_id = $item['post_id'];
        $url = get_permalink($post_id);
    }
    return $url;
}
add_filter('woocommerce_order_item_permalink', 'polydev_custom_order_item_permalink', 10, 3);

function polydev_custom_admin_order_item_permalink($url, $item, $order) {
    if (isset($item['custom_type']) && $item['custom_type'] === 'post') {
        $post_id = $item['post_id'];
        $url = get_permalink($post_id);
    }
    return $url;
}
add_filter('woocommerce_admin_order_item_permalink', 'polydev_custom_admin_order_item_permalink', 10, 3);

function polydev_modify_order_item_meta_data($formatted_meta, $item) {
    $custom_type = wc_get_order_item_meta($item->get_id(), 'custom_type', true);
    $post_id = wc_get_order_item_meta($item->get_id(), 'post_id', true);
    if ($custom_type === 'post' && $post_id) {
        $polydev_exists = false;
        foreach ($formatted_meta as $key => $meta) {
            if ($meta->key === 'polydev') {
                $polydev_exists = true;
            }
            if ($meta->key === 'custom_type') {
                unset($formatted_meta[$key]);
            }
            if ($meta->key === 'post_id') {
                $categories = get_the_terms($post_id, 'category');
                if ($categories && !is_wp_error($categories)) {
                    $category_links = array_map(function ($cat) {
                        return '<a href="' . esc_url(get_term_link($cat)) . '" target="_blank">' . esc_html($cat->name) . '</a>';
                    }, $categories);
                    $category_list = implode(', ', $category_links);
                    $formatted_meta[$key]->display_key = 'Category';
                    $formatted_meta[$key]->display_value =  $category_list;
                } else {
                    $formatted_meta[$key]->display_key = 'Category';
                    $formatted_meta[$key]->display_value = 'No category assigned';
                }
            }
        }
    }
    return $formatted_meta;
}
add_filter('woocommerce_order_item_get_formatted_meta_data', 'polydev_modify_order_item_meta_data', 10, 2);

function polydev_custom_admin_order_item_link($item_id, $item, $order) {
    $post_id = wc_get_order_item_meta($item_id, 'post_id', true);
    $custom_type = wc_get_order_item_meta($item_id, 'custom_type', true);
    if ($custom_type === 'post' && $post_id) {
        echo '<div class="polydev-custom-order-item">';
        $post_title = get_the_title($post_id);
        $post_url = get_permalink($post_id);
        $thumbnail = get_the_post_thumbnail($post_id, 'thumbnail');
        if ($thumbnail) {
            echo '<div class="polydev-woocommerce-order-item-thumbnail">' . $thumbnail . '</div>';
        }
        echo '<a href="' . esc_url($post_url) . '" target="_blank">' . esc_html($post_title) . '</a>';
        echo '<a href="' . esc_url(admin_url('post.php?post=' . $post_id . '&action=edit')) . '" target="_blank"><i class="dashicons dashicons-edit"></i></a>';
        echo '<style>.wc-order-item-name { display: none; }.thumb { display: none!important; }</style>';
    }
}
add_action('woocommerce_before_order_itemmeta', 'polydev_custom_admin_order_item_link', 10, 3);

function polydev_wrap_order_item_end($item_id, $item, $order) {
    echo '</div>';
}
add_action('woocommerce_after_order_itemmeta', 'polydev_wrap_order_item_end', 10, 3);

function polydev_custom_modify_cart_error_message($error) {
    if (strpos($error, POLYDEV_PLACEHOLDER_PRODUCT_TITLE) !== false && isset($_GET['post_id'])) {
        $current_post_id = intval($_GET['post_id']);
        foreach (WC()->cart->get_cart() as $cart_item) {
            if (isset($cart_item['custom_type']) && $cart_item['custom_type'] === 'post' && $cart_item['post_id'] == $current_post_id) {
                $post_title = get_the_title($current_post_id);
                $error = sprintf(__('You cannot add another "%s" to your cart.', 'polydev'), $post_title);
                break;
            }
        }
    }
    return $error;
}
add_filter('woocommerce_add_error', 'polydev_custom_modify_cart_error_message');

function polydev_remove_cart_notices() {
    if (is_cart()) {
        ?>
        <script type="text/javascript">
            function removePolyDevNotices() {
                const messages = document.querySelectorAll('.woocommerce-message');
                messages.forEach(message => {
                    if (message.innerText.includes('<?php echo POLYDEV_PLACEHOLDER_PRODUCT_TITLE?>')) {
                        message.remove();
                    }
                });
            }
            document.addEventListener('DOMContentLoaded', function() {
                removePolyDevNotices();
                jQuery(document.body).on('updated_wc_div', function() {
                    removePolyDevNotices();
                });
            });
        </script>
        <?php
    }
}
add_action('wp_footer', 'polydev_remove_cart_notices');

function polydev_custom_cart_item_removed($cart_item_key, $cart) {
    if (isset($cart->removed_cart_contents[$cart_item_key]['custom_type']) && $cart->removed_cart_contents[$cart_item_key]['custom_type'] === 'post') {
        $post_id = $cart->removed_cart_contents[$cart_item_key]['post_id'];
        $post_title = get_the_title($post_id);
        wc_add_notice(sprintf(__('"%s" has been removed from your cart.', 'polydev'), $post_title), 'success');
    }
}
add_action('woocommerce_cart_item_removed', 'polydev_custom_cart_item_removed', 10, 2);

function polydev_custom_cart_item_thumbnail($thumbnail, $cart_item, $cart_item_key) {
    if (isset($cart_item['custom_type']) && $cart_item['custom_type'] === 'post') {
        $post_id = $cart_item['post_id'];
        $post_thumbnail = get_the_post_thumbnail($post_id, 'thumbnail');
        if ($post_thumbnail) {
            $thumbnail = $post_thumbnail;
        }
    }
    return $thumbnail;
}
add_filter('woocommerce_cart_item_thumbnail', 'polydev_custom_cart_item_thumbnail', 10, 3);

function polydev_custom_cart_item_name($name, $cart_item, $cart_item_key) {
    if (isset($cart_item['custom_type']) && $cart_item['custom_type'] === 'post') {
        $post_id = $cart_item['post_id'];
        $post_title = get_the_title($post_id);
        $post_url = get_permalink($post_id);
        $name = '<a href="' . esc_url($post_url) . '" target="_blank">' . esc_html($post_title) . '</a>';
    }
    return $name;
}
add_filter('woocommerce_cart_item_name', 'polydev_custom_cart_item_name', 10, 3);

function polydev_add_download_code_button_to_order($item_id, $item, $order) {
    if ($order->has_status('completed')) {
        $post_id = $item->get_meta('post_id');
        if (!empty($post_id)) {
            $code_blocks = get_post_meta($post_id, '_polydev_code_blocks', true);
            $has_premium_content = false;
            if (!empty($code_blocks) && is_array($code_blocks)) {
                foreach ($code_blocks as $block) {
                    if (isset($block['is_premium']) && $block['is_premium'] === 1) {
                        $has_premium_content = true;
                        break;
                    }
                }
            }
            if ($has_premium_content) {
                $download_url = add_query_arg(array(
                    'polydev_code' => '1',
                    'post_id' => $post_id,
                    'order_id' => $order->get_id()
                ), home_url());
                echo '<p><a href="' . esc_url($download_url) . '" class="button">Download code</a></p>';
            }
        }
    }
}
add_action('woocommerce_order_item_meta_end', 'polydev_add_download_code_button_to_order', 10, 3);

function handle_polydev_code_download() {
    if (!isset($_GET['polydev_code']) || !isset($_GET['post_id']) || !isset($_GET['order_id'])) {
        return;
    }
    $order_id = intval($_GET['order_id']);
    $post_id = intval($_GET['post_id']);
    $order = wc_get_order($order_id);
    if (!$order || $order->get_user_id() !== get_current_user_id() || !$order->has_status('completed')) {
        wp_safe_redirect(wc_get_account_endpoint_url('orders'));
        exit;
    }
    $code_blocks = get_post_meta($post_id, '_polydev_code_blocks', true);
    $premium_code_content = array();
    $language_extensions = array(
        'htmlmixed' => 'html',
        'js' => 'js',
        'css' => 'css',
        'python' => 'py',
        'csharp' => 'cs'
    );
    if (!empty($code_blocks) && is_array($code_blocks)) {
        foreach ($code_blocks as $block) {
            if (isset($block['is_premium']) && $block['is_premium'] === 1) {
                $extension = isset($language_extensions[$block['language']]) ? $language_extensions[$block['language']] : 'txt';
                $premium_code_content[] = array(
                    'content' => $block['content'],
                    'extension' => $extension
                );
            }
        }
    }
    if (!empty($premium_code_content)) {
        $user_name = Helper::get_current_username() ?? '';
        $subfix_file_name = '_thank_you' . ($user_name ? '_' . $user_name : '');
        if (count($premium_code_content) === 1) {
            $content = $premium_code_content[0]['content'];
            $extension = $premium_code_content[0]['extension'];
            $post_title = get_the_title($post_id);
            $file_name_slug = sanitize_title($post_title);
            header('Content-Type: text/plain');
            header('Content-Disposition: attachment; filename="' . $file_name_slug . $subfix_file_name . '.' . $extension . '"');
            echo $content;
            exit;
        } else {
            $zip = new ZipArchive();
            $zip_file = tempnam(sys_get_temp_dir(), 'polydev_code_') . '.zip';
            if ($zip->open($zip_file, ZipArchive::CREATE) === TRUE) {
                foreach ($premium_code_content as $index => $block) {
                    $content = $block['content'];
                    $extension = $block['extension'];
                    $zip->addFromString("code_block_" . ($index + 1) . "." . $extension, $content);
                }
                $zip->close();
                $post_title = get_the_title($post_id);
                $file_name_slug = sanitize_title($post_title);
                header('Content-Type: application/zip');
                header('Content-Disposition: attachment; filename="' . $file_name_slug . $subfix_file_name . '.zip"');
                readfile($zip_file);
                unlink($zip_file);
                exit;
            }
        }
    }
    wp_safe_redirect(wc_get_account_endpoint_url('orders'));
    exit;
}
add_action('template_redirect', 'handle_polydev_code_download');