Home Modules Sales Magento_Sales Integrations
Integrations

Magento_Sales Integrations

Magento_Sales Integrations

Magento 2.4.7+ Magento_Sales

Magento_Sales Integrations

Overview

The Magento_Sales module integrates deeply with numerous other Magento modules to provide complete order management functionality. This document details all major integration points, data flows, dependency relationships, and best practices for extending these integrations.

Core Module Integrations

Integration with Magento_Quote

The Quote module provides the cart/checkout foundation that converts to orders.

Quote to Order Conversion Flow

<?php
namespace Magento\Quote\Model;

use Magento\Sales\Api\Data\OrderInterface;

/**
 * Quote to order conversion
 *
 * Core integration point between Quote and Sales modules
 */
class QuoteManagement
{
    public function __construct(
        private \Magento\Quote\Model\Quote\Address\ToOrder $quoteAddressToOrder,
        private \Magento\Quote\Model\Quote\Address\ToOrderAddress $quoteAddressToOrderAddress,
        private \Magento\Quote\Model\Quote\Item\ToOrderItem $quoteItemToOrderItem,
        private \Magento\Quote\Model\Quote\Payment\ToOrderPayment $quotePaymentToOrderPayment,
        private \Magento\Sales\Model\OrderFactory $orderFactory,
        private \Magento\Framework\DataObject\Copy $objectCopyService
    ) {}

    /**
     * Convert quote to order
     *
     * @param \Magento\Quote\Model\Quote $quote
     * @return OrderInterface
     */
    protected function submitQuote(\Magento\Quote\Model\Quote $quote): OrderInterface
    {
        $order = $this->orderFactory->create();

        // Copy quote data to order using data mapping
        $this->quoteAddressToOrder->convert($quote, $order);

        // Convert addresses
        if ($quote->getBillingAddress()) {
            $billingAddress = $this->quoteAddressToOrderAddress->convert(
                $quote->getBillingAddress()
            );
            $order->setBillingAddress($billingAddress);
        }

        if (!$quote->getIsVirtual() && $quote->getShippingAddress()) {
            $shippingAddress = $this->quoteAddressToOrderAddress->convert(
                $quote->getShippingAddress()
            );
            $order->setShippingAddress($shippingAddress);
        }

        // Convert payment
        $payment = $this->quotePaymentToOrderPayment->convert($quote->getPayment());
        $order->setPayment($payment);

        // Convert items
        foreach ($quote->getAllVisibleItems() as $quoteItem) {
            $orderItem = $this->quoteItemToOrderItem->convert($quoteItem);
            $order->addItem($orderItem);
        }

        return $order;
    }
}

Field Mapping Configuration

<!-- etc/fieldset.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:DataObject/etc/fieldset.xsd">

    <!-- Quote to Order field mapping -->
    <scope id="sales_convert_quote">
        <fieldset id="sales_convert_quote">
            <field name="store_id">
                <aspect name="to_order"/>
            </field>
            <field name="base_currency_code">
                <aspect name="to_order"/>
            </field>
            <field name="store_currency_code">
                <aspect name="to_order"/>
            </field>
            <field name="order_currency_code">
                <aspect name="to_order"/>
            </field>
            <field name="grand_total">
                <aspect name="to_order"/>
            </field>
            <field name="base_grand_total">
                <aspect name="to_order"/>
            </field>
            <field name="customer_id">
                <aspect name="to_order"/>
            </field>
            <field name="customer_email">
                <aspect name="to_order"/>
            </field>
            <field name="customer_firstname">
                <aspect name="to_order"/>
            </field>
            <field name="customer_lastname">
                <aspect name="to_order"/>
            </field>
            <field name="customer_taxvat">
                <aspect name="to_order"/>
            </field>
            <field name="customer_dob">
                <aspect name="to_order"/>
            </field>
            <field name="customer_gender">
                <aspect name="to_order"/>
            </field>
            <field name="customer_group_id">
                <aspect name="to_order"/>
            </field>
            <field name="remote_ip">
                <aspect name="to_order"/>
            </field>
            <field name="x_forwarded_for">
                <aspect name="to_order"/>
            </field>
        </fieldset>
    </scope>

    <!-- Quote Item to Order Item -->
    <scope id="sales_convert_quote_item">
        <fieldset id="sales_convert_quote_item">
            <field name="product_id">
                <aspect name="to_order_item"/>
            </field>
            <field name="sku">
                <aspect name="to_order_item"/>
            </field>
            <field name="name">
                <aspect name="to_order_item"/>
            </field>
            <field name="weight">
                <aspect name="to_order_item"/>
            </field>
            <field name="qty">
                <aspect name="to_order_item">
                    <attribute name="qty_ordered"/>
                </aspect>
            </field>
            <field name="price">
                <aspect name="to_order_item"/>
            </field>
            <field name="base_price">
                <aspect name="to_order_item"/>
            </field>
            <field name="row_total">
                <aspect name="to_order_item"/>
            </field>
            <field name="base_row_total">
                <aspect name="to_order_item"/>
            </field>
            <field name="tax_amount">
                <aspect name="to_order_item"/>
            </field>
            <field name="base_tax_amount">
                <aspect name="to_order_item"/>
            </field>
        </fieldset>
    </scope>
