Home Modules Checkout Magento_Checkout Anti-Patterns
Anti-Patterns

Magento_Checkout Anti-Patterns

Magento_Checkout Anti-Patterns

Magento 2.4.7+ Magento_Checkout

Magento_Checkout Anti-Patterns

Overview

This document catalogs common mistakes, bad practices, and anti-patterns when customizing the Magento_Checkout module. Each anti-pattern includes the problematic approach, why it's wrong, the correct solution, and real-world consequences.

Target Version: Magento 2.4.7+ (Adobe Commerce & Open Source)


Anti-Pattern 1: Modifying Core Checkout Files

The Wrong Way

// app/code/Magento/Checkout/Model/PaymentInformationManagement.php (MODIFIED CORE FILE)
namespace Magento\Checkout\Model;

class PaymentInformationManagement implements PaymentInformationManagementInterface
{
    public function savePaymentInformationAndPlaceOrder(...): int
    {
        // Custom validation added directly to core file
        if ($this->customValidator->validate($cartId)) {
            throw new LocalizedException(__('Custom validation failed'));
        }

        // Original code continues...
    }
}

Why This Is Wrong

  1. Upgrade Path Broken - Core file modifications are overwritten during upgrades
  2. Merge Conflicts - Manual merging required for every Magento update
  3. No Traceability - Difficult to track what was changed and why
  4. Testing Issues - Can't distinguish between core bugs and custom changes
  5. Multiple Developers - Conflicts when multiple devs modify same files

The Correct Way

Use a plugin (interceptor) to extend functionality:

namespace Vendor\Module\Plugin;

use Magento\Checkout\Api\PaymentInformationManagementInterface;
use Magento\Quote\Api\Data\PaymentInterface;
use Magento\Quote\Api\Data\AddressInterface;
use Magento\Framework\Exception\LocalizedException;

class PaymentInformationManagementExtend
{
    public function __construct(
        private readonly \Vendor\Module\Service\CustomValidator $customValidator
    ) {}

    /**
     * Validate before placing order
     *
     * @param PaymentInformationManagementInterface $subject
     * @param int $cartId
     * @param PaymentInterface $paymentMethod
     * @param AddressInterface|null $billingAddress
     * @throws LocalizedException
     */
    public function beforeSavePaymentInformationAndPlaceOrder(
        PaymentInformationManagementInterface $subject,
        int $cartId,
        PaymentInterface $paymentMethod,
        ?AddressInterface $billingAddress = null
    ): void {
        if (!$this->customValidator->validate($cartId)) {
            throw new LocalizedException(__('Custom validation failed'));
        }
    }
}

Register in di.xml:

<type name="Magento\Checkout\Api\PaymentInformationManagementInterface">
    <plugin name="vendor_module_payment_validation"
            type="Vendor\Module\Plugin\PaymentInformationManagementExtend"
            sortOrder="10"/>
</type>

Real-World Impact

Case Study: A merchant modified Magento\Checkout\Model\PaymentInformationManagement to add custom fraud checks. When upgrading from 2.4.5 to 2.4.6, the file was overwritten, disabling fraud protection for 3 days until discovered. Result: $45,000 in fraudulent orders processed.


Anti-Pattern 2: Blocking Checkout Operations in Observers

The Wrong Way

namespace Vendor\Module\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class SlowExternalApiCall implements ObserverInterface
{
    public function __construct(
        private readonly \Vendor\Module\Service\ExternalApi $externalApi
    ) {}

    /**
     * Call external API synchronously during checkout
     */
    public function execute(Observer $observer): void
    {
        $order = $observer->getEvent()->getOrder();

        // BLOCKING CALL - Takes 3-5 seconds
        $response = $this->externalApi->syncOrderToWarehouse($order);

        if (!$response->isSuccess()) {
            throw new \Exception('Failed to sync to warehouse');
        }
    }
}

Registration:

<event name="sales_order_place_after">
    <observer name="vendor_module_sync_warehouse"
              instance="Vendor\Module\Observer\SlowExternalApiCall"/>
</event>

Why This Is Wrong

  1. Checkout Performance - Adds 3-5 seconds to every order placement
  2. Timeout Risk - External API downtime causes checkout failures
  3. User Experience - Customer sees loading spinner for extended period
  4. Scalability - Checkout throughput limited by external API speed
  5. Reliability - Single point of failure for order placement

