33 * Copyright 2025 Adobe
44 * All Rights Reserved.
55 */
6+
67declare (strict_types=1 );
78
8- namespace Magento \Checkout \Test \ Integration ;
9+ namespace Magento \Checkout \Helper ;
910
1011use Magento \Checkout \Helper \Data ;
1112use Magento \Framework \ObjectManagerInterface ;
2122/**
2223 * Integration test for sending the "payment failed" email on virtual product order failure.
2324 *
25+ * Ensures that when a virtual product order fails during payment, the appropriate failure email is
26+ * sent and does not include any shipping address or method information.
27+ *
2428 * @magentoAppIsolation enabled
2529 * @magentoDbIsolation enabled
26- * @magentoApiDataFixture Magento/Checkout/_files/quote.php
2730 * @magentoDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
2831 */
29- class VirtualProductFailedPaymentEmailTest extends TestCase
32+ class DataTest extends TestCase
3033{
31- /** @var ObjectManagerInterface */
34+ /**
35+ * Magento Object Manager
36+ *
37+ * @var ObjectManagerInterface
38+ */
3239 private ObjectManagerInterface $ objectManager ;
3340
34- /** @var QuoteManagement */
41+ /**
42+ * Quote management service
43+ *
44+ * @var QuoteManagement
45+ */
3546 private QuoteManagement $ quoteManagement ;
3647
37- /** @var QuoteFactory */
48+ /**
49+ * Quote factory
50+ *
51+ * @var QuoteFactory
52+ */
3853 private QuoteFactory $ quoteFactory ;
3954
40- /** @var Data */
55+ /**
56+ * Checkout data helper
57+ *
58+ * @var Data
59+ */
4160 private Data $ checkoutHelper ;
4261
43- /** @var OrderRepositoryInterface */
62+ /**
63+ * Order repository
64+ *
65+ * @var OrderRepositoryInterface
66+ */
4467 private OrderRepositoryInterface $ orderRepository ;
4568
69+ /**
70+ * Set up required Magento services for the test
71+ *
72+ * @return void
73+ */
4674 protected function setUp (): void
4775 {
76+ parent ::setUp ();
77+
4878 $ this ->objectManager = Bootstrap::getObjectManager ();
4979 $ this ->quoteManagement = $ this ->objectManager ->get (QuoteManagement::class);
5080 $ this ->quoteFactory = $ this ->objectManager ->get (QuoteFactory::class);
5181 $ this ->checkoutHelper = $ this ->objectManager ->get (Data::class);
5282 $ this ->orderRepository = $ this ->objectManager ->get (OrderRepositoryInterface::class);
5383 }
5484
85+ /**
86+ * Test the sending of the "payment failed" email for a virtual product order.
87+ *
88+ * Asserts that:
89+ * - The email is sent
90+ * - The email does not contain shipping address or shipping method
91+ *
92+ * @return void
93+ */
5594 public function testSendPaymentFailedEmail (): void
5695 {
5796 [$ order , $ quote ] = $ this ->prepareOrderFromFixtureQuote ();
@@ -66,15 +105,16 @@ public function testSendPaymentFailedEmail(): void
66105
67106 /** @var TransportBuilderMock $transportBuilder */
68107 $ transportBuilder = $ this ->objectManager ->get (TransportBuilderMock::class);
108+
69109 /** @var \Magento\Framework\Mail\EmailMessageInterface $message */
70110 $ message = $ transportBuilder ->getSentMessage ();
71111 $ this ->assertNotNull ($ message , 'Expected a payment failed email to be sent. ' );
72112
73- // Compatibility with Magento 2.4.6+ and 2.4.7+
74113 $ emailBody = $ message ->getBody ();
75114 $ emailContent = method_exists ($ emailBody , 'bodyToString ' )
76115 ? quoted_printable_decode ($ emailBody ->bodyToString ())
77116 : $ emailBody ->getParts ()[0 ]->getRawContent ();
117+
78118 $ this ->assertStringNotContainsString (
79119 'Shipping Address ' ,
80120 $ emailContent ,
@@ -86,26 +126,46 @@ public function testSendPaymentFailedEmail(): void
86126 'Shipping method should not appear in the payment failed email for virtual product. '
87127 );
88128 }
129+
130+ /**
131+ * Prepare an order from a quote fixture containing a virtual product.
132+ *
133+ * Loads a predefined quote and submits it to create an order.
134+ *
135+ * @return array{0: Order, 1: Quote}
136+ */
89137 private function prepareOrderFromFixtureQuote (): array
90138 {
91- /** @var \Magento\Quote\Model\ Quote $quote */
139+ /** @var Quote $quote */
92140 $ quote = $ this ->objectManager ->create (Quote::class)
93141 ->load ('test_order_with_virtual_product ' , 'reserved_order_id ' );
142+
94143 $ this ->assertTrue ((bool )$ quote ->getId (), 'Quote was not loaded from fixture. ' );
95144 $ this ->assertNotEmpty ($ quote ->getAllItems (), 'Quote from fixture is empty. ' );
145+
96146 $ quote ->getPayment ()->setMethod ('checkmo ' );
97147
98148 $ order = $ this ->quoteManagement ->submit ($ quote );
99149 $ this ->assertNotNull ($ order ->getId (), 'Order was not created. ' );
100150 $ this ->assertNotEmpty ($ order ->getIncrementId (), 'Order increment ID is missing. ' );
151+
101152 return [$ order , $ quote ];
102153 }
154+
155+ /**
156+ * Simulate a payment failure by cancelling the order and adding a history comment.
157+ *
158+ * @param Order $order
159+ * @return void
160+ */
103161 private function simulatePaymentFailure (Order $ order ): void
104162 {
105163 $ order ->setStatus (Order::STATE_CANCELED )
106164 ->setState (Order::STATE_CANCELED )
107165 ->addCommentToStatusHistory (__ ('Simulated: Payment failure due to gateway timeout. ' ));
166+
108167 $ this ->orderRepository ->save ($ order );
168+
109169 $ this ->assertSame (
110170 Order::STATE_CANCELED ,
111171 $ order ->getState (),
0 commit comments