</config>

Custom Field Mapping

<?php
namespace Vendor\Module\Plugin\Quote\Model\Quote\Address;

use Magento\Quote\Model\Quote\Address\ToOrder;
use Magento\Sales\Api\Data\OrderInterface;

/**
 * Plugin to add custom field mapping
 */
class ToOrderExtend
{
    /**
     * Add custom fields to order conversion
     *
     * @param ToOrder $subject
     * @param OrderInterface $result
     * @param \Magento\Quote\Model\Quote\Address $address
     * @return OrderInterface
     */
    public function afterConvert(
        ToOrder $subject,
        OrderInterface $result,
        \Magento\Quote\Model\Quote\Address $address
    ): OrderInterface {
        // Map custom quote fields to order
        $quote = $address->getQuote();

        if ($customField = $quote->getData('custom_field')) {
            $result->setData('custom_field', $customField);
        }

        // Map extension attributes
        $quoteExtension = $quote->getExtensionAttributes();
        if ($quoteExtension && $quoteExtension->getCustomData()) {
            $orderExtension = $result->getExtensionAttributes();
            $orderExtension->setCustomData($quoteExtension->getCustomData());
            $result->setExtensionAttributes($orderExtension);
        }

        return $result;
    }
}

Integration with Magento_Payment

Payment module handles all payment method operations during order processing.

Payment Authorization During Order Placement

<?php
namespace Magento\Sales\Model\Order;

/**
 * Order payment operations
 *
 * Integrates with payment methods for authorization/capture
 */
