Plugins & Observers Reference

Magento_Sales Module

Complete documentation of 16 plugins and 25 observers

16
Total Plugins
25
Total Observers
10
Frontend Plugins
8
Grid Sync Observers

Plugins Overview

The Magento_Sales module implements 16 plugins to intercept and modify behavior at critical execution points in the order management lifecycle.

Plugin Naming Convention

Pattern: Plugins use descriptive names that reflect their purpose (e.g., authentication, addressUpdate, authorization).

Why: Core Magento code predates strict naming conventions. Third-party modules should follow the "Extend" suffix pattern.

Plugin Execution Priority

sortOrder Value Purpose Example Use Cases
1 Authorization and validation plugins Must run first to prevent unauthorized access
10 Standard business logic plugins Default execution order for most operations
100+ Cosmetic or non-critical plugins Formatting, logging, analytics

Frontend Plugins

Frontend

These plugins are registered in etc/frontend/di.xml and only apply to customer-facing storefront requests.

Order Controller Authentication Plugins (1-10)

Critical Security

Purpose: Validates customer has permission to view their orders, invoices, shipments, and credit memos.

These 10 plugins all use the same class but intercept different controllers:

Plugin Name Intercepts Purpose
authentication Order\Creditmemo Validate access to credit memo view
authentication Order\History Validate access to order history
authentication Order\Invoice Validate access to invoice view
authentication Order\View Validate access to order detail view
...and 6 more authentication plugins for print actions, shipment, and reorder

Plugin Class

Class: Magento\Sales\Controller\Order\Plugin\Authentication
Sort Order: 10 (default)

Behavior

  1. Extract order ID from request parameters
  2. Load order from repository
  3. Check if current customer owns the order
  4. If mismatch: redirect to 404 (prevent information disclosure)
  5. If match: allow request to proceed

Security Note

Returns 404 (not 403) to prevent order ID enumeration attacks. This prevents attackers from determining which order IDs exist in the system.

Code Example

public function beforeExecute(\Magento\Framework\App\ActionInterface $subject)
{
    $orderId = (int) $subject->getRequest()->getParam('order_id');

    if (!$orderId) {
        throw new NotFoundException(__('Page not found.'));
    }

    $customer = $this->customerRepository->get($orderId);
    $customerId = $this->customerSession->getCustomerId();

    if ($order->getCustomerId() != $customerId) {
        // Return 404 to prevent order ID enumeration
        throw new NotFoundException(__('Page not found.'));
    }

    return null; // Proceed with action
}

Security Impact

Critical: Without these plugins, any customer could view any order by guessing order IDs.

Attack Vector Prevented

# Without plugin:
GET /sales/order/view/order_id/123  → Shows order details
GET /sales/order/view/order_id/124  → Shows different customer's order ❌

# With plugin:
GET /sales/order/view/order_id/123  → Shows order details (if owned)
GET /sales/order/view/order_id/124  → 404 Not Found ✓

Global Plugins

Global

These plugins are registered in etc/di.xml and apply across all areas (frontend, admin, API).

14. Authorization Plugin

Critical Security

Intercepts: Magento\Sales\Model\ResourceModel\Order

Purpose: Validates user has permission to modify orders via repository operations.

Behavior: Before any order save/delete:

  1. Check current user's ACL permissions
  2. Verify user has Magento_Sales::actions_edit permission
  3. If admin user: check role restrictions
  4. If API token: verify scope includes sales operations
  5. Throw AuthorizationException if denied

15. ConvertBlobToString Plugin (Shipping Labels)

Intercepts: Magento\Sales\Api\ShipmentRepositoryInterface

Purpose: Converts binary shipping label data to base64 string for API responses.

Why: API responses cannot contain binary data. Base64 encoding required for JSON/XML.

{
  "entity_id": 12345,
  "increment_id": "000000123",
  "extension_attributes": {
    "shipping_label": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXB..."
  }
}

16. AddTransactionCommentAfterCapture Plugin

Intercepts: Magento\Sales\Model\Service\InvoiceService

Purpose: Automatically adds payment transaction comments to order after invoice capture.

Use Cases:

  • Payment Reconciliation: Match Magento transactions with payment gateway reports
  • Fraud Investigation: Complete payment capture history
  • Customer Service: Quick reference to payment details

Observers Overview

The Magento_Sales module implements 25 observers that react to events dispatched during sales operations.

