Skip to content

Commit 660f26f

Browse files
committed
fix coverage
1 parent 80c76f4 commit 660f26f

File tree

3 files changed

+542
-1
lines changed

3 files changed

+542
-1
lines changed

app/Actions/Shop/Gateway/PaypalGateway.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use Omnipay\Common\Message\ResponseInterface;
1919
use PaypalServerSdkLib\Authentication\ClientCredentialsAuthCredentialsBuilder;
2020
use PaypalServerSdkLib\Environment;
21-
use PaypalServerSdkLib\Exceptions\ErrorException;
2221
use PaypalServerSdkLib\Models\Builders\AmountBreakdownBuilder;
2322
use PaypalServerSdkLib\Models\Builders\AmountWithBreakdownBuilder;
2423
use PaypalServerSdkLib\Models\Builders\ItemBuilder;
@@ -78,6 +77,18 @@ class PaypalGateway extends AbstractGateway implements GatewayInterface
7877
*/
7978
private ?PaypalServerSdkClient $client = null;
8079

80+
/**
81+
* Setter for unit tests.
82+
*
83+
* @param PaypalServerSdkClient $client
84+
*
85+
* @return void
86+
*/
87+
public function setClient(PaypalServerSdkClient $client): void
88+
{
89+
$this->client = $client;
90+
}
91+
8192
/**
8293
* Get the human-readable name of the payment gateway.
8394
*

tests/Webshop/Checkout/CheckoutFinalizeOrCancelControllerTest.php

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
namespace Tests\Webshop\Checkout;
2020

21+
use App\Actions\Shop\CheckoutService;
2122
use App\Enum\OmnipayProviderType;
2223
use App\Enum\PaymentStatusType;
2324
use Illuminate\Support\Facades\Session;
@@ -227,4 +228,241 @@ public function testTransactionIdGeneration(): void
227228
$this->assertRedirect($response);
228229
$response->assertRedirect(route('shop.checkout.complete'));
229230
}
231+
232+
/**
233+
* Test finalizing PayPal payment successfully returns CheckoutResource.
234+
*
235+
* @return void
236+
*/
237+
public function testFinalizePaypalPaymentSuccess(): void
238+
{
239+
// Mock the CheckoutService
240+
$mockCheckoutService = \Mockery::mock(CheckoutService::class);
241+
242+
// Set up order in processing state with PayPal provider
243+
$this->test_order->status = PaymentStatusType::PROCESSING;
244+
$this->test_order->provider = OmnipayProviderType::PAYPAL;
245+
$this->test_order->save();
246+
247+
// Mock handlePaymentReturn to update order status to COMPLETED
248+
$mockCheckoutService->shouldReceive('handlePaymentReturn')
249+
->once()
250+
->andReturnUsing(function ($order, $provider) {
251+
$order->status = PaymentStatusType::COMPLETED;
252+
$order->save();
253+
254+
return $order;
255+
});
256+
257+
// Bind the mock to the service container
258+
$this->app->instance(CheckoutService::class, $mockCheckoutService);
259+
260+
$provider = OmnipayProviderType::PAYPAL->value;
261+
$transaction_id = $this->test_order->transaction_id;
262+
263+
$response = $this->get('/api/v2/Shop/Checkout/Finalize/' . $provider . '/' . $transaction_id);
264+
265+
// For PayPal, it should return JSON (CheckoutResource), not a redirect
266+
$this->assertOk($response);
267+
$response->assertJson([
268+
'is_success' => true,
269+
'complete_url' => route('shop.checkout.complete'),
270+
'redirect_url' => null,
271+
'message' => 'Payment completed successfully.',
272+
]);
273+
274+
// Verify order status was updated to COMPLETED
275+
$this->assertDatabaseHas('orders', [
276+
'id' => $this->test_order->id,
277+
'status' => PaymentStatusType::COMPLETED->value,
278+
]);
279+
}
280+
281+
/**
282+
* Test finalizing PayPal payment failure returns CheckoutResource.
283+
*
284+
* @return void
285+
*/
286+
public function testFinalizePaypalPaymentFailure(): void
287+
{
288+
// Mock the CheckoutService
289+
$mockCheckoutService = \Mockery::mock(CheckoutService::class);
290+
291+
// Set up order in processing state with PayPal provider
292+
$this->test_order->status = PaymentStatusType::PROCESSING;
293+
$this->test_order->provider = OmnipayProviderType::PAYPAL;
294+
$this->test_order->save();
295+
296+
// Mock handlePaymentReturn to return order with PROCESSING status (failed payment)
297+
$mockCheckoutService->shouldReceive('handlePaymentReturn')
298+
->once()
299+
->andReturnUsing(function ($order, $provider) {
300+
// Return a fresh instance to ensure attributes are loaded
301+
return $this->test_order;
302+
});
303+
304+
// Bind the mock to the service container
305+
$this->app->instance(CheckoutService::class, $mockCheckoutService);
306+
307+
$provider = OmnipayProviderType::PAYPAL->value;
308+
$transaction_id = $this->test_order->transaction_id;
309+
310+
$response = $this->get('/api/v2/Shop/Checkout/Finalize/' . $provider . '/' . $transaction_id);
311+
312+
// For PayPal, it should return JSON (CheckoutResource), not a redirect
313+
$response->assertStatus(400);
314+
$response->assertJson([
315+
'is_success' => false,
316+
'complete_url' => null,
317+
'redirect_url' => route('shop.checkout.failed'),
318+
'message' => 'Payment failed or was not completed.',
319+
]);
320+
321+
// Verify order status was NOT updated to COMPLETED
322+
$this->assertDatabaseHas('orders', [
323+
'id' => $this->test_order->id,
324+
'status' => PaymentStatusType::PROCESSING->value,
325+
]);
326+
}
327+
328+
/**
329+
* Test finalizing PayPal payment with cancelled status.
330+
*
331+
* @return void
332+
*/
333+
public function testFinalizePaypalPaymentCancelled(): void
334+
{
335+
// Mock the CheckoutService
336+
$mockCheckoutService = \Mockery::mock(CheckoutService::class);
337+
338+
// Set up order in processing state with PayPal provider
339+
$this->test_order->status = PaymentStatusType::PROCESSING;
340+
$this->test_order->provider = OmnipayProviderType::PAYPAL;
341+
$this->test_order->save();
342+
343+
// Mock handlePaymentReturn to update order status to CANCELLED
344+
$mockCheckoutService->shouldReceive('handlePaymentReturn')
345+
->once()
346+
->andReturnUsing(function ($order, $provider) {
347+
$order->status = PaymentStatusType::CANCELLED;
348+
$order->save();
349+
350+
// Return a fresh instance to ensure attributes are loaded properly
351+
return $order->fresh();
352+
});
353+
354+
// Bind the mock to the service container
355+
$this->app->instance(CheckoutService::class, $mockCheckoutService);
356+
357+
$provider = OmnipayProviderType::PAYPAL->value;
358+
$transaction_id = $this->test_order->transaction_id;
359+
360+
$response = $this->get('/api/v2/Shop/Checkout/Finalize/' . $provider . '/' . $transaction_id);
361+
362+
// For PayPal, it should return JSON (CheckoutResource), not a redirect
363+
$response->assertStatus(400);
364+
$response->assertJson([
365+
'is_success' => false,
366+
'complete_url' => null,
367+
'redirect_url' => route('shop.checkout.failed'),
368+
'message' => 'Payment failed or was not completed.',
369+
]);
370+
371+
// Verify order status was updated to CANCELLED
372+
$this->assertDatabaseHas('orders', [
373+
'id' => $this->test_order->id,
374+
'status' => PaymentStatusType::CANCELLED->value,
375+
]);
376+
}
377+
378+
/**
379+
* Test that PayPal finalization returns CheckoutResource, not RedirectResponse.
380+
*
381+
* @return void
382+
*/
383+
public function testFinalizePaypalReturnsJsonNotRedirect(): void
384+
{
385+
// Mock the CheckoutService
386+
$mockCheckoutService = \Mockery::mock(CheckoutService::class);
387+
388+
// Set up order in processing state with PayPal provider
389+
$this->test_order->status = PaymentStatusType::PROCESSING;
390+
$this->test_order->provider = OmnipayProviderType::PAYPAL;
391+
$this->test_order->save();
392+
393+
// Mock handlePaymentReturn to update order status to COMPLETED
394+
$mockCheckoutService->shouldReceive('handlePaymentReturn')
395+
->once()
396+
->andReturnUsing(function ($order, $provider) {
397+
$order->status = PaymentStatusType::COMPLETED;
398+
$order->save();
399+
400+
return $order;
401+
});
402+
403+
// Bind the mock to the service container
404+
$this->app->instance(CheckoutService::class, $mockCheckoutService);
405+
406+
$provider = OmnipayProviderType::PAYPAL->value;
407+
$transaction_id = $this->test_order->transaction_id;
408+
409+
$response = $this->get('/api/v2/Shop/Checkout/Finalize/' . $provider . '/' . $transaction_id);
410+
411+
// For PayPal, verify it returns JSON content type, not a redirect
412+
$this->assertOk($response);
413+
$response->assertHeader('Content-Type', 'application/json');
414+
415+
// Verify the response structure matches CheckoutResource
416+
$response->assertJsonStructure([
417+
'is_success',
418+
'complete_url',
419+
'redirect_url',
420+
'message',
421+
'order' => [
422+
'id',
423+
'transaction_id',
424+
'status',
425+
],
426+
]);
427+
}
428+
429+
/**
430+
* Test that non-PayPal provider still returns redirect.
431+
*
432+
* @return void
433+
*/
434+
public function testFinalizeNonPayPalReturnsRedirect(): void
435+
{
436+
// Set up order in processing state with DUMMY provider (not PayPal)
437+
$this->test_order->status = PaymentStatusType::PROCESSING;
438+
$this->test_order->provider = OmnipayProviderType::DUMMY;
439+
$this->test_order->save();
440+
441+
$provider = OmnipayProviderType::DUMMY->value;
442+
$transaction_id = $this->test_order->transaction_id;
443+
444+
Session::put('metadata.' . $this->test_order->id, [
445+
'payment_id' => 'dummy-payment-123',
446+
'status' => 'completed',
447+
'transactionReference' => $this->test_order->transaction_id,
448+
'card' => [
449+
'number' => self::VALID_CARD_NUMBER_SUCCESS,
450+
'expiryMonth' => '12',
451+
'expiryYear' => '2025',
452+
'cvv' => '123',
453+
],
454+
]);
455+
456+
$response = $this->get('/api/v2/Shop/Checkout/Finalize/' . $provider . '/' . $transaction_id);
457+
458+
// For non-PayPal providers, it should still return a redirect
459+
$this->assertRedirect($response);
460+
$response->assertRedirect(route('shop.checkout.complete'));
461+
}
462+
463+
public function tearDown(): void
464+
{
465+
\Mockery::close();
466+
parent::tearDown();
467+
}
230468
}

0 commit comments

Comments
 (0)