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 a very secure implementation which hides additional error messages when placing an order at the Checkout Payment Page. Even though, a 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 catches 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.
[php]
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;
}
[/php]
As a result, a customer sees the same error message over and over again even though different errors and reasons for unsuccessful transaction exist.
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 a great option since we want to show custom error messages on the Checkout Payment Page.
Our plan is the following:
- Create around plugin for the
Magento\Checkout\Model\GuestPaymentInformationManagement::savePaymentInformationAndPlaceOrder()
method. It will cover guest checkout order placement (when a customer isn’t logged in). - Create around plugin for the
Magento\Checkout\Model\PaymentInformationManagement::savePaymentInformationAndPlaceOrder()
method. It will cover logged in customer flow. - 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 overrides original call to the Magento\Checkout\Model\PaymentInformationManagement::savePaymentInformationAndPlaceOrder()
method.
[php]
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;
}
}
[/php]
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
[php]
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;
}
[/php]
Once classes are created we have to add plugin declaration into the di.xml
configuration file.
[xml]
<?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>
[/xml]
As a result, additional error messages will be shown on the Checkout Payment Page.
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:
[ssh]
$ composer require pronko/magento-2-payment-messages
[/ssh]
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.
Leave a Reply
You must be logged in to post a comment.