Magento: change shipping method on existing order
Asked Answered
P

1

13

I'm trying to change the shipping on an existing order in Magento. This works fine from the admin backend, even if it's quite the process since I have to manually update a lot of the order fields/attributes after I set the new shipping method on the shipping address object and recalculate the quote totals.

My problem is when running the same code on the frontend, it doesn't work at all, the quote collectTotals will revert any changes I've made in the shippingAddress, and I have no idea how to solve it or why it works from the backend.

This is how it looked:

    $shippingAddress = $quote->getShippingAddress();

    $shippingAddress->setShippingMethod('dynamicshipping_'.$shippingCode);
    $shippingAddress->setCollectShippingRates(true);
    $shippingAddress->collectShippingRates();

    $quote->setUseCustomerBalance(1)->setTotalsCollectedFlag(false)->collectTotals()->save();

    $order->setShippingHiddenTaxAmount($shippingAddress->getShippingHiddenTaxAmount());
    $order->setBaseShippingHiddenTaxAmount($shippingAddress->getBaseShippingHiddenTaxAmount());
    $order->setBaseShippingHiddenTaxAmnt($shippingAddress->getBaseShippingHiddenTaxAmnt());
    $order->setShippingInclTax($shippingAddress->getShippingInclTax());
    $order->setBaseShippingInclTax($shippingAddress->getBaseShippingInclTax());
    $order->setShippingTaxAmount($shippingAddress->getShippingTaxAmount());
    $order->setBaseShippingTaxAmount($shippingAddress->getBaseShippingTaxAmount());
    $order->setShippingAmount($shippingAddress->getShippingAmount());
    $order->setBaseShippingAmount($shippingAddress->getBaseShippingAmount());
    $order->setShippingDiscountAmount($shippingAddress->getShippingDiscountAmount());
    $order->setBaseShippingDiscountAmount($shippingAddress->getBaseShippingDiscountAmount());
    $order->setGrandTotal($shippingAddress->getGrandTotal());
    $order->setBaseGrandTotal($shippingAddress->getBaseGrandTotal());
    $order->setShippingMethod('dynamicshipping_'.$shippingCode);
    $order->setShippingDescription($shippingDescription);

    $order->setServicePoint($servicePoint);
    $order->save();

And as I said, that worked fine every time from the backend, but not when called from the frontend.

I've tried variations, such as this to try and eradicate any trace of the old shipping method, with no luck.

    $quote->getShippingAddress()->removeAllShippingRates()
        ->setShippingMethod('dynamicshipping_'.$shippingCode)
        ->setShippingDescription($shippingDescription)
        //->setBaseShippingAmount(0)
        //->setBaseShippingTaxAmount(0)
        //->setShippingTaxAmount(0)
        //->setShippingInclTax(0)
        ->setCollectShippingRates(true)
        //->unsetData('cached_items_all')
        //->unsetData('cached_items_nominal')
        //->unsetData('cached_items_nonnominal')
        ->collectShippingRates()
        //->collectTotals()
        ->save();

It looks to me as if the quote is using an older/diffrent copy of the shipping address when I'm calling collectTotals, no matter what I do.

Any suggestions, or perhaps insight on how it's even possible that this works in the backend but not the frontend?

EDIT

After more debugging, I can see that the shipping does change both in frontend and backend. The problem is, the fee will only change when running this code through the backend. Very strange. It just refuses to update shipping fee.

Peoples answered 24/9, 2015 at 17:45 Comment(7)
Could you provide a more complete code (where did you put your snippet) and more info about your use case? I would need some context, to answer your question.Kodiak
It's the complete code, and i run it from admin and frontend whenever the shipping is changed on a placed orderPeoples
I meant which class did you put your snippet in, and how do you propose to engage this code from the front-end?Kodiak
It's located in a helper function and called from a frontend-controller that takes an ajax-call when the user changes shipping on the placed order.Peoples
Hmm, I think I've narrowed it down to the "sales_quote_collect_totals_before" event. I can run collectTotals() on the address itself, but If I run it on the quote this event will revert the shipping method back to the original.Peoples
Have you tried setting both setShippingAmount() and setBaseShippingAmount() to the fee you need?Slender
Yes. Embarrassing as it is, I think I might have caused this issue from the start, using a method that started collectTotals(), while inside a collectTotals observer.... :PPeoples
P
2

Looks like I had some issues with an observer on collectTotals, which is the reason it worked in the backend where the event wasn't fired.

The complete code for reference, which I recently changed to use a more fail-safe method to copy all the fields back to the order.

    /* @var $order Mage_Sales_Model_Order */
    /* @var $quote Mage_Sales_Model_Quote */

    $shippingAddress = $quote->getShippingAddress();
    $shippingAddress->setShippingMethod('dynamicshipping_'.$shippingCode);
    $shippingAddress->setShippingDescription($shippingDescription);

    $shippingAddress->setCollectShippingRates(true)->collectShippingRates();
    $quote->collectTotals();

    if ($this->updateMagentoOrder($order, $quote)) {

        // here's where I check if we successfully updated the authorized
        // amount at the payment gateway, before saving anything
        // wrapping the payment update and save in a try-catch

        $quote->save();
        $order->save();
    }

And using this method for updating all the order fields:

/**
 * Updates a Magento order based on quote changes
 * will not save anything, up to the caller.
 * deleting items not supported.
 *
 * @param  $order Mage_Sales_Model_Order
 * @param  $quote Mage_Sales_Model_Quote
 * @return bool
 */
public function updateMagentoOrder($order, $quote) {
    if (!$order instanceof Mage_Sales_Model_Order || !$quote instanceof Mage_Sales_Model_Quote) {
        return false;
    }

    try {
        $converter = Mage::getSingleton('sales/convert_quote');
        $converter->toOrder($quote, $order);

        foreach ($quote->getAllItems() as $quoteItem) {

            $orderItem     = $converter->itemToOrderItem($quoteItem);
            $quoteItemId   = $quoteItem->getId();
            $origOrderItem = empty($quoteItemId) ? null : $order->getItemByQuoteItemId($quoteItemId);

            if ($origOrderItem) {
                $origOrderItem->addData($orderItem->getData());
            } else {
                if ($quoteItem->getParentItem()) {
                    $orderItem->setParentItem(
                        $order->getItemByQuoteItemId($quoteItem->getParentItem()->getId())
                    );
                    $orderItem->setParentItemId($quoteItem->getParentItemId());
                }
                $order->addItem($orderItem);
            }
        }

        if ($shippingAddress = $quote->getShippingAddress()) {
            $converter->addressToOrder($shippingAddress, $order);
        }
    } catch (Exception $e) {
        Mage::logException($e);
        return false;
    }

    return true;
}

For reference, the method above could loop $order->getAllItems() and do $orderItem->cancel()->delete(); on them first - but I won't support deleting items right now.

The cancel() part before deletion is so that the CatalogInventory module can restore stock. It's listening for the sales_order_item_cancel event.

Peoples answered 5/10, 2015 at 13:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.