Skip to content

Commit

Permalink
Invalid Blik code redirects to error page (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
lchrusciel authored Oct 31, 2024
2 parents a22da86 + b673061 commit 695fb50
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 23 deletions.
22 changes: 18 additions & 4 deletions src/Controller/DisplayPaymentFailedPageAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace CommerceWeavers\SyliusTpayPlugin\Controller;

use CommerceWeavers\SyliusTpayPlugin\Model\PaymentDetails;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\Model\PaymentInterface;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand All @@ -22,12 +24,24 @@ public function __construct(
public function __invoke(Request $request, string $orderToken): Response
{
$order = $this->findOrderOr404($orderToken);
$payment = $order->getLastPayment();
$newPayment = $order->getLastPayment();
$failedPayment = $order->getLastPayment(PaymentInterface::STATE_FAILED);

return new Response($this->twig->render('@CommerceWeaversSyliusTpayPlugin/shop/cart/complete/payment_failed.html.twig', [
$context = [
'order' => $order,
'payment' => $payment,
]));
'payment' => $newPayment,
];

if (null !== $failedPayment) {
$paymentDetails = PaymentDetails::fromArray($failedPayment->getDetails());

$context['errorMessage'] = $paymentDetails->getErrorMessage();
}

return new Response($this->twig->render(
'@CommerceWeaversSyliusTpayPlugin/shop/cart/complete/payment_failed.html.twig',
$context,
));
}

private function findOrderOr404(string $orderToken): OrderInterface
Expand Down
13 changes: 13 additions & 0 deletions src/Model/PaymentDetails.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public function __construct(
private ?string $tpayChannelId = null,
#[\SensitiveParameter]
private ?string $visaMobilePhoneNumber = null,
private ?string $errorMessage = null,
) {
}

Expand Down Expand Up @@ -197,6 +198,16 @@ public function setVisaMobilePhoneNumber(?string $visaMobilePhoneNumber): void
$this->visaMobilePhoneNumber = $visaMobilePhoneNumber;
}

public function getErrorMessage(): ?string
{
return $this->errorMessage;
}

public function setErrorMessage(?string $errorMessage): void
{
$this->errorMessage = $errorMessage;
}

public function clearSensitiveData(): void
{
$this->applePayToken = null;
Expand Down Expand Up @@ -228,6 +239,7 @@ public static function fromArray(array $details): self
$details['tpay']['failure_url'] ?? null,
$details['tpay']['tpay_channel_id'] ?? null,
$details['tpay']['visa_mobile_phone_number'] ?? null,
$details['tpay']['error_message'] ?? null,
);
}

