Plugins & Observers

Extension points, event subscriptions, and DI preferences

Plugin Declarations

The Payment module declares 2 plugins that modify checkout behavior.

Plugin Name Target Class Area Purpose
PaymentMethodProcess Block\Form\Container global Processes payment method forms in container
ProcessPaymentConfiguration Checkout\Block\Checkout\LayoutProcessor frontend Injects payment config into checkout layout

Plugin Details

PaymentMethodProcess

Located in Plugin\PaymentMethodProcess. Handles Braintree CC Vault integration.

// di.xml configuration
<type name="Magento\Payment\Block\Form\Container">
    <plugin name="PaymentMethodProcess"
            type="Magento\Payment\Plugin\PaymentMethodProcess"/>
</type>

<type name="Magento\Payment\Plugin\PaymentMethodProcess">
    <arguments>
        <argument name="braintreeCCVault" xsi:type="string">braintree_cc_vault</argument>
    </arguments>
</type>

ProcessPaymentConfiguration

Located in Plugin\PaymentConfigurationProcess. Adds payment configuration to checkout JS config.

// frontend/di.xml
<type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
    <plugin name="ProcessPaymentConfiguration"
            type="Magento\Payment\Plugin\PaymentConfigurationProcess"/>
</type>

// Plugin adds payment component config to:
// jsLayout > components > checkout > children > steps >
//   children > billing-step > children > payment

Observer Declarations

The Payment module subscribes to 2 events in the global area.

Event Observer Name Class
sales_order_save_before payment_sales_order_save_before Observer\SalesOrderBeforeSaveObserver
sales_order_status_unassign sales_order_status_update Observer\UpdateOrderStatusForPaymentMethodsObserver

Observer Details

SalesOrderBeforeSaveObserver

Validates payment data integrity before order is saved to database.

// Observer\SalesOrderBeforeSaveObserver::execute()
public function execute(Observer $observer)
{
    $order = $observer->getEvent()->getOrder();
    $payment = $order->getPayment();

    // Validates payment method is still available
    // Checks payment additional information
    // Ensures required data is present
}

UpdateOrderStatusForPaymentMethodsObserver

Updates payment method configuration when order status is unassigned.

// Observer\UpdateOrderStatusForPaymentMethodsObserver::execute()
public function execute(Observer $observer)
{
    $status = $observer->getEvent()->getStatus();
    $state = $observer->getEvent()->getState();

    // Updates payment/method/order_status config
    // when a status is unassigned from a state
}

DI Preferences

The Payment module declares 8 interface-to-implementation preferences.

Interface Implementation
Api\Data\PaymentMethodInterface Model\PaymentMethod
Api\Data\PaymentAdditionalInfoInterface Model\PaymentAdditionalInfo
Api\PaymentMethodListInterface Model\PaymentMethodList
Gateway\Validator\ResultInterface Gateway\Validator\Result
Gateway\ConfigFactoryInterface Gateway\Config\ConfigFactory
Gateway\Command\CommandManagerPoolInterface Gateway\Command\CommandManagerPool
Gateway\Data\PaymentDataObjectFactoryInterface Gateway\Data\PaymentDataObjectFactory
Gateway\ErrorMapper\ErrorMessageMapperInterface Gateway\ErrorMapper\ErrorMessageMapper

Key Virtual Types

Virtual types are extensively used to configure payment logging and error mapping.

Payment Logger

<!-- Dedicated payment log file -->
<virtualType name="Magento\Payment\Model\Method\VirtualDebug"
             type="Magento\Framework\Logger\Handler\Base">
    <arguments>
        <argument name="fileName" xsi:type="string">/var/log/payment.log</argument>
    </arguments>
</virtualType>

<virtualType name="Magento\Payment\Model\Method\VirtualLogger"
             type="Magento\Framework\Logger\Monolog">
    <arguments>
        <argument name="handlers" xsi:type="array">
            <item name="debug" xsi:type="object">
                Magento\Payment\Model\Method\VirtualDebug
            </item>
        </argument>
    </arguments>
</virtualType>

Error Mapping Configuration

<!-- Error mapping reader and schema -->
<virtualType name="Magento\Payment\Gateway\ErrorMapper\VirtualSchemaLocator"
             type="Magento\Framework\Config\GenericSchemaLocator">
    <arguments>
        <argument name="moduleName" xsi:type="string">Magento_Payment</argument>
        <argument name="schema" xsi:type="string">error_mapping.xsd</argument>
    </arguments>
</virtualType>

<virtualType name="Magento\Payment\Gateway\ErrorMapper\VirtualConfigReader"
             type="Magento\Framework\Config\Reader\Filesystem">
    <arguments>
        <argument name="converter" xsi:type="object">
            Magento\Payment\Gateway\ErrorMapper\XmlToArrayConverter
        </argument>
        <argument name="schemaLocator" xsi:type="object">
            Magento\Payment\Gateway\ErrorMapper\VirtualSchemaLocator
        </argument>
        <argument name="fileName" xsi:type="string">error_mapping.xml</argument>
    </arguments>
</virtualType>

Extension Best Practices

DO

  • + Use Gateway framework for new payment methods
  • + Configure methods via di.xml virtual types
  • + Implement error_mapping.xml for user-friendly errors
  • + Use Method\Logger for payment debugging
  • + Implement SpecificationInterface for availability rules

DON'T

  • - Extend AbstractMethod for new methods
  • - Store sensitive data in payment additional_info
  • - Log full credit card numbers
  • - Expose raw gateway error codes to customers
  • - Override core payment observers

Adding a Custom Payment Method

Minimum configuration required to add a Gateway-based payment method:

<!-- etc/di.xml -->
<!-- 1. Method Facade -->
<virtualType name="MyPaymentFacade" type="Magento\Payment\Model\Method\Adapter">
    <arguments>
        <argument name="code" xsi:type="const">My\Payment\Model\Ui\ConfigProvider::CODE</argument>
        <argument name="formBlockType" xsi:type="string">Magento\Payment\Block\Form</argument>
        <argument name="infoBlockType" xsi:type="string">Magento\Payment\Block\Info</argument>
        <argument name="valueHandlerPool" xsi:type="object">MyPaymentValueHandlerPool</argument>
        <argument name="commandPool" xsi:type="object">MyPaymentCommandPool</argument>
    </arguments>
</virtualType>

<!-- 2. Command Pool -->
<virtualType name="MyPaymentCommandPool" type="Magento\Payment\Gateway\Command\CommandPool">
    <arguments>
        <argument name="commands" xsi:type="array">
            <item name="authorize" xsi:type="string">MyPaymentAuthorizeCommand</item>
        </argument>
    </arguments>
</virtualType>

<!-- 3. Value Handler Pool -->
<virtualType name="MyPaymentValueHandlerPool" type="Magento\Payment\Gateway\Config\ValueHandlerPool">
    <arguments>
        <argument name="handlers" xsi:type="array">
            <item name="default" xsi:type="string">MyPaymentConfigValueHandler</item>
        </argument>
    </arguments>
</virtualType>

<!-- etc/config.xml -->
<config>
    <default>
        <payment>
            <mypayment>
                <active>1</active>
                <model>MyPaymentFacade</model>
                <title>My Payment Method</title>
                <payment_action>authorize</payment_action>
            </mypayment>
        </payment>
    </default>
</config>