33 * Copyright 2025 Adobe
44 * All Rights Reserved.
55 */
6+
67declare (strict_types=1 );
78
89namespace Magento \Checkout \Helper ;
2728 * @magentoAppIsolation enabled
2829 * @magentoDbIsolation enabled
2930 * @magentoDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
30- * @magentoConfigFixture default_store checkout/payment_failed/template payment_failed_template
31- * @magentoConfigFixture default_store checkout/payment_failed/identity support
3231 *
3332 * @AllureSuite("Checkout")
3433 * @AllureFeature("Payment Failed Email")
@@ -41,48 +40,52 @@ class DataTest extends TestCase
4140 * @var ObjectManagerInterface
4241 */
4342 private ObjectManagerInterface $ objectManager ;
43+
4444 /**
4545 * Quote management service
4646 *
4747 * @var QuoteManagement
4848 */
4949 private QuoteManagement $ quoteManagement ;
50+
5051 /**
5152 * Quote factory
5253 *
5354 * @var QuoteFactory
5455 */
5556 private QuoteFactory $ quoteFactory ;
57+
5658 /**
5759 * Checkout data helper
5860 *
5961 * @var Data
6062 */
6163 private Data $ checkoutHelper ;
64+
6265 /**
6366 * Order repository
6467 *
6568 * @var OrderRepositoryInterface
6669 */
6770 private OrderRepositoryInterface $ orderRepository ;
71+
6872 /**
6973 * Transport builder mock
7074 *
7175 * @var TransportBuilderMock
7276 */
7377 private TransportBuilderMock $ transportBuilder ;
78+
7479 /**
7580 * Reserved order ID used in fixture.
7681 */
7782 private const FIXTURE_RESERVED_ORDER_ID = 'test_order_with_virtual_product ' ;
83+
7884 /**
7985 * Payment method code to use in test.
8086 */
8187 private const PAYMENT_METHOD = 'checkmo ' ;
82- /**
83- * Payment failure message used in test.
84- */
85- private const PAYMENT_FAILURE_MESSAGE = 'Simulated payment failure ' ;
88+
8689 /**
8790 * Set up required Magento services for the test.
8891 *
@@ -91,6 +94,7 @@ class DataTest extends TestCase
9194 protected function setUp (): void
9295 {
9396 parent ::setUp ();
97+
9498 $ this ->objectManager = Bootstrap::getObjectManager ();
9599 $ this ->quoteManagement = $ this ->objectManager ->get (QuoteManagement::class);
96100 $ this ->quoteFactory = $ this ->objectManager ->get (QuoteFactory::class);
@@ -103,49 +107,48 @@ protected function setUp(): void
103107 * Test sending the "payment failed" email for an order with a virtual product.
104108 *
105109 * This test verifies that:
106- * - The payment failure email is sent successfully
110+ * - The payment failure email is sent successfully.
107111 * - The email content does not include shipping address or shipping method
108- * - The email contains appropriate payment failure information
109- * - The email is properly formatted for virtual products
112+ * since the product is virtual.
110113 *
111114 * @return void
112115 */
113116 public function testSendPaymentFailedEmail (): void
114117 {
115118 [$ order , $ quote ] = $ this ->prepareOrderFromFixtureQuote ();
116119 $ this ->simulatePaymentFailure ($ order );
120+
117121 $ this ->checkoutHelper ->sendPaymentFailedEmail (
118122 $ quote ,
119- (string )__ (self :: PAYMENT_FAILURE_MESSAGE ),
123+ (string )__ (' Simulated payment failure ' ),
120124 $ quote ->getPayment ()->getMethod (),
121125 $ quote ->getCheckoutMethod ()
122126 );
127+
123128 $ message = $ this ->transportBuilder ->getSentMessage ();
124129 $ this ->assertNotNull ($ message , 'Expected a payment failed email to be sent. ' );
125- $ emailContent = $ this ->extractEmailContent ($ message ->getBody ());
126- $ this ->assertVirtualProductEmailContent ($ emailContent );
127- }
128- /**
129- * Test payment failed email with custom checkout method.
130- *
131- * @return void
132- */
133- public function testSendPaymentFailedEmailWithCustomCheckoutMethod (): void
134- {
135- [$ order , $ quote ] = $ this ->prepareOrderFromFixtureQuote ();
136- $ quote ->setCheckoutMethod ('custom_method ' );
137- $ this ->simulatePaymentFailure ($ order );
138- $ this ->checkoutHelper ->sendPaymentFailedEmail (
139- $ quote ,
140- (string )__ (self ::PAYMENT_FAILURE_MESSAGE ),
141- $ quote ->getPayment ()->getMethod (),
142- $ quote ->getCheckoutMethod ()
130+
131+ $ emailBody = $ message ->getBody ();
132+ if (method_exists ($ emailBody , 'bodyToString ' )) {
133+ $ emailContent = quoted_printable_decode ($ emailBody ->bodyToString ());
134+ } elseif (method_exists ($ emailBody , 'getParts ' ) && isset ($ emailBody ->getParts ()[0 ])) {
135+ $ emailContent = $ emailBody ->getParts ()[0 ]->getRawContent ();
136+ } else {
137+ $ this ->fail ('Unable to extract email content for assertion. ' );
138+ }
139+
140+ $ this ->assertStringNotContainsString (
141+ 'Shipping Address ' ,
142+ $ emailContent ,
143+ 'Shipping address should not appear in the payment failed email for virtual product. '
144+ );
145+ $ this ->assertStringNotContainsString (
146+ 'Shipping Method ' ,
147+ $ emailContent ,
148+ 'Shipping method should not appear in the payment failed email for virtual product. '
143149 );
144- $ message = $ this ->transportBuilder ->getSentMessage ();
145- $ this ->assertNotNull ($ message , 'Expected a payment failed email to be sent with custom checkout method. ' );
146- $ emailContent = $ this ->extractEmailContent ($ message ->getBody ());
147- $ this ->assertVirtualProductEmailContent ($ emailContent );
148150 }
151+
149152 /**
150153 * Prepare an order from a fixture quote containing a virtual product.
151154 *
@@ -159,16 +162,20 @@ private function prepareOrderFromFixtureQuote(): array
159162 /** @var Quote $quote */
160163 $ quote = $ this ->objectManager ->create (Quote::class)
161164 ->load (self ::FIXTURE_RESERVED_ORDER_ID , 'reserved_order_id ' );
165+
162166 $ this ->assertNotNull ($ quote ->getId (), 'Failed to load quote from fixture. ' );
163167 $ this ->assertNotEmpty ($ quote ->getAllItems (), 'Quote from fixture is empty. ' );
164- $ this -> assertTrue ( $ quote -> hasVirtualItems (), ' Quote should contain virtual items. ' );
168+
165169 $ quote ->getPayment ()->setMethod (self ::PAYMENT_METHOD );
166- $ quote -> collectTotals ();
170+
167171 $ order = $ this ->quoteManagement ->submit ($ quote );
172+
168173 $ this ->assertNotNull ($ order ->getId (), 'Order was not created from quote. ' );
169174 $ this ->assertNotEmpty ($ order ->getIncrementId (), 'Order increment ID is missing. ' );
175+
170176 return [$ order , $ quote ];
171177 }
178+
172179 /**
173180 * Simulate a payment failure by cancelling the order and adding a history comment.
174181 *
@@ -183,145 +190,13 @@ private function simulatePaymentFailure(Order $order): void
183190 $ order ->setState (Order::STATE_CANCELED )
184191 ->setStatus (Order::STATE_CANCELED )
185192 ->addCommentToStatusHistory ((string )__ ('Simulated: Payment failure due to gateway timeout. ' ));
193+
186194 $ this ->orderRepository ->save ($ order );
195+
187196 $ this ->assertSame (
188197 Order::STATE_CANCELED ,
189198 $ order ->getState (),
190199 'Order state should be canceled after simulating payment failure. '
191200 );
192201 }
193- /**
194- * Extract email content for testing from various email body formats.
195- *
196- * @param mixed $emailBody
197- * @return string
198- */
199- private function extractEmailContent ($ emailBody ): string
200- {
201- // Try different methods to extract email content
202- if (method_exists ($ emailBody , 'bodyToString ' )) {
203- return quoted_printable_decode ($ emailBody ->bodyToString ());
204- }
205- if (method_exists ($ emailBody , 'getParts ' )) {
206- $ parts = $ emailBody ->getParts ();
207- if (!empty ($ parts ) && method_exists ($ parts [0 ], 'getRawContent ' )) {
208- return $ parts [0 ]->getRawContent ();
209- }
210- }
211- if (method_exists ($ emailBody , 'getContent ' )) {
212- return $ emailBody ->getContent ();
213- }
214- if (method_exists ($ emailBody , '__toString ' )) {
215- return (string )$ emailBody ;
216- }
217- $ this ->fail (
218- 'Unable to extract email content. Email body type: ' . get_class ($ emailBody ) .
219- '. Available methods: ' . implode (', ' , get_class_methods ($ emailBody ))
220- );
221- }
222- /**
223- * Assert virtual product email content meets requirements.
224- *
225- * @param string $emailContent
226- * @return void
227- */
228- private function assertVirtualProductEmailContent (string $ emailContent ): void
229- {
230- // Negative assertions - what should NOT be included for virtual products
231- $ this ->assertStringNotContainsString (
232- 'Shipping Address ' ,
233- $ emailContent ,
234- 'Shipping address should not appear in payment failed email for virtual product. '
235- );
236- $ this ->assertStringNotContainsString (
237- 'Shipping Method ' ,
238- $ emailContent ,
239- 'Shipping method should not appear in payment failed email for virtual product. '
240- );
241- $ this ->assertStringNotContainsString (
242- 'Delivery ' ,
243- $ emailContent ,
244- 'Delivery information should not appear in payment failed email for virtual product. '
245- );
246- $ this ->assertStringNotContainsString (
247- 'Ship to ' ,
248- $ emailContent ,
249- 'Ship to information should not appear in payment failed email for virtual product. '
250- );
251- // Positive assertions - what should be included
252- $ this ->assertStringContainsString (
253- self ::PAYMENT_FAILURE_MESSAGE ,
254- $ emailContent ,
255- 'Payment failure message should be present in email. '
256- );
257- $ this ->assertStringContainsString (
258- self ::PAYMENT_METHOD ,
259- $ emailContent ,
260- 'Payment method should be mentioned in email. '
261- );
262- // Verify email is not empty
263- $ this ->assertNotEmpty (
264- trim ($ emailContent ),
265- 'Email content should not be empty. '
266- );
267- // Verify email contains order information
268- $ this ->assertThat (
269- $ emailContent ,
270- $ this ->logicalOr (
271- $ this ->stringContains ('Order ' ),
272- $ this ->stringContains ('Payment ' ),
273- $ this ->stringContains ('Failed ' )
274- ),
275- 'Email should contain order or payment related information. '
276- );
277- }
278- /**
279- * Assert that email contains billing information but not shipping information.
280- *
281- * @param string $emailContent
282- * @return void
283- */
284- private function assertBillingButNoShippingInformation (string $ emailContent ): void
285- {
286- // Billing information should be present
287- $ this ->assertStringContainsString (
288- 'Billing Address ' ,
289- $ emailContent ,
290- 'Billing address should be present in payment failed email. '
291- );
292- // Shipping information should not be present
293- $ this ->assertStringNotContainsString (
294- 'Shipping Address ' ,
295- $ emailContent ,
296- 'Shipping address should not be present for virtual products. '
297- );
298- }
299- /**
300- * Test that email template variables are properly set.
301- *
302- * @return void
303- */
304- public function testEmailTemplateVariables (): void
305- {
306- [$ order , $ quote ] = $ this ->prepareOrderFromFixtureQuote ();
307- $ this ->simulatePaymentFailure ($ order );
308- $ this ->checkoutHelper ->sendPaymentFailedEmail (
309- $ quote ,
310- (string )__ (self ::PAYMENT_FAILURE_MESSAGE ),
311- $ quote ->getPayment ()->getMethod (),
312- $ quote ->getCheckoutMethod ()
313- );
314- $ message = $ this ->transportBuilder ->getSentMessage ();
315- $ this ->assertNotNull ($ message , 'Expected a payment failed email to be sent. ' );
316- // Verify email headers
317- $ headers = $ message ->getHeaders ();
318- $ this ->assertNotNull ($ headers , 'Email headers should be present. ' );
319- // Verify email subject
320- $ subject = $ message ->getSubject ();
321- $ this ->assertNotEmpty ($ subject , 'Email subject should not be empty. ' );
322- // Verify email recipient
323- $ to = $ message ->getTo ();
324- $ this ->assertNotEmpty ($ to , 'Email recipient should be present. ' );
325- }
326202}
327-
0 commit comments