How to Build a Custom Pre-Order Module in Magento 2

Dans Magento 2, pre-commandes peut être a powerful way to gauge demand, smooth out cash flow, and launch new products when your clients are ready. Ce guide walks you through building a custom pre-commande module à partir de zéro, with a relaxed, colleague-to-colleague tone and concrete code exemples. We’ll cover architecture, database design, payment integration, admin flux de travails, client notifications, and performance bonnes pratiques. The goal is to give you a practical blueprint you can adapt to your store without relying on a paid extension, while keeping the code approcheable for a neophyte développeur.

Why a Custom Pre-Order Module in Magento 2?

Pre-commandes let you offer products before they are in stock, manage waitlists, and capture client interest early. A custom solution vous donne: - Full control over when pre-commandes start and end, and how they convert to real commandes. - The ability to keep your main catalog undisturbed, ensuring stock levels aren’t affected until you actually fulfill an commande. - The chance to tailor notifications and admin flux de travails to your entreprise rules. - A learning path for your team: you’ll understand data flows, payments, and Magento internals better than with a black-box extension.

High-Level Architecture: How to Structure Pre-Orders Without Hurting Stock

The core idea is to keep pre-commandes in a separate data structure from the main stock. Instead of decrementing inventaire when a client adds a pre-commande item to the cart, you store a pre-commande record and only adjust stock when the pre-commande transitions to a confirmed commande at the capture moment. Cette approche reduces risk of stock inconsistencies and makes rapporting clearer.

Key composants we’ll design:

  • Pre-commande modèle de données to track who reserved what and when it becomes available.
  • Link between pre-commandes and actual commandes so you can trace conversions and handle refunds cleanly.
  • Payment authorization vs. capture to secure funds without finally charging clients before fulfillment.

We’ll also discuss comment keep the main catalog performant, with careful queries and indexation so the pre-commande logic doesn’t slow down product blignesing for normal clients.

Database Design: The Pre-Order Tables

Premièrement, create a dedicated table to store pre-commande data. Below is a pragmatic SQL sketch you can adapt to your environment. This design avoids touching inventaire counts until a pre-commande becomes an actual commande.

-- Pre-order table (SKU-based, multi-store friendly)
CREATE TABLE IF NOT EXISTS mage_pre_order (
  pre_order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  product_id INT NOT NULL,
  sku VARCHAR(64) NOT NULL,
  qty INT NOT NULL,
  customer_id BIGINT DEFAULT NULL,
  reserved_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  available_from DATETIME NULL,
  status ENUM('PENDING','ACTIVE','FULFILLED','CANCELLED') NOT NULL DEFAULT 'PENDING',
  order_id BIGINT DEFAULT NULL,
  PRIMARY KEY (pre_order_id),
  KEY idx_product_id (product_id),
  KEY idx_status (status),
  KEY idx_available_from (available_from)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Notes: - We store product_id (and optionally sku) to tie the pre-commande to a product and its stock-keeping unit. - available_from vous permet de auto-launch pre-commandes after a date, or trigger a notification window. - status vous aide à distinguish between awaiting fulfillment and closed cases.

Optionally, you can create a companion table to map pre-commandes to commandes when fulfillment happens, for cleaner rapportage:

CREATE TABLE IF NOT EXISTS mage_pre_order_mapping (
  map_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  pre_order_id BIGINT NOT NULL,
  order_id BIGINT NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (map_id),
  KEY idx_pre_order (pre_order_id),
  KEY idx_order (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

In Magento, you’ll typically place these in a module’s script de setup. The exact fichier structure dépend de your Magento version, but the logic remains the same: create tables during module install/mise à jour, and reference them from your models/repositories.

Step-by-Step Setup: Create the Module Skeleton

Let’s create a minimal Magento 2 module named Magefine_PreOrder. We’ll go through registration, module.xml, and a basic script de setup to create our mage_pre_order table. C'est intentionally approcheable for a beginner, but robust enough to evolve with your needs.

// app/code/Magefine/PreOrder/registration.php
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magefine_PreOrder', __DIR__);

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
  <module name="Magefine_PreOrder" setup_version="0.1.0">
  </module>
</config>

Ensuite, create a basic InstallSchema to create mage_pre_commande:

// app/code/Magefine/PreOrder/Setup/InstallSchema.php
namespace Magefine\PreOrder\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();

        if (!$installer->tableExists('mage_pre_order')) {
            $table = $installer->getConnection()->newTable(
                $installer->getTable('mage_pre_order')
            )
            ->addColumn('pre_order_id', Table::TYPE_BIGINT, null, ['identity' => true, 'unsigned' => true, 'nullable' => false], 'Pre-Order ID')
            ->addColumn('product_id', Table::TYPE_INTEGER, null, ['nullable' => false], 'Product ID')
            ->addColumn('qty', Table::TYPE_INTEGER, null, ['nullable' => false], 'Quantity')
            ->addColumn('customer_id', Table::TYPE_BIGINT, null, ['nullable' => true], 'Customer ID')
            ->addColumn('reserved_at', Table::TYPE_DATETIME, null, ['nullable' => false], 'Reserved At')
            ->addColumn('available_from', Table::TYPE_DATETIME, null, ['nullable' => true], 'Available From')
            ->addColumn('status', Table::TYPE_TEXT, 20, [], 'Status')
            ->addColumn('order_id', Table::TYPE_BIGINT, null, ['nullable' => true], 'Linked Order ID')
            ->setComment('Magefine Pre-Order Table')
            ->setOption('type', 'InnoDB');

            $installer->getConnection()->createTable($table);
        }

        $installer->endSetup();
    }
}

Enfin, register the Setup script in di.xml if needed by your Magento version. This skeleton vous donne a working database layer to start experimenting with pre-commandes.

Checkout and Catalog: How to Handle Pre-Orders Without Touching Stock

The heart of the implémentation is to detect pre-commande products at paiement and avoid decrementing the main stock until the commande is captured. We’ll outline a clean approche using an is_pre_commande flag on the product and a small observateur to intercept the add-to-cart process.

Assumptions:

  • Pre-commande products have a attribut personnalisé pre_order (boolean) or a product type extension flag.
  • When a cart contains a pre-commande item, we flag the quote item with is_pre_order = true.
  • We do not reduce stock for pre-commandes; stock is adjusted when the commande is captured.

Code sketch: detect pre-commande during quote collection and skip stock deduction:

// Observer: Magefine\PreOrder\Observer\AddToCartPreOrder.php
namespace Magefine\PreOrder\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Checkout\Model\Cart;

class AddToCartPreOrder implements ObserverInterface
{
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $quoteItem = $observer->getEvent()->getQuoteItem();
        $product = $quoteItem->getProduct();
        if (method_exists($product, 'getData') && $product->getData('pre_order')) {
            $quoteItem->setData('is_pre_order', true);
        }
    }
}