class Payment extends \Magento\Payment\Model\Info
{
    /**
     * Place payment (authorize)
     *
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function place(): self
    {
        $order = $this->getOrder();
        $methodInstance = $this->getMethodInstance();

        // Set amounts to authorize
        $this->setAmountOrdered($order->getTotalDue());
        $this->setBaseAmountOrdered($order->getBaseTotalDue());

        // Dispatch pre-authorization event
        $this->_eventManager->dispatch(
            'sales_order_payment_place_start',
            ['payment' => $this]
        );

        try {
            // Call payment method authorization
            $methodInstance->authorize($this, $order->getBaseTotalDue());

            // Create authorization transaction
            if ($this->getTransactionId()) {
                $transaction = $this->_transactionFactory->create();
                $transaction->setOrderPaymentObject($this)
                    ->setTxnId($this->getTransactionId())
                    ->setTxnType(\Magento\Sales\Model\Order\Payment\Transaction::TYPE_AUTH)
                    ->setIsClosed(0)
                    ->setAdditionalInformation(
                        \Magento\Sales\Model\Order\Payment\Transaction::RAW_DETAILS,
                        $this->getAdditionalInformation()
                    );

                $this->addTransaction($transaction);
                $this->setParentTransactionId($transaction->getTransactionId());
            }

        } catch (\Magento\Framework\Exception\LocalizedException $e) {
            $this->_logger->error('Payment authorization failed', [
                'order_id' => $order->getEntityId(),
                'method' => $methodInstance->getCode(),
                'error' => $e->getMessage()
            ]);

            throw $e;
        }

        // Dispatch post-authorization event
        $this->_eventManager->dispatch(
            'sales_order_payment_place_end',
            ['payment' => $this]
        );

        return $this;
    }

    /**
     * Capture payment
     *
     * @param \Magento\Sales\Model\Order\Invoice|null $invoice
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function capture($invoice = null): self
    {
        if ($invoice === null) {
            $invoice = $this->_invoice;
        }

        $methodInstance = $this->getMethodInstance();
        $amount = $invoice ? $invoice->getBaseGrandTotal() : $this->getBaseAmountOrdered();

        // Dispatch pre-capture event
        $this->_eventManager->dispatch(
            'sales_order_payment_capture',
            ['payment' => $this, 'invoice' => $invoice]
        );

        try {
            // Call payment method capture
            $methodInstance->setStore($this->getOrder()->getStoreId());
            $methodInstance->capture($this, $amount);

            // Update payment amounts
            $this->setBaseAmountPaid($this->getBaseAmountPaid() + $amount);
            $this->setAmountPaid(
                $this->getAmountPaid() + $this->getOrder()->getStore()->convertPrice($amount)
            );

            // Create capture transaction
            if ($this->getTransactionId()) {
                $transaction = $this->_transactionFactory->create();
                $transaction->setOrderPaymentObject($this)
                    ->setTxnId($this->getTransactionId())
                    ->setTxnType(\Magento\Sales\Model\Order\Payment\Transaction::TYPE_CAPTURE)
                    ->setParentTxnId($this->getParentTransactionId())
                    ->setIsClosed(1);

                $this->addTransaction($transaction);
            }

        } catch (\Exception $e) {
            $this->_logger->error('Payment capture failed', [
                'order_id' => $this->getOrder()->getEntityId(),
                'method' => $methodInstance->getCode(),
                'amount' => $amount,
                'error' => $e->getMessage()
            ]);

            throw new \Magento\Framework\Exception\LocalizedException(
                __('Payment capture failed: %1', $e->getMessage())
            );
        }

        return $this;
    }

    /**
     * Refund payment
     *
     * @param \Magento\Sales\Model\Order\Creditmemo $creditmemo
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function refund($creditmemo): self
    {
        $methodInstance = $this->getMethodInstance();
        $baseAmount = $creditmemo->getBaseGrandTotal();

        try {
            // Call payment method refund
            $methodInstance->refund($this, $baseAmount);

            // Update payment amounts
            $this->setBaseAmountRefunded($this->getBaseAmountRefunded() + $baseAmount);
            $this->setAmountRefunded(
                $this->getAmountRefunded() + $creditmemo->getGrandTotal()
            );

            // Create refund transaction
            if ($this->getTransactionId()) {
                $transaction = $this->_transactionFactory->create();
                $transaction->setOrderPaymentObject($this)
                    ->setTxnId($this->getTransactionId())
                    ->setTxnType(\Magento\Sales\Model\Order\Payment\Transaction::TYPE_REFUND)
                    ->setParentTxnId($this->getParentTransactionId())
                    ->setIsClosed(1);

                $this->addTransaction($transaction);
            }

        } catch (\Exception $e) {
            $this->_logger->error('Payment refund failed', [
                'order_id' => $this->getOrder()->getEntityId(),
                'creditmemo_id' => $creditmemo->getEntityId(),
                'amount' => $baseAmount,
                'error' => $e->getMessage()
            ]);

            throw new \Magento\Framework\Exception\LocalizedException(
                __('Payment refund failed: %1', $e->getMessage())
            );
        }

        return $this;
    }
}

Custom Payment Method Integration

<?php
namespace Vendor\Module\Model\Payment;

use Magento\Payment\Model\Method\AbstractMethod;

/**
 * Custom payment method
 *
 * Integrates with Sales module for order payment
 */
class CustomPayment extends AbstractMethod
{
    protected $_code = 'custom_payment';
    protected $_isGateway = true;
    protected $_canAuthorize = true;
    protected $_canCapture = true;
    protected $_canRefund = true;
    protected $_canVoid = true;

    /**
     * Authorize payment
     *
     * @param \Magento\Payment\Model\InfoInterface $payment
     * @param float $amount
     * @return $this
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount)
    {
        if (!$this->canAuthorize()) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('The authorize action is not available.')
            );
        }

        /** @var \Magento\Sales\Model\Order $order */
        $order = $payment->getOrder();

        try {
            // Call external payment gateway
            $response = $this->gatewayClient->authorize([
                'amount' => $amount,
                'currency' => $order->getBaseCurrencyCode(),
                'order_id' => $order->getIncrementId(),
                'customer_email' => $order->getCustomerEmail(),
                'billing_address' => $this->formatAddress($order->getBillingAddress())
            ]);

            if (!$response->isSuccess()) {
                throw new \Magento\Framework\Exception\LocalizedException(
                    __('Payment authorization failed: %1', $response->getMessage())
                );
            }

            // Store transaction details
            $payment->setTransactionId($response->getTransactionId());
            $payment->setIsTransactionClosed(false);
            $payment->setAdditionalInformation('gateway_response', $response->getData());

        } catch (\Exception $e) {
            $this->_logger->error('Payment gateway authorization failed', [
                'order_id' => $order->getEntityId(),
                'amount' => $amount,
                'error' => $e->getMessage()
            ]);

            throw new \Magento\Framework\Exception\LocalizedException(
                __('Payment gateway error: %1', $e->getMessage())
            );
        }

        return $this;
    }

