3
3
* Copyright 2025 Adobe
4
4
* All Rights Reserved.
5
5
*/
6
+
6
7
declare (strict_types=1 );
7
8
8
9
namespace Magento \Checkout \Helper ;
27
28
* @magentoAppIsolation enabled
28
29
* @magentoDbIsolation enabled
29
30
* @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
32
31
*
33
32
* @AllureSuite("Checkout")
34
33
* @AllureFeature("Payment Failed Email")
@@ -41,48 +40,52 @@ class DataTest extends TestCase
41
40
* @var ObjectManagerInterface
42
41
*/
43
42
private ObjectManagerInterface $ objectManager ;
43
+
44
44
/**
45
45
* Quote management service
46
46
*
47
47
* @var QuoteManagement
48
48
*/
49
49
private QuoteManagement $ quoteManagement ;
50
+
50
51
/**
51
52
* Quote factory
52
53
*
53
54
* @var QuoteFactory
54
55
*/
55
56
private QuoteFactory $ quoteFactory ;
57
+
56
58
/**
57
59
* Checkout data helper
58
60
*
59
61
* @var Data
60
62
*/
61
63
private Data $ checkoutHelper ;
64
+
62
65
/**
63
66
* Order repository
64
67
*
65
68
* @var OrderRepositoryInterface
66
69
*/
67
70
private OrderRepositoryInterface $ orderRepository ;
71
+
68
72
/**
69
73
* Transport builder mock
70
74
*
71
75
* @var TransportBuilderMock
72
76
*/
73
77
private TransportBuilderMock $ transportBuilder ;
78
+
74
79
/**
75
80
* Reserved order ID used in fixture.
76
81
*/
77
82
private const FIXTURE_RESERVED_ORDER_ID = 'test_order_with_virtual_product ' ;
83
+
78
84
/**
79
85
* Payment method code to use in test.
80
86
*/
81
87
private const PAYMENT_METHOD = 'checkmo ' ;
82
- /**
83
- * Payment failure message used in test.
84
- */
85
- private const PAYMENT_FAILURE_MESSAGE = 'Simulated payment failure ' ;
88
+
86
89
/**
87
90
* Set up required Magento services for the test.
88
91
*
@@ -91,6 +94,7 @@ class DataTest extends TestCase
91
94
protected function setUp (): void
92
95
{
93
96
parent ::setUp ();
97
+
94
98
$ this ->objectManager = Bootstrap::getObjectManager ();
95
99
$ this ->quoteManagement = $ this ->objectManager ->get (QuoteManagement::class);
96
100
$ this ->quoteFactory = $ this ->objectManager ->get (QuoteFactory::class);
@@ -103,49 +107,48 @@ protected function setUp(): void
103
107
* Test sending the "payment failed" email for an order with a virtual product.
104
108
*
105
109
* This test verifies that:
106
- * - The payment failure email is sent successfully
110
+ * - The payment failure email is sent successfully.
107
111
* - 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.
110
113
*
111
114
* @return void
112
115
*/
113
116
public function testSendPaymentFailedEmail (): void
114
117
{
115
118
[$ order , $ quote ] = $ this ->prepareOrderFromFixtureQuote ();
116
119
$ this ->simulatePaymentFailure ($ order );
120
+
117
121
$ this ->checkoutHelper ->sendPaymentFailedEmail (
118
122
$ quote ,
119
- (string )__ (self :: PAYMENT_FAILURE_MESSAGE ),
123
+ (string )__ (' Simulated payment failure ' ),
120
124
$ quote ->getPayment ()->getMethod (),
121
125
$ quote ->getCheckoutMethod ()
122
126
);
127
+
123
128
$ message = $ this ->transportBuilder ->getSentMessage ();
124
129
$ 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. '
143
149
);
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 );
148
150
}
151
+
149
152
/**
150
153
* Prepare an order from a fixture quote containing a virtual product.
151
154
*
@@ -159,16 +162,20 @@ private function prepareOrderFromFixtureQuote(): array
159
162
/** @var Quote $quote */
160
163
$ quote = $ this ->objectManager ->create (Quote::class)
161
164
->load (self ::FIXTURE_RESERVED_ORDER_ID , 'reserved_order_id ' );
165
+
162
166
$ this ->assertNotNull ($ quote ->getId (), 'Failed to load quote from fixture. ' );
163
167
$ this ->assertNotEmpty ($ quote ->getAllItems (), 'Quote from fixture is empty. ' );
164
- $ this -> assertTrue ( $ quote -> hasVirtualItems (), ' Quote should contain virtual items. ' );
168
+
165
169
$ quote ->getPayment ()->setMethod (self ::PAYMENT_METHOD );
166
- $ quote -> collectTotals ();
170
+
167
171
$ order = $ this ->quoteManagement ->submit ($ quote );
172
+
168
173
$ this ->assertNotNull ($ order ->getId (), 'Order was not created from quote. ' );
169
174
$ this ->assertNotEmpty ($ order ->getIncrementId (), 'Order increment ID is missing. ' );
175
+
170
176
return [$ order , $ quote ];
171
177
}
178
+
172
179
/**
173
180
* Simulate a payment failure by cancelling the order and adding a history comment.
174
181
*
@@ -183,145 +190,13 @@ private function simulatePaymentFailure(Order $order): void
183
190
$ order ->setState (Order::STATE_CANCELED )
184
191
->setStatus (Order::STATE_CANCELED )
185
192
->addCommentToStatusHistory ((string )__ ('Simulated: Payment failure due to gateway timeout. ' ));
193
+
186
194
$ this ->orderRepository ->save ($ order );
195
+
187
196
$ this ->assertSame (
188
197
Order::STATE_CANCELED ,
189
198
$ order ->getState (),
190
199
'Order state should be canceled after simulating payment failure. '
191
200
);
192
201
}
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
- }
326
202
}
327
-
0 commit comments