Skip to content
  1. Extras
  2. MiniShop3
  3. Development
  4. Events
  5. Order item

Order product events

Events for order line items: add, update, remove.

Context

Since 1.12 the manager edits lines via Manager API (OrdersController: POST/PUT/DELETE …/orders/{id}/products/…), not legacy processors. Parameters include msOrder and msOrderProduct.

For storefront cart events see Cart events.

After hooks do not roll back DB

msOnCreateOrderProduct, msOnUpdateOrderProduct, msOnRemoveOrderProduct run after save() / remove(). If an after-plugin returns output, core logs a warning only — the Vue client still gets success. Validation and veto belong in matching msOnBefore*.

msOnBeforeCreateOrderProduct

Fired before persisting a new line (Manager API).

Parameters

ParameterTypeDescription
msOrderProductmsOrderProductNew line (not in DB yet)
msOrdermsOrderParent order
modeintmodSystemEvent::MODE_NEW

Aborting the operation

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeCreateOrderProduct':
        /** @var \MiniShop3\Model\msOrderProduct $orderProduct */
        $orderProduct = $scriptProperties['msOrderProduct'];

        $productId = $orderProduct->get('product_id');
        $count = $orderProduct->get('count');

        $msProduct = $modx->getObject(\MiniShop3\Model\msProduct::class, $productId);
        if ($msProduct) {
            $stock = $msProduct->get('stock') ?? 0;
            if ($count > $stock) {
                $modx->event->output('Not enough stock');
                return;
            }
        }
        break;
}

msOnCreateOrderProduct

Fired after successful save(). A plugin error does not roll back the row.

Parameters

ParameterTypeDescription
msOrderProductmsOrderProductSaved line
msOrdermsOrderOrder
modeintmodSystemEvent::MODE_NEW

Example

php
<?php
switch ($modx->event->name) {
    case 'msOnCreateOrderProduct':
        $orderProduct = $scriptProperties['msOrderProduct'];

        $productId = $orderProduct->get('product_id');
        $count = $orderProduct->get('count');

        $msProduct = $modx->getObject(\MiniShop3\Model\msProduct::class, $productId);
        if ($msProduct) {
            $stock = $msProduct->get('stock') ?? 0;
            $msProduct->set('stock', max(0, $stock - $count));
            $msProduct->save();
        }

        $modx->log(modX::LOG_LEVEL_INFO, sprintf(
            '[OrderProduct] Added product #%d, qty: %d',
            $productId,
            $count
        ));
        break;
}

msOnBeforeUpdateOrderProduct

Fired before save() on an updated line.

Parameters

ParameterTypeDescription
msOrderProductmsOrderProductLine with new field values
msOrdermsOrderOrder
modeintmodSystemEvent::MODE_UPD

Aborting the operation

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeUpdateOrderProduct':
        $orderProduct = $scriptProperties['msOrderProduct'];

        $newCount = $orderProduct->get('count');
        $oldCount = $orderProduct->getPrevious('count');

        if ($newCount > $oldCount) {
            $diff = $newCount - $oldCount;
            $productId = $orderProduct->get('product_id');

            $msProduct = $modx->getObject(\MiniShop3\Model\msProduct::class, $productId);
            if ($msProduct) {
                $stock = $msProduct->get('stock') ?? 0;
                if ($diff > $stock) {
                    $modx->event->output('Not enough stock to increase quantity');
                    return;
                }
            }
        }
        break;
}

msOnUpdateOrderProduct

Fired after successful save(). A plugin error does not roll back changes.

Parameters

ParameterTypeDescription
msOrderProductmsOrderProductUpdated line
msOrdermsOrderOrder
modeintmodSystemEvent::MODE_UPD

Example

php
<?php
switch ($modx->event->name) {
    case 'msOnUpdateOrderProduct':
        $orderProduct = $scriptProperties['msOrderProduct'];

        $newCount = $orderProduct->get('count');
        $oldCount = $orderProduct->getPrevious('count');

        if ($newCount != $oldCount) {
            $diff = $newCount - $oldCount;
            $productId = $orderProduct->get('product_id');

            $msProduct = $modx->getObject(\MiniShop3\Model\msProduct::class, $productId);
            if ($msProduct) {
                $stock = $msProduct->get('stock') ?? 0;
                $msProduct->set('stock', $stock - $diff);
                $msProduct->save();
            }
        }
        break;
}

msOnBeforeRemoveOrderProduct

Fired before remove(). API rejects deleting the last line (HTTP 400 before the event).

Parameters

ParameterTypeDescription
msOrderProductmsOrderProductLine to remove
msOrdermsOrderOrder
idintmsOrderProduct id

Aborting the operation

php
<?php
switch ($modx->event->name) {
    case 'msOnBeforeRemoveOrderProduct':
        /** @var \MiniShop3\Model\msOrder $order */
        $order = $scriptProperties['msOrder'];

        if ($order && (int) $order->get('status_id') === 2) {
            $modx->event->output('Cannot remove lines from a finalized order');
            return;
        }
        break;
}

msOnRemoveOrderProduct

Fired after successful remove(). A plugin error does not restore the row.

Parameters

ParameterTypeDescription
msOrderProductmsOrderProductRemoved line (still in xPDO memory)
msOrdermsOrderOrder
idintRemoved line id

Example

php
<?php
switch ($modx->event->name) {
    case 'msOnRemoveOrderProduct':
        $orderProduct = $scriptProperties['msOrderProduct'];

        $productId = $orderProduct->get('product_id');
        $count = $orderProduct->get('count');

        $msProduct = $modx->getObject(\MiniShop3\Model\msProduct::class, $productId);
        if ($msProduct) {
            $stock = $msProduct->get('stock') ?? 0;
            $msProduct->set('stock', $stock + $count);
            $msProduct->save();
        }
        break;
}

Full example: stock control

php
<?php
/**
 * Plugin: Order line stock control
 * Events: msOnBeforeCreateOrderProduct, msOnBeforeUpdateOrderProduct,
 *         msOnCreateOrderProduct, msOnUpdateOrderProduct, msOnRemoveOrderProduct
 */

switch ($modx->event->name) {

    case 'msOnBeforeCreateOrderProduct':
    case 'msOnBeforeUpdateOrderProduct':
        $orderProduct = $scriptProperties['msOrderProduct'];
        $productId = $orderProduct->get('product_id');
        $count = $orderProduct->get('count');

        $msProduct = $modx->getObject(\MiniShop3\Model\msProduct::class, $productId);
        if ($msProduct && $count > ($msProduct->get('stock') ?? 0)) {
            $modx->event->output('Insufficient stock');
            return;
        }
        break;

    case 'msOnCreateOrderProduct':
    case 'msOnRemoveOrderProduct':
        // Adjust stock after persistence (after errors are logged only)
        adjustStock($modx, $scriptProperties['msOrderProduct'], $modx->event->name);
        break;

    case 'msOnUpdateOrderProduct':
        $orderProduct = $scriptProperties['msOrderProduct'];
        $diff = $orderProduct->get('count') - $orderProduct->getPrevious('count');
        if ($diff !== 0) {
            adjustStockDelta($modx, $orderProduct->get('product_id'), -$diff);
        }
        break;
}

function adjustStock($modx, $orderProduct, $eventName) {
    // ...
}

function adjustStockDelta($modx, $productId, $delta) {
    // ...
}