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 line1nAddress 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 line1nAddress 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 WebsitenMain StorenEnglish', 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 line1nAddress 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 line1nAddress 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 WebsitenMain StorenEnglish', 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.