Expand All @@ -250,6 +262,7 @@ public function toArray(): array
'failure_url' => $this->failureUrl,
'tpay_channel_id' => $this->tpayChannelId,
'visa_mobile_phone_number' => $this->visaMobilePhoneNumber,
'error_message' => $this->errorMessage,
],
];
}
Expand Down
5 changes: 5 additions & 0 deletions src/Payum/Action/Api/CreateBlikLevelZeroTransactionAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ protected function doExecute(Generic $request, PaymentInterface $model, PaymentD
onSuccess: function (array $response) use ($paymentDetails) {
$paymentDetails->setTransactionId($response['transactionId']);
$paymentDetails->setStatus($response['status']);

if (isset($response['payments']['errors'])) {
$paymentDetails->setStatus(PaymentInterface::STATE_FAILED);
$paymentDetails->setErrorMessage($response['payments']['errors'][0]['errorMessage'] ?? null);
}
},
onFailure: function (array $response) use ($paymentDetails) {
if (array_keys($response) !== ['payments']) {
Expand Down
9 changes: 6 additions & 3 deletions src/Payum/Processor/CreateTransactionProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace CommerceWeavers\SyliusTpayPlugin\Payum\Processor;

use CommerceWeavers\SyliusTpayPlugin\Api\Command\Exception\PaymentFailedException;
use CommerceWeavers\SyliusTpayPlugin\Model\PaymentDetails;
use CommerceWeavers\SyliusTpayPlugin\Payum\Factory\CreateTransactionFactoryInterface;
use Payum\Core\GatewayInterface;
use Payum\Core\Payum;
Expand All @@ -30,7 +31,7 @@ public function process(PaymentInterface $payment): void
$this->refreshPayment($payment);

if ($payment->getState() === PaymentInterface::STATE_FAILED) {
$this->handlePaymentFailure();
$this->handlePaymentFailure($payment);
}
}

Expand All @@ -53,10 +54,12 @@ private function refreshPayment(PaymentInterface $payment): void
/**
* @throws PaymentFailedException
*/
private function handlePaymentFailure(): void
private function handlePaymentFailure(PaymentInterface $payment): void
{
$paymentDetails = PaymentDetails::fromArray($payment->getDetails());

throw new PaymentFailedException(
$this->translator->trans('commerce_weavers_sylius_tpay.shop.payment_failed.error', domain: 'messages'),
$paymentDetails->getErrorMessage() ?? $this->translator->trans('commerce_weavers_sylius_tpay.shop.payment_failed.error'),
);
}

Expand Down
8 changes: 7 additions & 1 deletion templates/shop/cart/complete/payment_failed.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@
<i class="circular close icon"></i>
<div class="content">
{{ 'commerce_weavers_sylius_tpay.shop.payment_failed.title'|trans }}
<div class="sub header">{{ 'commerce_weavers_sylius_tpay.shop.payment_failed.subtitle'|trans }}</div>
<div class="sub header">
{{ 'commerce_weavers_sylius_tpay.shop.payment_failed.subtitle'|trans }}
</div>
</div>
</h1>

<div class="ui negative message">
{{ errorMessage ?? 'commerce_weavers_sylius_tpay.shop.payment_failed.error'|trans }}
</div>

<div class="ui hidden divider"></div>

<form action="{{ path(constant('CommerceWeavers\\SyliusTpayPlugin\\Routing::SHOP_RETRY_PAYMENT'), {'orderToken': order.tokenValue}) }}" method="post" novalidate>
Expand Down
2 changes: 1 addition & 1 deletion tests/Api/Shop/PayingForOrdersByBlikTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function test_it_handles_tpay_error_while_paying_with_blik_based_payment_

$this->assertResponseCode($response, 424);
$this->assertStringContainsString(
'An error occurred while processing your payment. Please try again or contact store support.',
'Podany kod jest nieprawidłowy, bądź utracił ważność.',
$response->getContent(),
);
}
Expand Down
13 changes: 13 additions & 0 deletions tests/E2E/Checkout/TpayPaymentCheckoutTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,17 @@ public function test_it_completes_the_checkout_using_blik(): void

$this->assertPageTitleContains('Waiting for payment');
}

public function test_it_fails_completing_the_checkout_using_invalid_blik_token(): void
{
$this->processWithPaymentMethod('tpay_blik');
$this->fillBlikToken(self::FORM_ID, '999123');
$this->placeOrder();

$this->assertPageTitleContains('Something went wrong with your payment | Web Channel');
$this->assertSame(
'Podany kod jest nieprawidłowy, bądź utracił ważność.',
$this->findElementByXpath("//div[contains(@class, 'message') and contains(@class, 'negative')]")->getText(),
);
}
}
1 change: 1 addition & 0 deletions tests/Helper/PaymentDetailsHelperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ protected function getExpectedDetails(...$overriddenDetails): array
'failure_url' => null,
'tpay_channel_id' => null,
'visa_mobile_phone_number' => null,
'error_message' => null,
],
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,59 @@ public function test_it_throws_an_exception_when_a_gateway_name_cannot_be_determ
$this->createTestSubject()->execute($request->reveal());
}

