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.
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.
Related
I have this class
class Api extends \Magento\Framework\Model\AbstractModel
{
public function __construct(
\Magento\Framework\Message\ManagerInterface $messageManager,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\MyModule\Payment\Helper\Data $helper
) {
$this->messageManager = $messageManager;
$this->scopeConfig = $scopeConfig;
$this->storeManager = $storeManager;
$this->helper = $helper;
$this->contentType = $this->helper->getConfigData('content_type');
}
.
.
.
function createOnlinePurchase($authToken, $lastOrder)
{
.
.
.
//here I pass lastOrder's increment id to my payment gateway
$lastOrder->setData('test','test data');
$lastOrder->save();
//make payment gateway api call, get payment url
return url;
}
}
this class is then used by a custom controller:
class Index extends \Magento\Framework\App\Action\Action
{
public function __construct(
\Magento\Framework\App\Action\Context $context,
\MyModule\Payment\Model\Api $moduleApi,
\Magento\Checkout\Model\Session $session
) {
parent::__construct($context);
$this->moduleApi = $moduleApi;
$this->session = $session;
}
public function execute() {
$token = $this->moduleApi->requestAuthToken();
if ($this->session->getLastRealOrder() && $token !== null) {
$url = $this->moduleApi->createOnlinePurchase($token, $this->session->getLastRealOrder());
if ($url !== null && substr($url, 0, 4) == 'http') {
$this->session->clearStorage();
return $this->resultRedirectFactory->create()->setUrl($url);
}
else {
$this->messageManager->addError(__("Url invalid: ".$url));
return $this->resultRedirectFactory->create()->setPath('checkout/onepage/failure');
}
}
}
on a SECOND custom controller Callback, which is triggered by my payment gateway, I retrieved the order object using $order = $this->getOrderFactory->create()->loadByIncrementId($incrementId)
where $this->getOrderFactory is an instance of \Magento\Sales\Model\OrderFactory I injected.
I got the increment id back from my payment gateway.
Somehow within this Callback class, when I use $order->getData('test'), I get nothing
My question is
Is there some core magento concept I'm missing here?
Or is there any other way I can retrieve this test data in Callback which only have the information of increment Id (because at the point of Callback, user have already left magento and come back)
It's weird to me beause I can edit and save the order from Callback but my extra data is not saved/associated with the order object itself
Thanks in advance!
UPDATE
I confirmed that I'm getting the same order object(row) by using the order id I get from my payment gateway as the one from session's Last Order
I called addStatusHistoryComment on lastOrder in Api class above and also called addStatusHistoryComment in my Callback class
both calls are updating the same order in my admin dashboard
I have also confirmed calling getData('test') right after I set it gives me the data I want.
So I don't understand why getData doesn't work when called from Callback
You can't just add data to order object, every model is mapped automatically to DB tables columns, object will store temporarily the data and not error out but it will not persist in database. The reason why comments work, is because they have a place in database and on save and on load there is extra code that saves and adds it to the model.
In order to persists new data in the order you need to add new order attribute or simply add a new column on sales order table. When saving, key much match exactly the name of the new column you created.
I ended up using setCustomerNote in place of setData with a custom key, which is weird that it works because it is literally doing:
return $this->setData(OrderInterface::CUSTOMER_NOTE, $customerNote);
I can only assume on magento 2.4.x (which is what i'm using btw), setData is restricted to predefined keys only.
Just finished migrating a Magento website to a new server. Using the following steps:
-Backup the database in the backend
-Import the database on new server
-Copy all files from old server to new server
-Set correct file permissions
-Update /app/etc/local.xml
-Update secure and unsecure base_url in database
-Remove cache and session files
The website itself works fine, but it seems to give a fatal error on the order button now (see screenshot of source)
This is the block of code the error refers to:
/**
* Product type instance factory
*
* #param Mage_Catalog_Model_Product $product
* #param bool $singleton
* #return Mage_Catalog_Model_Product_Type_Abstract
*/
public static function factory($product, $singleton = false)
{
$types = self::getTypes();
$typeId = $product->getTypeId();
if (!empty($types[$typeId]['model'])) {
$typeModelName = $types[$typeId]['model'];
} else {
$typeModelName = self::DEFAULT_TYPE_MODEL;
$typeId = self::DEFAULT_TYPE;
}
if ($singleton === true) {
$typeModel = Mage::getSingleton($typeModelName);
}
else {
$typeModel = Mage::getModel($typeModelName);
$typeModel->setProduct($product);
}
$typeModel->setConfig($types[$typeId]); /**this is line 80**/
return $typeModel;
}
I also tried to make a fresh install and copy the needed files, but that did not work either.
This is my first time working with Magento, and searching online for a solution did not bring up anything useful to me. Could anyone point me to the right direction?
I use a finance payment module on my site which redirects the user after placing an order to the finance website to fill in their details.
I've just installed the 'One step checkout' from Magestore and testing the order cycle to make sure it worked like before, it doesn't.
After placing the order I get the error in the title
It's telling me there's an error on line 20 in StatusUpdate.php, below is the code for this file.
Can anyone tell me what's going on? Screengrab of error
<?php
class C3_V12Finance_Helper_StatusUpdate extends Mage_Core_Helper_Abstract
{
public function handleOrderStatusUpdate(Mage_Sales_Model_Order $order, $configPath) {
if ($status = Mage::getStoreConfig($configPath)) {
$state = $this->getStateFromStatus($status);
/* Magento doesn't allow some states to be changed manually ('complete' and 'closed'). This validation only
* occurs however when you change state via setState. The validation doesn't occur on setData, however so
* we use this as a workaround. */
$order->setData('state', $state)
->setStatus($status)
->save();
return true;
}
return false;
}
public function getStateFromStatus($status) {
$collection = Mage::getResourceModel('sales/order_status_collection')
->addStatusFilter($status)
->joinStates();
// Magento will actually prevent you from ever being able to have multiple statuses with the same code, but just to be safe...
if ($collection->getSize() > 1) {
Mage::throwException("Multiple statuses found with code " . $status . ". Ensure only one is present.");
}
$statusModel = $collection->getFirstItem();
return $statusModel->getState();
}
}
I have just started getting familiar with the Google Checkout API, but there's something I have a question about. On the Google Checkout Documentation, the only way of submitting the actual cart is via a button that is created via an echo call, like so echo $cart->CheckoutButtonCode("LARGE"); This however is not what I need. I want to manually submit my cart from my PHP script.
However, unlike the PayPal API, there appears to be no submit type function in the Google Checkout script. After some further research, I noticed that the HTML examples post their fields to https://sandbox.google.com/checkout/api/checkout/v2/checkout/Merchant/MERCHANT_ID_HERE.
How can I do this in PHP? I am using their official API. This is what I have done so far:
$merchant_id = $google_merchant_ID;
$merchant_key = $google_merchant_key;
if ($enable_Google_Sandbox == 1)
{
$server_type = "sandbox";
}
$currency = $currency_code;
$cart = new GoogleCart($merchant_id, $merchant_key, $server_type,
$currency);
$shop_cart = $_SESSION['cart'];
foreach ($shop_cart as $value)
{
$k_product = $value['Product'];
$k_quantity = $value['Quantity'];
$k_price = $value['Price'];
$k_orderID = $_SESSION['order_id'];
if (isset($_SESSION['Discount']))
{
$k_discount = $_SESSION['Discount'];
$k_price = $k_price - $k_discount;
}
$cart_item = new GoogleItem($k_product,
"Some Product",
$k_quantity,
$k_price);
$cart_item->SetMerchantItemId(generateProductID());
$cart->AddItem($cart_item);
}
// Specify <edit-cart-url>
$cart->SetEditCartUrl("http://192.168.100.100:8888/order.php?action=showCart");
// Specify "Return to xyz" link
$cart->SetContinueShoppingUrl("http://192.168.100.100:8888/store.php");
// Request buyer's phone number
$cart->SetRequestBuyerPhone(false);
There's no $cart->submitCart(); type function, so what do I do?
list($status, $error) = $cart->CheckoutServer2Server();
That should solve your problem. As you're using the PHP api, i can recommend the PHP demos. CheckoutServer2Server is demonstrated in this example (DigitalUsecase()). (digitalCart.php)
CheckoutServer2Server docs:
/**
* Submit a server-to-server request.
* Creates a GoogleRequest object (defined in googlerequest.php) and sends
* it to the Google Checkout server.
*
* more info:
* {#link http://code.google.com/apis/checkout/developer/index.html#alternate_technique}
*
* #return array with the returned http status code (200 if OK) in index 0
* and the redirect url returned by the server in index 1
*/
I'm currently developing a payment method and things are working quite well.
Just one thing: The customer enters some information along the payment method and through debugging I can see that it gets written into the InfoInstance via Mage_Payment_Model_Method_Abstract::assignData()
Unfortunately, I can't read that data when I'm in the capture()-Method. I retrieve the InfoInstance and try to read the information, but it's not set.
assignData() method:
public function assignData($data) {
if (!($data instanceof Varien_Object)) {
$data = new Varien_Object($data);
}
$info = $this->getInfoInstance();
$info->setEtixType($data->getEtixType());
return $this;
}
capture() method:
public function capture(Varien_Object $payment, $amount) {
// ...
$info = $this->getInfoInstance();
Mage::log('etix_type: '.$info->getEtixType()); //I expect something like "etix_type: cc"
// ...
}
Any help is appreciated. I'm sure I missed something.
Found it,
Assigning veriables directly to the InfoInstance works, but it does not persist through the whole checkout process. Instead, you have to set it on the additional_data:
$info = $this->getInfoInstance();
$info->setAdditionalInformation('etix_type', $data->getEtixType());
And later you can read it via:
$info = $this->getInfoInstance();
$etix_type = $info->getAdditionalInformation('etix_type');