VirtueMart Forum

VirtueMart 2 + 3 + 4 => Virtuemart Development and bug reports => Coding Central => Topic started by: sandomatyas on September 26, 2025, 14:45:36 PM

Title: VirtueMart shipping/payment ordering issue
Post by: sandomatyas on September 26, 2025, 14:45:36 PM
Hi everyone,

It seems that for quite a long time people have been struggling with the fact that in VirtueMart you can't really control the order of shipping and payment methods.

Even if you set the list ordering value, the way it works in cart/view.html.php is like this:

$returnValues = vDispatcher::trigger('plgVmDisplayListFEShipment', array($this->cart, $selectedShipment, &$this->shipments_shipment_rates));
This means that first the methods are collected in the order of the vmshipment plugins, and only inside each plugin does the ordering from the database apply.

I was thinking this could be fixed quite easily, because after the trigger the array $this->shipments_shipment_rates['shipment'] already uses the virtuemart_shipmentmethod_id as keys. That makes it simple to reorder the whole list afterwards by the ordering column in the database.

Since I didn't want to hack the core, I tried a different approach: I wrote a tiny vmshipment plugin that I put last in the plugin order. This plugin doesn't need to register any shipment method at all. But because the trigger also runs on it, it receives the $shipments_shipment_rates array by reference and can reorder it before return.

Here's the minimal code:

defined('_JEXEC') or die('Restricted access');

class plgVmShipmentReorder extends vmPSPlugin
{
    function __construct(& $subject, $config)
    {
        parent::__construct($subject, $config);
    }

    public function plgVmDisplayListFEShipment($cart, $selected, &$shipments_shipment_rates)
    {
        if (empty($shipments_shipment_rates['shipment'])) {
            return;
        }

        $db  = JFactory::getDbo();
        $ids = array_keys($shipments_shipment_rates['shipment']);

        $query = $db->getQuery(true)
            ->select($db->quoteName(['virtuemart_shipmentmethod_id', 'ordering']))
            ->from($db->quoteName('#__virtuemart_shipmentmethods'))
            ->where('virtuemart_shipmentmethod_id IN (' . implode(',', $ids) . ')');
        $db->setQuery($query);
        $orders = $db->loadAssocList('virtuemart_shipmentmethod_id', 'ordering');

        if (!$orders) {
            return;
        }

        uksort($shipments_shipment_rates['shipment'], function($a, $b) use ($orders) {
            return $orders[$a] <=> $orders[$b];
        });
    }
}

Of course this is only a custom workaround – ideally this logic should be part of the core (for example in the corresponding model).

What do you think? Would it make sense to handle the ordering like this in VirtueMart itself?

The same approach could be applied for payment methods as well.