示例#1
0
/**
 * Update the status of an order.
 *
 * @param mixed Either an array of order IDs to update, or an integer for a single order ID.
 * @param int The new status of the order.
 * @param boolean Should emails be sent out if the email on status change feature is enabled?
 * @param boolean Set to true if this status update is in a pingback from a payment module and payment modules should not be notified of the change.
 * @return boolean True if successful.
 */
function UpdateOrderStatus($orderIds, $status, $email=true, $preventModuleUpdateCallback=false)
{
	if(!is_array($orderIds)) {
		$orderIds = array($orderIds);
	}

	foreach($orderIds as $orderId) {
		$order = GetOrder($orderId, false);

		if (!$order || !$order['orderid']) {
			return false;
		}

		// Start transaction
		$GLOBALS['ISC_CLASS_DB']->Query("START TRANSACTION");

		$existing_status = $order['ordstatus'];

		// If the order is incomplete, it needs to be completed first
		if($existing_status == 0) {
			CompletePendingOrder($order['ordtoken'], $status, $email);
		}

		$updatedOrder = array(
			"ordstatus" => (int)$status,
			"ordlastmodified" => time(),
		);

		// If the order status is 2 or 10 (completed, shipped) then set the orddateshipped timestamp
		if (OrderIsComplete($status)) {
			$updatedOrder['orddateshipped'] = time();
		}

		// Update the status for this order
		if ($GLOBALS['ISC_CLASS_DB']->UpdateQuery("orders", $updatedOrder, "orderid=" . (int)$orderId)) {
			// Fetch the name of the status this order was changed to
			$query = sprintf("SELECT statusdesc FROM [|PREFIX|]order_status WHERE statusid='%d'", $GLOBALS['ISC_CLASS_DB']->Quote($status));
			$result2 = $GLOBALS['ISC_CLASS_DB']->Query($query);
			$statusName = $GLOBALS['ISC_CLASS_DB']->FetchOne($result2);

			// Log this action if we are in the control panel
			if (defined('ISC_ADMIN_CP')) {
				$GLOBALS['ISC_CLASS_LOG']->LogAdminAction($orderId, $statusName);
			}

			// This order was marked as refunded or cancelled
			if ($status == ORDER_STATUS_REFUNDED || $status == ORDER_STATUS_CANCELLED) {
				// If the inventory levels for products in this order have previously been changed, we need to
				// return the inventory too
				if ($order['ordinventoryupdated'] == 1) {
					UpdateInventoryOnReturn($orderId);
				}

				// Marked as refunded or cancelled, need to cancel the gift certificates in this order too if there are any
				$updatedCertificates = array(
					"giftcertstatus" => 3
				);
				$GLOBALS['ISC_CLASS_DB']->UpdateQuery("gift_certificates", $updatedCertificates, "giftcertorderid='" . $GLOBALS['ISC_CLASS_DB']->Quote($orderId) . "'");
			}
			// This order was marked as completed/shipped as long as the inventory hasn't been adjusted previously
			else if (OrderIsComplete($status)) {
				if ($order['ordinventoryupdated'] == 0) {
					DecreaseInventoryFromOrder($orderId);
				}

				// Send out gift certificates if the order wasn't already complete
				if (!OrderIsComplete($existing_status)) {
					$GLOBALS['ISC_CLASS_GIFT_CERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES');
					$GLOBALS['ISC_CLASS_GIFT_CERTIFICATES']->ActivateGiftCertificates($orderId);
				}
			}
		}

		// Was there an error? If not, commit
		if ($GLOBALS['ISC_CLASS_DB']->Error() == "") {
			$GLOBALS['ISC_CLASS_DB']->Query("COMMIT");

			// Does the customer now need to be notified for this status change?
			$statuses = explode(",", GetConfig('OrderStatusNotifications'));
			if (in_array($status, $statuses) && $email == true) {
				foreach($orderIds as $orderId) {
					EmailOnStatusChange($orderId, $status);
				}
			}

			// If the checkout module that was used for an order is still enabled and has a function
			// to handle a status change, then call that function
			if($preventModuleUpdateCallback == false) {
				$valid_checkout_modules = GetAvailableModules('checkout', true, true);
				$valid_checkout_module_ids = array();
				foreach ($valid_checkout_modules as $valid_module) {
					$valid_checkout_module_ids[] = $valid_module['id'];
				}

				foreach($orderIds as $orderId) {
					$order = GetOrder($orderId, false);

					if (in_array($order['orderpaymentmodule'], $valid_checkout_module_ids)) {
						GetModuleById('checkout', $checkout_module, $order['orderpaymentmodule']);
						if (method_exists($checkout_module, 'HandleStatusChange')) {
							call_user_func(array($checkout_module, 'HandleStatusChange'), $orderId, $existing_status, $status, 0);
						}
					}
				}
			}

			return true;
		}
		else {
			return false;
		}
	}

	return false;
}
示例#2
0
 public function UpdateReturnStatus(&$return, $status, $crediting = false)
 {
     // Start a transaction
     $GLOBALS['ISC_CLASS_DB']->Query("START TRANSACTION");
     if ($status == 5 && $return['retstatus'] != 5) {
         // Changing the status of this return to "Refunded", so we need to perform some additional things
         $refundAmount = $return['retprodcost'] * $return['retprodqty'];
         // Grab the order if it still exists to provide a refund on the tax as well
         $order = GetOrder($return['retorderid']);
         if ($order['ordtotalincludestax'] == 0 && $order['ordtaxrate'] > 0) {
             $taxCharged = number_format($refundAmount / 100 * $order['ordtaxrate'], GetConfig('DecimalPlaces'), '.', '');
             $refundAmount += $taxCharged;
         }
         $updatedProduct = array("ordprodrefundamount" => $return['retprodcost'], "ordprodrefunded" => $return['retprodqty'], "ordprodreturnid" => $return['returnid']);
         $GLOBALS['ISC_CLASS_DB']->UpdateQuery("order_products", $updatedProduct, "orderprodid='" . $GLOBALS['ISC_CLASS_DB']->Quote($return['retordprodid']) . "'");
         // Fetch the total for this order
         $query = sprintf("SELECT ordsubtotal, ordtotalamount FROM [|PREFIX|]orders WHERE orderid='%s'", $return['retorderid']);
         $result = $GLOBALS['ISC_CLASS_DB']->Query($query);
         $orderTotal = $GLOBALS['ISC_CLASS_DB']->Fetch($result);
         // Reduce the order total by retprodcost x retprodqty (the price we paid x the quantity being returned)
         $orderSubTotal = $orderTotal['ordsubtotal'] - $refundAmount;
         if ($orderSubTotal <= 0) {
             $orderSubTotal = 0;
         }
         $orderTotalAmount = $orderTotal['ordtotalamount'] - $orderTotal['ordsubtotal'] + $orderSubTotal;
         if ($orderTotalAmount <= 0) {
             $orderTotalAmount = 0;
         }
         $updatedOrder = array("ordsubtotal" => $orderSubTotal, "ordtotalamount" => $orderTotalAmount);
         // If the amount of tax has changed, need to update that total too
         if (isset($taxCharged)) {
             $updatedOrder['ordtaxtotal'] = $order['ordtaxtotal'] - $taxCharged;
         } else {
             if ($order['ordtotalincludestax']) {
                 $taxCharged = $refundAmount / (100 + $order['ordtaxrate']) * $order['ordtaxrate'];
                 $taxCharged = number_format($taxCharged, GetConfig('DecimalPlaces'), '.', '');
                 $updatedOrder['ordtaxtotal'] = $order['ordtaxtotal'] - $taxCharged;
             }
         }
         if ($orderTotalAmount == 0) {
             $updatedOrder['ordtaxtotal'] = 0;
         }
         // Have all items in this order been refunded? Mark the order as refunded.
         $query = sprintf("SELECT SUM(ordprodqty-ordprodrefunded) FROM [|PREFIX|]order_products WHERE orderorderid=%d", $return['retorderid']);
         $result = $GLOBALS['ISC_CLASS_DB']->Query($query);
         $remainingItems = $GLOBALS['ISC_CLASS_DB']->FetchOne($result);
         if ($remainingItems == 0) {
             $updatedOrder['ordstatus'] = 4;
         }
         $GLOBALS['ISC_CLASS_DB']->UpdateQuery("orders", $updatedOrder, "orderid='" . $GLOBALS['ISC_CLASS_DB']->Quote($return['retorderid']) . "'");
         // Update the status of this return
         $updatedReturn = array("retstatus" => 5, "retuserid" => $GLOBALS['ISC_CLASS_ADMIN_AUTH']->GetUserId());
         $GLOBALS['ISC_CLASS_DB']->UpdateQuery("returns", $updatedReturn, "returnid='" . $GLOBALS['ISC_CLASS_DB']->Quote($return['returnid']) . "'");
         // Update the product inventory for this returned item
         $query = sprintf("SELECT * FROM [|PREFIX|]order_products WHERE ordprodid='%d'", $return['retordprodid']);
         $result = $GLOBALS['ISC_CLASS_DB']->Query($query);
         $row = $GLOBALS['ISC_CLASS_DB']->Fetch($result);
         UpdateInventoryOnReturn($return['retordprodid']);
         // dont send a refund through the checkout module if a store credit was issued
         if (!$crediting) {
             // If the checkout module that was used for an order is still enabled and has a function
             // to handle a status change, then call that function
             $valid_checkout_modules = GetAvailableModules('checkout', true, true);
             $valid_checkout_module_ids = array();
             foreach ($valid_checkout_modules as $valid_module) {
                 $valid_checkout_module_ids[] = $valid_module['id'];
             }
             $newStatus = $order['ordstatus'];
             if (isset($updatedOrder['ordstatus'])) {
                 $newStatus = $updatedOrder['ordstatus'];
             }
             // attempt to refund this amount with the checkout provider
             $order = GetOrder($return['retorderid'], false);
             if (in_array($order['orderpaymentmodule'], $valid_checkout_module_ids)) {
                 GetModuleById('checkout', $checkout_module, $order['orderpaymentmodule']);
                 if (method_exists($checkout_module, 'HandleStatusChange')) {
                     call_user_func(array($checkout_module, 'HandleStatusChange'), $return['retorderid'], $order['ordstatus'], $newStatus, $refundAmount);
                 }
             }
         }
     } else {
         // Update the status of this return
         $updatedReturn = array("retstatus" => $status);
         $GLOBALS['ISC_CLASS_DB']->UpdateQuery("returns", $updatedReturn, "returnid='" . $GLOBALS['ISC_CLASS_DB']->Quote($return['returnid']) . "'");
     }
     $return['retstatus'] = $status;
     if (GetConfig('NotifyOnReturnStatusChange') == 1) {
         $this->EmailReturnStatusChange($return);
     }
     if ($GLOBALS['ISC_CLASS_DB']->GetErrorMsg() == "") {
         $GLOBALS['ISC_CLASS_DB']->Query("COMMIT");
         return true;
     } else {
         return false;
     }
 }