Puis, ensure Magento’s stock deduction logic respects is_pre_commande. Vous pouvez override the stock management flow or implement a plugin on the stock inventaire module to skip deduction for items where is_pre_order is true. The exact approche dépend de your Magento version and whether you use MSI (Multi-Source Inventory). A practical starting point is to wrap the stock update in a conditional that checks the quote item flag.

Per-quote totals and taxes should still be calculated normally for pre-commandes, so clients see accurate tarification and tax on paiement even though stock remains unchanged until capture.

Payment Integration: Temporary Authorizations and Deferred Capture

One of the trickiest parts is handling money without charging clients before fulfillment. The standard approche is to place a temporary authorization for the commande total and perform capture only when the pre-commande converts to a real commande. Magento supports authorization at paiement with many méthodes de paiement, but you typically need to add a few guardrails for pre-commandes:

  • Authorize the amount at paiement for pre-commande items.
  • Store the authorization id alongside the pre-commande record.

High-level étapes:

  1. Detect pre-commande in the quote during the payment étape.
  2. Call the méthode de paiement’s authorize() for the commande total.
  3. Save the authorization ID and mark the pre-commande ligne as PENDING.
  4. On the actual commande capture date (or when stock becomes available), perform the capture automatically and associate the captured amount with the pre-commande ligne.

Code sketch: a minimal payment helper that triggers authorization for pre-commandes:

// Magefine\PreOrder\Model\Payment\PreOrderAuthorization.php
namespace Magefine\PreOrder\Model\Payment;

class PreOrderAuthorization
{
    public function authorize($payment, $amount, $preOrderId)
    {
        // Pseudo-code: call Magento payment interface
        // This will depend on your payment method, e.g., authorize() call
        $result = $payment->authorize($amount);
        if ($result->isSuccessful()) {
            // Save authorization id against pre-order
            // e.g., save $result->getAuthorizationId() in mage_pre_order
        }
        return $result;
    }
}

Capture flux de travail:

// Capture when pre-order becomes a real order
$authorizationId = $preOrder->getAuthorizationId();
$payment = $order->getPayment();
$payment->capture($authorizationId, $preOrder->getTotal());
// Bridge cancellation/partial captures as needed

Important notes:

  • Support for authorization and capture dépend de the passerelle de paiement and Magento version. Always test in a sandbox environment.
  • Handle edge cases: partial captures, card expiration, refunds on cancellations, and failed authorizations.

Admin Workflows: Date Control and Availability Thresholds

A good admin interface vous permet der team manage pre-commandes without wrestling with spreadsheets. Build a dedicated section under Admin > Magefine Pre-Order to view, edit, and schedule pre-commandes. Core fonctionnalités to implement:

  • List of pre-commande items by product with status and available_from date.
  • Bulk actions to activate/deactivate pre-commandes and adjust thresholds.
  • Automated triggers when a pre-commande becomes available or when inventaire reaches the threshold.

UI sketch (adminhtml): a grid for pre-commandes and a detail form for thresholds:

// Admin grid and form via UI components would go here. This is a scaffold.
// Magefine\PreOrder\Controller\Adminhtml\PreOrder\Index::execute()
// Magefine\PreOrder\Block\Adminhtml\PreOrder\Grid

Notifications: Automatic Alerts When Pre-Orders Move to Real Orders

