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 );
+	}
 }