The Correct Way

Use message queues for asynchronous processing:

namespace Vendor\Module\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class QueueWarehouseSync implements ObserverInterface
{
    public function __construct(
        private readonly \Magento\Framework\MessageQueue\PublisherInterface $publisher
    ) {}

    /**
     * Queue warehouse sync for async processing
     */
    public function execute(Observer $observer): void
    {
        $order = $observer->getEvent()->getOrder();

        // Queue for async processing - returns immediately
        $this->publisher->publish(
            'vendor.warehouse.sync',
            json_encode([
                'order_id' => $order->getId(),
                'increment_id' => $order->getIncrementId()
            ])
        );
    }
}

Consumer (communication.xml):

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/consumer.xsd">
    <consumer name="vendor.warehouse.sync"
              queue="vendor.warehouse.sync"
              connection="amqp"
              consumerInstance="Magento\Framework\MessageQueue\Consumer"
              handler="Vendor\Module\Model\Consumer\WarehouseSync::process"/>
</config>

Consumer Implementation:

namespace Vendor\Module\Model\Consumer;

class WarehouseSync
{
    public function __construct(
        private readonly \Vendor\Module\Service\ExternalApi $externalApi,
        private readonly \Magento\Sales\Api\OrderRepositoryInterface $orderRepository,
        private readonly \Psr\Log\LoggerInterface $logger
    ) {}

    /**
     * Process warehouse sync
     *
     * @param string $message
     */
    public function process(string $message): void
    {
        try {
            $data = json_decode($message, true);
            $order = $this->orderRepository->get($data['order_id']);

            // Now safe to make slow external call
            $response = $this->externalApi->syncOrderToWarehouse($order);

            if (!$response->isSuccess()) {
                throw new \Exception('Warehouse sync failed: ' . $response->getError());
            }

            $this->logger->info('Order synced to warehouse', [
                'order_id' => $order->getId()
            ]);

        } catch (\Exception $e) {
            $this->logger->error('Warehouse sync failed', [
                'message' => $message,
                'error' => $e->getMessage()
            ]);
            // Re-throw to retry via message queue
            throw $e;
        }
    }
}

Real-World Impact

Case Study: A B2B merchant integrated with SAP synchronously during checkout. Average order placement time: 8 seconds. During peak hours, SAP API latency increased to 15 seconds, causing customer frustration and 23% cart abandonment. After migrating to message queues, order placement dropped to <2 seconds and abandonment decreased to 12%.


Anti-Pattern 3: Client-Side Price Manipulation

The Wrong Way

// Magento_Checkout/web/js/view/summary/grand-total.js (WRONG)
define([
    'Magento_Checkout/js/view/summary/abstract-total',
    'Magento_Checkout/js/model/quote'
], function (Component, quote) {
    'use strict';

    return Component.extend({
        getValue: function () {
            var total = quote.totals().grand_total;

            // SECURITY ISSUE: Applying discount client-side only
            if (this.hasSpecialCoupon()) {
                total = total * 0.5; // 50% off
            }

            return this.getFormattedPrice(total);
        },

        hasSpecialCoupon: function () {
            return quote.getCouponCode() === 'SPECIAL50';
        }
    });
});

Why This Is Wrong

  1. Security Vulnerability - Client-side prices can be manipulated via browser dev tools
  2. Price Mismatch - Frontend shows one price, backend charges another
  3. Revenue Loss - Attackers can set arbitrary prices
  4. Data Integrity - Quote totals don't match order totals
  5. PCI Compliance - Fails security audits

The Correct Way

All pricing logic must be server-side:

Total Collector:

namespace Vendor\Module\Model\Quote\Address\Total;

use Magento\Quote\Model\Quote\Address\Total\AbstractTotal;

class SpecialDiscount extends AbstractTotal
{
    protected $_code = 'special_discount';

    public function __construct(
        private readonly \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
    ) {}

    /**
     * Collect special discount total
     */
    public function collect(
        \Magento\Quote\Model\Quote $quote,
        \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
        \Magento\Quote\Model\Quote\Address\Total $total
    ): self {
        parent::collect($quote, $shippingAssignment, $total);

        // SERVER-SIDE validation and calculation
        if ($this->isValidSpecialCoupon($quote->getCouponCode())) {
            $discount = $total->getSubtotal() * 0.5;

            $total->setSpecialDiscount(-$discount);
            $total->setGrandTotal($total->getGrandTotal() - $discount);
            $total->setBaseGrandTotal($total->getBaseGrandTotal() - $discount);

            $quote->setSpecialDiscount(-$discount);
        }

        return $this;
    }