Observer Execution

  • Synchronous: Observers execute immediately when event is dispatched (blocking)
  • Transaction Safety: Most observers run inside database transactions, so exceptions rollback entire operation
  • Performance: Grid sync observers are the most performance-sensitive (run on every order save)

Grid Synchronization Observers

These 8 observers maintain the sales grid tables (denormalized copies of order/invoice/shipment/creditmemo data for fast admin grid display).

Grid Insert Observers (2-5) - Synchronous

Observer Event Grid Table
OrderGridSyncInsert sales_order_process_relation sales_order_grid
InvoiceGridSyncInsert sales_order_invoice_process_relation sales_invoice_grid
ShipmentGridSyncInsert sales_order_shipment_process_relation sales_shipment_grid
CreditmemoGridSyncInsert sales_order_creditmemo_process_relation sales_creditmemo_grid

Why Needed

Admin order grid queries sales_order_grid instead of sales_order for performance. Grid tables contain denormalized data optimized for fast filtering and sorting.

Performance Comparison

Mode Performance Use Case
Synchronous Slower order save (blocks on grid UPDATE) Small stores, realtime accuracy critical
Asynchronous Faster order save (queue message only) High-volume stores, eventual consistency OK

Quote Integration Observers

20. AssignOrderToCustomerObserver

Business Critical

Event: customer_save_after_data_object

Purpose: Links guest orders to customer account after customer registration.

Execution Logic:

  1. Only for new customers (registration)
  2. Find guest orders with matching email
  3. Update customer_id and customer_is_guest fields
  4. Customer can now see all their orders (guest + registered)

Use Cases

  • Guest Checkout → Registration: Customer places order as guest, then creates account
  • Order History: Customer can now see all their orders (guest + registered)
  • Loyalty Programs: Past guest orders count toward loyalty points

21. CustomerValidateVatNumberObserver

Event: sales_quote_address_collect_totals_after

Purpose: Validates customer VAT number during checkout totals calculation to determine tax exemption.

Performance Warning

External API Call: VIES validation can take 1-3 seconds. Blocks checkout!

Recommendation: Cache VAT validation results or use async validation.

Observer Best Practices

1. Avoid Heavy Operations in Synchronous Observers

BAD: External API calls

// BAD - blocks order save
public function execute(Observer $observer): void
{
    $order = $observer->getEvent()->getOrder();
    $this->externalApi->notifyOrderPlaced($order); // 2s timeout!
}

GOOD: Queue for async processing

// GOOD - async processing
public function execute(Observer $observer): void
{
    $order = $observer->getEvent()->getOrder();
    $this->publisher->publish('sales.order.placed', $order->getId());
}

2. Check for Data Changes Before Processing

public function execute(Observer $observer): void
{
    $order = $observer->getEvent()->getOrder();

    // Check if status changed
    if (!$order->dataHasChangedFor('status')) {
        return; // No change, skip processing
    }

    // Expensive status change logic
}

3. Handle Exceptions Gracefully

public function execute(Observer $observer): void
{
    try {
        // Risky operation
        $this->externalService->notify($data);
    } catch (\Exception $e) {
        // Log error but don't fail order save
        $this->logger->error('Notification failed: ' . $e->getMessage());
        // Don't rethrow - allow order save to succeed
    }
}

4. Use Grid Async Indexing for Performance

Production Recommendation:

# Enable async grid indexing
bin/magento config:set dev/grid/async_indexing 1

# Enable async email sending
bin/magento config:set sales_email/general/async_sending 1

Result: Faster order placement, better scalability.

Troubleshooting

Grid Not Updating

Symptom: Order visible in database but not in admin grid.

Cause: Grid sync observer failed or async indexing lagged.

Fix:

# Reindex grids manually
bin/magento indexer:reindex sales_order_grid
bin/magento indexer:reindex sales_invoice_grid
bin/magento indexer:reindex sales_shipment_grid
bin/magento indexer:reindex sales_creditmemo_grid

Email Not Sending

Symptom: Orders placed but no confirmation emails.

Cause: Async email queue not processing or SMTP failure.

Debug:

# Check email queue
bin/magento queue:consumers:list | grep email

# Process queue manually
bin/magento queue:consumers:start sales.email.sender

Document Version: 1.0.0

Last Updated: 2025-12-04

Magento Version: 2.4.8