How to Show Payment Errors on the Checkout Payment Page in Magento 2

How to Show Payment Errors on the Checkout Payment Page in Magento 2
Step-by-step tutorial on how to enable additional error messages when placing order on the Checkout Payment page. Especially it is important for customers to show relevant error messages.

Usually, when a customer hits the Place Order button on the Checkout Payment Page the expectation is that the order would be placed successfully. Sometimes there might be an error returned back from Payment Service Provider in response to payment payload request sent from Magento 2 payment module. The error might provide additional details to a customer e.g. Credit card has been expired, Bank has declined the transaction and so on.

General Error Message Problem

Magento 2.0 and 2.1 releases have very secure implementation which hide additional error messages when placing an order at the Checkout Payment Page. Even though, friendly error message is returned back from a Payment Service Provider or its implementation in the custom payment module.

You may also find interesting to read about adding Custom Frontend Payment Validation in Magento 2 post.

The Magento\Checkout\Model\PaymentInformationManagement class implements savePaymentInformationAndPlaceOrder() method from the Magento\Checkout\Api\PaymentInformationManagementInterface interface. There is also similar to the PaymentInformationManagement class, the Magento\Checkout\Model\GuestPaymentInformationManagement class responsible for guest checkout.

The savePaymentInformationAndPlaceOrder() method cathes all exceptions and throws the Magento\Framework\Exception\CouldNotSaveException exception with the "An error occurred on the server. Please try to place the order again." message.

    public function savePaymentInformationAndPlaceOrder(
        $cartId,
        $email,
        \Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
        \Magento\Quote\Api\Data\AddressInterface $billingAddress = null
    ) {
        $this->savePaymentInformation($cartId, $email, $paymentMethod, $billingAddress);
        try {
            $orderId = $this->cartManagement->placeOrder($cartId);
        } catch (\Exception $e) {
            throw new CouldNotSaveException(
                __('An error occurred on the server. Please try to place the order again.'),
                $e
            );
        }
        return $orderId;
    }

As a result, customer sees the same error message over and over again even though different errors and reasons for unsuccessful transaction exist. Magento 2 Checkout General Error Message

Solution #1 - Implement Interfaces

First solution requires us to implement 2 interfaces Magento\Checkout\Api\PaymentInformationManagementInterface and Magento\Checkout\Api\GuestPaymentInformationManagementInterface with custom implementation. Additional preferences should be added for the implemented interfaces in the di.xml configuration file.

This solution will work for custom local projects. Magento Marketplace technical review will be failed due to copy-pasted code from the Magento_Checkout module. Why copy-pasted you ask? Simply because the functionality and implementation is absolutely the same except additional 2-3 lines of code to cover custom LocalizedException exceptions.

Solution #2 - Create Around Plugin

The interceptor or plugin mechanism allows to override existing method and add custom logic before, after or instead (around) of the original method. The plugin with the around method will be great option since we want to show custom error messages on the Checkout Payment Page.