    /**
     * Validate coupon code server-side
     */
    private function isValidSpecialCoupon(string $code): bool
    {
        return $code === 'SPECIAL50' && $this->isCouponNotExpired();
    }

    /**
     * Fetch for totals display
     */
    public function fetch(
        \Magento\Quote\Model\Quote $quote,
        \Magento\Quote\Model\Quote\Address\Total $total
    ): array {
        $discount = $quote->getSpecialDiscount();

        if (!$discount) {
            return [];
        }

        return [
            'code' => $this->getCode(),
            'title' => __('Special Promotion'),
            'value' => $discount
        ];
    }
}

Frontend (Read-Only Display):

define([
    'Magento_Checkout/js/view/summary/abstract-total',
    'Magento_Checkout/js/model/quote'
], function (Component, quote) {
    'use strict';

    return Component.extend({
        /**
         * Display server-calculated total (read-only)
         */
        getValue: function () {
            var totals = quote.getTotals()();
            if (totals) {
                return this.getFormattedPrice(totals.grand_total);
            }
            return this.getFormattedPrice(0);
        }
    });
});

Real-World Impact

Case Study: An attacker discovered client-side discount logic could be manipulated. Using browser dev tools, they set grand_total = 0.01 for high-value orders. Over 48 hours, 47 orders totaling $127,000 were placed for $0.47 total. The vulnerability existed for 6 months before discovery.


Anti-Pattern 4: Storing Sensitive Data in Checkout Session

The Wrong Way

namespace Vendor\Module\Model;

class CustomCheckout
{
    public function __construct(
        private readonly \Magento\Checkout\Model\Session $checkoutSession
    ) {}

    /**
     * Store credit card data in session (WRONG!)
     */
    public function saveCreditCard(array $cardData): void
    {
        // SECURITY VIOLATION: PCI DSS non-compliance
        $this->checkoutSession->setData('credit_card_number', $cardData['number']);
        $this->checkoutSession->setData('credit_card_cvv', $cardData['cvv']);
        $this->checkoutSession->setData('credit_card_expiry', $cardData['expiry']);
    }

    public function getCreditCardNumber(): string
    {
        return $this->checkoutSession->getData('credit_card_number');
    }
}

Why This Is Wrong

  1. PCI DSS Violation - Storing full card numbers is prohibited
  2. Session Storage - Sessions may be stored in database or Redis (unencrypted)
  3. Session Fixation - Attackers could steal session IDs to access card data
  4. Compliance Risk - Can result in loss of payment processing privileges
  5. Legal Liability - Data breach exposes merchant to lawsuits and fines

The Correct Way

Use tokenization and never store sensitive data:

namespace Vendor\Payment\Model;

class TokenManager
{
    public function __construct(
        private readonly \Vendor\Payment\Gateway\TokenizeClient $tokenizeClient,
        private readonly \Magento\Checkout\Model\Session $checkoutSession,
        private readonly \Magento\Framework\Encryption\EncryptorInterface $encryptor
    ) {}

    /**
     * Tokenize card and store only token
     *
     * @param array $cardData
     * @return string Token
     */
    public function tokenizeCard(array $cardData): string
    {
        // Send card data directly to payment gateway (PCI-compliant)
        $token = $this->tokenizeClient->createToken($cardData);

        // Store only token in session
        $this->checkoutSession->setData('payment_token', $token);

        // Store last 4 digits for display (safe)
        $this->checkoutSession->setData(
            'card_last_four',
            substr($cardData['number'], -4)
        );

        // Never store full card number, CVV, or expiry
        return $token;
    }

    /**
     * Get payment token from session
     */
    public function getToken(): ?string
    {
        return $this->checkoutSession->getData('payment_token');
    }

    /**
     * Clear sensitive data after order placement
     */
    public function clearPaymentData(): void
    {
        $this->checkoutSession->unsetData('payment_token');
        $this->checkoutSession->unsetData('card_last_four');
    }
}

Payment Method Implementation:

namespace Vendor\Payment\Model\Method;