    /**
     * Capture payment
     *
     * @param \Magento\Payment\Model\InfoInterface $payment
     * @param float $amount
     * @return $this
     */
    public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount)
    {
        $authTransactionId = $payment->getParentTransactionId();

        if ($authTransactionId) {
            // Capture existing authorization
            $response = $this->gatewayClient->capture(
                $authTransactionId,
                $amount
            );
        } else {
            // Sale (authorize + capture in one step)
            $response = $this->gatewayClient->sale([
                'amount' => $amount,
                'currency' => $payment->getOrder()->getBaseCurrencyCode(),
                'order_id' => $payment->getOrder()->getIncrementId()
            ]);
        }

        if (!$response->isSuccess()) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Payment capture failed: %1', $response->getMessage())
            );
        }

        $payment->setTransactionId($response->getTransactionId());
        $payment->setIsTransactionClosed(true);

        return $this;
    }

    /**
     * Refund payment
     *
     * @param \Magento\Payment\Model\InfoInterface $payment
     * @param float $amount
     * @return $this
     */
    public function refund(\Magento\Payment\Model\InfoInterface $payment, $amount)
    {
        $captureTransactionId = $payment->getParentTransactionId();

        $response = $this->gatewayClient->refund(
            $captureTransactionId,
            $amount
        );

        if (!$response->isSuccess()) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Payment refund failed: %1', $response->getMessage())
            );
        }

        $payment->setTransactionId($response->getTransactionId());
        $payment->setIsTransactionClosed(true);

        return $this;
    }
}

Integration with Magento_Tax

Tax calculation and application to orders, invoices, and credit memos.

Tax Collection for Orders

<?php
namespace Magento\Tax\Model\Sales\Total\Quote;

/**
 * Tax totals collector
 *
 * Applied during quote collection, carried to order
 */
class Tax extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal
{
    /**
     * Collect tax totals
     *
     * @param \Magento\Quote\Model\Quote $quote
     * @param \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment
     * @param \Magento\Quote\Model\Quote\Address\Total $total
     * @return $this
     */
    public function collect(
        \Magento\Quote\Model\Quote $quote,
        \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
        \Magento\Quote\Model\Quote\Address\Total $total
    ) {
        $this->clearValues($total);

        if (!$shippingAssignment->getItems()) {
            return $this;
        }

        $address = $shippingAssignment->getShipping()->getAddress();

        // Calculate tax for each item
        $taxDetails = $this->taxCalculation->calculateTax(
            $this->getAddressQuoteDetails($shippingAssignment, $total),
            $quote->getStoreId()
        );

        // Apply tax to items
        foreach ($taxDetails->getItems() as $itemTaxDetails) {
            $quoteItem = $this->getQuoteItem($itemTaxDetails->getCode(), $shippingAssignment);

            if ($quoteItem) {
                $quoteItem->setTaxAmount($itemTaxDetails->getRowTax());
                $quoteItem->setBaseTaxAmount($itemTaxDetails->getRowTax());
                $quoteItem->setTaxPercent($itemTaxDetails->getTaxPercent());

                // Store applied taxes for order
                $appliedTaxes = [];
                foreach ($itemTaxDetails->getAppliedTaxes() as $appliedTax) {
                    $appliedTaxes[] = [
                        'title' => $appliedTax->getTitle(),
                        'percent' => $appliedTax->getPercent(),
                        'amount' => $appliedTax->getAmount(),
                        'rates' => $appliedTax->getRates()
                    ];
                }

                $quoteItem->setAppliedTaxes($appliedTaxes);
            }
        }

        // Set total tax amounts
        $total->setTaxAmount($taxDetails->getTaxAmount());
        $total->setBaseTaxAmount($taxDetails->getTaxAmount());
        $total->setGrandTotal($total->getGrandTotal() + $taxDetails->getTaxAmount());
        $total->setBaseGrandTotal($total->getBaseGrandTotal() + $taxDetails->getTaxAmount());

        return $this;
    }
}

Tax in Order Entity

<?php
// Tax data stored in order
$order->setTaxAmount($taxAmount);
$order->setBaseTaxAmount($baseTaxAmount);
$order->setShippingTaxAmount($shippingTaxAmount);
$order->setBaseShippingTaxAmount($baseShippingTaxAmount);

// Tax applied to order items
foreach ($order->getAllItems() as $item) {
    $item->setTaxAmount($itemTaxAmount);
    $item->setBaseTaxAmount($baseItemTaxAmount);
    $item->setTaxPercent($taxPercent);

    // Detailed tax breakdown
    $item->setAppliedTaxes([
        [
            'title' => 'US-CA-*-Rate 1',
            'percent' => 8.25,
            'amount' => 16.50,
            'rates' => [
                [
                    'code' => 'US-CA-*-Rate 1',
                    'title' => 'California State Tax',
                    'percent' => 8.25
                ]
            ]
        ]
    ]);
}

