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