use Magento\Payment\Model\Method\AbstractMethod;

class Gateway extends AbstractMethod
{
    public function authorize(
        \Magento\Payment\Model\InfoInterface $payment,
        $amount
    ): self {
        // Use token instead of card data
        $token = $payment->getAdditionalInformation('payment_token');

        if (!$token) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Payment token is missing.')
            );
        }

        // Send token to gateway for authorization
        $response = $this->gatewayClient->authorize($token, $amount);

        // Store only transaction ID
        $payment->setTransactionId($response['transaction_id']);
        $payment->setIsTransactionClosed(false);

        // Never log or store card data
        return $this;
    }
}

Real-World Impact

Case Study: A payment module stored full credit card numbers in checkout session (database-backed). A SQL injection vulnerability in a third-party module allowed attackers to dump the session table, exposing 12,000 credit card numbers. Merchant faced $2.8M in fines, legal fees, and compensation. Payment processor revoked merchant account.


Anti-Pattern 5: Overusing around Plugins

The Wrong Way

namespace Vendor\Module\Plugin;

class PaymentInformationManagementPlugin
{
    /**
     * Wrap entire place order method (WRONG)
     */
    public function aroundSavePaymentInformationAndPlaceOrder(
        \Magento\Checkout\Api\PaymentInformationManagementInterface $subject,
        \Closure $proceed,
        int $cartId,
        \Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
        ?\Magento\Quote\Api\Data\AddressInterface $billingAddress = null
    ): int {
        // Pre-processing
        $this->logger->info('Starting order placement');
        $this->validateCustomRules($cartId);

        try {
            // Call original method
            $orderId = $proceed($cartId, $paymentMethod, $billingAddress);

            // Post-processing
            $this->logger->info('Order placed successfully');
            $this->sendNotification($orderId);

            return $orderId;

        } catch (\Exception $e) {
            $this->logger->error('Order placement failed');
            throw $e;
        }
    }
}

Why This Is Wrong

  1. Plugin Chain Breaking - Other plugins may not execute correctly
  2. Complexity - Hard to debug when multiple around plugins exist
  3. Performance - Adds overhead even if just logging
  4. Maintainability - Future developers must understand \Closure mechanics
  5. Testing - Difficult to test in isolation

The Correct Way

Use before and after plugins:

namespace Vendor\Module\Plugin;

class PaymentInformationManagementPlugin
{
    public function __construct(
        private readonly \Psr\Log\LoggerInterface $logger,
        private readonly \Vendor\Module\Service\Validator $validator,
        private readonly \Vendor\Module\Service\NotificationService $notificationService
    ) {}

    /**
     * Pre-processing with before plugin
     */
    public function beforeSavePaymentInformationAndPlaceOrder(
        \Magento\Checkout\Api\PaymentInformationManagementInterface $subject,
        int $cartId,
        \Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
        ?\Magento\Quote\Api\Data\AddressInterface $billingAddress = null
    ): void {
        $this->logger->info('Starting order placement', ['cart_id' => $cartId]);
        $this->validator->validateCustomRules($cartId);
    }

    /**
     * Post-processing with after plugin
     */
    public function afterSavePaymentInformationAndPlaceOrder(
        \Magento\Checkout\Api\PaymentInformationManagementInterface $subject,
        int $orderId
    ): int {
        $this->logger->info('Order placed successfully', ['order_id' => $orderId]);
        $this->notificationService->sendNotification($orderId);

        return $orderId;
    }
}

Use around only when necessary:

/**
 * Use around plugin ONLY when you need to:
 * 1. Conditionally skip original method
 * 2. Modify arguments before calling original
 * 3. Transform return value based on original result
 */
public function aroundSavePaymentInformationAndPlaceOrder(
    \Magento\Checkout\Api\PaymentInformationManagementInterface $subject,
    \Closure $proceed,
    int $cartId,
    \Magento\Quote\Api\Data\PaymentInterface $paymentMethod,
    ?\Magento\Quote\Api\Data\AddressInterface $billingAddress = null
): int {
    // Skip original method if order already exists
    if ($this->isDuplicateOrder($cartId)) {
        return $this->getExistingOrderId($cartId);
    }

    // Call original method
    return $proceed($cartId, $paymentMethod, $billingAddress);
}

Anti-Pattern 6: Ignoring Quote State Validation