public function test_it_handles_payment_error(): void
{
$order = $this->prophesize(OrderInterface::class);
$order->getLocaleCode()->willReturn('pl_PL');

$payment = $this->prophesize(PaymentInterface::class);
$payment->getOrder()->willReturn($order);
$payment->getDetails()->willReturn([]);
$payment->setDetails(
$this->getExpectedDetails(
transaction_id: 'tr4ns4ct!0n_id',
status: 'failed',
error_message: 'I do not know what happened here but it does not work LOL!',
),
)->shouldBeCalled();

$token = $this->prophesize(TokenInterface::class);
$token->getGatewayName()->willReturn('tpay');

$request = $this->prophesize(CreateTransaction::class);
$request->getModel()->willReturn($payment);
$request->getToken()->willReturn($token);

$notifyToken = $this->prophesize(TokenInterface::class);
$notifyToken->getTargetUrl()->willReturn('https://cw.org/notify');

$transactions = $this->prophesize(TransactionsApi::class);
$transactions->createTransaction(['factored' => 'payload'])->willReturn([
'result' => 'success',
'status' => 'pending',
'transactionId' => 'tr4ns4ct!0n_id',
'payments' => [
'errors' => [
[
'errorCode' => 'yolo',
'errorMessage' => 'I do not know what happened here but it does not work LOL!',
],
],
],
]);

$this->api->transactions()->willReturn($transactions);

$this->notifyTokenFactory->create($payment, 'tpay', 'pl_PL')->willReturn($notifyToken);

$this->createBlikLevelZeroPaymentPayloadFactory
->createFrom($payment, null, 'https://cw.org/notify', 'pl_PL')
->willReturn(['factored' => 'payload'])
;

$this->createTestSubject()->execute($request->reveal());
}

public function test_it_handles_errors_and_throws_exception_if_unexpected_error_occurred(): void
{
$this->expectException(\Exception::class);
Expand Down
33 changes: 31 additions & 2 deletions tests/Unit/Tpay/Processor/CreateTransactionProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,35 @@ public function test_it_processes_a_transaction_creation(): void
$this->createTestSubject()->process($payment->reveal());
}

public function test_it_throws_an_exception_if_payment_failed(): void
public function test_it_throws_an_exception_with_an_error_message_if_payment_failed(): void
{
$this->expectException(PaymentFailedException::class);
$this->expectExceptionMessage('So long and thanks for all the fish');

$gatewayConfig = $this->prophesize(GatewayConfigInterface::class);
$gatewayConfig->getGatewayName()->willReturn('tpay');

$paymentMethod = $this->prophesize(PaymentMethodInterface::class);
$paymentMethod->getGatewayConfig()->willReturn($gatewayConfig);

$payment = $this->prophesize(PaymentInterface::class);
$payment->getMethod()->willReturn($paymentMethod);
$payment->getState()->willReturn(PaymentInterface::STATE_FAILED);
$payment->getDetails()->willReturn(['tpay' => ['error_message' => 'So long and thanks for all the fish']]);

$this->createTransactionFactory->createNewWithModel($payment)->willReturn($createTransaction = $this->prophesize(CreateTransaction::class));
$this->getStatusFactory->createNewWithModel($payment)->willReturn($getStatus = $this->prophesize(GetStatus::class));

$gateway = $this->prophesize(GatewayInterface::class);
$gateway->execute($createTransaction, true)->shouldBeCalled();
$gateway->execute($getStatus, true)->shouldBeCalled();

$this->payum->getGateway('tpay')->willReturn($gateway);

$this->createTestSubject()->process($payment->reveal());
}

public function test_it_throws_an_exception_with_a_default_message_if_payment_failed(): void
{
$this->expectException(PaymentFailedException::class);
$this->expectExceptionMessage('Payment failed');
Expand All @@ -80,6 +108,7 @@ public function test_it_throws_an_exception_if_payment_failed(): void
$payment = $this->prophesize(PaymentInterface::class);
$payment->getMethod()->willReturn($paymentMethod);
$payment->getState()->willReturn(PaymentInterface::STATE_FAILED);
$payment->getDetails()->willReturn([]);

$this->createTransactionFactory->createNewWithModel($payment)->willReturn($createTransaction = $this->prophesize(CreateTransaction::class));
$this->getStatusFactory->createNewWithModel($payment)->willReturn($getStatus = $this->prophesize(GetStatus::class));
Expand All @@ -89,7 +118,7 @@ public function test_it_throws_an_exception_if_payment_failed(): void
$gateway->execute($getStatus, true)->shouldBeCalled();

$this->payum->getGateway('tpay')->willReturn($gateway);
$this->translator->trans('commerce_weavers_sylius_tpay.shop.payment_failed.error', [], 'messages')->willReturn('Payment failed');
$this->translator->trans('commerce_weavers_sylius_tpay.shop.payment_failed.error')->willReturn('Payment failed');

$this->createTestSubject()->process($payment->reveal());
}
Expand Down
Loading

0 comments on commit 695fb50

Please sign in to comment.