Integration with Magento_Shipping

Shipping method selection and rate calculation integration.

Shipping in Orders

<?php
namespace Magento\Sales\Model\Order;

/**
 * Shipping information in orders
 */
class Shipping
{
    /**
     * Get shipping information from order
     *
     * @param \Magento\Sales\Model\Order $order
     * @return array
     */
    public function getShippingInfo(\Magento\Sales\Model\Order $order): array
    {
        return [
            'method' => $order->getShippingMethod(),
            'description' => $order->getShippingDescription(),
            'amount' => $order->getShippingAmount(),
            'base_amount' => $order->getBaseShippingAmount(),
            'tax_amount' => $order->getShippingTaxAmount(),
            'base_tax_amount' => $order->getBaseShippingTaxAmount(),
            'discount_amount' => $order->getShippingDiscountAmount(),
            'base_discount_amount' => $order->getBaseShippingDiscountAmount()
        ];
    }
}

Shipment Label Generation

<?php
namespace Vendor\Module\Model\Shipment;

use Magento\Sales\Model\Order\Shipment;

/**
 * Generate shipping labels for shipments
 */
class LabelGenerator
{
    public function __construct(
        private \Magento\Shipping\Model\CarrierFactory $carrierFactory,
        private \Psr\Log\LoggerInterface $logger
    ) {}

    /**
     * Generate shipping label
     *
     * @param Shipment $shipment
     * @return string Base64 encoded label
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function generateLabel(Shipment $shipment): string
    {
        $order = $shipment->getOrder();
        $shippingMethod = $order->getShippingMethod();

        // Parse carrier code from shipping method
        list($carrierCode, $methodCode) = explode('_', $shippingMethod, 2);

        // Get carrier instance
        $carrier = $this->carrierFactory->create($carrierCode, $order->getStoreId());

        if (!$carrier || !$carrier->isShippingLabelsAvailable()) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Shipping labels are not available for this carrier.')
            );
        }

        try {
            // Request label from carrier
            $request = $this->buildLabelRequest($shipment, $carrier);
            $response = $carrier->requestToShipment($request);

            if ($response->hasErrors()) {
                throw new \Magento\Framework\Exception\LocalizedException(
                    __('Carrier error: %1', implode(', ', $response->getErrors()))
                );
            }

            // Save tracking number
            if ($trackingNumber = $response->getInfo()[0]['tracking_number'] ?? null) {
                $track = $this->trackFactory->create();
                $track->setNumber($trackingNumber)
                    ->setCarrierCode($carrierCode)
                    ->setTitle($carrier->getConfigData('title'));

                $shipment->addTrack($track);
            }

            // Return label content
            return $response->getInfo()[0]['label_content'] ?? '';

        } catch (\Exception $e) {
            $this->logger->error('Shipping label generation failed', [
                'shipment_id' => $shipment->getEntityId(),
                'carrier' => $carrierCode,
                'error' => $e->getMessage()
            ]);

            throw new \Magento\Framework\Exception\LocalizedException(
                __('Failed to generate shipping label: %1', $e->getMessage())
            );
        }
    }

    /**
     * Build label request for carrier
     *
     * @param Shipment $shipment
     * @param \Magento\Shipping\Model\Carrier\AbstractCarrier $carrier
     * @return \Magento\Framework\DataObject
     */
    private function buildLabelRequest(
        Shipment $shipment,
        \Magento\Shipping\Model\Carrier\AbstractCarrier $carrier
    ): \Magento\Framework\DataObject {
        $order = $shipment->getOrder();

        $request = new \Magento\Framework\DataObject([
            'order_shipment' => $shipment,
            'shipper_contact_person_name' => $carrier->getConfigData('contact_name'),
            'shipper_contact_person_first_name' => $carrier->getConfigData('contact_first_name'),
            'shipper_contact_person_last_name' => $carrier->getConfigData('contact_last_name'),
            'shipper_contact_company_name' => $carrier->getConfigData('company_name'),
            'shipper_contact_phone_number' => $carrier->getConfigData('phone_number'),
            'shipper_email' => $carrier->getConfigData('email'),
            'shipper_address_street' => $carrier->getConfigData('street'),
            'shipper_address_city' => $carrier->getConfigData('city'),
            'shipper_address_state_or_province_code' => $carrier->getConfigData('region_id'),
            'shipper_address_postal_code' => $carrier->getConfigData('postcode'),
            'shipper_address_country_code' => $carrier->getConfigData('country_id'),
            'recipient_contact_person_name' => $order->getShippingAddress()->getName(),
            'recipient_contact_phone_number' => $order->getShippingAddress()->getTelephone(),
            'recipient_email' => $order->getCustomerEmail(),
            'recipient_address_street' => implode(' ', $order->getShippingAddress()->getStreet()),
            'recipient_address_city' => $order->getShippingAddress()->getCity(),
            'recipient_address_state_or_province_code' => $order->getShippingAddress()->getRegionCode(),
            'recipient_address_postal_code' => $order->getShippingAddress()->getPostcode(),
            'recipient_address_country_code' => $order->getShippingAddress()->getCountryId(),
            'packages' => $this->buildPackagesData($shipment),
            'order_id' => $order->getIncrementId(),
            'reference_data' => $order->getIncrementId()
        ]);

        return $request;
    }