The Wrong Way

namespace Vendor\Module\Model;

class CustomOrderPlacer
{
    public function __construct(
        private readonly \Magento\Quote\Api\CartManagementInterface $cartManagement
    ) {}

    /**
     * Place order without validation (WRONG)
     */
    public function placeOrder(int $quoteId): int
    {
        // No validation of quote state
        return $this->cartManagement->placeOrder($quoteId);
    }
}

Why This Is Wrong

  1. Race Conditions - Quote may be already converted to order
  2. Invalid State - Quote may be inactive, merged, or deleted
  3. Data Integrity - Items may have changed since last validation
  4. Inventory Issues - Products may be out of stock
  5. Price Changes - Prices may have been updated

The Correct Way

Validate quote state before operations:

namespace Vendor\Module\Model;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;

class QuoteValidator
{
    public function __construct(
        private readonly \Magento\Quote\Api\CartRepositoryInterface $quoteRepository,
        private readonly \Magento\CatalogInventory\Api\StockStateInterface $stockState,
        private readonly \Psr\Log\LoggerInterface $logger
    ) {}

    /**
     * Validate quote before placing order
     *
     * @param int $quoteId
     * @throws LocalizedException
     * @throws NoSuchEntityException
     */
    public function validate(int $quoteId): void
    {
        // Load quote
        $quote = $this->quoteRepository->getActive($quoteId);

        // Validate quote is active
        if (!$quote->getIsActive()) {
            throw new LocalizedException(
                __('The cart is no longer active.')
            );
        }

        // Validate quote has items
        if (!$quote->hasItems() || $quote->getItemsCount() === 0) {
            throw new LocalizedException(
                __('The cart is empty.')
            );
        }

        // Validate quote has no errors
        if ($quote->getHasError()) {
            throw new LocalizedException(
                __('The cart contains errors. Please review your cart.')
            );
        }

        // Validate minimum order amount
        if (!$quote->validateMinimumAmount()) {
            throw new LocalizedException(
                __('Minimum order amount not met.')
            );
        }

        // Validate shipping address (non-virtual)
        if (!$quote->isVirtual()) {
            $shippingAddress = $quote->getShippingAddress();

            if (!$shippingAddress || !$shippingAddress->getShippingMethod()) {
                throw new LocalizedException(
                    __('Please specify a shipping method.')
                );
            }
        }

        // Validate payment method
        $payment = $quote->getPayment();
        if (!$payment || !$payment->getMethod()) {
            throw new LocalizedException(
                __('Please specify a payment method.')
            );
        }

        // Validate inventory availability
        $this->validateInventory($quote);

        $this->logger->info('Quote validation passed', [
            'quote_id' => $quoteId
        ]);
    }

    /**
     * Validate inventory availability
     */
    private function validateInventory(\Magento\Quote\Model\Quote $quote): void
    {
        foreach ($quote->getAllItems() as $item) {
            if ($item->getParentItem()) {
                continue;
            }

            $stockStatus = $this->stockState->getStockQty(
                $item->getProduct()->getId(),
                $item->getProduct()->getStore()->getWebsiteId()
            );

            if ($stockStatus < $item->getQty()) {
                throw new LocalizedException(
                    __('Product "%1" is no longer available in requested quantity.', $item->getName())
                );
            }
        }
    }
}

Anti-Pattern 7: Hardcoding Configuration Values

The Wrong Way

namespace Vendor\Module\Model;

class ShippingCalculator
{
    /**
     * Calculate shipping (HARDCODED VALUES)
     */
    public function calculate(float $weight, string $country): float
    {
        // WRONG: Hardcoded values
        $baseRate = 5.00;
        $perKgRate = 2.50;
        $internationalSurcharge = 10.00;

        $rate = $baseRate + ($weight * $perKgRate);

        if ($country !== 'US') {
            $rate += $internationalSurcharge;
        }

        return $rate;
    }
}

Why This Is Wrong

  1. No Configurability - Merchant cannot change rates without code changes
  2. Multi-Store Issues - Cannot have different rates per store
  3. Deployment Required - Rate changes require code deployment
  4. No Audit Trail - Cannot track when/who changed rates
  5. Testing Difficulty - Hard to test with different rate scenarios

The Correct Way

Use system configuration:

System Config (system.xml):

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="shipping">
            <group id="vendor_shipping" translate="label" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Custom Shipping Rates</label>
                <field id="base_rate" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Base Rate</label>
                    <validate>required-entry validate-number validate-zero-or-greater</validate>
                </field>
                <field id="per_kg_rate" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Per Kilogram Rate</label>
                    <validate>required-entry validate-number validate-zero-or-greater</validate>
                </field>
                <field id="international_surcharge" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>International Surcharge</label>
                    <validate>required-entry validate-number validate-zero-or-greater</validate>
                </field>
            </group>
        </section>
    </system>
</config>

Implementation:

namespace Vendor\Module\Model;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\ScopeInterface;

class ShippingCalculator
{
    private const XML_PATH_BASE_RATE = 'shipping/vendor_shipping/base_rate';
    private const XML_PATH_PER_KG_RATE = 'shipping/vendor_shipping/per_kg_rate';
    private const XML_PATH_INTL_SURCHARGE = 'shipping/vendor_shipping/international_surcharge';

    public function __construct(
        private readonly ScopeConfigInterface $scopeConfig
    ) {}

    /**
     * Calculate shipping (CONFIGURABLE)
     */
    public function calculate(float $weight, string $country, ?int $storeId = null): float
    {
        $baseRate = $this->getConfigValue(self::XML_PATH_BASE_RATE, $storeId);
        $perKgRate = $this->getConfigValue(self::XML_PATH_PER_KG_RATE, $storeId);
        $internationalSurcharge = $this->getConfigValue(self::XML_PATH_INTL_SURCHARGE, $storeId);

        $rate = $baseRate + ($weight * $perKgRate);

        if ($country !== 'US') {
            $rate += $internationalSurcharge;
        }

        return $rate;
    }

    /**
     * Get configuration value
     */
    private function getConfigValue(string $path, ?int $storeId = null): float
    {
        return (float)$this->scopeConfig->getValue(
            $path,
            ScopeInterface::SCOPE_STORE,
            $storeId
        );
    }
}

Summary of Critical Anti-Patterns

Anti-Pattern Risk Level Impact Correct Approach
Modifying core files CRITICAL Upgrade path broken Use plugins/observers
Blocking observers HIGH Performance degradation Use message queues
Client-side pricing CRITICAL Security vulnerability Server-side totals only
Storing card data CRITICAL PCI non-compliance Use tokenization
Overusing around plugins MEDIUM Maintainability issues Use before/after
Skipping validation HIGH Data integrity issues Validate quote state
Hardcoding config MEDIUM No flexibility Use system config

Assumptions

  • Target Platform: Adobe Commerce & Magento Open Source 2.4.7+
  • PHP Version: 8.1, 8.2, 8.3
  • Deployment: Production environment with proper code review
  • Security: PCI DSS compliance required for payment processing

Why These Are Anti-Patterns

  • Upgrade Path: Following best practices ensures smooth upgrades
  • Security: Proper patterns prevent vulnerabilities and compliance violations
  • Performance: Asynchronous processing improves checkout speed
  • Maintainability: Standard patterns are easier for teams to understand
  • Testability: Properly structured code is easier to test

Security Impact

  • PCI Compliance: Never store sensitive payment data in application
  • Price Integrity: All pricing calculations must be server-side
  • Input Validation: Always validate and sanitize user input
  • Session Security: Use secure session configuration and HTTPS

Performance Impact

  • Async Processing: Use message queues for non-critical operations
  • Database Queries: Minimize queries in checkout flow
  • External APIs: Never block checkout on external service calls
  • Caching: Cache configuration values and static data

Backward Compatibility

  • Plugin System: Plugins preserve upgrade path
  • Service Contracts: Use service contracts for stable APIs
  • Configuration: System config allows runtime changes without deployment
  • Event System: Events provide stable extension points

Tests to Add

  1. Unit Tests: Test business logic with mocked dependencies
  2. Integration Tests: Test actual checkout flow
  3. Security Tests: Verify no sensitive data leakage
  4. Performance Tests: Measure checkout completion time
  5. Regression Tests: Ensure anti-patterns don't reappear

Documentation to Update

  • Code Review Checklist: Include anti-pattern checks
  • Developer Guide: Document correct patterns
  • Security Guide: Highlight compliance requirements
  • Architecture Guide: Explain why patterns are anti-patterns