示例#3
0
	public function UpdateReturnStatus(&$return, $status, $crediting = false)
	{

		// Start a transaction
		$GLOBALS['ISC_CLASS_DB']->Query("START TRANSACTION");

		// Changing the status of this return to "Refunded", so we need to perform some additional things
		if($status == 5 && $return['retstatus'] != 5) {
			$refundAmount = $return['retprodcost'] * $return['retprodqty'];
			$updatedProduct = array(
				"ordprodrefundamount" => $return['retprodcost'],
				"ordprodrefunded" => $return['retprodqty'],
				"ordprodreturnid" => $return['returnid']
			);

			$order = getOrder($return['retorderid']);
			if (!$order) {
				return false;
			}

			$GLOBALS['ISC_CLASS_DB']->UpdateQuery("order_products", $updatedProduct, "orderprodid='".$GLOBALS['ISC_CLASS_DB']->Quote($return['retordprodid'])."'");

			$query = "
				UPDATE [|PREFIX|]orders
				SET ordrefundedamount = ordrefundedamount + ".$refundAmount."
				WHERE orderid='".$return['retorderid']."'
			";
			$this->db->query($query);

			// Have all items in this order been refunded? Mark the order as refunded.
			$query = sprintf("SELECT SUM(ordprodqty-ordprodrefunded) FROM [|PREFIX|]order_products WHERE orderorderid=%d", $return['retorderid']);
			$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
			$remainingItems = $GLOBALS['ISC_CLASS_DB']->FetchOne($result);
			if($remainingItems == 0) {
				$updatedOrder = array(
					'ordstatus' => 4
				);
				$GLOBALS['ISC_CLASS_DB']->UpdateQuery("orders", $updatedOrder, "orderid='".$GLOBALS['ISC_CLASS_DB']->Quote($return['retorderid'])."'");
			}

			// Update the status of this return
			$updatedReturn = array(
				"retstatus" => 5,
				"retuserid" => $GLOBALS['ISC_CLASS_ADMIN_AUTH']->GetUserId()
			);
			$GLOBALS['ISC_CLASS_DB']->UpdateQuery("returns", $updatedReturn, "returnid='".$GLOBALS['ISC_CLASS_DB']->Quote($return['returnid'])."'");

			// Update the product inventory for this returned item
			$query = sprintf("SELECT * FROM [|PREFIX|]order_products WHERE ordprodid='%d'", $return['retordprodid']);
			$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
			$row = $GLOBALS['ISC_CLASS_DB']->Fetch($result);

			UpdateInventoryOnReturn($return['retordprodid']);

			// dont send a refund through the checkout module if a store credit was issued
			if (!$crediting) {
				// If the checkout module that was used for an order is still enabled and has a function
				// to handle a status change, then call that function
				$valid_checkout_modules = GetAvailableModules('checkout', true, true);
				$valid_checkout_module_ids = array();
				foreach ($valid_checkout_modules as $valid_module) {
					$valid_checkout_module_ids[] = $valid_module['id'];
				}

				$newStatus = $order['ordstatus'];
				if (isset($updatedOrder['ordstatus'])) {
					$newStatus = $updatedOrder['ordstatus'];
				}

				// attempt to refund this amount with the checkout provider
				$order = GetOrder($return['retorderid'], false);
				if (in_array($order['orderpaymentmodule'], $valid_checkout_module_ids)) {
					GetModuleById('checkout', $checkout_module, $order['orderpaymentmodule']);
					if (method_exists($checkout_module, 'HandleStatusChange')) {
						call_user_func(array($checkout_module, 'HandleStatusChange'), $return['retorderid'], $order['ordstatus'], $newStatus, $refundAmount);
					}
				}
			}
		}
		else {
			// Update the status of this return
			$updatedReturn = array(
				"retstatus" => $status
			);
			$GLOBALS['ISC_CLASS_DB']->UpdateQuery("returns", $updatedReturn, "returnid='".$GLOBALS['ISC_CLASS_DB']->Quote($return['returnid'])."'");
		}

		$return['retstatus'] = $status;

		if(GetConfig('NotifyOnReturnStatusChange') == 1) {
			$this->EmailReturnStatusChange($return);
		}

		if($GLOBALS['ISC_CLASS_DB']->GetErrorMsg() == "") {
			$GLOBALS['ISC_CLASS_DB']->Query("COMMIT");
			return true;
		}
		else {
			return false;
		}
	}
