Commit 9b2237b2662 for woocommerce
commit 9b2237b2662ba4e54187a166775d231ebd78c1c1
Author: Andrey <akidinov@gmail.com>
Date: Mon Aug 17 12:03:00 2026 +0200
Fix missing discount line on printed order receipts (#67661)
* Fix discount and payment method lines on printed order receipts
* Add changelog entry for order receipt fixes
* Keep isset() guard on the payment method section for PHPStan
* Compare the receipt price against the kses-filtered expectation
* Treat a whitespace-only payment method title as empty
diff --git a/plugins/woocommerce/changelog/fix-receipt-order-discount-line b/plugins/woocommerce/changelog/fix-receipt-order-discount-line
new file mode 100644
index 00000000000..bcbf7d45069
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-receipt-order-discount-line
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix printed order receipts: show the discount line for orders discounted without a coupon so the amounts add up to the total paid, and stop rendering an empty payment method section.
diff --git a/plugins/woocommerce/src/Internal/ReceiptRendering/ReceiptRenderingEngine.php b/plugins/woocommerce/src/Internal/ReceiptRendering/ReceiptRenderingEngine.php
index 0148a976ad6..0ba28601a94 100644
--- a/plugins/woocommerce/src/Internal/ReceiptRendering/ReceiptRenderingEngine.php
+++ b/plugins/woocommerce/src/Internal/ReceiptRendering/ReceiptRenderingEngine.php
@@ -322,13 +322,19 @@ class ReceiptRenderingEngine {
'amount' => wc_price( $order->get_subtotal(), $get_price_args ),
);
- $coupon_names = ArrayUtil::select( $order->get_coupons(), 'get_name', ArrayUtil::SELECT_BY_OBJECT_METHOD );
- if ( ! empty( $coupon_names ) ) {
+ $coupon_names = ArrayUtil::select( $order->get_coupons(), 'get_name', ArrayUtil::SELECT_BY_OBJECT_METHOD );
+ $total_discount = (float) $order->get_total_discount();
+ if ( $total_discount || ! empty( $coupon_names ) ) {
+ if ( empty( $coupon_names ) ) {
+ $discount_title = __( 'Discount', 'woocommerce' );
+ } else {
+ /* translators: %s = comma-separated list of coupon codes */
+ $discount_title = sprintf( __( 'Discount (%s)', 'woocommerce' ), join( ', ', $coupon_names ) );
+ }
$line_items_info[] = array(
'type' => 'discount',
- /* translators: %s = comma-separated list of coupon codes */
- 'title' => sprintf( __( 'Discount (%s)', 'woocommerce' ), join( ', ', $coupon_names ) ),
- 'amount' => wc_price( -$order->get_total_discount(), $get_price_args ),
+ 'title' => $discount_title,
+ 'amount' => wc_price( -$total_discount, $get_price_args ),
);
}
diff --git a/plugins/woocommerce/src/Internal/ReceiptRendering/Templates/order-receipt.php b/plugins/woocommerce/src/Internal/ReceiptRendering/Templates/order-receipt.php
index 366adcfa8d9..5ac085642c5 100644
--- a/plugins/woocommerce/src/Internal/ReceiptRendering/Templates/order-receipt.php
+++ b/plugins/woocommerce/src/Internal/ReceiptRendering/Templates/order-receipt.php
@@ -22,7 +22,7 @@
<h3 id="payment_status_section_title"><?php echo strtoupper( $data['texts']['payment_status_section_title'] ); ?></h3>
<p><?php echo $data['texts']['payment_status']; ?></p>
- <?php if ( isset( $data['payment_method'] ) ) { ?>
+ <?php if ( isset( $data['payment_method'] ) && ( '' !== trim( $data['payment_method'] ) || ! $data['show_payment_method_title'] ) ) { ?>
<h3 id="payment_method_section_title"><?php echo strtoupper( $data['texts']['payment_method_section_title'] ); ?></h3>
<p>
<?php if ( $data['show_payment_method_title'] ) { ?>
diff --git a/plugins/woocommerce/tests/php/src/Internal/ReceiptRendering/ReceiptRenderingEngineTest.php b/plugins/woocommerce/tests/php/src/Internal/ReceiptRendering/ReceiptRenderingEngineTest.php
index dcfce320b8d..ce76146f156 100644
--- a/plugins/woocommerce/tests/php/src/Internal/ReceiptRendering/ReceiptRenderingEngineTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/ReceiptRendering/ReceiptRenderingEngineTest.php
@@ -6,6 +6,7 @@ use Automattic\WooCommerce\Internal\ReceiptRendering\ReceiptRenderingEngine;
use Automattic\WooCommerce\Internal\TransientFiles\TransientFilesEngine;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
+use WC_Order;
/**
* Tests for the ReceiptRenderingEngine class.
@@ -252,4 +253,70 @@ class ReceiptRenderingEngineTest extends \WC_Unit_Test_Case {
$filename = $this->sut->get_existing_receipt( $order );
$this->assertNull( $filename );
}
+
+ /**
+ * Captures the HTML that 'generate_receipt' passes to the transient files engine.
+ *
+ * @param WC_Order $order The order to render the receipt for.
+ * @return string The rendered receipt HTML.
+ */
+ private function render_receipt( WC_Order $order ): string {
+ $rendered = '';
+
+ $this->tfe_mock
+ ->expects( self::once() )
+ ->method( 'create_transient_file' )
+ ->willReturnCallback(
+ function ( $contents ) use ( &$rendered ) {
+ $rendered = $contents;
+ return 'the_generated_file_name';
+ }
+ );
+
+ $this->sut->generate_receipt( $order, null, true );
+
+ return $rendered;
+ }
+
+ /**
+ * @testdox 'generate_receipt' renders the discount line for an order that has a line item discount but no coupons.
+ */
+ public function test_generate_receipt_renders_discount_line_for_order_without_coupons() {
+ $order = OrderHelper::create_order();
+
+ $item = current( $order->get_items() );
+ $item->set_total( (float) $item->get_subtotal() - 6 );
+ $item->save();
+ $order->calculate_totals( false );
+
+ $rendered = $this->render_receipt( $order );
+
+ $this->assertStringContainsString( '<td>Discount</td>', $rendered );
+ $expected_amount = wp_kses_post( wc_price( -6, array( 'currency' => $order->get_currency() ) ) );
+ $this->assertStringContainsString( $expected_amount, $rendered );
+ }
+
+ /**
+ * @testdox 'generate_receipt' renders no discount line for an order that has no discount at all.
+ */
+ public function test_generate_receipt_renders_no_discount_line_when_there_is_no_discount() {
+ $order = OrderHelper::create_order();
+
+ $rendered = $this->render_receipt( $order );
+
+ $this->assertStringNotContainsString( '<td>Discount</td>', $rendered );
+ }
+
+ /**
+ * @testdox 'generate_receipt' renders no payment method section for an order that has no payment method title.
+ */
+ public function test_generate_receipt_renders_no_payment_method_section_when_title_is_empty() {
+ $order = OrderHelper::create_order();
+ $order->set_payment_method_title( '' );
+ $order->save();
+
+ $rendered = $this->render_receipt( $order );
+
+ $this->assertStringNotContainsString( 'payment_method_section_title', $rendered );
+ }
}