Magento’s “Quote/Order/Invoice” workflow

Featured Image

The purpose of this article is to shed some light onto the flow of things that happen behind the scene in the process of creating and later invoicing order in Magento. In order to properly code new functionality surrounding order creation process, one must have a solid understanding of that process otherwise you may impose some serious issues on to the system via custom coding around orders.

If you would like to follow up, please set up your own test Magento store. In my example I am using Magento Community Edition, 1.5.

So where do we start. I suggest we start from empty sales tables, meaning we run the following query on database:

TRUNCATE `sales_bestsellers_aggregated_daily`;
TRUNCATE `sales_bestsellers_aggregated_monthly`;
TRUNCATE `sales_bestsellers_aggregated_yearly`;
TRUNCATE `sales_billing_agreement`;
TRUNCATE `sales_billing_agreement_order`;
TRUNCATE `sales_flat_creditmemo`;
TRUNCATE `sales_flat_creditmemo_comment`;
TRUNCATE `sales_flat_creditmemo_grid`;
TRUNCATE `sales_flat_creditmemo_item`;
TRUNCATE `sales_flat_invoice`;
TRUNCATE `sales_flat_invoice_comment`;
TRUNCATE `sales_flat_invoice_grid`;
TRUNCATE `sales_flat_invoice_item`;
TRUNCATE `sales_flat_order`;
TRUNCATE `sales_flat_order_address`;
TRUNCATE `sales_flat_order_grid`;
TRUNCATE `sales_flat_order_item`;
TRUNCATE `sales_flat_order_payment`;
TRUNCATE `sales_flat_order_status_history`;
TRUNCATE `sales_flat_quote`;
TRUNCATE `sales_flat_quote_address`;
TRUNCATE `sales_flat_quote_address_item`;
TRUNCATE `sales_flat_quote_item`;
TRUNCATE `sales_flat_quote_item_option`;
TRUNCATE `sales_flat_quote_payment`;
TRUNCATE `sales_flat_quote_shipping_rate`;
TRUNCATE `sales_flat_shipment`;
TRUNCATE `sales_flat_shipment_comment`;
TRUNCATE `sales_flat_shipment_grid`;
TRUNCATE `sales_flat_shipment_item`;
TRUNCATE `sales_flat_shipment_track`;
TRUNCATE `sales_invoiced_aggregated`;
TRUNCATE `sales_invoiced_aggregated_order`;
TRUNCATE `sales_order_aggregated_created`;
TRUNCATE `sales_order_tax`;
TRUNCATE `sales_payment_transaction`;
TRUNCATE `sales_recurring_profile`;
TRUNCATE `sales_recurring_profile_order`;
TRUNCATE `sales_refunded_aggregated`;
TRUNCATE `sales_refunded_aggregated_order`;
TRUNCATE `sales_shipping_aggregated`;
TRUNCATE `sales_shipping_aggregated_order`;

Now we will go to the frontend or our store and add 3 different items to our cart. You can check out the screenshot of the added items.

This simple action of adding 3 items made following entries into the tables:

-- ----------------------------
-- Records of sales_flat_quote
-- ----------------------------
INSERT INTO `sales_flat_quote` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:21:23', null, '1', '0', '0', '3', '10.0000', '0', '1.0000', '1.0000', '1.0000', '1.0000', 'USD', 'USD', 'USD', 'USD', '6196.9900', '6196.9900', null, null, '3', '0', null, null, null, null, null, null, null, null, '1', '0', null, '127.0.0.1', null, null, null, null, '6196.9900', '6196.9900', '6196.9900', '6196.9900', null, '1', '0', null, null, '0');
-- ----------------------------
-- Records of sales_flat_quote_address
-- ----------------------------
INSERT INTO `sales_flat_quote_address` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:21:23', null, '0', null, 'billing', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, '0', '0', '0', null, null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, 'a:0:{}', null, null, null, '0.0000', null, null, null, null, null, null, '0.0000', '0.0000');
INSERT INTO `sales_flat_quote_address` VALUES ('2', '1', '2011-07-26 11:20:34', '2011-07-26 11:21:23', null, '0', null, 'shipping', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, '1', '0', '0', null, null, '29.0000', '6196.9900', '6196.9900', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '6196.9900', '6196.9900', null, 'a:0:{}', null, '0.0000', '0.0000', '6196.9900', null, null, '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000');
-- ----------------------------
-- Records of sales_flat_quote_item
-- ----------------------------
INSERT INTO `sales_flat_quote_item` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:20:34', '17', '1', null, '0', 'bb8100', 'BlackBerry 8100 Pearl', null, null, null, '0', '0', '0', '15.2000', '1.0000', '349.9900', '349.9900', null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '349.9900', '349.9900', '0.0000', '15.2000', 'simple', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', null, '29.9900', '349.9900', '349.9900', '349.9900', '349.9900', null, null);
INSERT INTO `sales_flat_quote_item` VALUES ('2', '1', '2011-07-26 11:20:49', '2011-07-26 11:21:23', '166', '1', null, '0', 'HTC Touch Diamond', 'HTC Touch Diamond', null, null, null, '0', '0', '0', '0.3000', '6.0000', '750.0000', '750.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '4500.0000', '4500.0000', '0.0000', '1.8000', 'simple', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', null, null, '750.0000', '750.0000', '4500.0000', '4500.0000', null, null);
INSERT INTO `sales_flat_quote_item` VALUES ('3', '1', '2011-07-26 11:21:12', '2011-07-26 11:21:12', '44', '1', null, '0', 'Rebel XT', 'Canon Digital Rebel XT 8MP Digital SLR Camera', null, null, null, '0', '0', '0', '4.0000', '3.0000', '449.0000', '449.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '1347.0000', '1347.0000', '0.0000', '12.0000', 'simple', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', null, '0.0000', '0.0000', '0.0000', '0.0000', null, '200.0000', '449.0000', '449.0000', '1347.0000', '1347.0000', null, null);
-- ----------------------------
-- Records of sales_flat_quote_item_option
-- ----------------------------
INSERT INTO `sales_flat_quote_item_option` VALUES ('1', '1', '17', 'info_buyRequest', 'a:4:{s:4:\"uenc\";s:124:\"aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2JsYWNrYmVycnktODEwMC1wZWFybC5odG1s\";s:7:\"product\";s:2:\"17\";s:15:\"related_product\";s:0:\"\";s:3:\"qty\";s:1:\"1\";}');
INSERT INTO `sales_flat_quote_item_option` VALUES ('2', '2', '166', 'info_buyRequest', 'a:4:{s:4:\"uenc\";s:120:\"aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2h0Yy10b3VjaC1kaWFtb25kLmh0bWw,\";s:7:\"product\";s:3:\"166\";s:15:\"related_product\";s:0:\"\";s:3:\"qty\";s:1:\"6\";}');
INSERT INTO `sales_flat_quote_item_option` VALUES ('3', '3', '44', 'info_buyRequest', 'a:4:{s:4:\"uenc\";s:224:\"aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NhbWVyYXMvZGlnaXRhbC1jYW1lcmFzL2Nhbm9uLWRpZ2l0YWwtcmViZWwteHQtOG1wLWRpZ2l0YWwtc2xyLWNhbWVyYS13aXRoLWVmLXMtMTgtNTVtbS1mMy01LTUtNi1sZW5zLWJsYWNrLmh0bWw,\";s:7:\"product\";s:2:\"44\";s:15:\"related_product\";s:0:\"\";s:3:\"qty\";s:1:\"3\";}');

Based on this simple example of 3 “simple” product types we have all those entires in the database. As you can see, all of the entries are done on only “sales_flat_quote_” prefixed tables, some of them, not all. Since we tested against very simple case.

Let us proceed now and do the checkout based on the items we have in the cart. In order to keep the process with minimum data in the system, I will go trough the checkout as “guest” user using “Flat Rate” shipping and “Check / Money order” payment.