示例#4
0
	/**
	 * Insert/Update the node with the response record
	 *
	 * Method will insert/update the node with the response record
	 *
	 * @access protected
	 * @param array $responseData The reponse data from QB
	 * @param array $nodeData The optional node data array. If set then update, else insert
	 * @return int The new or updtaed node ID on success, FALSE on error
	 */
	protected function syncResponseRecord2Store($responseData, $nodeData=false)
	{
		if (!is_array($responseData)) {
			$xargs = func_get_args();
			throw new QBException("Invalid arguments when syncing order record from QB", $xargs);
		}

		/**
		 * Get our stored customer ID if we have one
		 */
		$customerId = $this->getCustomer4Order($responseData["TxnID"]);

		/**
		 * Get the customer ListID and find the matching customer ID if we can. We need to have a customer ListID
		 */
		if (is_null($customerId)) {
			if (!isset($responseData["CustomerRef"]["ListID"])) {
				throw new QBException("Unable to find customer ListID when syncing orders", $responseData);
			}

			$customerRef = $this->accounting->getReference("customer", '', $responseData["CustomerRef"]["ListID"], '', false);

			/**
			 * If we don't have a reference then use the existing customer ID if we have one
			 */
			if (!is_array($customerRef)) {

				if (is_array($nodeData) && array_key_exists("ordcustid", $nodeData) && isId($nodeData["ordcustid"])) {
					$customerId = $nodeData["ordcustid"];

				/**
				 * Else if we do have a nodeData but no customer ID then its a guest checkout
				 */
				} else if (is_array($nodeData) && (!array_key_exists("ordcustid", $nodeData) || !isId($nodeData["ordcustid"]))) {
					$customerId = '';

				/**
				 * Else it is a new customer which we do not have a record for
				 */
				} else {

					$lastKid = end($this->spool["children"]);

					if ($lastKid["nodeType"] == "customer" && $lastKid["service"] == "query") {

						/**
						 * If we couldn't find the record then error out
						 */
						if ($lastKid["errNo"] > 0) {
							throw new QBException("Unable to find customer when syncing orders from QB", $responseData["CustomerRef"]["ListID"]);
						}

						/**
						 * Check to see if this is an anonymous customer (guest checkout). If so then don't create a customer record
						 */
						if ($this->accounting->isCustomerGuestCheckout($lastKid["response"])) {
							$customerId = '';

						/**
						 * Else it is a real customer so create it
						 */
						 } else {

							$customerId = $this->customerSyncAPI->syncResponseRecord2Store($lastKid["response"]);

							if (!isId($customerId)) {
								throw new QBException("Unable to create customer record when syncing orders from QB", $lastKid["response"]);
							}

							$referenceDataSetup = $this->customerSyncAPI->referenceDataSetup;
							$referenceDataExternalKey = $this->customerSyncAPI->referenceDataExternalKey;

							$referenceReturn = $this->setReferenceDataStatically("customer", $customerId, $lastKid["response"], '', $referenceDataSetup, $referenceDataExternalKey);

							if (isId($referenceReturn)) {
								$customerRef = $this->accounting->getReference("customer", $referenceReturn, '', '', false);
							}
						}

					} else {
						$childRequestData = array(
												"ListID" => $responseData["CustomerRef"]["ListID"]
						);

						return $this->execChildService("customer", "query", $childRequestData);
					}
				}

			/**
			 * Else we have the customer but not the order yet
			 */
			} else if (is_array($customerRef)) {
				$customerId = $customerRef["accountingrefnodeid"];

			/**
			 * Else we got no customer
			 */
			} else {
				$customerId = '';
			}

			/**
			 * Save the customer ID for this order
			 */
			$this->setCustomer2Order($responseData["TxnID"], $customerId);
		}

		/**
		 * If we have a custom ID then get the customer record as we'll need it later on
		 */
		$customerNodeData = '';

		if (isId($customerId)) {
			$customerNodeData = $this->customerAPI->get($customerId);
		}

		if ($this->accounting->getValue("orderoption") == "order") {
			$salesLineRetTag = "SalesOrderLineRet";
		} else {
			$salesLineRetTag = "SalesReceiptLineRet";
		}

		/**
		 * OK, we got the customer, now we need to get all the products
		 */

		if (!isset($responseData[$salesLineRetTag]) || !is_array($responseData[$salesLineRetTag]) || empty($responseData[$salesLineRetTag])) {
			throw new QBException("Missing/Invalid product array when syncing orders", array("tag" => $salesLineRetTag, "response" => $responseData));
		}

		/**
		 * Set aside some vars for shipping costs and the tax component
		 */
		$productSubTotal = 0;
		$shippingCost = 0;
		$taxCost = 0;

		/**
		 * Sanatize it
		 */
		if (!isset($responseData[$salesLineRetTag][0])) {
			$responseData[$salesLineRetTag] = array($responseData[$salesLineRetTag]);
		}

		foreach ($responseData[$salesLineRetTag] as $product) {

			/**
			 * Check to see if we have already recorded this product
			 */
			if ($this->checkProductListId4Order($responseData["TxnID"], $product["ItemRef"]["ListID"])) {
				continue;
			}

			/**
			 * OK, we haven't done this one yet so lets do it. If we have any kids then deal with them first
			 */
			$lastKid = end($this->spool["children"]);

			if ($lastKid["service"] == "query" && ($lastKid["nodeType"] == "product" || $lastKid["nodeType"] == "productvariation")) {

				/**
				 * If we couldn't find the record then error out
				 */
				if ($lastKid["errNo"] > 0) {
					throw new QBException("Unable to find product when syncing orders from QB", $product["ItemRef"]["ListID"]);
				}

				/**
				 * Else try to add in this product/variation
				 */
				if ($lastKid["nodeType"] == "productvariation") {
					$productFatcory =& $this->productVariationSyncAPI;
				} else {
					$productFatcory =& $this->productSyncAPI;
				}

				$productData = $productFatcory->searchNodeByDB($lastKid["response"]);
				$productId = $productFatcory->syncResponseRecord2Store($lastKid["response"], $productData);

				/**
				 * Dam! We can't add it. Error out of here as we really needed that product for the order
				 */
				if (!isId($productId)) {
					throw new QBException("Unable to create product/variation record when syncing orders from QB", $lastKid["response"]);
				}

				/**
				 * Set the reference for this product
				 */
				$referenceDataSetup = $productFatcory->referenceDataSetup;
				$referenceDataExternalKey = $productFatcory->referenceDataExternalKey;

				$this->setReferenceDataStatically($lastKid["nodeType"], $productId, $lastKid["response"], '', $referenceDataSetup, $referenceDataExternalKey);
			}

			/**
			 * There aren't any query kids so try and find the reference for this product/variation/other product
			 */
			$checkTypes = array("product", "productvariation", "prerequisite");
			$productRef = "";
			$productType = "";

			foreach ($checkTypes as $checkType) {
				$productRef = $this->accounting->getReference($checkType, '', $product["ItemRef"]["ListID"], '', false);

				if (is_array($productRef)) {
					$productType = $checkType;
					break;
				}
			}

			/**
			 * Check to see if this is a prerequisite (shipping & tax costs)
			 */
			if ($productType == "prerequisite") {
				switch (isc_strtolower(trim($productRef["accountingrefvalue"]["Type"]))) {
					case "shipping":
						$cost = ($product["Quantity"] * $product["Rate"]);
						break;

					case "tax":
						$cost = ($product["Quantity"] * $product["Rate"]);
						break;
				}

				$productNodeData = array(
										"Type" => isc_strtolower(trim($productRef["accountingrefvalue"]["Type"])),
										"Cost" => $cost
				);

				$this->setProductListId2Order($responseData["TxnID"], $product["ItemRef"]["ListID"], $productType, $productNodeData, $product);

				/**
				 * We don't want to insert this in the order_products table
				 */
				continue;
			}

			/**
			 * OK, prerequisites are done, now for the rest. If no reference then send out a query child
			 */
			if (!is_array($productRef)) {

				if ($this->accounting->isProductVariationShortName($product["ItemRef"]["FullName"])) {
					$productType = "productvariation";
				} else {
					$productType = "product";
				}

				$childRequestData = array(
										"ListID" => $product["ItemRef"]["ListID"]
				);

				return $this->execChildService($productType, "query", $childRequestData);
			}

			/**
			 * Must have a reference by now
			 */
			if (!is_array($productRef)) {
				throw new QBException("Unable to find product reference when syncing order ID: " . $this->spool["nodeId"], $responseData);
			}

			$prodNodeData = '';

			if ($productType == "productvariation") {
				$prodNodeData = $this->productVariationAPI->get($productRef["accountingrefnodeid"]);
			} else {
				$prodNodeData = $this->productAPI->get($productRef["accountingrefnodeid"]);
			}

			/**
			 * If no prodNodeData then no go
			 */
			if (!is_array($prodNodeData)) {
				throw new QBException("Unable to find " . $productType . " node data when syncing order ID: " . $this->spool["nodeId"], array("order" => $responseData, "prodNodeId" => $productRef["accountingrefnodeid"]));
			}

			/**
			 * Lastly, save this product to our tmp cache
			 */
			$this->setProductListId2Order($responseData["TxnID"], $product["ItemRef"]["ListID"], $productType, $prodNodeData, $product);
		}

		/**
		 * OK, now retrieve all our product from our tmp cache to build the products for this order
		 */
		$products = array();
		$taxCost = $shippingCost = 0;
		$cacheProducts = $this->getProductListIds4Order($responseData["TxnID"]);

		if (!is_array($cacheProducts) || empty($cacheProducts)) {
			throw new QBException("Empty product cache array when syncing order ID: " . $this->spool["nodeId"], $responseData);
		}

		foreach ($cacheProducts as $productListId => $product) {

			/**
			 * Add up our stored shipping and tax costs if we have any
			 */
			if ($product["productType"] == "prerequisite") {
				switch (isc_strtolower(trim($product["productNodeData"]["Type"]))) {
					case "shipping":
						$shippingCost = $product["productNodeData"]["Cost"];
						break;

					case "tax":
						$taxCost = $product["productNodeData"]["Cost"];
						break;
				}

				continue;
			}

			$prodCode = '';
			$prodVariationId = 0;
			$prodOptions = array();

			if ($product["productType"] == "productvariation") {
				$prodCode = $product["productNodeData"]["vcsku"];
				$prodVariationId = $product["productNodeData"]["combinationid"];
				$prodOptions = $product["productNodeData"]["prodvariationarray"];
			}

			if (trim($prodCode) == '') {
				$prodCode = $product["productNodeData"]["prodcode"];
			}

			$products[] = array(
								"product_id" => $product["productNodeData"]["productid"],
								"product_name" => $product["productNodeData"]["prodname"],
								"product_code" => $prodCode,
								"quantity" => max(1, $product["productResponse"]["Quantity"]),
								"product_price" => $product["productResponse"]["Rate"],
								"original_price" => $product["productResponse"]["Rate"],
								"variation_id" => $prodVariationId,
								"options" => $prodOptions
			);

			/**
			 * Check to see if this is an existing product in an already existing order
			 */
			if (is_array($nodeData) && isset($nodeData["products"]) && is_array($nodeData["products"])) {
				foreach ($nodeData["products"] as $existingProduct) {
					if ($existingProduct["productid"] == $product["productNodeData"]["productid"] && isset($existingProduct["prodorderid"])) {
						$products[count($products)-1]["existing_order_product"] = $existingProduct["prodorderid"];
					}
				}
			}

			/**
			 * Add up our sub total
			 */
			$productSubTotal += $product["productResponse"]["Amount"];
		}

		/**
		 * OK, we have all the products and the customer details. Now for the actual order details
		 */
		$savedata = array(
						"ordcustid" => $customerId,
						"subtotal_ex_tax" => $productSubTotal,
						"total_tax" => $taxCost,
						"shipping_cost_ex_tax" => $shippingCost,
						"total_inc_tax" => ($productSubTotal + $taxCost + $shippingCost),
						"products" => $products
		);

		if (isset($responseData["Memo"])) {
			$savedata["ordnotes"] = $responseData["Memo"];
		}

		/**
		 * Add in the addresses
		 */
		$addressMap = array(
						"shipaddress1" => "Addr1",
						"shipaddress2" => "Addr2",
						"shipcity" => "City",
						"shipstate" => "State",
						"shipzip" => "PostalCode",
						"shipcountry" => "Country"
		);

		foreach (array("BillAddress", "ShipAddress") as $addressType) {

			if (!array_key_exists($addressType, $responseData) || !is_array($responseData[$addressType])) {
				$responseData[$addressType] = array();
			}

			if ($addressType == "BillAddress") {
				$addressKey = "billingaddress";
			} else {
				$addressKey = "shippingaddress";
			}

			$savedata[$addressKey] = array();

			foreach ($addressMap as $columnName => $refKey) {

				if (!isset($responseData[$addressType][$refKey]) && !is_array($nodeData)) {
					$responseData[$addressType][$refKey] = '';
				}

				if (isset($responseData[$addressType][$refKey])) {
					$savedata[$addressKey][$columnName] = $responseData[$addressType][$refKey];
				}
			}

			/**
			 * Find the country and state IDs
			 */
			$countryId = $this->getCountryId(@$savedata[$addressKey]["shipcountry"], $properCountryName);
			$stateId = '';

			if (isId($countryId) && trim(@$savedata[$addressKey]["shipstate"]) !== '') {
				$savedata[$addressKey]["shipcountry"] = $properCountryName;
				$stateId = $this->getStateId($savedata[$addressKey]["shipstate"], $countryId, $properStateName);
				if (!isId($stateId)) {
					$stateId = '';
				} else if (trim($properStateName) !== '') {
					$savedata[$addressKey]["shipstate"] = $properStateName;
				}
			} else {
				$countryId = '';
			}

			if (is_array($nodeData) || !isId($stateId)) {
				$savedata[$addressKey]["shipstateid"] = $stateId;
			}

			if (is_array($nodeData) || !isId($countryId)) {
				$savedata[$addressKey]["shipcountryid"] = $countryId;
			}

			/**
			 * Fill in the name. Use whatever QB gave us regardless
			 */
			$customerName = @$responseData["CustomerRef"]["FullName"];

			if ($this->accounting->isCustomerShortName($customerName)) {
				$tmpName = $this->accounting->qbCustomerShortName2CustomerNameId($customerName);
				if (is_array($tmpName) && array_key_exists("customername", $tmpName)) {
					$customerName = $tmpName["customername"];
				}
			} else if ($this->accounting->isCustomerGuestShortName($customerName)) {
				$tmpName = $this->accounting->qbCustomerGuestShortName2CustomerGuestNameId($customerName);
				if (is_array($tmpName) && array_key_exists("customerguestname", $tmpName)) {
					$customerName = $tmpName["customerguestname"];
				}
			}

			$nameParts = explode(" ", $customerName);

			if (count($nameParts) > 2) {
				$firstName = implode(" ", array_slice($nameParts, 0, count($nameParts)-1));
				$lastName = $nameParts[count($nameParts)-1];
			} else if (count($nameParts) == 1) {
				$firstName = $nameParts[0];
				$lastName = "";
			} else {
				$firstName = $nameParts[0];
				$lastName = $nameParts[1];
			}

			$savedata[$addressKey]["shipfirstname"] = $firstName;
			$savedata[$addressKey]["shiplastname"] = $lastName;

			/**
			 * Set something to each field if it is NULL as the database can't handle NULL values for this schema
			 */
			foreach ($savedata[$addressKey] as $addKey => $addVal) {
				if (is_null($addVal)) {
					$savedata[$addressKey][$addKey] = '';
				}
			}
		}

		/**
		 * If we don't have a $nodeData then we can still fill in some blanks
		 */
		if (!is_array($nodeData)) {
			$savedata["ordtoken"] = GenerateOrderToken();
			$savedata["ordstatus"] = ORDER_STATUS_COMPLETED;
			$savedata["orderpaymentmodule"] = "manual";
			$savedata["orderpaymentmethod"] = GetLang("QuickBooksDefaultPaymentName");
			$savedata["total_inc_tax"] = $savedata["totalcost"];
			$savedata["handling_cost_ex_tax"] = 0;
			$savedata["handling_cost_inc_tax"] = 0;

			if (isset($savedata["billingaddress"]["shipcountry"])) {
				$savedata["ordgeoipcountry"] = $savedata["billingaddress"]["shipcountry"];
				$savedata["ordgeoipcountrycode"] = GetCountryISO2ByName($savedata["billingaddress"]["shipcountry"]);
			}

			if (is_array($customerNodeData)) {
				$savedata["ordbillemail"] = $customerNodeData["custconemail"];
				$savedata["ordbillphone"] = $customerNodeData["custconphone"];
				$savedata["ordshipemail"] = $customerNodeData["custconemail"];
				$savedata["ordshipphone"] = $customerNodeData["custconphone"];
			}
		} else {
			$savedata["orderid"] = $nodeData["orderid"];
		}

		/**
		 * Alright, we have EVERYTHING, now create/update EVERYTHING
		 */
		$orderId = false;
		if (is_array($nodeData)) {

			/**
			 * Reset the inventory levels before we update it
			 */
			if ($this->accounting->getValue("invlevels") !== ACCOUNTING_QUICKBOOKS_TYPE_QUICKBOOKS) {
				UpdateInventoryOnReturn($savedata["orderid"]); /* /lib/orders.php */
			}

			if ($this->entityAPI->edit($savedata) !== false) {
				$orderId = $savedata["orderid"];
			}

			/**
			 * Now sync back the inventory levels
			 */
			if ($this->accounting->getValue("invlevels") !== ACCOUNTING_QUICKBOOKS_TYPE_QUICKBOOKS) {
				DecreaseInventoryFromOrder($orderId);
			}

		} else {
			$orderId = $this->entityAPI->add($savedata);

			/**
			 * Sync up the inventory levels as each order is marked as completed
			 */
			if ($this->accounting->getValue("invlevels") !== ACCOUNTING_QUICKBOOKS_TYPE_QUICKBOOKS) {
				DecreaseInventoryFromOrder($orderId);
			}
		}

		if (!isId($orderId)) {
			$this->accounting->logError("ORDER DATA", array("SaveData" => $savedata, "NodeData" => $nodeData, "DB" => $GLOBALS["ISC_CLASS_DB"]->GetError()));
			throw new QBException("Cannot save order record with data from QB", array("SaveData" => $savedata, "NodeData" => $nodeData, "DB" => $GLOBALS["ISC_CLASS_DB"]->GetError()));
		}

		return $orderId;
	}