    /**
     * Build packages data for shipment
     *
     * @param Shipment $shipment
     * @return array
     */
    private function buildPackagesData(Shipment $shipment): array
    {
        $packages = [];
        $totalWeight = 0;
        $totalValue = 0;

        foreach ($shipment->getAllItems() as $item) {
            $orderItem = $item->getOrderItem();
            $totalWeight += $orderItem->getWeight() * $item->getQty();
            $totalValue += $orderItem->getPrice() * $item->getQty();
        }

        $packages[] = [
            'params' => [
                'weight' => $totalWeight,
                'customs_value' => $totalValue,
                'length' => 10,
                'width' => 10,
                'height' => 10,
                'weight_units' => 'POUND',
                'dimension_units' => 'INCH',
                'content_type' => 'MERCHANDISE',
                'content_type_other' => ''
            ],
            'items' => []
        ];

        return $packages;
    }
}

Integration with Magento_Inventory (MSI)

Multi-Source Inventory integration for stock management.

Inventory Reservation During Order Placement

<?php
namespace Magento\InventorySales\Plugin\Sales\OrderManagement;

use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\OrderManagementInterface;

/**
 * Plugin to create inventory reservations
 */
class AppendReservationsAfterOrderPlacementExtend
{
    public function __construct(
        private \Magento\InventorySalesApi\Api\PlaceReservationsForSalesEventInterface $placeReservations,
        private \Magento\InventoryReservations\Model\ReservationBuilder $reservationBuilder,
        private \Magento\InventorySales\Model\GetSkuFromOrderItem $getSkuFromOrderItem
    ) {}

    /**
     * Create inventory reservations after order placement
     *
     * @param OrderManagementInterface $subject
     * @param OrderInterface $order
     * @return OrderInterface
     */
    public function afterPlace(
        OrderManagementInterface $subject,
        OrderInterface $order
    ): OrderInterface {
        $reservations = [];

        foreach ($order->getItems() as $item) {
            if (!$item->getIsVirtual()) {
                $sku = $this->getSkuByProductId->execute((int)$item->getProductId());

                // Create reservation (negative qty = reserved)
                $reservations[] = $this->reservationBuilder
                    ->setSku($sku)
                    ->setQuantity(-(float)$item->getQtyOrdered())
                    ->setStockId($this->getStockIdForOrder($order))
                    ->setMetadata(json_encode([
                        'event_type' => 'order_placed',
                        'object_type' => 'order',
                        'object_id' => $order->getEntityId(),
                        'object_increment_id' => $order->getIncrementId()
                    ]))
                    ->build();
            }
        }

        if ($reservations) {
            $this->placeReservations->execute($reservations);
        }

        return $order;
    }

    /**
     * Get stock ID for order
     *
     * @param OrderInterface $order
     * @return int
     */
    private function getStockIdForOrder(OrderInterface $order): int
    {
        // Get stock ID based on order's sales channel (website)
        return $this->stockByWebsiteId->execute((int)$order->getStore()->getWebsiteId());
    }
}

Inventory Compensation During Order Cancellation

<?php
namespace Magento\InventorySales\Plugin\Sales\OrderManagement;

use Magento\Sales\Api\OrderManagementInterface;

/**
 * Plugin to compensate inventory reservations on cancellation
 */
class CancelOrderReservationExtend
{
    /**
     * Compensate reservation after order cancellation
     *
     * @param OrderManagementInterface $subject
     * @param bool $result
     * @param int $orderId
     * @return bool
     */
    public function afterCancel(
        OrderManagementInterface $subject,
        bool $result,
        $orderId
    ): bool {
        if ($result) {
            $order = $this->orderRepository->get($orderId);
            $reservations = [];

            foreach ($order->getItems() as $item) {
                if (!$item->getIsVirtual()) {
                    $sku = $this->getSkuByProductId->execute((int)$item->getProductId());

                    // Create compensation (positive qty = released)
                    $reservations[] = $this->reservationBuilder
                        ->setSku($sku)
                        ->setQuantity((float)$item->getQtyOrdered() - $item->getQtyRefunded())
                        ->setStockId($this->getStockIdForOrder($order))
                        ->setMetadata(json_encode([
                            'event_type' => 'order_canceled',
                            'object_type' => 'order',
                            'object_id' => $order->getEntityId()
                        ]))
                        ->build();
                }
            }

            if ($reservations) {
                $this->placeReservations->execute($reservations);
            }
        }

        return $result;
    }
}