Right before we click the “Place Order” button on our one page checkout, additional quote tables get filled. Below is the example that follows the previous state.

-- ----------------------------
-- Records of sales_flat_quote_address
-- ----------------------------
INSERT INTO `sales_flat_quote_address` VALUES ('1', '1', '2011-07-26 11:20:34', '2011-07-26 11:39:27', null, '1', null, 'billing', 'email_address@gmail.com', null, 'Branko', null, 'Ajzele', null, 'Inchoo', 'Sample address line1\nAddress line 2', 'Osijek', null, null, '31431', 'HR', '0038531888777', 'admin', '0', '0', '0', null, null, '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, 'a:0:{}', null, null, null, '0.0000', null, null, null, null, null, null, '0.0000', '0.0000');
INSERT INTO `sales_flat_quote_address` VALUES ('2', '1', '2011-07-26 11:20:34', '2011-07-26 11:39:27', null, '0', null, 'shipping', 'email_address@gmail.com', null, 'Branko', null, 'Ajzele', null, 'Inchoo', 'Sample address line1\nAddress line 2', 'Osijek', null, null, '31431', 'HR', '0038531888777', 'admin', '1', '0', '0', 'flatrate_flatrate', 'Flat Rate - Fixed', '29.0000', '6196.9900', '6196.9900', '0.0000', '0.0000', '0.0000', '0.0000', '50.0000', '50.0000', '0.0000', '0.0000', '0.0000', '0.0000', '6246.9900', '6246.9900', null, 'a:0:{}', null, '0.0000', '0.0000', '6196.9900', null, null, '0.0000', '0.0000', '0.0000', null, '50.0000', '50.0000');

As you can see, our sales_flat_quote_address table got it’s records updated with new values. In previous (first) one we had no values for “email” or other columns, but now we do once we are passed the checkout steps all the way to final “Place Order” button.

-- ----------------------------
-- Records of sales_flat_quote_payment
-- ----------------------------
INSERT INTO `sales_flat_quote_payment` VALUES ('1', '1', '2011-07-26 11:29:36', '2011-07-26 11:39:27', 'checkmo', null, null, null, null, null, '0', '0', null, '0', '0', null, null, null, null, null, null, null, null, null, null);
-- ----------------------------
-- Records of sales_flat_quote_shipping_rate
-- ----------------------------
INSERT INTO `sales_flat_quote_shipping_rate` VALUES ('2', '2', '2011-07-26 11:39:01', '2011-07-26 11:39:27', 'flatrate', 'Flat Rate', 'flatrate_flatrate', 'flatrate', null, '50.0000', 'Fixed', null);

So basically, all the way prior to finally clicking the “Place Order” button we have our data stored in “quote” tables. Meaning, “Quote” model is “data storage” from which the “Order” model is actually created. Or by more fancy definition, “Quote” is a formal offer for products or services proposed at specific prices and related payment terms tied to a given customer.

After we hit the “Place Order” button we get our order created. In that regards to previously said about the quote, “Order” is a quote that has been accepted.

So after the order has been successfully created, there are several new entries in the “sales_flat_order_” prefixed tables. Let’s take a look at those.

-- ----------------------------
-- Records of sales_flat_order
-- ----------------------------
INSERT INTO `sales_flat_order` VALUES ('1', 'new', 'pending', null, 'b71db3', 'Flat Rate - Fixed', '0', '1', null, '0.0000', null, null, null, '6246.9900', '50.0000', null, null, null, '0.0000', null, '6196.9900', null, null, null, '0.0000', null, null, null, '1.0000', '1.0000', null, null, null, null, null, null, null, null, '0.0000', null, null, null, '6246.9900', '50.0000', null, null, null, '0.0000', null, '1.0000', '1.0000', '6196.9900', null, null, null, '0.0000', null, null, null, null, null, null, null, null, '10.0000', null, null, null, '1', '1', '1', '0', null, '1', null, null, null, null, null, '1', '2', null, null, null, null, '0.0000', '6196.9900', null, null, '0.0000', '6196.9900', null, '29.0000', null, '100000001', null, 'USD', 'email_address@gmail.com', 'Branko', 'Ajzele', null, null, null, null, null, null, null, 'USD', null, null, 'USD', null, null, null, null, null, '127.0.0.1', 'flatrate_flatrate', 'USD', 'Main Website\nMain Store\nEnglish', null, null, '2011-07-26 11:39:27', '2011-07-26 11:39:31', '3', null, null, null, null, null, null, null, null, null, null, null, '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, null, '50.0000', '50.0000');
-- ----------------------------
-- Records of sales_flat_order_address
-- ----------------------------
INSERT INTO `sales_flat_order_address` VALUES ('1', '1', null, null, null, null, 'admin', null, '31431', 'Ajzele', 'Sample address line1\nAddress line 2', 'Osijek', 'email_address@gmail.com', '0038531888777', 'HR', 'Branko', 'billing', null, null, null, 'Inchoo', null, null, null);
INSERT INTO `sales_flat_order_address` VALUES ('2', '1', null, null, null, null, 'admin', null, '31431', 'Ajzele', 'Sample address line1\nAddress line 2', 'Osijek', 'email_address@gmail.com', '0038531888777', 'HR', 'Branko', 'shipping', null, null, null, 'Inchoo', null, null, null);
-- ----------------------------
-- Records of sales_flat_order_grid
-- ----------------------------
INSERT INTO `sales_flat_order_grid` VALUES ('1', 'pending', '1', 'Main Website\nMain Store\nEnglish', null, '6246.9900', null, '6246.9900', null, '100000001', 'USD', 'USD', 'Branko Ajzele', 'Branko Ajzele', '2011-07-26 11:39:27', '2011-07-26 11:39:31');
-- ----------------------------
-- Records of sales_flat_order_item
-- ----------------------------
INSERT INTO `sales_flat_order_item` VALUES ('1', '1', null, '1', '1', '2011-07-26 11:39:27', '2011-07-26 11:39:27', '17', 'simple', 'a:1:{s:15:\"info_buyRequest\";a:4:{s:4:\"uenc\";s:124:\"aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2JsYWNrYmVycnktODEwMC1wZWFybC5odG1s\";s:7:\"product\";s:2:\"17\";s:15:\"related_product\";s:0:\"\";s:3:\"qty\";s:1:\"1\";}}', '15.2000', '0', 'bb8100', 'BlackBerry 8100 Pearl', null, null, null, '0', '0', '0', null, '0.0000', '0.0000', '1.0000', '0.0000', '0.0000', '29.9900', '349.9900', '349.9900', '349.9900', '349.9900', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '349.9900', '349.9900', '0.0000', '0.0000', '15.2000', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, '349.9900', '349.9900', '349.9900', '349.9900', null, null, null, null, null, null, '0', null, null, null);
INSERT INTO `sales_flat_order_item` VALUES ('2', '1', null, '2', '1', '2011-07-26 11:39:29', '2011-07-26 11:39:29', '166', 'simple', 'a:1:{s:15:\"info_buyRequest\";a:4:{s:4:\"uenc\";s:120:\"aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NlbGwtcGhvbmVzL2h0Yy10b3VjaC1kaWFtb25kLmh0bWw,\";s:7:\"product\";s:3:\"166\";s:15:\"related_product\";s:0:\"\";s:3:\"qty\";s:1:\"6\";}}', '0.3000', '0', 'HTC Touch Diamond', 'HTC Touch Diamond', null, null, null, '0', '0', '0', null, '0.0000', '0.0000', '6.0000', '0.0000', '0.0000', null, '750.0000', '750.0000', '750.0000', '750.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '4500.0000', '4500.0000', '0.0000', '0.0000', '1.8000', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, '750.0000', '750.0000', '4500.0000', '4500.0000', null, null, null, null, null, null, '0', null, null, null);
INSERT INTO `sales_flat_order_item` VALUES ('3', '1', null, '3', '1', '2011-07-26 11:39:30', '2011-07-26 11:39:30', '44', 'simple', 'a:1:{s:15:\"info_buyRequest\";a:4:{s:4:\"uenc\";s:224:\"aHR0cDovL21hZ2VudG8tMS42LjAuMC1yYzIubG9jYWwvaW5kZXgucGhwL2VsZWN0cm9uaWNzL2NhbWVyYXMvZGlnaXRhbC1jYW1lcmFzL2Nhbm9uLWRpZ2l0YWwtcmViZWwteHQtOG1wLWRpZ2l0YWwtc2xyLWNhbWVyYS13aXRoLWVmLXMtMTgtNTVtbS1mMy01LTUtNi1sZW5zLWJsYWNrLmh0bWw,\";s:7:\"product\";s:2:\"44\";s:15:\"related_product\";s:0:\"\";s:3:\"qty\";s:1:\"3\";}}', '4.0000', '0', 'Rebel XT', 'Canon Digital Rebel XT 8MP Digital SLR Camera', null, null, null, '0', '0', '0', null, '0.0000', '0.0000', '3.0000', '0.0000', '0.0000', '200.0000', '449.0000', '449.0000', '449.0000', '449.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '1347.0000', '1347.0000', '0.0000', '0.0000', '12.0000', null, null, null, null, 'a:0:{}', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', '0.0000', null, null, null, '449.0000', '449.0000', '1347.0000', '1347.0000', null, null, null, null, null, null, '0', null, null, null);
-- ----------------------------
-- Records of sales_flat_order_payment
-- ----------------------------
INSERT INTO `sales_flat_order_payment` VALUES ('1', '1', null, null, null, null, null, null, null, null, '50.0000', '50.0000', null, null, '6246.9900', null, null, null, '6246.9900', null, null, null, null, '0', '0', null, 'checkmo', null, null, null, null, null, null, null, null, null, null, null, '0', null, null, null, null, null, null, null, '0', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
-- ----------------------------
-- Records of sales_flat_order_status_history
-- ----------------------------
INSERT INTO `sales_flat_order_status_history` VALUES ('1', '1', '1', '0', null, 'pending', '2011-07-26 11:39:31', 'order');

Now that we have our real order it is logical to make the final step and bill it / charge it. Usually if you are using PayPal or Authorize.net or some other fancy payment gateway this process of charging is done automatically. Once charged order changes it’s state from “new” or “pending” to “processing”. Which leads us to the next model in this workflow called “Invoice”. It’s easy to conclude that “Invoice” is by definition an order that has been billed. This “billed” data is then saved in invoice related/named tables. It’s easy to find those in Magento database as table names start with the “sales_flat_invoice” prefix.

Once we click the “Invoice” button on the Magento order view page in the admin, these invoice tables get populated with relevant data. Before I dump the SQL queries that filled my invoice table, its important to note that I invoiced entire order, meaning all items at once. General concept of Magento allows you to create several invoices for one order, meaning you can invoice partial quantities of ordered items or individual items only per single invoice.

-- ----------------------------
-- Records of sales_flat_invoice
-- ----------------------------
INSERT INTO `sales_flat_invoice` VALUES ('1', '1', '6246.9900', '0.0000', '0.0000', '0.0000', '1.0000', '0.0000', '0.0000', '1.0000', '6246.9900', '50.0000', '6196.9900', '6196.9900', '1.0000', '50.0000', '10.0000', '1.0000', '6196.9900', '6196.9900', '0.0000', '1', null, '1', null, '0', '2', '2', null, 'USD', null, 'USD', 'USD', 'USD', '100000001', '2011-07-26 12:09:56', '2011-07-26 12:09:56', null, null, null, null, null, null, null, '0.0000', '0.0000', '0.0000', null, '50.0000', '50.0000', null);
-- ----------------------------
-- Records of sales_flat_invoice_grid
-- ----------------------------
INSERT INTO `sales_flat_invoice_grid` VALUES ('1', '1', '6246.9900', '6246.9900', '1', '2', 'USD', 'USD', 'USD', 'USD', '100000001', '100000001', '2011-07-26 12:09:56', '2011-07-26 11:39:27', 'Branko Ajzele');
-- ----------------------------
-- Records of sales_flat_invoice_item
-- ----------------------------
INSERT INTO `sales_flat_invoice_item` VALUES ('1', '1', '349.9900', '0.0000', '0.0000', '0.0000', '0.0000', '349.9900', null, '349.9900', '0.0000', null, '0.0000', '349.9900', '0.0000', '0.0000', '349.9900', '1.0000', '0.0000', '29.9900', '0.0000', '349.9900', '349.9900', '349.9900', '17', '1', null, null, 'a:0:{}', 'bb8100', 'BlackBerry 8100 Pearl', null, '0.0000', '0.0000');
INSERT INTO `sales_flat_invoice_item` VALUES ('2', '1', '750.0000', '0.0000', '0.0000', '0.0000', '0.0000', '4500.0000', null, '4500.0000', '0.0000', null, '0.0000', '750.0000', '0.0000', '0.0000', '750.0000', '6.0000', '0.0000', null, '0.0000', '750.0000', '4500.0000', '4500.0000', '166', '2', null, null, 'a:0:{}', 'HTC Touch Diamond', 'HTC Touch Diamond', null, '0.0000', '0.0000');
INSERT INTO `sales_flat_invoice_item` VALUES ('3', '1', '449.0000', '0.0000', '0.0000', '0.0000', '0.0000', '1347.0000', null, '1347.0000', '0.0000', null, '0.0000', '449.0000', '0.0000', '0.0000', '449.0000', '3.0000', '0.0000', '200.0000', '0.0000', '449.0000', '1347.0000', '1347.0000', '44', '3', null, null, 'a:0:{}', 'Rebel XT', 'Canon Digital Rebel XT 8MP Digital SLR Camera', null, '0.0000', '0.0000');

Basically this circles the simplest story around the order creation process. Meaning there are 3 concepts/terms to remember here: Quote, Order, Invoice.

There is also a Shipment, but we will leave this for another time 🙂

As a developer you should have a pretty good understanding of “Quote, Order, Invoice” relation in Magento, especcially if you are doing any custom coding around it. Coding such as “custom order statuses”, “change of order states based on some info retrieved from external web service”, etc.

So now that all this “theory” is behind us, lets try to programmatically create order order and invoice it. Below is the code that does just that for the simplest possible case of using just “simple” product types.

<?php
 
require_once 'app/Mage.php';
 
Mage::app('default');
 
$store = Mage::app()->getStore('default');
 
$customer = Mage::getModel('customer/customer');
$customer->setStore($store);
$customer->loadByEmail('email_address@gmail.com');
 
$quote = Mage::getModel('sales/quote');
$quote->setStore($store);
$quote->assignCustomer($customer);
 
$product1 = Mage::getModel('catalog/product')->load(166); /* HTC Touch Diamond */
$buyInfo1 = array('qty' => 1);
 
$product2 = Mage::getModel('catalog/product')->load(18); /* Sony Ericsson W810i */
$buyInfo2 = array('qty' => 3);
 
$quote->addProduct($product1, new Varien_Object($buyInfo1));
$quote->addProduct($product2, new Varien_Object($buyInfo2));
 
$billingAddress = $quote->getBillingAddress()->addData($customer->getPrimaryBillingAddress());
$shippingAddress = $quote->getShippingAddress()->addData($customer->getPrimaryShippingAddress());
 
$shippingAddress->setCollectShippingRates(true)->collectShippingRates()
                ->setShippingMethod('flatrate_flatrate')
                ->setPaymentMethod('checkmo');
 
$quote->getPayment()->importData(array('method' => 'checkmo'));
 
$quote->collectTotals()->save();
 
$service = Mage::getModel('sales/service_quote', $quote);
$service->submitAll();
$order = $service->getOrder();
 
$invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice();
$invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE);
$invoice->register();
 
$transaction = Mage::getModel('core/resource_transaction')
                    ->addObject($invoice)
                    ->addObject($invoice->getOrder());
 
$transaction->save();

I realize there are lot of things left unexplained in the code above, such as “where does the $service come from”, “why are we using $transaction resource for invoice creation”, etc. For example, if in the above code I where to code a order creation process for “grouped” or “configurable” product type, possibly for product with custom options, my code would be far more complex. Not to mention custom/new product types created by you or some other third party extension. There is no unique formula to cover all, especially custom product types. All I can say, is that in order to get a true understanding of the process in the background you really need to trace the code thoroughly for specific case.

Hope this article was, to some extent, useful.

Cheers.


37 comments

  1. after completing the order is there need of creating again quote id for next order.Or we can use previous quote id?

  2. Thank you for the article.

    Might I suggest a small edit, and that is to include `log_quote` in the list for truncation. Without truncating this table as well, if there are already past records then saving of a new quote causes exceptions to be thrown, as Magento will attempt to insert pre-existing IDs.

  3. hey hi i gone through you code its nice im working on some order system i mean i want to create a suborder number for each item so the code which you have written how to use this in module??

  4. Dear Branko,
    i came across a problem, that i have a record in sales_flat_quote_payment table but not in sales_flat_order.
    so there is no record in magento backend, neither about this order, nor about this customer…
    do you have an idea, what could happen?

    1. If you happened to have a customer checking out using paypal, there is an interesting situation where after the customer returns from paypal’s offsite process, and then clicks place order…. and paypal is unavailable at that very instant, you will see this situation happen. I have a ticket open with magento right now.

  5. Hi and thanks for that article.
    I followed that to add a downloadable product to the cart.
    The problem is that probable that’s not the right way to do it. It appears on the orders,with price and everything, but not the liniks.I use url link for the downloadable product. Also if I create the order programmatically and invoice it after manually the links are available.If I invoice it programmatically the links again are not available.
    I would be glad if someone could explain what is going on.Thanks

    $params = array();
    $links = Mage::getModel('downloadable/product_type')->getLinks( $product );
    $linkId = 0;
    foreach ($links as $link) {
        $linkId = $link->getId();
    }
    
    $params['product'] = $product->getId();
    $params['qty'] = "1";
    $params['related_product'] = "";
    $params['links'] = array($linkId);
    $request = new Varien_Object();
    $request->setData($params);
  6. just been working with a magento extension that handles the quote-to-order workflow in a great way, just thought I’d mention it here, might be of help. it’s the Cart2Quote extension. there’s a free version but also some extended versions with some more options.
    check it out on http://www.cart2quote.com

    a happy customer 😉

  7. I have used this code and created successfully invoiced but in invoice i also want to show discount amount.I have done it with setDiscountAmount() function with quote object .it never gives any error but Main problem is that discount amount not set it always shows zero amount in invoice.please help

  8. do you have a visual of this process as an actual process or workflow would really facilitate communications with our client

  9. I had to change the two lines below in order for this script to work;

    $billingAddress = $quote->getBillingAddress()->addData($customer->getPrimaryBillingAddress());
    $shippingAddress = $quote->getShippingAddress()->addData($customer->getPrimaryShippingAddress());

    had to change the addData to setData

  10. How to get all product list at order creation time from backend. I am creating orderer from backend i need list of all products

  11. Very good post!! Thanks for that.

    But I have an another question:
    – How could I make an order split? I’ve tried to modify the quote _saveOrder method but the quote seams a kind of unic instance and every change it’s applied over the two order results. I would need to get two orders with the result of split the main order, but I allways get a couple of orders with the same products.

    I need:
    In -> 1 order with 5 products
    Out -> 2 orders with 2 products and 3 products, respectively and obviously two Order Ids.

    My actual result:
    In -> 1 order with 5 products
    Out -> 2 orders with same 2 products, and two Order Ids.

    Could you help me with some advice or indication? If you want I can show my code and explain better my problem.

    Thank you very much

  12. Hello,
    Thank you for this interesting posts.
    I have a problem , my magento 1.5 works nice, but sometimes for one order many invoices are created, is there any way to fix it or to force magento to create only one invoice for every order.
    thank you in advance for help.

    Best wishes!

  13. @Robert,

    This is off the topic, but
    I think, that I have good news for you.

    1 ) Create clean / empty DB ( no tables )
    2 ) Import sql dump from 1.4 over the clean DB
    3 ) Install Magento 1.7 and when the installation wizard asks you for DB details ( db name, user, pass ) set the details for the DB where you’ve imported the DB from Magento 1.4
    4 ) Cross fingers and pray

    Magento 1.7 will automatically upgrade the old DB 1.4 schema to 1.7 DB schema. This is big jump from version 1.4 to 1.7 so if it doesn’t work well you can try to upgrade with small steps 1.4.0.0 -> 1.4.0.1 -> 1.4.1.0 …. -> 1.6.2.0 -> 1.7.0.0

    Good Luck, Enjoy it!

  14. Hello,

    I have a big problem with my magento, hope someone could help me.
    Before I deleted my magento 1.4 I backed up the magento folder, and exported sql dump file from the database.
    Now I have a fresh new magento 1.7 with no products. How do I transfer the products from the old one? apart from the media folder, what tables should I import, or what should I do exactly? I don’t have a magento product export file, but I have a backup of the database (sql) and the old magento folder untouched.

    Thanks very much!
    Robert

  15. When I create an order I want all shopping cart and catalog rules to be applied. I tried to assign a coupon code which gives me 10% discount and it is applied correctly just before the collectTotals() call.

    $quote->setDiscountCode('somecode');

    But if I have a rule without code like for example free shipping if over subtotal is over 50$, then the total amount is not reduced and the amounts are wrong.
    Any ideas/suggestions are highly welcome.
    Thanks in advance

  16. Ref: To my prev comment => my bad, the problem was from my magento install 🙂 the code is working fine for the grid table also 🙂

  17. Hi -> quite nice and useful article,

    I have a question…

    Does this code normally add a record in the sales_flat_order_grid table also?

    // Because it does not do that for me… i.e. it adds the records in the other sales_flat_order_… tables but not in the grid one

    Can I add only a record to the sales_flat_order_grid table afterwards and how?

  18. Hello, Thanks for this article.
    Someone can tell me how do I submit email confirmation with invoice.
    I need to add a method in the part to invoice or in transaction. I don’t know, help me please.

    Thanks,
    Harold

  19. Just to add, what I meant by $service->submitAll(); works great, was the code executes without error, but the order is not saved.

  20. Great Article, it answered a lot of questions for me. However when I have been trying to recreate the example on my magento installation I seem to crash at

    $invoice = Mage::getModel(‘sales/service_order’, $order)->prepareInvoice();

    I am using m 1.4 however is this code 1.5 specific? If so how would you adapt this example to work in 1.4?

    Also do you need to include the $invoice and $transaction part of the example in order to save the order?

    In my example http://pastebin.com/YfCdJhm3 everything up to and including the $service->submitAll(); works great, and I can confirm with the help of a debugger that $service does have the quote object within it. Am I correct in assuming that submitAll() is supposed to save the order?

    Any help or pointers on this would be much appreciated.

    Thanks

    Chris

  21. I enjoyed this article. I think I will investigate the differences between configurable and grouped products in order creation.

    One thing that would be interesting given this is to discuss ways of hooking into the quote/order/invoice process. Events such as ‘checkout_quote_init’ or ‘checkout_cart_product_add_after’

    Thanks
    Tim

  22. Many thanks for such a beautiful, required & detailed article on this Order / Invoice / Quote trilogy.

    Really you saved many lives of novice developers.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.