I'm creating automated checkout feature, which will create few orders at once:
What's wrong with that?
This script let's say creates 4 different orders with different prodcuts, for different users, but problem lies here, each order created after first order has got subtotal and grand total of first order. How can I reset that?
Mage::app('default');
/**
* Get the resource model
*/
$resource = Mage::getSingleton('core/resource');
/**
* Retrieve the read connection
*/
$readConnection = $resource->getConnection('core_read');
$query = 'SELECT * FROM m4gsrepeated_orders WHERE execution <= CURDATE()';
$all_orders = $readConnection->fetchAll($query);
function addCartItems($products_array) {
$cart = Mage::getModel('checkout/cart');
foreach($products_array as $productw) {
$sku = $productw[0];
/** #var $productCollection Mage_Catalog_Model_Resource_Product_Collection */
$productCollection = Mage::getModel( 'catalog/product' )
->getResourceCollection()
->addFieldToFilter( 'sku', $sku );
/** #var $product Mage_Catalog_Model_Product */
$product = $productCollection->getFirstItem();
// you should have the product now, maybe it isn't even necessary to get the ID
$product = $product->load($product->getId());
$cart->addProduct($product, $productw[1]);
}
$cart->save();
}
foreach ($all_orders as $order) {
//Set basic data
Mage::getSingleton('checkout/session')->getQuote()->setReservedOrderId(null);
$customer = Mage::getModel('customer/customer')
->setWebsiteId(Mage::app()->getStore()->getWebsiteId())
->load($order['user_id']);
// Set as Logged In and Clear Session
Mage::getSingleton('customer/session')->setCustomerAsLoggedIn($customer)->renewSession();
//Set up cart
$cart = Mage::getModel('checkout/cart')->getQuote();
//get current cart items;s
$i=1;
foreach ($cart->getAllItems() as $item) {
$products_current[$i][0] = $item->getProduct()->getSku();
$products_current[$i][1] = intval($item->getQty());
$i++;
}
$i=1;
$cart = Mage::getModel('checkout/cart');
foreach( Mage::getSingleton('checkout/session')->getQuote()->getItemsCollection() as $item ){
$cart->removeItem( $item->getId() );
}
$products_delayed = json_decode($order['items']);
addCartItems($products_delayed);
$cart->save();
//LUUUTA CREATE ORDER
# Get customer default shipping information
$customerAddressId = Mage::getSingleton('customer/session')->getCustomer()->getDefaultShipping();
if ($customerAddressId){
$address = Mage::getModel('customer/address')->load($customerAddressId);
$shippingAddress = $address->getData();
}
# Get customer default billing information
$customerAddressId = Mage::getSingleton('customer/session')->getCustomer()->getDefaultBilling();
if ($customerAddressId){
$address = Mage::getModel('customer/address')->load($customerAddressId);
$billingAddress = $address->getData();
}
$quote = Mage::getSingleton('checkout/session')->getQuote();
/*
* One page checkout consists of following steps
* (1) Customer login method aka Checkout method
* (2) Billing information (address)
* (3) Shipping information (address)
* (4) Shipping method
* (5) Payment information
* (6) Order review, in short: DO THE ORDER
*/
$storeId = Mage::app()->getStore()->getId();
$checkout = Mage::getSingleton('checkout/type_onepage');
$checkout->initCheckout();
$checkout->saveShippingMethod('excellence_excellence');
$quote->getShippingAddress()->setShippingMethod('excellence_excellence');
$quote->getShippingAddress()->unsGrandTotal(); //clear the values so they won't take part in calculating the totals
$quote->getShippingAddress()->unsBaseGrandTotal();
$quote->getShippingAddress()->setCollectShippingRates(true)->save();
$quote->getShippingAddress()->collectTotals(); //collect totals and ensure the initialization of the shipping methods
$quote->collectTotals();
//STEP(1)
$checkout->saveCheckoutMethod('register');
//STEP(2)
$checkout->saveBilling($billingAddress, false);
//STEP(3)
$checkout->saveShipping($shippingAddress, false);
//STEP(4)
$checkout->saveShippingMethod('excellence_excellence');
//STEP(5)
$checkout->savePayment(array('method'=>'pay'));
Mage::getSingleton('checkout/type_onepage')->getQuote()->getShippingAddress()->setShippingMethod('excellence_excellence');
//STEP(6)
/*
* $checkout->saveOrder() returns array holding empty object
* of type Mage_Checkout_Model_Type_Onepage
*/
try {
$checkout->saveOrder();
}
catch (Exception $ex) {
echo $ex->getMessage();
}
//addCartItems($products_delayed);
$cart->truncate();
$cart->save();
Mage::getSingleton('checkout/session')->setCartWasUpdated(true);
Mage::getSingleton('customer/session')->logout();
$customerAddressId = '';
}
Problem is fixed:
That lied in checkout/session and customer/session which had to be cleared.
Mage::getSingleton('checkout/session')->clear();
Mage::getSingleton('customer/session')->clear();
That might help someone when resolving batch ordering solution with similar method.
Thanks,
Adam
Related
I implemented a price collector / processor exactly as described in the docs (https://developer.shopware.com/docs/guides/plugins/plugins/checkout/cart/change-price-of-item).
For testing purposes every line item product in the cart has a price of 100€.
When applying a discount (10%) that I have created in the admin, the discount is applied on the original price of the product but not on the actual new cart price.
What am I missing here? My OverwritePriceCollector.php looks as following:
<?php declare(strict_types=1);
namespace Test\TestPlugin\Core\Checkout\Cart;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartBehavior;
use Shopware\Core\Checkout\Cart\CartDataCollectorInterface;
use Shopware\Core\Checkout\Cart\CartProcessorInterface;
use Shopware\Core\Checkout\Cart\LineItem\CartDataCollection;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\Price\AbsolutePriceCalculator;
use Shopware\Core\Checkout\Cart\Price\PercentagePriceCalculator;
use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
use Shopware\Core\Checkout\Cart\Price\Struct\AbsolutePriceDefinition;
use Shopware\Core\Checkout\Cart\Price\Struct\PercentagePriceDefinition;
use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
class OverwritePriceCollector implements CartDataCollectorInterface, CartProcessorInterface
{
/**
* #var QuantityPriceCalculator
*/
private $calculator;
public function __construct(QuantityPriceCalculator $calculator) {
$this->calculator = $calculator;
}
public function collect(CartDataCollection $data, Cart $original, SalesChannelContext $context, CartBehavior $behavior): void
{
// get all product ids of current cart
$productIds = $original->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE)->getReferenceIds();
// remove all product ids which are already fetched from the database
$filtered = $this->filterAlreadyFetchedPrices($productIds, $data);
// Skip execution if there are no prices to be saved
if (empty($filtered)) {
return;
}
foreach ($filtered as $id) {
$key = $this->buildKey($id);
// Needs implementation, just an example
$newPrice = 100;
// we have to set a value for each product id to prevent duplicate queries in next calculation
$data->set($key, $newPrice);
}
}
public function process(CartDataCollection $data, Cart $original, Cart $toCalculate, SalesChannelContext $context, CartBehavior $behavior): void
{
// get all product line items
$products = $toCalculate->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE);
foreach ($products as $product) {
$key = $this->buildKey($product->getReferencedId());
// no overwritten price? continue with next product
if (!$data->has($key) || $data->get($key) === null) {
continue;
}
$newPrice = $data->get($key);
// build new price definition
$definition = new QuantityPriceDefinition(
$newPrice,
$product->getPrice()->getTaxRules(),
$product->getPrice()->getQuantity()
);
// build CalculatedPrice over calculator class for overwritten price
$calculated = $this->calculator->calculate($definition, $context);
// set new price into line item
$product->setPrice($calculated);
$product->setPriceDefinition($definition);
}
}
private function filterAlreadyFetchedPrices(array $productIds, CartDataCollection $data): array
{
$filtered = [];
foreach ($productIds as $id) {
$key = $this->buildKey($id);
// already fetched from database?
if ($data->has($key)) {
continue;
}
$filtered[] = $id;
}
return $filtered;
}
private function buildKey(string $id): string
{
return 'price-overwrite-'.$id;
}
}
I think your problem is that you registered your Collector with the same priority as in documentation (4500).
Shopware PromotionProcessor is registered with priority 4900, so your code is called after PromotionProcessor.
So what do you need is to register your OverwritePriceCollector with higher priority e.g. 5000.
I am currently trying to add a product to my cart useing this code.
$quote = $this->_session->getQuote();
$quote->addProduct($product);
$this->_cartRepository->save($quote);
When I do this in a new session, the price of the product and the subtotal show as 0.00, but in the summary the Subtotal and Order Total are correct.
After editing the product quantity, the prices all function as they should.
I have tried to use $quote->collectTotals();, but this gives no visible changes.
How can I update cart so that the price of the product shows when I open the cart page?
Try with this code:
use Magento\Checkout\Model\Cart as Quote;
class Add {
protected $quote = null;
public function __construct( Quote $quote){
$this->quote = $quote;
}
public function test(\Magento\Catalog\Model\Product $product){
$options = ['qty'=> 1];
$this->quote->addProduct($product, $options);
//OR $this->quote->addProductsByIds([$product->getId()]);
$this->quote->save();
}
}
Or
public function test(\Magento\Catalog\Model\Product $product){
$quote = $this->_objectManager->get(\Magento\Checkout\Model\Cart:class);
$options = ['qty'=> 1];
$quote->addProduct($product, $options);
//OR $quote->addProductsByIds([$product->getId()]);
$quote->save();
}
You need to reload the quote from a DB to have all collectedTotals flags empty there.
/** #var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */
$quoteRepository->get($quoteId);
$quote->addProduct($product, $request);
$quote->collectTotals()->save();
See Magento\Quote\Model\Quote::collectTotals and Magento\Quote\Model\Quote\Address\Total\Subtotal::collect
I'm trying to create order from admin (for telephonic order). In that I've a situation that I need to get quote id from observer. I tried below observer(s)
sales_quote_save_after
sales_model_service_quote_submit_success
sales_quote_product_add_after
I tried to get id using this,
$id = $observer->getQuoteId();
And
I tried to print that quote items but I'm getting empty values.
Can any one help me to sort this out ?
In the event sales_quote_product_add_after the quote_item is passed to the Observer.
To get the quote from this Observer and the id:
public function yourMethod($observer)
{
$quoteItem = $observer->getEvent()->getQuoteItem();
$quote = $quoteItem->getQuote();
$id = $quote->getId();
}
In the event sales_model_service_quote_submit_success you have passed the order and the quote
public function yourMethod($observer)
{
$order= $observer->getEvent()->getOrder();
$quote= $observer->getEvent()->getQuote();
$id = $quote->getId();
}
In the event sales_quote_save_after you have passed quote since in app/code/core/Mage/Sales/Model/Quote.php
protected $_eventObject = 'quote';
Then in your observer you can get it:
public function yourMethod($observer)
{
$quote= $observer->getEvent()->getQuote();
$id = $quote->getId();
}
I fixed this using below solution,
I used below event
sales_quote_item_set_product
Actually I tried to set price for configurable product corresponding associated product's price. And my observer is,
$event = $observer->getEvent();
$quote_item = $event->getQuoteItem();
$storeId = $quote_item->getStoreId();
if(Mage::app()->getStore()->isAdmin()) {
$item = $observer->getQuoteItem();
$product = $observer->getProduct();
$sku = $product->getSku();
$productDetails = Mage::getModel('catalog/product')
->setStoreId($storeId)
->loadByAttribute('sku',$sku);
$price = $productDetails->getPrice();
$sprice = $productDetails->getFinalPrice();
$item->setOriginalCustomPrice($sprice);
$item->setOriginalPrice($price);
}
$quote = Mage::getSingleton('adminhtml/session_quote')->getQuote();
I have the following magento helper class.
class CommissionJunction extends Mage_Core_Helper_Data
{
/**
* Get SKU, quantity, price and discount amount for each product in a given order
* #param object $order
* #return array
*/
private function _getOrderProductsList($order) {
$orderItems = $order->getAllItems();
$purchasedSkus = array();
$count_orderItems = count($orderItems);
for($i = 0; $i < $count_orderItems; $i++) {
$purchasedSkus[$i] = array(
'ITEM' => $orderItems[$i]['sku'],
'QTY' => number_format($orderItems[$i]['qty_ordered'],0), // no decimals
'AMT' => number_format($orderItems[$i]['price'],2) // 2 decimal places
'DCNT' => number_format(abs($orderItems[$i]['discount_amount']),2) */
);
}
return $purchasedSkus;
}
/**
* Get the Universal Data (JSON) Object for Commission Junction.
* This object contains the order details passed on to Commission Junction for reporting purposes
* on the Checkout Success / Order Confirmation page.
* Notes:
* - CID, TYPE AND CURRENCY are hard coded
* #param string $orderId
* #return JSON object Universal Data Object for Commission Junction $json_masterTmsUdp
*/
public function getCommissionJunctionUdo($orderId) {
$order = Mage::getModel('sales/order')->loadByIncrementId($orderId);
$udo = array();
$udo['CID'] = 'XXXX';
$udo['TYPE'] = 'XXXX';
$udo['CURRENCY'] = 'USD';
$udo['OID'] = $orderId;
$udo['DISCOUNT'] = number_format(abs($order->discount_amount),2);
$order_coupon_code = $order->coupon_code;
if(!is_null($order_coupon_code) && !empty($order_coupon_code)) {
$udo['COUPON'] = $order_coupon_code;
}
$udo['PRODUCTLIST'] = self::_getOrderProductsList($order);
if(Mage::getModel('core/cookie')->get('aff_commissionjunction') == 'cjafflx') {
$udo['FIRECJ'] = "TRUE";
}
else {
$udo['FIRECJ'] = "FALSE";
}
$masterTmsUdo['CJ'] = $udo;
$json_masterTmsUdo = json_encode($masterTmsUdo);
return $json_masterTmsUdo;
}
}
And I found this site where they explain how to register a helper class
http://codegento.com/2011/02/creating-a-helper-class/
The only thing I dont know its:
Where in the magento structure should I add this php class?
Where is the config.xml I should edit?
User created classes should be placed in this folder:
app/code/community
Another issue, you should use the same naming convention for your class as Magento uses i.e.
MOduleNameSpace_ComissionJunction_Helper_Data
Also the config XML should be placed in your app/code/community/ MOduleNameSpace/ComissionJunction/etc folder
My client is requesting that each simple product within a bundled product they are selling (Clothing Top and Bottom) be added as a separate line item in the cart whenever a user adds it. Can anyone direct me in how to accomplish this? I am fairly good with MVC and the Zend Framework, but I need a bit of help finding the exact files that control adding bundled products to the cart, or an alternate method for getting these items added separately. Please assume that the only possible product type for this clothing is the Bundled product type.
You will need an observer:
<checkout_cart_product_add_after>
<observers>
<reporting>
<type>singleton</type>
<class>Yourcompany_yourmodelname_Model_Observer</class>
<method>onCartProductAdd</method>
</reporting>
</observers>
</checkout_cart_product_add_after>
Then do the observer:
class ourcompany_yourmodelname_Model_Observer extends Mage_Core_Model_Abstract
{
/**
* Binds to the checkout_cart_product_add_after Event and passes control to the helper function to process the quote
*
* #param Varien_Event_Observer $observer
* #return void
*/
public function onCartProductAdd($observer){
$product = $observer->getProduct();
$isProductBundle = ($product->getTypeId() == 'bundle');
$items_to_add = array();
$oTypeInstance = $oProduct->getTypeInstance(true);
$aSelections = $oTypeInstance->getSelectionsCollection($aOptionIds, $product );
$aOptions = $oTypeInstance->getOptionsByIds($aOptionIds, $product);
$bundleOptions = $aOptions->appendSelections($aSelections, true);
foreach ($bundleOptions as $bundleOption) {
if ($bundleOption->getSelections()) {
$bundleSelections = $bundleOption->getSelections();
foreach ($bundleSelections as $bundleSelection) {
$items_to_add[] = $bundleSelection.getID();
}
}
}
insertExtractedProducts($items_to_add);
}
/**
* Add extracted products into quote
*
* #param array $items_to_add
*/
public function insertExtractedProducts($items_to_add){
/**#var $cart Mage_Checkout_Model_Cart**/
$cart = Mage::helper('checkout/cart')->getCart();
$ids_to_add = array();
foreach($items_to_add as $item_to_be_added){
$ids_to_add[] = $item_to_be_added->getProductId();
}
$cart->addProductsByIDs($ids_to_add);
$cart->save();
Mage::getSingleton('checkout/session')->setCartWasUpdated(true);
}
}
Just a simple sample, but it might help.
Bundled products can be complicated to understand, when working with it via code:
Here is a sample image:
Each Bundled product, have one to many options, which in the end will be links to products to be added to the bundle in the Shopping Cart.
Each Option consists of one to many Selections, which will be the linked products that will end up in the Shopping cart, under this bundled product. One Selection, can typically be set as the default, and will already be selected on the product page. More information can be found at this link on how to create and configure bundled products, because in this document, we will only discuss the programming side of it.
The bundled product’s display page, will look like this:
It could look like this in the shopping cart, once you clicked on “Add to Cart”:
Bundle Sample
Sample Shampoo
1 x Moisturiser-125ml $29.95
Default Conditioner
1 x Moisturiser-60g $99.95
When interrogating this product through code, you load it like any normal product:
$oProduct->load($vProductId);
Once it is loaded, you need to get a product type instance, so that you can load the options for this product type.
$oTypeInstance = $oProduct->getTypeInstance(true);
Now we can get the list of option ID’s for this product in this manner:
$oTypeInstance = $oProduct->getTypeInstance(true);
To interrogate the Selections, we will add the list of options to a collection, then get the Options collection, as well as their respective Selections:
$aSelections = $oTypeInstance->getSelectionsCollection($aOptionIds, $oProduct );
$aOptions = $oTypeInstance->getOptionsByIds($aOptionIds, $oProduct);
$bundleOptions = $aOptions->appendSelections($aSelections, true);
Now we have a Collection of Options, and each Option will have a collection of Selections. We can now loop through the options, and look at their Selctions.
foreach ($bundleOptions as $bundleOption) {
if ($bundleOption->getSelections()) {
$bundleSelections = $bundleOption->getSelections();
foreach ($bundleSelections as $bundleSelection) {
// get some data here
$vName = $bundleOption->getTitle();
}
}
}
To get a list of the Required Selection Products for the Bundled product, you can use the following code:
$requiredChildren = $this->getChildrenIds($product->getId(),$required=true);
You can then loop through the array of ID’s and load the products by their ID’s to get more information regarding those products.
Bundled products in the shopping cart
In order to loop through the selected options of a Bundled product in the shopping card, you can use something like this:
/**
* #var Mage_Bundle_Model_Product_Type
*/
$typeInstance = $this->getProduct()->getTypeInstance(true);
// get bundle options
$optionsQuoteItemOption = $this->getItem()->getOptionByCode('bundle_option_ids');
$bundleOptionsIds = unserialize($optionsQuoteItemOption->getValue());
if ($bundleOptionsIds) {
/**
* #var Mage_Bundle_Model_Mysql4_Option_Collection
*/
$optionsCollection = $typeInstance->getOptionsByIds($bundleOptionsIds, $this->getProduct());
// get and add bundle selections collection
$selectionsQuoteItemOption = $this->getItem()->getOptionByCode('bundle_selection_ids');
$selectionsCollection = $typeInstance->getSelectionsByIds(
unserialize($selectionsQuoteItemOption->getValue()),
$this->getProduct()
);
$bundleOptions = $optionsCollection->appendSelections($selectionsCollection, true);
foreach ($bundleOptions as $bundleOption) {
if ($bundleOption->getSelections()) {
$label = $bundleOption->getTitle()
$bundleSelections = $bundleOption->getSelections();
foreach ($bundleSelections as $bundleSelection) {
$sName = $bundleSelection->getName();
}
// some more code here to do stuff
}
}
}
This code gets the Options from the Quote Item Bundled Product, and then gets the Options for that product in a collection, and then finds the “Selected” Option Selection.
hth,
Shaun
Here's how you can do this in Magento 2.3 via an "around" plugin (interceptor) for the Magento\Checkout\Model\Cart->addProduct() method.
The addProduct() method is called when the customer adds a product to the cart. By adding code to this method via a plugin, you can alter the way the bundle product is added to the cart.
Define the plugin in Vendor/Module/etc/frontend/di.xml:
<?xml version="1.0"?>
<!--
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="\Magento\Checkout\Model\Cart">
<plugin name="add-bundle-products-separate" type="Vendor\Module\Plugin\Checkout\Model\CartPlugin" sortOrder="1"/>
</type>
</config>
This is what tells Magento a plugin exists for this particular method.
Reference:
https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html
Create the plugin's class.
In Vendor/Module/Plugin/Checkout/Model/CartPlugin.php:
<?php
namespace Vendor\Module\Plugin\Checkout\Model;
use \Magento\Catalog\Model\Product;
use \Magento\Framework\DataObject;
use \Magento\Checkout\Model\Cart;
/**
* Class CartPlugin
*
* #package Ppwd\CrossSell\Plugin\Checkout\Model
*
*/
class CartPlugin {
/**
* #param Cart $subject
* #param \Closure $proceed
* #param Product $productInfo
* #param DataObject|int|array $requestInfo
* #return Cart $subject
* #SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function aroundAddProduct(
$subject,
$proceed,
$productInfo,
$requestInfo = null
) {
// Detect if we are adding a bundle product to cart
if (!is_numeric($productInfo) && $productInfo->getTypeId() == 'bundle') {
$buyRequest = new DataObject($requestInfo);
// List of products selected as part of the bundle
$cartCandidates = $productInfo->getTypeInstance()->prepareForCartAdvanced($buyRequest, $productInfo);
$productIds = [];
// Add each item in bundle as if it were separately added to cart
/** #var Product $cartCandidate */
foreach ($cartCandidates as $cartCandidate) {
if ($cartCandidate->getTypeId() != 'bundle') {
for ($i = 0; $i < $cartCandidate->getCartQty(); $i++) {
$productIds[] = $cartCandidate->getId();
}
}
}
$subject->addProductsByIds($productIds);
return $subject;
}
// Return original result from addProduct() as if plugin didn't exist
$result = $proceed($productInfo, $requestInfo);
return $result;
}
}
When done, if you add the bundle to the cart the line items will appear separately instead of grouped together like a bundle product normally is.
I did this to solve the customer request to add bundle content as separate items in the cart. Just replace
$cart->addProduct($product, $params)
with
if ($product->getTypeId() == 'bundle') {
$request = new Varien_Object($params);
$cartCandidates = $product->getTypeInstance(true)->prepareForCartAdvanced($request, $product, Mage_Catalog_Model_Product_Type_Abstract::PROCESS_MODE_FULL);
$idstoadd = array();
foreach ($cartCandidates as $cartCandidate) {
if ($cartCandidate->getTypeId() == 'simple') {
for ($i = 0; $i < $cartCandidate->getCartQty(); $i++) {
$idstoadd[] = $cartCandidate->getId();
}
}
}
$cart->addProductsByIds($idstoadd);
} else {
$cart->addProduct($product, $params);
}
in the file cartController.