Our plan is the following:

  1. Create around plugin for the Magento\Checkout\Model\GuestPaymentInformationManagement::savePaymentInformationAndPlaceOrder() method. It will cover guest checkout order placement (when customer isn't logged in).
  2. Create around plugin for the Magento\Checkout\Model\PaymentInformationManagement::savePaymentInformationAndPlaceOrder() method. It will cover logged in customer flow.
  3. Assign new plugins via di.xml configuration file.

Plugins

Both plugins methods are similar to its original savePaymentInformationAndPlaceOrder() ones with the only difference of additional catch statement.

The Pronko\PaymentMessages\Plugin\PaymentInformationManagement plugin with the aroundSavePaymentInformationAndPlaceOrder() method override original call to the Magento\Checkout\Model\PaymentInformationManagement::savePaymentInformationAndPlaceOrder() method.

namespace Pronko\PaymentMessages\Plugin;

use Magento\Checkout\Model\PaymentInformationManagement as CheckoutPaymentInformationManagement;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Quote\Api\CartManagementInterface;
use Psr\Log\LoggerInterface;

/**
 * Class PaymentInformationManagement
 */
class PaymentInformationManagement
{
    /**
     * @var CartManagementInterface
     */
    private $cartManagement;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * PaymentInformationManagement constructor.
     * @param CartManagementInterface $cartManagement
     * @param LoggerInterface $logger
     */
    public function __construct(
        CartManagementInterface $cartManagement,
        LoggerInterface $logger
    ) {
        $this->cartManagement = $cartManagement;
        $this->logger = $logger;
    }

    /**
     * @param CheckoutPaymentInformationManagement $subject
     * @param \Closure $proceed
     * @param $cartId
     * @param \Magento\Quote\Api\Data\PaymentInterface $paymentMethod
     * @param \Magento\Quote\Api\Data\AddressInterface|null $billingAddress
     * @return int
     * @throws CouldNotSaveException
     */
    public function aroundSavePaymentInformationAndPlaceOrder(
        CheckoutPaymentInformationManagement $subject,
        \Closure $proceed,
        $cartId,
        \Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
        \Magento\Quote\Api\Data\AddressInterface $billingAddress = null
    ) {
        $subject->savePaymentInformation($cartId, $paymentMethod, $billingAddress);
        try {
            $orderId = $this->cartManagement->placeOrder($cartId);
        } catch (LocalizedException $exception) {
            throw new CouldNotSaveException(__($exception->getMessage()));
        } catch (\Exception $exception) {
            $this->logger->critical($exception);
            throw new CouldNotSaveException(
                __('An error occurred on the server. Please try to place the order again.'),
                $exception
            );
        }
        return $orderId;
    }
}

In case custom payment module throws an exception which is an instance of the Magento\Framework\Exception\LocalizedException class our plugin will use exception message to create the Magento\Framework\Exception\CouldNotSaveException exception. For all other cases the exception message will be hidden. It is also important to log the critical exception for further investigation purposes.

Please note, the Magento\Payment\Gateway\Command\CommandException class extends the Magento\Framework\Exception\LocalizedException. In same cases the CommandException class is used to throw system-related messages which should not be shown to a customer.

The Pronko\PaymentMessages\Plugin\GuestPaymentInformationManagement plugin is similar to the Pronko\PaymentMessages\Plugin\PaymentInformationManagement with the only difference that it has to pass $email variable.

Example: Pronko\PaymentMessages\Plugin\GuestPaymentInformationManagement 

    public function aroundSavePaymentInformationAndPlaceOrder(
        CheckoutGuestPaymentInformationManagement $subject,
        \Closure $proceed,
        $cartId,
        $email,
        \Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
        \Magento\Quote\Api\Data\AddressInterface $billingAddress = null
    ) {
        $subject->savePaymentInformation($cartId, $email, $paymentMethod, $billingAddress);
        try {
            $orderId = $this->cartManagement->placeOrder($cartId);
        } catch (LocalizedException $exception) {
            throw new CouldNotSaveException(__($exception->getMessage()));
        } catch (\Exception $exception) {
            $this->logger->critical($exception);
            throw new CouldNotSaveException(
                __('An error occurred on the server. Please try to place the order again.'),
                $exception
            );
        }
        return $orderId;
    }

Once classes are created we have to add plugin declaration into the di.xml configuration file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Checkout\Model\GuestPaymentInformationManagement">
        <plugin name="PronkoPaymentMessagesGuestPaymentInformationManagement" type="Pronko\PaymentMessages\Plugin\GuestPaymentInformationManagement" />
    </type>
    <type name="Magento\Checkout\Model\PaymentInformationManagement">
        <plugin name="PronkoPaymentMessagesPaymentInformationManagement" type="Pronko\PaymentMessages\Plugin\PaymentInformationManagement" />
    </type>
</config>

As a result additional error messages will be shown on the Checkout Payment Page. Magento 2 Custom Payment Error Message

Magento 2.2

The Magento 2.2 release includes fixes for both Magento\Checkout\Model\PaymentInformationManagement and Magento\Checkout\Model\GuestPaymentInformationManagement classes and enables custom error messages to be shown on the Checkout Payment Page. But if you are still running Magento 2.0 or 2.1 you have to do some development to show custom messages.

Payment Messages Module

I've created small open source module to cover Magento 2.0 and Magento 2.1 versions. It will help both extension developers to decrease lines of code in their custom projects and payment extensions. Merchants will be happy to see customer friendly messages during on the Checkout Payment Page.

Download Magento 2 Payment Messages module: GitHub, Packagist

Or simply add it into your project:

composer require pronko/magento-2-payment-messages

Recommendations

Read more about Plugins (Interceptors) in Magento 2 to get better idea on how it works.

Read about Payments and API if you would like to create your own payment method.

Subscribe to my online course "Building Payment integration in Magento 2 Class".

Before you finish reading

Magento 2 provides powerful extension mechanism which allows us, Magento Developers easily modify core behaviour of the system. In case of error messages and order processing we have added 2 plugins to accomplish our task. Of course, approach depends on complexity of the implementation you are about to extend or modify.

Feel free to reach out to me with any questions.

magento 2 checkout payment howto

Related Articles

Comments

Next Article Previous Article

LinkedIn Twitter Facebook