Search code examples
shopwareshopware6shopware6-app

Finalize() Method of Shopware 6 Payment Plugin Refuses To Run


I am implementing the Shopware 6 Asynchronous payment method following the guide Payment Plugin Guide.

The pay(AsyncPaymentTransactionStruct $transaction, RequestDataBag $dataBag, SalesChannelContext $salesChannelContext) method works well and redirects properly to my payment processor.

My return URL works fine too and returns to http://localhost/account, on return, I expected the finalize(AsyncPaymentTransactionStruct $transaction, Request $request, SalesChannelContext $salesChannelContext) method to run but nothing happens.

What am I doing wrong, I can as well implement my own solution but I would like to get this to work.

Here is my code

<?php declare(strict_types=1);

namespace PaystackShopware6Plugin\Service;

use Symfony\Component\HttpFoundation\Request;
use UIPaymentShopware6Plugin\Service\PaymentLink;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct;
use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentProcessException;
use Shopware\Core\Checkout\Payment\Exception\CustomerCanceledAsyncPaymentException;
use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler;
use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\AsynchronousPaymentHandlerInterface;

class PayUI implements AsynchronousPaymentHandlerInterface
{
    private OrderTransactionStateHandler $transactionStateHandler;
    private PaymentLink $paymentLink;

    public function __construct(OrderTransactionStateHandler $transactionStateHandler, PaymentLink $paymentLink) {
        $this->transactionStateHandler = $transactionStateHandler;
        $this->paymentLink = $paymentLink;
    }

    /**
     * @throws AsyncPaymentProcessException
     */
    public function pay(AsyncPaymentTransactionStruct $transaction, RequestDataBag $dataBag, SalesChannelContext $salesChannelContext): RedirectResponse
    {
        // Method that sends the return URL to the external gateway and gets a redirect URL back
        try {
/**the getPaymentLink(param) function returns a 'payment link' and accepts a parameter (param) which tells the payment processor which link to redirect the user to after a successful or failed transaction

*/
            $redirectUrl = $this->paymentLink->getPaymentLink('localhost/account');//$this->sendReturnUrlToExternalGateway(

$transaction->getReturnUrl());
        } catch (\Exception $e) {
            throw new AsyncPaymentProcessException(
                $transaction->getOrderTransaction()->getId(),
                'An error occurred during the communication with external payment gateway' . PHP_EOL . $e->getMessage()
            );
        }

        // Redirect to external gateway
        return new RedirectResponse($redirectUrl);
    }

    /**
     * @throws CustomerCanceledAsyncPaymentException
     */
    public function finalize(AsyncPaymentTransactionStruct $transaction, Request $request, SalesChannelContext $salesChannelContext): void
    {

        
        $transactionId = $transaction->getOrderTransaction()->getId();

        // Example check if the user cancelled. Might differ for each payment provider
        if ($request->query->getBoolean('cancel')) {
            throw new CustomerCanceledAsyncPaymentException(
                $transactionId,
                'Customer canceled the payment on the PayPal page'
            );
        }

        // Example check for the actual status of the payment. Might differ for each payment provider
        $paymentState = $request->query->getAlpha('status');

        $context = $salesChannelContext->getContext();
        if ($paymentState === 'completed') {
            // Payment completed, set transaction status to "paid"
            $this->transactionStateHandler->paid($transaction->getOrderTransaction()->getId(), $context);
        } else {
            // Payment not completed, set transaction status to "open"
            $this->transactionStateHandler->reopen($transaction->getOrderTransaction()->getId(), $context);
        }
    }

    private function sendReturnUrlToExternalGateway(string $getReturnUrl): string
    {
        $paymentProviderUrl = '';
        // Do some API Call to your payment provider
        return $paymentProviderUrl;
    }
}


Solution

  • You can't directly redirect to shopUrl/account from your payment processor, as shopware needs to be aware that the payment on your side of things completed and needs to call the finalize() method of the payment handler. Therefore your payment processor should rather redirect to the returnUrl provided in the pay step, this is a callback url with the id of the current pending transaction, and when it is called it finish the payment by executing the finalize method of the payment handler and then redirecting the customer to the storefront.

    So that means you probably need to pass the returnUrl you get in the pay() step forward to your payment processor, so that that processor can then redirect back to that callback url, when the transaction handling is finished on the processor side, so that shopware can also finish/finalize the transaction.

    Refer to the docs for a overview of how the workflow is expected to be executed.

    So my guess would be that the code you provided should be changed in the following way: Instead of

    $redirectUrl = $this->paymentLink->getPaymentLink('localhost/account');
    

    You should use:

    $redirectUrl = $this->paymentLink->getPaymentLink($transaction->getReturnUrl());