Commit 12c05032f7 for woocommerce
commit 12c05032f7e2e0988a2ae3abee196600b38507d9
Author: Seun Olorunsola <30554163+triple0t@users.noreply.github.com>
Date: Mon Jan 26 08:16:23 2026 +0100
Create block email template for partial refunds emails (#62895)
* Add new email class for partially refunded orders and corresponding block template
Introduce `WC_Email_Customer_Partially_Refunded_Order` class to handle notifications for partially refunded orders, maintaining compatibility with existing integrations. Also, add a block template for the email content, allowing customization through the WooCommerce email editor.
* Enhance email notifications by adding support for partially refunded orders
Introduce the `customer_partially_refunded_order` email type in the transactional emails class and enable the corresponding email class for the block email editor. This update allows for better handling of partially refunded order notifications within WooCommerce.
* Fix lint errors
* Add changelog file
* Use the correct file path for block email template
* Add tests for WC_Email_Customer_Partially_Refunded_Order class
diff --git a/plugins/woocommerce/changelog/stomail-7745-emails-from-the-same-store-should-follow-the-same-visual b/plugins/woocommerce/changelog/stomail-7745-emails-from-the-same-store-should-follow-the-same-visual
new file mode 100644
index 0000000000..9f886cf1be
--- /dev/null
+++ b/plugins/woocommerce/changelog/stomail-7745-emails-from-the-same-store-should-follow-the-same-visual
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add dedicated email class and block template for partially refunded orders in the block email editor to ensure consistent visual styling across all transactional emails.
diff --git a/plugins/woocommerce/includes/class-wc-emails.php b/plugins/woocommerce/includes/class-wc-emails.php
index 959fd22936..cae9c05dd7 100644
--- a/plugins/woocommerce/includes/class-wc-emails.php
+++ b/plugins/woocommerce/includes/class-wc-emails.php
@@ -308,6 +308,11 @@ class WC_Emails {
$this->emails[ $class ] = include $path;
}
+ // Enable custom partially refunded order email for the block email editor.
+ if ( FeaturesUtil::feature_is_enabled( 'block_email_editor' ) ) {
+ $this->emails['WC_Email_Customer_Partially_Refunded_Order'] = include __DIR__ . '/emails/class-wc-email-customer-partially-refunded-order.php';
+ }
+
/**
* Filter the email classes.
*
diff --git a/plugins/woocommerce/includes/emails/class-wc-email-customer-partially-refunded-order.php b/plugins/woocommerce/includes/emails/class-wc-email-customer-partially-refunded-order.php
new file mode 100644
index 0000000000..2efaa4b7b8
--- /dev/null
+++ b/plugins/woocommerce/includes/emails/class-wc-email-customer-partially-refunded-order.php
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Class WC_Email_Customer_Partially_Refunded_Order file.
+ *
+ * @package WooCommerce\Emails
+ */
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit; // Exit if accessed directly.
+}
+
+// Include the parent class.
+require_once __DIR__ . '/class-wc-email-customer-refunded-order.php';
+
+if ( ! class_exists( 'WC_Email_Customer_Partially_Refunded_Order', false ) ) :
+
+ /**
+ * Customer Partially Refunded Order Email.
+ *
+ * Partial refund emails are sent to customers when their order is partially refunded.
+ *
+ * This email is a variant of the WC_Email_Customer_Refunded_Order email used only for the block email editor.
+ *
+ * The WC_Email_Customer_Refunded_Order email is used for both full and partial refunds.
+ *
+ * We created this custom class to maintain backwards compatibility with other integrations that use the WC_Email_Customer_Refunded_Order email.
+ *
+ * The next version of WooCommerce will move more of the functionality from the parent class to this custom class.
+ *
+ * @class WC_Email_Customer_Partially_Refunded_Order
+ * @version 10.6.0
+ * @package WooCommerce\Classes\Emails
+ */
+ class WC_Email_Customer_Partially_Refunded_Order extends WC_Email_Customer_Refunded_Order {
+
+ /**
+ * Constructor.
+ */
+ public function __construct() {
+ parent::__construct();
+
+ $this->id = 'customer_partially_refunded_order';
+ $this->title = __( 'Partially refunded order', 'woocommerce' );
+ $this->description = __( 'Notifies customers when their order has been partially refunded.', 'woocommerce' );
+ $this->partial_refund = true;
+ $this->template_block = 'emails/block/customer-partially-refunded-order.php';
+
+ // Remove triggers for this email because they will be handled by the parent class.
+ remove_action( 'woocommerce_order_fully_refunded_notification', array( $this, 'trigger_full' ), 10 );
+ remove_action( 'woocommerce_order_partially_refunded_notification', array( $this, 'trigger_partial' ), 10 );
+ }
+
+ /**
+ * Get block editor email template content.
+ *
+ * @return string
+ */
+ public function get_block_editor_email_template_content() {
+ return wc_get_template_html(
+ $this->template_block_content,
+ array(
+ 'order' => $this->object,
+ 'refund' => $this->refund,
+ 'partial_refund' => $this->partial_refund,
+ 'sent_to_admin' => false,
+ 'plain_text' => false,
+ 'email' => $this,
+ )
+ );
+ }
+ }
+
+endif;
+
+return new WC_Email_Customer_Partially_Refunded_Order();
diff --git a/plugins/woocommerce/src/Internal/EmailEditor/WCTransactionalEmails/WCTransactionalEmails.php b/plugins/woocommerce/src/Internal/EmailEditor/WCTransactionalEmails/WCTransactionalEmails.php
index 0079036aca..fab28c8949 100644
--- a/plugins/woocommerce/src/Internal/EmailEditor/WCTransactionalEmails/WCTransactionalEmails.php
+++ b/plugins/woocommerce/src/Internal/EmailEditor/WCTransactionalEmails/WCTransactionalEmails.php
@@ -30,6 +30,7 @@ class WCTransactionalEmails {
'customer_on_hold_order',
'customer_processing_order',
'customer_refunded_order',
+ 'customer_partially_refunded_order',
'customer_reset_password',
'failed_order',
'new_order',
diff --git a/plugins/woocommerce/templates/emails/block/customer-partially-refunded-order.php b/plugins/woocommerce/templates/emails/block/customer-partially-refunded-order.php
new file mode 100644
index 0000000000..ad7a7364c5
--- /dev/null
+++ b/plugins/woocommerce/templates/emails/block/customer-partially-refunded-order.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Customer partially refunded order email (initial block version)
+ *
+ * This template can be overridden by editing it in the WooCommerce email editor.
+ *
+ * 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\Emails\Block
+ * @version 10.6.0
+ */
+
+use Automattic\WooCommerce\Internal\EmailEditor\BlockEmailRenderer;
+
+defined( 'ABSPATH' ) || exit;
+
+// phpcs:disable Squiz.PHP.EmbeddedPhp.ContentBeforeOpen -- removed to prevent empty new lines.
+// phpcs:disable Squiz.PHP.EmbeddedPhp.ContentAfterEnd -- removed to prevent empty new lines.
+?>
+
+<!-- wp:heading -->
+<h2 class="wp-block-heading"><?php
+/* translators: %s: Order number */
+printf( esc_html__( 'Partial refund: Order %s', 'woocommerce' ), '<!--[woocommerce/order-number]-->' );
+?></h2>
+<!-- /wp:heading -->
+
+<!-- wp:paragraph -->
+<p><?php
+ /* translators: %s: Customer first name */
+ printf( esc_html__( 'Hi %s,', 'woocommerce' ), '<!--[woocommerce/customer-first-name]-->' );
+?></p>
+<!-- /wp:paragraph -->
+
+<!-- wp:paragraph -->
+<p><?php
+/* translators: %s: Site title */
+printf( esc_html__( 'Your order from %s has been partially refunded.', 'woocommerce' ), '<!--[woocommerce/site-title]-->' );
+?></p>
+<!-- /wp:paragraph -->
+
+<!-- wp:paragraph -->
+<p> <?php echo esc_html__( 'Here’s a reminder of what you’ve ordered:', 'woocommerce' ); ?> </p>
+<!-- /wp:paragraph -->
+
+<!-- wp:woocommerce/email-content {"lock":{"move":false,"remove":true}} -->
+<div class="wp-block-woocommerce-email-content"> <?php echo esc_html( BlockEmailRenderer::WOO_EMAIL_CONTENT_PLACEHOLDER ); ?> </div>
+<!-- /wp:woocommerce/email-content -->
+
+<!-- wp:paragraph -->
+<p><?php
+/* translators: %s: Store admin email */
+printf( esc_html__( 'If you need any help with your order, please contact us at %s', 'woocommerce' ), '<!--[woocommerce/store-email]-->' );
+?></p>
+<!-- /wp:paragraph -->
diff --git a/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-partially-refunded-order-test.php b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-partially-refunded-order-test.php
new file mode 100644
index 0000000000..6f99702125
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/emails/class-wc-email-customer-partially-refunded-order-test.php
@@ -0,0 +1,238 @@
+<?php
+declare( strict_types = 1 );
+
+use Automattic\WooCommerce\Internal\Features\FeaturesController;
+use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
+
+/**
+ * WC_Email_Customer_Partially_Refunded_Order test.
+ *
+ * @covers WC_Email_Customer_Partially_Refunded_Order
+ */
+class WC_Email_Customer_Partially_Refunded_Order_Test extends \WC_Unit_Test_Case {
+ /**
+ * Features controller instance.
+ *
+ * @var FeaturesController
+ */
+ private $features_controller;
+
+ /**
+ * Original block_email_editor feature state.
+ *
+ * @var bool
+ */
+ private $original_block_email_editor_enabled;
+
+ /**
+ * Load up the email classes since they aren't loaded by default.
+ */
+ public function setUp(): void {
+ parent::setUp();
+
+ $bootstrap = \WC_Unit_Tests_Bootstrap::instance();
+ require_once $bootstrap->plugin_dir . '/includes/emails/class-wc-email.php';
+ require_once $bootstrap->plugin_dir . '/includes/emails/class-wc-email-customer-refunded-order.php';
+ require_once $bootstrap->plugin_dir . '/includes/emails/class-wc-email-customer-partially-refunded-order.php';
+ require_once $bootstrap->plugin_dir . '/includes/class-wc-emails.php';
+
+ $this->features_controller = wc_get_container()->get( FeaturesController::class );
+ $this->original_block_email_editor_enabled = $this->features_controller->feature_is_enabled( 'block_email_editor' );
+ }
+
+ /**
+ * Restore original feature state.
+ */
+ public function tearDown(): void {
+ $this->features_controller->change_feature_enable( 'block_email_editor', $this->original_block_email_editor_enabled );
+ parent::tearDown();
+ }
+
+ /**
+ * @testdox Email has correct ID set to 'customer_partially_refunded_order'.
+ */
+ public function test_email_has_correct_id() {
+ // When instantiating the email class.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+
+ // Then the email ID is correct.
+ $this->assertEquals( 'customer_partially_refunded_order', $email->id );
+ }
+
+ /**
+ * @testdox Email has correct title set to 'Partially refunded order'.
+ */
+ public function test_email_has_correct_title() {
+ // When instantiating the email class.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+
+ // Then the email title is correct.
+ $this->assertEquals( 'Partially refunded order', $email->title );
+ }
+
+ /**
+ * @testdox Email has partial_refund property set to true.
+ */
+ public function test_email_has_partial_refund_set_to_true() {
+ // When instantiating the email class.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+
+ // Then the partial_refund property is true.
+ $this->assertTrue( $email->partial_refund );
+ }
+
+ /**
+ * @testdox Email has correct block template path set.
+ */
+ public function test_email_has_correct_block_template_path() {
+ // When instantiating the email class.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+
+ // Then the block template path is correct.
+ $this->assertEquals( 'emails/block/customer-partially-refunded-order.php', $email->template_block );
+ }
+
+ /**
+ * @testdox Email extends the WC_Email_Customer_Refunded_Order class.
+ */
+ public function test_email_extends_refunded_order_email() {
+ // When instantiating the email class.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+
+ // Then the email is an instance of the parent class.
+ $this->assertInstanceOf( WC_Email_Customer_Refunded_Order::class, $email );
+ }
+
+ /**
+ * @testdox Email is loaded by WC_Emails when block_email_editor feature is enabled.
+ */
+ public function test_email_is_loaded_when_block_email_editor_is_enabled() {
+ // Given the block_email_editor feature is enabled.
+ $this->features_controller->change_feature_enable( 'block_email_editor', true );
+
+ // When initializing emails.
+ $emails = new WC_Emails();
+
+ // Then the partially refunded order email is loaded.
+ $this->assertArrayHasKey( 'WC_Email_Customer_Partially_Refunded_Order', $emails->emails );
+ $this->assertInstanceOf( WC_Email_Customer_Partially_Refunded_Order::class, $emails->emails['WC_Email_Customer_Partially_Refunded_Order'] );
+ }
+
+ /**
+ * @testdox Email is not loaded by WC_Emails when block_email_editor feature is disabled.
+ */
+ public function test_email_is_not_loaded_when_block_email_editor_is_disabled() {
+ // Given the block_email_editor feature is disabled.
+ $this->features_controller->change_feature_enable( 'block_email_editor', false );
+
+ // When initializing emails.
+ $emails = new WC_Emails();
+
+ // Then the partially refunded order email is not loaded.
+ $this->assertArrayNotHasKey( 'WC_Email_Customer_Partially_Refunded_Order', $emails->emails );
+ }
+
+ /**
+ * @testdox Email is marked as customer email.
+ */
+ public function test_email_is_customer_email() {
+ // When instantiating the email class.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+
+ // Then the email is marked as a customer email.
+ $this->assertTrue( $email->is_customer_email() );
+ }
+
+ /**
+ * @testdox Email inherits placeholders from parent class.
+ */
+ public function test_email_inherits_placeholders_from_parent() {
+ // When instantiating the email class.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+
+ // Then the email has the expected placeholders.
+ $this->assertArrayHasKey( '{order_date}', $email->placeholders );
+ $this->assertArrayHasKey( '{order_number}', $email->placeholders );
+ }
+
+ /**
+ * @testdox Email uses partial refund subject from parent class.
+ */
+ public function test_email_uses_partial_refund_subject() {
+ // Given an order.
+ $order = OrderHelper::create_order();
+ $order->save();
+
+ // When instantiating the email and setting the order.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+ $email->object = $order;
+
+ // Populate placeholders.
+ $email->placeholders['{order_number}'] = $order->get_order_number();
+
+ // Then the subject contains partial refund text.
+ $subject = $email->get_subject();
+ $this->assertStringContainsString( 'partially refunded', strtolower( $subject ) );
+ }
+
+ /**
+ * @testdox Email uses partial refund heading from parent class.
+ */
+ public function test_email_uses_partial_refund_heading() {
+ // Given an order.
+ $order = OrderHelper::create_order();
+ $order->save();
+
+ // When instantiating the email and setting the order.
+ $email = new WC_Email_Customer_Partially_Refunded_Order();
+ $email->object = $order;
+
+ // Populate placeholders.
+ $email->placeholders['{order_number}'] = $order->get_order_number();
+
+ // Then the heading contains partial refund text.
+ $heading = $email->get_heading();
+ $this->assertStringContainsString( 'Partial', $heading );
+ }
+
+ /**
+ * @testdox Partially refunded order email has different ID from regular refunded order email.
+ */
+ public function test_partially_refunded_email_has_different_id_from_regular_email() {
+ // When instantiating both email classes.
+ $partial_email = new WC_Email_Customer_Partially_Refunded_Order();
+ $regular_email = new WC_Email_Customer_Refunded_Order();
+
+ // Then they have different IDs.
+ $this->assertNotEquals( $regular_email->id, $partial_email->id );
+ $this->assertEquals( 'customer_refunded_order', $regular_email->id );
+ $this->assertEquals( 'customer_partially_refunded_order', $partial_email->id );
+ }
+
+ /**
+ * @testdox Partially refunded order email has different title from regular refunded order email.
+ */
+ public function test_partially_refunded_email_has_different_title_from_regular_email() {
+ // When instantiating both email classes.
+ $partial_email = new WC_Email_Customer_Partially_Refunded_Order();
+ $regular_email = new WC_Email_Customer_Refunded_Order();
+
+ // Then they have different titles.
+ $this->assertNotEquals( $regular_email->title, $partial_email->title );
+ }
+
+ /**
+ * @testdox Partially refunded order email has partial_refund set while regular email does not by default.
+ */
+ public function test_partially_refunded_email_has_partial_refund_set_while_regular_does_not() {
+ // When instantiating both email classes.
+ $partial_email = new WC_Email_Customer_Partially_Refunded_Order();
+ $regular_email = new WC_Email_Customer_Refunded_Order();
+
+ // Then partially refunded email has partial_refund set to true.
+ $this->assertTrue( $partial_email->partial_refund );
+
+ // And regular email has partial_refund as null by default.
+ $this->assertNull( $regular_email->partial_refund );
+ }
+}