Integration with Magento_Customer

Customer data and account integration.

Customer Order History

<?php
namespace Vendor\Module\Model\Customer;

use Magento\Sales\Api\OrderRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;

/**
 * Customer order history service
 */
class OrderHistory
{
    public function __construct(
        private OrderRepositoryInterface $orderRepository,
        private SearchCriteriaBuilder $searchCriteriaBuilder
    ) {}

    /**
     * Get customer order history
     *
     * @param int $customerId
     * @param int $pageSize
     * @param int $currentPage
     * @return \Magento\Sales\Api\Data\OrderSearchResultInterface
     */
    public function getOrderHistory(
        int $customerId,
        int $pageSize = 20,
        int $currentPage = 1
    ): \Magento\Sales\Api\Data\OrderSearchResultInterface {
        $searchCriteria = $this->searchCriteriaBuilder
            ->addFilter('customer_id', $customerId, 'eq')
            ->addSortOrder('created_at', 'DESC')
            ->setPageSize($pageSize)
            ->setCurrentPage($currentPage)
            ->create();

        return $this->orderRepository->getList($searchCriteria);
    }

    /**
     * Get customer lifetime value
     *
     * @param int $customerId
     * @return float
     */
    public function getCustomerLifetimeValue(int $customerId): float
    {
        $searchCriteria = $this->searchCriteriaBuilder
            ->addFilter('customer_id', $customerId, 'eq')
            ->addFilter('state', ['complete', 'processing'], 'in')
            ->create();

        $orders = $this->orderRepository->getList($searchCriteria);

        $lifetimeValue = 0.0;
        foreach ($orders->getItems() as $order) {
            $lifetimeValue += $order->getGrandTotal();
        }

        return $lifetimeValue;
    }
}

External System Integrations

ERP Integration Pattern

<?php
namespace Vendor\Module\Service;

use Magento\Sales\Api\Data\OrderInterface;

/**
 * ERP integration service
 *
 * Pattern for integrating orders with external ERP systems
 */
class ErpIntegration
{
    public function __construct(
        private \Vendor\Module\Gateway\ErpClient $erpClient,
        private \Vendor\Module\Model\ErpOrderMapper $orderMapper,
        private \Vendor\Module\Model\ErpSyncQueueFactory $queueFactory,
        private \Vendor\Module\Model\ResourceModel\ErpSyncQueue $queueResource,
        private \Psr\Log\LoggerInterface $logger
    ) {}

    /**
     * Sync order to ERP
     *
     * @param OrderInterface $order
     * @return bool
     */
    public function syncOrder(OrderInterface $order): bool
    {
        try {
            // Map Magento order to ERP format
            $erpOrderData = $this->orderMapper->mapOrderToErp($order);

            // Send to ERP
            $response = $this->erpClient->createOrder($erpOrderData);

            if (!$response->isSuccess()) {
                throw new \RuntimeException(
                    'ERP order creation failed: ' . $response->getError()
                );
            }

            // Store ERP order ID on Magento order
            $order->setData('erp_order_id', $response->getErpOrderId());
            $this->orderRepository->save($order);

            $this->logger->info('Order synced to ERP', [
                'order_id' => $order->getEntityId(),
                'erp_order_id' => $response->getErpOrderId()
            ]);

            return true;

        } catch (\Exception $e) {
            $this->logger->error('ERP sync failed', [
                'order_id' => $order->getEntityId(),
                'error' => $e->getMessage()
            ]);

            // Queue for retry
            $this->queueForRetry($order);

            return false;
        }
    }

    /**
     * Queue order for ERP sync retry
     *
     * @param OrderInterface $order
     * @return void
     */
    private function queueForRetry(OrderInterface $order): void
    {
        $queueEntry = $this->queueFactory->create();
        $queueEntry->setData([
            'order_id' => $order->getEntityId(),
            'status' => 'pending',
            'retry_count' => 0,
            'next_retry_at' => date('Y-m-d H:i:s', strtotime('+5 minutes')),
            'created_at' => date('Y-m-d H:i:s')
        ]);

        $this->queueResource->save($queueEntry);
    }
}

Warehouse Management System (WMS) Integration

<?php
namespace Vendor\Module\Service;

