Category: WooCommerce

You’d think that when a customer views an order and the order has a pending or failed status, they should be able to pay right from that view order page, right? Yeah, but nope. The view-order template just renders the order info.

The Order Listing page does, however, include a Payment button though.

Why This Feature is Important

Allowing customers to pay directly from the view order page enhances user experience and reduces the friction in completing a purchase. This is particularly useful for orders that have failed or are pending, as it provides a quick and easy way for customers to complete their payment without navigating through multiple pages.

The Challenge

The default WooCommerce view-order template does not include a pay button. You can add this functionality by either customizing the WooCommerce View Order Template or by using WooCommerce hooks. I recommend the latter because customizing template files can lead to issues when WooCommerce updates its templates, potentially breaking your customizations if they are not kept up-to-date.

Adding the Pay Order Button Using Hooks

To add the pay order button, you need to add custom code to your functions.php file or create a custom plugin. Below, I'll show you the code you need and where to place it.

The Solution

The solution should look like this.

Here's a straightforward solution using hooks to add the payment button to the view-order page. This method ensures your customizations remain intact even after WooCommerce updates.

Copy and the following custom php and then you can paste it into functions.php file (omit the <?php opening tag because functions.php has one already otherwise your site will start showing php content and you won't want that.) or use a WordPress Plugin Generator (recommended approach) and copy and paste the code there. If you have used the plugin generator you'll only need to activate the plugin.

<?php

/**
 * This code hooks into woocommerce_view_order and woocommerce_after_order_details and adds a pay order button for pending or failed orders.
 * @see https://orbisius.com/7685
 * @author Svetoslav Marinov | https://orbisius.com
 * @license Creative Commons Zero v1.0 Universal
 */
$orb_post_7685_obj = Orb_Post_7685_Add_Payment_Button_to_View_Order::getInstance();

add_action( 'woocommerce_view_order', [ $orb_post_7685_obj, 'renderPaymentButtonOnViewOrder' ] );
add_action( 'woocommerce_after_order_details', [ $orb_post_7685_obj, 'renderPaymentButtonOnViewOrder' ] );

class Orb_Post_7685_Add_Payment_Button_to_View_Order {
    /**
     * View order just shows the order details. If it's a order that needs payment it must have a pay now button.
     * This shows up above the order details page title and there's no hook after the title in the template.
     * This is also rendered after the details but before customer info because it may not exist
     * @param int|WC_Order $order_id
     * @return void
     */
    public function renderPaymentButtonOnViewOrder( $order_id ) {
        $order = wc_get_order( $order_id );

        $payable_statuses = [
            'failed',
            'pending',
        ];

        if ( ! in_array( $order->get_status(), $payable_statuses ) ) {
            return;
        }

        $payment_url = $order->get_checkout_payment_url();
        printf( '<div class="qs_site_app_billing_payment_button_wrapper">  
                <a class="woocommerce-button button button-primary pay" href="%s">%s</a></div>', $payment_url, __("Pay for this order", "woocommerce") );
    }

    /**
     * Singleton pattern i.e. we have only one instance of this obj
     *
     * @staticvar static $instance
     * @return static
     */
    public static function getInstance() {
        static $instance = null;

        // This will make the calling class to be instantiated.
        // no need each sub class to define this method.
        if (is_null($instance)) {
            $instance = new static();
        }

        return $instance;
    }
}

View Order WooCommerce template [/plugins/woocommerce/templates/myaccount/view-order.php] looks like this

<?php
/**
 * View Order
 *
 * Shows the details of a particular order on the account page.
 *
 * This template can be overridden by copying it to yourtheme/woocommerce/myaccount/view-order.php.
 *
 * HOWEVER, on occasion WooCommerce will need to update template files and you
 * (the theme developer) will need to copy the new files to your theme to
 * maintain compatibility. We try to do this as little as possible, but it does
 * happen. When this occurs the version of the template file will be bumped and
 * the readme will list any important changes.
 *
 * @see     https://woocommerce.com/document/template-structure/
 * @package WooCommerce\Templates
 * @version 3.0.0
 */

defined( 'ABSPATH' ) || exit;

$notes = $order->get_customer_order_notes();
?>
<p>
<?php
printf(
	/* translators: 1: order number 2: order date 3: order status */
	esc_html__( 'Order #%1$s was placed on %2$s and is currently %3$s.', 'woocommerce' ),
	'<mark class="order-number">' . $order->get_order_number() . '</mark>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	'<mark class="order-date">' . wc_format_datetime( $order->get_date_created() ) . '</mark>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	'<mark class="order-status">' . wc_get_order_status_name( $order->get_status() ) . '</mark>' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
?>
</p>

<?php if ( $notes ) : ?>
	<h2><?php esc_html_e( 'Order updates', 'woocommerce' ); ?></h2>
	<ol class="woocommerce-OrderUpdates commentlist notes">
		<?php foreach ( $notes as $note ) : ?>
		<li class="woocommerce-OrderUpdate comment note">
			<div class="woocommerce-OrderUpdate-inner comment_container">
				<div class="woocommerce-OrderUpdate-text comment-text">
					<p class="woocommerce-OrderUpdate-meta meta"><?php echo date_i18n( esc_html__( 'l jS \o\f F Y, h:ia', 'woocommerce' ), strtotime( $note->comment_date ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
					<div class="woocommerce-OrderUpdate-description description">
						<?php echo wpautop( wptexturize( $note->comment_content ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
					</div>
					<div class="clear"></div>
				</div>
				<div class="clear"></div>
			</div>
		</li>
		<?php endforeach; ?>
	</ol>
<?php endif; ?>

<?php do_action( 'woocommerce_view_order', $order_id ); ?>

Order Details WooCommerce template [/plugins/woocommerce/templates/order/order-details.php] looks like this

<?php
/**
 * Order details
 *
 * This template can be overridden by copying it to yourtheme/woocommerce/order/order-details.php.
 *
 * HOWEVER, on occasion WooCommerce will need to update template files and you
 * (the theme developer) will need to copy the new files to your theme to
 * maintain compatibility. We try to do this as little as possible, but it does
 * happen. When this occurs the version of the template file will be bumped and
 * the readme will list any important changes.
 *
 * @see     https://woocommerce.com/document/template-structure/
 * @package WooCommerce\Templates
 * @version 8.5.0
 *
 * @var bool $show_downloads Controls whether the downloads table should be rendered.
 */

defined( 'ABSPATH' ) || exit;

$order = wc_get_order( $order_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited

if ( ! $order ) {
	return;
}

$order_items           = $order->get_items( apply_filters( 'woocommerce_purchase_order_item_types', 'line_item' ) );
$show_purchase_note    = $order->has_status( apply_filters( 'woocommerce_purchase_note_order_statuses', array( 'completed', 'processing' ) ) );
$show_customer_details = is_user_logged_in() && $order->get_user_id() === get_current_user_id();
$downloads             = $order->get_downloadable_items();

if ( $show_downloads ) {
	wc_get_template(
		'order/order-downloads.php',
		array(
			'downloads'  => $downloads,
			'show_title' => true,
		)
	);
}
?>
<section class="woocommerce-order-details">
	<?php do_action( 'woocommerce_order_details_before_order_table', $order ); ?>

	<h2 class="woocommerce-order-details__title"><?php esc_html_e( 'Order details', 'woocommerce' ); ?></h2>

	<table class="woocommerce-table woocommerce-table--order-details shop_table order_details">

		<thead>
			<tr>
				<th class="woocommerce-table__product-name product-name"><?php esc_html_e( 'Product', 'woocommerce' ); ?></th>
				<th class="woocommerce-table__product-table product-total"><?php esc_html_e( 'Total', 'woocommerce' ); ?></th>
			</tr>
		</thead>

		<tbody>
			<?php
			do_action( 'woocommerce_order_details_before_order_table_items', $order );

			foreach ( $order_items as $item_id => $item ) {
				$product = $item->get_product();

				wc_get_template(
					'order/order-details-item.php',
					array(
						'order'              => $order,
						'item_id'            => $item_id,
						'item'               => $item,
						'show_purchase_note' => $show_purchase_note,
						'purchase_note'      => $product ? $product->get_purchase_note() : '',
						'product'            => $product,
					)
				);
			}

			do_action( 'woocommerce_order_details_after_order_table_items', $order );
			?>
		</tbody>

		<tfoot>
			<?php
			foreach ( $order->get_order_item_totals() as $key => $total ) {
				?>
					<tr>
						<th scope="row"><?php echo esc_html( $total['label'] ); ?></th>
						<td><?php echo wp_kses_post( $total['value'] ); ?></td>
					</tr>
					<?php
			}
			?>
			<?php if ( $order->get_customer_note() ) : ?>
				<tr>
					<th><?php esc_html_e( 'Note:', 'woocommerce' ); ?></th>
					<td><?php echo wp_kses_post( nl2br( wptexturize( $order->get_customer_note() ) ) ); ?></td>
				</tr>
			<?php endif; ?>
		</tfoot>
	</table>

	<?php do_action( 'woocommerce_order_details_after_order_table', $order ); ?>
</section>

<?php
/**
 * Action hook fired after the order details.
 *
 * @since 4.4.0
 * @param WC_Order $order Order data.
 */
do_action( 'woocommerce_after_order_details', $order );

if ( $show_customer_details ) {
	wc_get_template( 'order/order-details-customer.php', array( 'order' => $order ) );
}

Join our mailing list

Get important news about cool products we release and invitations to beta test new products

Find out the Social networks we're on

Black and Cyber Monday Deal: Use BF24 code at checkout for 40% off