Customer communication matters. Set up automatic e-mails or in-app notifications when pre-commandes are fulfilled and converted into real commandes. Typical flows:

  • Reminder e-mails a few days before the available_from date.
  • Notification on the day of availability with a link to track the commande.
  • Post-purchase confirmation once the pre-commande is converted to a standard commande in the system.

Implementation approche:

  • Event-driven: listen for changes to mage_pre_commande.available_from and status transitions to ACTIVE.
  • Mailer integration: utilize Magento mail templates or a custom template stored in the database.

Code sketch: a simple observateur to send a notification when a pre-commande becomes ACTIVE:

// Magefine\PreOrder\Observer\PreOrderActivated.php
namespace Magefine\PreOrder\Observer;

use Magento\Framework\Event\ObserverInterface;

class PreOrderActivated implements ObserverInterface
{
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $preOrder = $observer->getEvent()->getData('pre_order');
        // send email - pseudo code
        // $this->emailService->sendPreOrderAvailableNotification($preOrder);
    }
}

Test scenarios: ensure you cover successful conversions, failed authorizations, and partial expéditions. Verify your e-mail templates render correctly in mulconseille languages if you serve a global audience.

Performance Bonnes pratiques: Keep the Pre-Order Layer Lightweight

A big risk with any new fonctionnalité is slowing down the catalog. Voici practical patterns to minimize performance impact:

  • Index pre-commande queries carefully, especially on product_id, status, and available_from.
  • Use read replicas or Magento’s indexeurs to keep pre-commande queries isolated from the catalogue de produits fetch path.
  • Cache results for frequently queried pre-commande counts per product, with invalidation on status changes.
  • Minimize joins in the main catalog queries; pull pre-commande data only when displaying a pre-commande-enabled product.

Code conseil: a lean query to fetch pre-commande counts for a set of products without touching the main stock table:

// Pseudo repository method to fetch pre-order counts for a batch of product IDs
public function getPreOrderCounts(array $productIds) {
    $db = $this->getConnection();
    $select = $db->select()
        ->from(['po' => 'mage_pre_order'], ['product_id', 'COUNT(*) AS pre_order_count'])
        ->where('product_id IN (?)', $productIds)
        ->group('product_id');
    return $db->fetchAll($select);
}

Cache stratégie suggestion: - Use tag-based caching with a short TTL for pre-commande related data. - Invalidate caches when a pre-statut de commande changes or when an available_from date triggers a transition to ACTIVE.

End-to-End Implementation Checklist

  1. Define the modèle de données for pre-commandes (mage_pre_commande) and the optional mapping table.
  2. Create a Magento 2 module skeleton and register the module.
  3. Set up InstallSchema to create pre-commande tables.
  4. Detect pre-commande items during cart/paiement and avoid stock deductions.
  5. Implement temporary payment authorization at paiement and a deferred capture plan.
  6. Build an admin UI to manage pre-commande windows and thresholds.
  7. Configure automated client notifications for pre-commande progression.
  8. Apply optimisation des performancess to protect the main catalog.
  9. Test end-to-end in a staging environment with mulconseille scenarios.

With this structure in place, you’ll have a solid foundation to iterate on UX, rapporting, and automation as your entreprise glignes.

Testing Strategy: How to Validate Your Pre-Order Module

Testing is crucial. Create test cases for common paths and edge cases:

  • Single pre-commande item with an available_from date in the future.
  • Mulconseille pre-commande items in the same cart, mixing pre-commande and standard products.
  • Cart paiement with a pre-commande item and a passerelle de paiement that supports authorization only.
  • Conversion of pre-commandes to commandes after the available_from date, including auto-captures.
  • Refunds and partial refunds for pre-commandes that are canceled before fulfillment.

Automated tests are highly recommended. Use Magento's test framework or your favorite PHP test tool to cover these scenarios. Document each case with expected vs. actual outcomes for future maintenance.

Deployment and Maintenance: How to Roll This Out

Deployment bonnes pratiques matter. Consider les éléments suivants when moving from development to production:

  • Back up your database and codebase before enabling pre-commandes on a live store.
  • Run Magento setup:mise à jour and indexeur:réindexer after installing or updating the module.
  • Monitor performance and erreur logs during the first week and adjust caching as needed.
  • Provide a simple rollback path if a critical problème arises (disable the pre-commande fonctionnalité and revert changes).

Documentation and internal knowledge sharing are essential. Keep a living doc with the modèle de données, admin actions, and a change log so future développeurs can understand the system quickly.

Conclusion

Building a custom pre-commande module in Magento 2 is a rewarding challenge. It vous donne granular control over the pre-sales process, ensures your stock remains accurate, and enhances your client experience with timely notifications and precise administration. The étapes above provide a practical blueprint—from database design to payment integration, admin flux de travails, and performance tuning. Start small, validate each piece, and iterate with your team. Happy coding, and may your pre-commandes convert into delighted clients.

Appendix: Quick Reference Commands

These commands are handy in a local development environment:

# Enable Magento modules (run in Magento root)
php bin/magento module:enable Magefine_PreOrder
php bin/magento setup:upgrade
php bin/magento cache:flush
php bin/magento indexer:reindex