use Magento\Sales\Api\Data\ShipmentInterface;

/**
 * WMS integration for shipment processing
 */
class WmsIntegration
{
    public function __construct(
        private \Vendor\Module\Gateway\WmsClient $wmsClient,
        private \Psr\Log\LoggerInterface $logger
    ) {}

    /**
     * Notify WMS of new order
     *
     * @param \Magento\Sales\Api\Data\OrderInterface $order
     * @return bool
     */
    public function notifyNewOrder(\Magento\Sales\Api\Data\OrderInterface $order): bool
    {
        try {
            $wmsOrder = [
                'order_number' => $order->getIncrementId(),
                'customer_name' => $order->getCustomerName(),
                'shipping_address' => [
                    'name' => $order->getShippingAddress()->getName(),
                    'street' => $order->getShippingAddress()->getStreet(),
                    'city' => $order->getShippingAddress()->getCity(),
                    'region' => $order->getShippingAddress()->getRegion(),
                    'postcode' => $order->getShippingAddress()->getPostcode(),
                    'country' => $order->getShippingAddress()->getCountryId(),
                    'telephone' => $order->getShippingAddress()->getTelephone()
                ],
                'items' => []
            ];

            foreach ($order->getItems() as $item) {
                $wmsOrder['items'][] = [
                    'sku' => $item->getSku(),
                    'name' => $item->getName(),
                    'qty' => $item->getQtyOrdered(),
                    'weight' => $item->getWeight()
                ];
            }

            $response = $this->wmsClient->createPickList($wmsOrder);

            if (!$response->isSuccess()) {
                throw new \RuntimeException($response->getError());
            }

            $this->logger->info('Order sent to WMS', [
                'order_id' => $order->getEntityId(),
                'wms_pick_list_id' => $response->getPickListId()
            ]);

            return true;

        } catch (\Exception $e) {
            $this->logger->error('WMS notification failed', [
                'order_id' => $order->getEntityId(),
                'error' => $e->getMessage()
            ]);

            return false;
        }
    }

    /**
     * Import shipment from WMS
     *
     * @param string $wmsShipmentId
     * @return ShipmentInterface|null
     */
    public function importShipment(string $wmsShipmentId): ?ShipmentInterface
    {
        try {
            // Get shipment data from WMS
            $wmsShipment = $this->wmsClient->getShipment($wmsShipmentId);

            // Find corresponding Magento order
            $order = $this->orderRepository->get($wmsShipment->getOrderId());

            // Create Magento shipment
            $shipment = $this->shipmentFactory->create($order);

            // Add tracking information
            foreach ($wmsShipment->getTrackingNumbers() as $tracking) {
                $track = $this->trackFactory->create();
                $track->setNumber($tracking->getNumber())
                    ->setCarrierCode($tracking->getCarrier())
                    ->setTitle($tracking->getCarrierTitle());

                $shipment->addTrack($track);
            }

            // Save shipment
            $shipment->register();
            $this->shipmentRepository->save($shipment);

            $this->logger->info('Shipment imported from WMS', [
                'order_id' => $order->getEntityId(),
                'shipment_id' => $shipment->getEntityId(),
                'wms_shipment_id' => $wmsShipmentId
            ]);

            return $shipment;

        } catch (\Exception $e) {
            $this->logger->error('WMS shipment import failed', [
                'wms_shipment_id' => $wmsShipmentId,
                'error' => $e->getMessage()
            ]);

            return null;
        }
    }
}

Assumptions: - Adobe Commerce 2.4.7+ with PHP 8.2+ - MSI (Multi-Source Inventory) enabled for inventory management - Custom integrations use message queues for async processing - External systems provide REST/SOAP APIs

Why This Approach: - Field mapping via fieldset.xml for upgrade-safe conversions - Extension attributes for custom data without table modifications - Plugin-based integrations maintain modularity - Message queues prevent blocking order operations

Security Impact: - External API credentials stored in encrypted core_config_data - API authentication tokens rotated regularly - PII data encrypted in transit to external systems - API rate limiting to prevent abuse

Performance Impact: - Quote to order conversion optimized with batch operations - External integrations queued for async processing - Inventory reservations use indexed tables - Tax calculations cached when possible

Backward Compatibility: - Field mappings stable across minor versions - Extension attributes backwards compatible - Service contract interfaces maintained - Plugin interception points preserved

Tests to Add: - Integration tests: Quote to order conversion with all data - Unit tests: Field mapping, tax calculation - Functional tests: End-to-end order with external integrations - API tests: External system communication

Docs to Update: - README.md: Link integration overview - ARCHITECTURE.md: Reference integration patterns - Custom module docs: Document your integrations