Reputation: 93
I am currently working on a plugin for a payment provider. I always get a GET or POST with the data after the payment process. Most of the time it is a GET, everything is great. Many payment methods work, only one does not.
Credit card is the only payment method that returns POST data instead of GET because of 3D-Secure. The problem is that you get logged out (either locally or on a test system).
I have tried several things. Among other things, I disabled CSRF protection completely, disabled it in the controller, with @CSRFExempt (or something similar, ChatGPT was that) in the controller. None of this helped. I also sent the _csrf_token to the payment provider and it came back in the response. This should be the most correct of all solutions, but it doesn't work.
It is noticeable that the token I pass looks completely different from the valid tokens stored in the session when a GET is returned? But the token is definitely something from the checkout and also exactly what is in the hidden field. Nothing has been changed at this point. From the checkout itself, only possible fields have been added to the payment methods (e.g. iDEAL needs the previous selection of a bank), but these are not transferred. So nothing has changed here.
Can you explain this? I have now temporarily logged the customer back in via the session by first passing me the session ID with a security hash and then getting it back. But I would prefer if this step was not necessary to eliminate potential sources of error.
By the way, there was a post about this in the forum back in November. So apparently I'm not the only one with this problem. https://forum.shopware.com/t/asynchronouspayment-unable-to-return-to-checkout-finish-orderid-xxx/97022
Cheers Chris
Upvotes: 2
Views: 168
Reputation: 13201
The problem is the Same-Site
attribute of the session cookie is set to lax
for security reasons. To quote:
Means that the cookie is not sent on cross-site requests, such as on requests to load images or frames, but is sent when a user is navigating to the origin site from an external site (for example, when following a link). This is the default behavior if the SameSite attribute is not specified.
A POST
method request is considered a cross-site request. Hence why you "lose" the the logged-in customer when navigating back to your shop through this method. It's not exactly best practice considering the return from the payment provider for that very reason.
There really aren't many ways around this. Either you keep doing what you're doing now, or you try to redirect the user. You'll have to do it client-side though, using javascript, to finish the POST
request and start a new GET
which wouldn't be considered cross-site. Not exactly a great solution either, but less complex.
You could implement a custom Response
class for that. Slightly changed from Symfony's RedirectResponse
.
use Symfony\Component\HttpFoundation\Response;
class ClientRedirectResponse extends Response
{
protected $targetUrl;
public function __construct(string $url, int $status = 302, array $headers = [])
{
parent::__construct('', $status, $headers);
$this->setTargetUrl($url);
if (!$this->isRedirect()) {
throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status));
}
if (301 == $status && !\array_key_exists('cache-control', array_change_key_case($headers, \CASE_LOWER))) {
$this->headers->remove('cache-control');
}
}
public function getTargetUrl(): string
{
return $this->targetUrl;
}
public function setTargetUrl(string $url): static
{
if ('' === $url) {
throw new \InvalidArgumentException('Cannot redirect to an empty URL.');
}
$this->targetUrl = $url;
$this->setContent(
sprintf('<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
<title>Redirecting to %1$s</title>
<script>window.location.href = "%1$s";</script>
</head>
<body>
Redirecting to <a href="%1$s">%1$s</a>.
</body>
</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
return $this;
}
}
Then in your return route action:
$finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]);
return new ClientRedirectResponse($finishUrl);
You may also be able to use RedirectResponse
, which uses the Location
header, but you may have to change the status code to force the use of the GET
method.
Upvotes: 0