Exemple #1
0
/**
 * Completes a pending order and marks it's status as whatever it should be next.
 * This function will process any payments, capture amounts from gateways, increase
 * # sold for each product in the order, etc.
 *
 * @param string The pending order token.
 * @param int The status to set the completed order to.
 * @return boolean True if successful, false on failure.
 */
function CompletePendingOrder($pendingOrderToken, $status, $sendInvoice=true)
{
	$orderData = LoadPendingOrdersByToken($pendingOrderToken, true);
	if($orderData === false) {
		return false;
	}

	$processedStoreCredit = false;
	$processedGiftCertificates = false;
	$orderStoreCredit = 0;
	$orderTotalAmount = 0;

	// Flag used to create the customer record but only if atleast one order was successful
	$createCustomer = false;

	// Sum up our total amount and store credit
	foreach ($orderData['orders'] as $order) {
		if ($order['ordstatus'] != 0) {
			continue;
		}

		$orderStoreCredit += $order['ordstorecreditamount'];
		$orderTotalAmount += $order['total_inc_tax'];
	}

	// flag to indicate if we should send notifications? only if the order was previously incomplete and the new status isn't declined/cancelled/refunded
	$sendNotifications = false;

	foreach($orderData['orders'] as $order) {
		$newStatus = $status;

		// Wait, was the order already complete? Then we don't do anything
		if($order['ordstatus'] != ORDER_STATUS_INCOMPLETE) {
			continue;
		}

		// If this order is digital, and the status is awaiting fulfillment, there's nothing
		// to actually fulfill, so set it to completed.
		if($order['ordisdigital'] && $newStatus == ORDER_STATUS_AWAITING_FULFILLMENT) {
			$newStatus = ORDER_STATUS_COMPLETED;
		}

		$extraInfo = @unserialize($order['extrainfo']);
		if(!is_array($extraInfo)) {
			$extraInfo = array();
		}

		// only email and update order data (coupons, certificates, store credit etc) if it's not a declined, cancelled or refunded order
		if($newStatus != ORDER_STATUS_DECLINED && $newStatus != ORDER_STATUS_CANCELLED && $newStatus != ORDER_STATUS_REFUNDED) {
			$createCustomer = true;
			$sendNotifications = true;

			if($sendInvoice && !EmailInvoiceToCustomer($order['orderid'], $newStatus)) {
				$GLOBALS['HideError'] = "";
				$GLOBALS['ErrorMessage'] = GetLang('ErroSendingInvoiceEmail');
				$GLOBALS['HideSuccess'] = "none";
			}

			// Are we updating the inventory levels when an order has been placed?
			if(GetConfig('UpdateInventoryLevels') == 1) {
				DecreaseInventoryFromOrder($order['orderid']);
			}

			// If this order now complete, we need to activate any gift certificates
			if(OrderIsComplete($newStatus)) {
				$GLOBALS['ISC_CLASS_GIFTCERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES');
				$GLOBALS['ISC_CLASS_GIFTCERTIFICATES']->ActivateGiftCertificates($order['orderid']);
			}

			// If we've had one or more coupons been applied to this order, we now need to increment the number of uses
			$couponIds = array();
			$query = "
				SELECT *
				FROM [|PREFIX|]order_coupons
				WHERE ordcouporderid='".(int)$order['orderid']."'
			";
			$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
			while($coupon = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
				$couponIds[] = $coupon['ordcouponid'];
			}
			if(!empty($couponIds)) {
				$couponsUsed = array_unique($couponIds);
				$couponList = implode(",", array_map("intval", $couponsUsed));
				$query = "
					UPDATE [|PREFIX|]coupons
					SET couponnumuses=couponnumuses+1
					WHERE couponid IN (".$couponList.")
				";
				$GLOBALS['ISC_CLASS_DB']->Query($query);

				foreach ($couponIds as $cid) {
					getclass('ISC_COUPON')->updatePerCustomerUsage($cid);
				}
			}

			// If we used store credit on this order, we now need to subtract it from the users account.
			if($order['ordstorecreditamount'] > 0 && $processedStoreCredit == false) {
				$GLOBALS['ISC_CLASS_CUSTOMER'] = GetClass('ISC_CUSTOMER');
				$currentCredit = $GLOBALS['ISC_CLASS_CUSTOMER']->GetCustomerStoreCredit($order['ordcustid']);
				$newCredit = $currentCredit - $orderStoreCredit;
				if($newCredit < 0) {
					$newCredit = 0;
				}
				$updatedCustomer = array(
					'custstorecredit' => $newCredit,
				);
				$GLOBALS['ISC_CLASS_DB']->UpdateQuery('customers', $updatedCustomer, "customerid='".(int)$order['ordcustid']."'");
				$processedStoreCredit = true;
			}

			// If one or more gift certificates were used we need to apply them to this order and subtract the total
			if($order['ordgiftcertificateamount'] > 0 && isset($extraInfo['giftcertificates']) && !empty($extraInfo['giftcertificates']) && $processedGiftCertificates == false) {
				$usedCertificates = array();
				$GLOBALS['ISC_CLASS_GIFT_CERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES');
				$GLOBALS['ISC_CLASS_GIFT_CERTIFICATES']->ApplyGiftCertificatesToOrder($order['orderid'], $orderTotalAmount + $order['ordgiftcertificateamount'], $extraInfo['giftcertificates'], $usedCertificates);
				unset($extraInfo['giftcertificates']);
				$processedGiftCertificates = true;
			}

			// If there are one or more digital products in this order then we need to create a record in the order_downloads table
			// for each of them and set the expiry dates
			$query = "
				SELECT ordprodid, ordprodqty
				FROM [|PREFIX|]order_products
				WHERE orderorderid='".$order['orderid']."' AND ordprodtype='digital'
			";
			$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
			$digitalProductIds = array();
			while($digitalProduct = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
				$digitalProductIds[$digitalProduct['ordprodid']] = $digitalProduct;
			}

			if(!empty($digitalProductIds)) {
				$query = "
					SELECT downloadid, productid, downexpiresafter, downmaxdownloads
					FROM [|PREFIX|]product_downloads
					WHERE productid IN (".implode(',', array_keys($digitalProductIds)).")
				";
				$result = $GLOBALS['ISC_CLASS_DB']->Query($query);
				while($digitalDownload = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
					$expiryDate = 0;

					// If this download has an expiry date, set it to now + expiry time
					if($digitalDownload['downexpiresafter'] > 0) {
						$expiryDate = time() + $digitalDownload['downexpiresafter'];
					}

					// If they've purchased more than one, we need to give them max downloads X quantity downloads
					$quantity = $digitalProductIds[$digitalDownload['productid']]['ordprodqty'];

					$newDownload = array(
						'orderid' => $order['orderid'],
						'downloadid' => $digitalDownload['downloadid'],
						'numdownloads' => 0,
						'downloadexpires' => $expiryDate,
						'maxdownloads' => $digitalDownload['downmaxdownloads'] * $quantity
					);
					$GLOBALS['ISC_CLASS_DB']->InsertQuery('order_downloads', $newDownload);
				}
			}
		}

		// Does a customer account need to be created?
		if(!empty($extraInfo['createAccount'])) {
			createOrderCustomerAccount($order, $extraInfo['createAccount']);
			unset($extraInfo['createAccount']);
		}

		// Now update the order and set the status
		$updatedOrder = array(
			"ordstatus" => $newStatus,
			"extrainfo" => serialize($extraInfo)
		);
		$GLOBALS['ISC_CLASS_DB']->UpdateQuery("orders", $updatedOrder, "orderid='".$order['orderid']."'");
	}

	if($sendNotifications) {
		// Trigger all active new order notification methods
		SendOrderNotifications($pendingOrderToken);

		// Do we need to add them to a Interspire Email Marketer mailing list?
		SubscribeCustomerToLists($pendingOrderToken);

		// Update the current uses of each rule
		$quote = getCustomerQuote();
		$appliedRules = array_keys(getCustomerQuote()->getAppliedDiscountRules());
		if(!empty($appliedRules)) {
			require_once ISC_BASE_PATH.'/lib/rule.php';
			updateRuleUses($appliedRules);
		}
	}

	// Empty the users cart and kill the checkout process
	EmptyCartAndKillCheckout();

	return true;
}
	/**
	* Ebay: Sent to a seller when a buyer completes the checkout process for an item. Not sent when an auction ends without bids.
	*
	* My notes: Seems to be triggered when the buyer's payment process for an AUCTION item has completed, is not fired for fixed price items which fire 'FixedPrice...' notifications instead
	*
	* @param array $body
	*/
	protected function _handleAuctionCheckoutComplete($body)
	{
		// The data fields in the notification are the same as those returned by the GetItemTransactions call with the default detail level.
		if (!empty ($body['Item']['ItemID']) && ISC_ADMIN_EBAY::validEbayItemId($body['Item']['ItemID'])) {
			// variables init
			$order = array();
			$orderId = 1;
			$order['ShippingInsuranceCost'] = 0;
			$completedPaymentHoldStatus = array('None', 'Released');
			$orderStatus = ORDER_STATUS_AWAITING_PAYMENT;
			$existingOrderId = 0;

			// Determine if the buyer purchase multiple items from the same seller
			if (!empty($body['TransactionArray']['Transaction']['ContainingOrder'])) {
			 // Call the operation to get the order transaction.
				$orderId = $body['TransactionArray']['Transaction']['ContainingOrder']['OrderID'];

				// if the record already exist, check if we need to update existing orders, that the payment hasn't been cleared previously.
				$existingOrder = GetOrderByEbayOrderId($orderId);
				$orderTransaction = ISC_ADMIN_EBAY_OPERATIONS::getOrderTransactions($orderId);
				$transactions = $orderTransaction->OrderArray->Order->TransactionArray->Transaction;

				$order['SubTotal'] = (string) $orderTransaction->OrderArray->Order->Subtotal;
				$order['ShippingCost'] = (string) $orderTransaction->OrderArray->Order->ShippingServiceSelected->ShippingServiceCost;
				$order['ShippingInsuranceCost'] = 0;
				$order['GrandTotal'] = (string) $orderTransaction->OrderArray->Order->Total;
				$order['TotalQuantityPurchased'] = 0;
				foreach ($transactions as $transaction) {
					$convertedTransaction = (array) $transaction;
					$variationOptionsString = '';
					if (isset($convertedTransaction['Variation']->VariationSpecifics)) {
						$variationNameValueList = (array) $convertedTransaction['Variation']->VariationSpecifics->NameValueList;
						$variationOptions = array();
						$variationSpecifics = (array) $convertedTransaction['Variation']->VariationSpecifics;
						if (is_array($variationSpecifics['NameValueList'])) {
							foreach ($variationSpecifics['NameValueList'] as $option) {
								$variationOptions[(string) $option->Name] = (string) $option->Value;
							}
						} else {
							$variationOptions[(string) $variationSpecifics['NameValueList']->Name] = (string) $variationSpecifics['NameValueList']->Value;
						}
						$variationOptionsString = serialize($variationOptions);
					}
					$quantityPurchased = $convertedTransaction['QuantityPurchased'];
					$transactionPrice = $convertedTransaction['TransactionPrice'];
					$itemId = (string) $convertedTransaction['Item']->ItemID;
					$transactionId = (string) $convertedTransaction['TransactionID'];
					$totalTransactionPrice = $transactionPrice * $quantityPurchased;
					$order['Transaction'][] = array(
						'QuantityPurchased' => $quantityPurchased,
						'TransactionPrice' => $transactionPrice,
						'ItemId' => $itemId,
						'TotalTransactionPrice' => $totalTransactionPrice,
						'VariationOptionsString' => $variationOptionsString,
						'TransactionId' => $transactionId,
					);
					$order['TotalQuantityPurchased'] += $quantityPurchased;
					$order['Currency'] = GetCurrencyByCode($body['TransactionArray']['Transaction']['AmountPaid']['!currencyID']);
					$buyerInfoShippingAddress = $body['TransactionArray']['Transaction']['Buyer']['BuyerInfo']['ShippingAddress'];
					$buyerEmailAddress = $body['TransactionArray']['Transaction']['Buyer']['Email'];
				}

				if ($existingOrder) {
					$existingOrderId = $existingOrder['orderid'];
				}
			}
			else {
				$transactions = $body['TransactionArray'];
				foreach ($transactions as $transaction) {
					$itemId = $body['Item']['ItemID'];
					$transactionId = $transaction['TransactionID'];
					$query = "
						SELECT *
						FROM [|PREFIX|]order_products
						WHERE ebay_item_id = '".$GLOBALS["ISC_CLASS_DB"]->Quote($itemId)."'
							AND ebay_transaction_id = '".$GLOBALS["ISC_CLASS_DB"]->Quote($transactionId)."'
						LIMIT 1
					";
					$res = $GLOBALS['ISC_CLASS_DB']->Query($query);
					$row = $GLOBALS['ISC_CLASS_DB']->Fetch($res);
					$eachItemPriceExTax = $transaction['TransactionPrice']['!'];
					$quantityPurchased = $transaction['QuantityPurchased'];
					$totalTransactionPrice = $quantityPurchased * $eachItemPriceExTax;
					$variationOptionsString = '';

					// do we have a variation for this product?
					if (isset($transaction['Variation']['VariationSpecifics'])) {
						$variationNameValueList = $transaction['Variation']['VariationSpecifics']['NameValueList'];
						$variationOptions = array();
						foreach ($variationNameValueList as $option) {
							$variationOptions[$option['Name']] = $option['Value'];
						}
						$variationOptionsString = serialize($variationOptions);
					}
					$order['TotalQuantityPurchased'] = $quantityPurchased;
					$order['SubTotal'] = $eachItemPriceExTax * $order['TotalQuantityPurchased'];
					$order['ShippingCost'] = $transaction['ShippingServiceSelected']['ShippingServiceCost']['!'];
					if (isset ($transaction['ShippingServiceSelected']['ShippingInsuranceCost']['!'])) {
						$order['ShippingInsuranceCost'] = $transaction['ShippingServiceSelected']['ShippingInsuranceCost']['!'];
					}
					$order['GrandTotal'] = $transaction['AmountPaid']['!'];
					$order['Transaction'][] = array(
						'QuantityPurchased' => $quantityPurchased,
						'TransactionPrice' => $eachItemPriceExTax,
						'ItemId' => $itemId,
						'TotalTransactionPrice' => $totalTransactionPrice,
						'VariationOptionsString' => $variationOptionsString,
						'TransactionId' => $transactionId,
					);
					$order['Currency'] = GetCurrencyByCode($transaction['AmountPaid']['!currencyID']);
					$buyerInfoShippingAddress = $transaction['Buyer']['BuyerInfo']['ShippingAddress'];
					$buyerEmailAddress = $transaction['Buyer']['Email'];

					if (!$row) {
						// only process the new transaction
						break;
					} else {
						$existingOrderId = $row['orderorderid'];
					}
				}
			}

			$paymentHoldStatus = $body['TransactionArray']['Transaction']['Status']['PaymentHoldStatus'];
			if (in_array(trim($paymentHoldStatus), $completedPaymentHoldStatus)) {
				$orderStatus = ORDER_STATUS_AWAITING_FULFILLMENT;
			}
			if ($existingOrderId != 0) {
				if (!isset ($existingOrder)) {
					$existingOrder = GetOrder($existingOrderId, false, true, true);
				}

				// check if there're any existing order need to be updated.
				// in the case, paypal release the hold payment of buyer
				if ($existingOrder['ordstatus'] == ORDER_STATUS_AWAITING_PAYMENT
				&& $orderStatus == ORDER_STATUS_AWAITING_FULFILLMENT) {
					// update the quantity for each transaction
					$GLOBALS["ISC_CLASS_DB"]->StartTransaction();
					foreach ($order['Transaction'] as $eachTransaction) {
						// Get product Id
						try {
							$itemObj = new ISC_ADMIN_EBAY_ITEMS($eachTransaction['ItemId']);
							$productId = $itemObj->getProductId();
						} catch (Exception $e) {
							$this->log->LogSystemDebug('ebay', $e->getMessage());
							return false;
						}

						// update the item quantity in store
						$updatedData['quantity_remaining'] = $itemObj->getQuantityRemaining() - $eachTransaction['QuantityPurchased'];
						if (!$GLOBALS['ISC_CLASS_DB']->UpdateQuery('ebay_items', $updatedData, "ebay_item_id='" . $eachTransaction['ItemId'] . "'")) {
							$this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error());
							$GLOBALS["ISC_CLASS_DB"]->RollbackTransaction();
							return false;
						}
						if (!UpdateOrderStatus($existingOrderId, $orderStatus, true, true)) {
							$GLOBALS["ISC_CLASS_DB"]->RollbackTransaction();
							return false;
						}
					}
					$GLOBALS["ISC_CLASS_DB"]->CommitTransaction();

					// update the store inventory if necessary
					if (GetConfig('UpdateInventoryLevels') == 1) {
						DecreaseInventoryFromOrder($existingOrderId);
					}
					$this->log->LogSystemDebug('ebay', 'The status of the store order ('. $existingOrderId .') has been updated to: Awaiting Fulfillment');
				}
				return true;
			}

			$order['ShippingTotalCost'] = $order['ShippingInsuranceCost'] + $order['ShippingCost'];

			// Buyer's address information
			$addressMap = array(
				'Name',
				'CompanyName',
				'Street1',
				'Street2',
				'CityName',
				'PostalCode',
				'Country',
				'CountryName',
				'Phone',
				'StateOrProvince',
			);

			// Initialize the value, make sure it's not empty
			foreach ($addressMap as $key) {
				if (!isset($buyerInfoShippingAddress[$key])) {
					$buyerInfoShippingAddress[$key] = '';
				}
			}
			$buyerCountryId = GetCountryIdByISO2($buyerInfoShippingAddress['Country']);
			$buyerStateId = GetStateByName($buyerInfoShippingAddress['StateOrProvince'], $buyerCountryId);
			$buyerStateName = $buyerInfoShippingAddress['StateOrProvince'];
			if (!$buyerStateId) {
				$buyerStateId = GetStateByAbbrev($buyerInfoShippingAddress['StateOrProvince'], $buyerCountryId);
				$stateInfo = GetStateInfoById($buyerStateId);
				$buyerStateName = $stateInfo['statename'];
			}

			// Tokenize buyer's first and last name
			$nameTokens = explode(' ', $buyerInfoShippingAddress['Name']);
			$buyerFirstName = $nameTokens[0];
			$buyerLastName = '';
			if (!empty($nameTokens[1])) {
				$buyerLastName = $nameTokens[1];
			}

			$orderToken = generateOrderToken();

			// Preparing data to be inserted to orders table
			$newOrder = array(
				'ordtoken' => $orderToken,
				'orderpaymentmodule' => '',
				'orderpaymentmethod' => '',
				'orderpaymentmodule' => '',
				'extraInfo' => serialize(array()),
				'orddefaultcurrencyid' => $order['Currency']['currencyid'],
				'orddate' => time(),
				'ordlastmodified' => time(),
				'ordcurrencyid' => $order['Currency']['currencyid'],
				'ordcurrencyexchangerate' => 1,
				'ordipaddress' => GetIP(),
				'ordcustmessage' => '',
				'ordstatus' => $orderStatus,
				'base_shipping_cost' => $order['ShippingTotalCost'],
				'base_handling_cost' => 0,
				'ordbillemail' => $buyerEmailAddress,
				'ordbillfirstname' => $buyerFirstName,
				'ordbilllastname' => $buyerLastName,
				'ordbillcompany' => $buyerInfoShippingAddress['CompanyName'],
				'ordbillstreet1' => $buyerInfoShippingAddress['Street1'],
				'ordbillstreet2' => $buyerInfoShippingAddress['Street2'],
				'ordbillsuburb' => $buyerInfoShippingAddress['CityName'],
				'ordbillzip' => $buyerInfoShippingAddress['PostalCode'],
				'ordbillcountrycode' => $buyerInfoShippingAddress['Country'],
				'ordbillphone' => $buyerInfoShippingAddress['Phone'],
				'ordbillstateid' => (int) $buyerStateId,
				'ordbillstate' => $buyerStateName,
				'ordbillcountry' => $buyerInfoShippingAddress['CountryName'],
				'ordbillcountryid' => (int) $buyerCountryId,
				'total_ex_tax' => $order['GrandTotal'],
				'total_inc_tax' => $order['GrandTotal'],
				'shipping_cost_ex_tax' => $order['ShippingTotalCost'],
				'shipping_cost_inc_tax' => $order['ShippingTotalCost'],
				'subtotal_inc_tax' => $order['SubTotal'],
				'subtotal_ex_tax' => $order['SubTotal'],
				'ebay_order_id' => $orderId,
			);
			ResetStartingOrderNumber();

			// Start the transaction
			$GLOBALS["ISC_CLASS_DB"]->StartTransaction();

			// Inserting order data
			$newOrderId = $GLOBALS["ISC_CLASS_DB"]->InsertQuery('orders', $newOrder);
			if (!$newOrderId) {
				$this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error());
				$GLOBALS["ISC_CLASS_DB"]->RollbackTransaction();
				return false;
			}

			$orderAddress = array(
				'first_name' => $buyerFirstName,
				'last_name' => $buyerLastName,
				'company' => $buyerInfoShippingAddress['CompanyName'],
				'address_1' => $buyerInfoShippingAddress['Street1'],
				'address_2' => $buyerInfoShippingAddress['Street2'],
				'city' => $buyerInfoShippingAddress['CityName'],
				'zip' => $buyerInfoShippingAddress['PostalCode'],
				'country_iso2' => $buyerInfoShippingAddress['Country'],
				'phone' => $buyerInfoShippingAddress['Phone'],
				'total_items' => $order['TotalQuantityPurchased'],
				'email' => $buyerEmailAddress,
				'country_id' => (int) $buyerCountryId,
				'country' => $buyerInfoShippingAddress['CountryName'],
				'state_id' => (int) $buyerStateId,
				'state' => $buyerStateName,
				'order_id' => $newOrderId,
			);

			$addressId = $GLOBALS['ISC_CLASS_DB']->insertQuery('order_addresses', $orderAddress);
			if (!$addressId) {
				$this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error());
				$GLOBALS["ISC_CLASS_DB"]->RollbackTransaction();
				return false;
			}

			// Inserting order shipping
			$orderShipping = array(
				'order_address_id' => $addressId,
				'order_id' => $newOrderId,
				'base_cost' => $order['ShippingTotalCost'],
				'cost_inc_tax' => $order['ShippingTotalCost'],
				'cost_ex_tax' => $order['ShippingTotalCost'],
				'method' => 'Available on eBay',
			);

			if (!$GLOBALS['ISC_CLASS_DB']->insertQuery('order_shipping', $orderShipping)) {
				$this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error());
				$GLOBALS["ISC_CLASS_DB"]->RollbackTransaction();
				return false;
			}

			// Go thru each sold item in the order
			foreach ($order['Transaction'] as $eachTransaction) {
				// Get product Id
				try {
					$itemObj = new ISC_ADMIN_EBAY_ITEMS($eachTransaction['ItemId']);
					$productId = $itemObj->getProductId();
				} catch (Exception $e) {
					$this->log->LogSystemDebug('ebay', $e->getMessage());
					return false;
				}

				// Inserting order product
				$productObj = new ISC_PRODUCT($productId);
				$newProduct = array(
					'orderorderid' => $newOrderId,
					'ordprodid' => $productId,
					'ordprodsku' => $productObj->GetSKU(),
					'ordprodname' => $productObj->GetProductName(),
					'ordprodtype' => $productObj->GetProductType(),
					'ordprodqty' => $eachTransaction['QuantityPurchased'],
					'base_price' => $eachTransaction['TransactionPrice'],
					'price_ex_tax' => $eachTransaction['TransactionPrice'],
					'price_inc_tax' => $eachTransaction['TransactionPrice'],
					'price_tax' => 0,
					'base_total' => $eachTransaction['TotalTransactionPrice'],
					'total_ex_tax' => $eachTransaction['TotalTransactionPrice'],
					'total_inc_tax' => $eachTransaction['TotalTransactionPrice'],
					'total_tax' => 0,
					'base_cost_price' => 0,
					'cost_price_inc_tax' => 0,
					'cost_price_inc_tax' => 0,
					'cost_price_tax' => 0,
					'ordprodweight' => $productObj->GetWeight(false),
					'ordprodoptions' => $eachTransaction['VariationOptionsString'],
					'ordprodvariationid' => $productObj->_prodvariationid,
					'ordprodwrapid' => 0,
					'ordprodwrapname' => '',
					'base_wrapping_cost' => 0,
					'wrapping_cost_ex_tax' => 0,
					'wrapping_cost_inc_tax' => 0,
					'wrapping_cost_tax' => 0,
					'ordprodwrapmessage' => '',
					'ordprodeventname' => '',
					'ordprodeventdate' => 0,
					'ordprodfixedshippingcost' => $productObj->GetFixedShippingCost(),
					'order_address_id' => $addressId,
					'ebay_item_id' => $eachTransaction['ItemId'],
					'ebay_transaction_id' => $eachTransaction['TransactionId'],
				);

				$orderProductId = $GLOBALS['ISC_CLASS_DB']->insertQuery('order_products', $newProduct);
				if (!$orderProductId) {
					$this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error());
					$GLOBALS["ISC_CLASS_DB"]->RollbackTransaction();
					return false;
				}

				if ($orderStatus == ORDER_STATUS_AWAITING_FULFILLMENT) {
					// update the item quantity in store
					$updatedData['quantity_remaining'] = $itemObj->getQuantityRemaining() - $eachTransaction['QuantityPurchased'];
					if (!$GLOBALS['ISC_CLASS_DB']->UpdateQuery('ebay_items', $updatedData, "ebay_item_id='" . $eachTransaction['ItemId'] . "'")) {
						$this->log->LogSystemDebug('ebay', $GLOBALS["ISC_CLASS_DB"]->Error());
						$GLOBALS["ISC_CLASS_DB"]->RollbackTransaction();
						return false;
					}
				}
			}
			$GLOBALS["ISC_CLASS_DB"]->CommitTransaction();

			// update the store inventory if necessary
			if (GetConfig('UpdateInventoryLevels') == 1) {
				DecreaseInventoryFromOrder($newOrderId);
			}

			// Trigger new order notifications
			SendOrderNotifications($orderToken);

			$this->log->LogSystemDebug('ebay', 'An Item ('. $body['Item']['ItemID'] .') has been paid by the buyer and added to the store order (' . $newOrderId. ').');
			return true;
		}
		return false;
	}
/**
 * Completes a pending order and marks it's status as whatever it should be next.
 * This function will process any payments, capture amounts from gateways, increase
 * # sold for each product in the order, etc.
 *
 * @param string The pending order token.
 * @param int The status to set the completed order to.
 * @return boolean True if successful, false on failure.
 */
function CompletePendingOrder($pendingOrderToken, $status, $sendInvoice = true)
{
    $orderData = LoadPendingOrdersByToken($pendingOrderToken, true);
    if ($orderData === false) {
        return false;
    }
    $processedStoreCredit = false;
    $processedGiftCertificates = false;
    $orderStoreCredit = 0;
    $orderTotalAmount = 0;
    // Sum up our total amount and store credit
    foreach ($orderData['orders'] as $order) {
        if ($order['ordstatus'] != 0) {
            continue;
        }
        $orderStoreCredit += $order['ordstorecreditamount'];
        $orderTotalAmount += $order['ordtotalamount'];
    }
    foreach ($orderData['orders'] as $order) {
        // Wait, was the order already complete? Then we don't do anything
        if ($order['ordstatus'] != 0) {
            continue;
        }
        // If this order is digital, and the status is awaiting fulfillment, there's nothing
        // to actually fulfill, so set it to completed.
        if ($order['ordisdigital'] && $status == ORDER_STATUS_AWAITING_FULFILLMENT) {
            $status = ORDER_STATUS_COMPLETED;
        }
        // Don't email the customer if this order was declined
        if ($status != ORDER_STATUS_DECLINED) {
            if ($sendInvoice && !EmailInvoiceToCustomer($order['orderid'], $status)) {
                $GLOBALS['HideError'] = "";
                $GLOBALS['ErrorMessage'] = GetLang('ErroSendingInvoiceEmail');
                $GLOBALS['HideSuccess'] = "none";
            }
            // Are we updating the inventory levels when an order has been placed?
            if (GetConfig('UpdateInventoryLevels') == 1) {
                DecreaseInventoryFromOrder($order['orderid']);
            }
        }
        // If this order now complete, we need to activate any gift certificates
        if (OrderIsComplete($status)) {
            $GLOBALS['ISC_CLASS_GIFTCERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES');
            $GLOBALS['ISC_CLASS_GIFTCERTIFICATES']->ActivateGiftCertificates($order['orderid']);
        }
        // If we've had one or more coupons been applied to this order, we now need to increment the number of uses
        $couponIds = array();
        $query = "\n\t\t\tSELECT *\n\t\t\tFROM [|PREFIX|]order_coupons\n\t\t\tWHERE ordcouporderid='" . (int) $order['orderid'] . "'\n\t\t";
        $result = $GLOBALS['ISC_CLASS_DB']->Query($query);
        while ($coupon = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
            $couponIds[] = $coupon['ordcouponid'];
        }
        if (!empty($couponIds)) {
            $couponsUsed = array_unique($couponIds);
            $couponList = implode(",", array_map("intval", $couponsUsed));
            $query = "\n\t\t\t\tUPDATE [|PREFIX|]coupons\n\t\t\t\tSET couponnumuses=couponnumuses+1\n\t\t\t\tWHERE couponid IN (" . $couponList . ")\n\t\t\t";
            $GLOBALS['ISC_CLASS_DB']->Query($query);
        }
        // If we used store credit on this order, we now need to subtract it from the users account.
        if ($order['ordstorecreditamount'] > 0 && $processedStoreCredit == false) {
            $GLOBALS['ISC_CLASS_CUSTOMER'] = GetClass('ISC_CUSTOMER');
            $currentCredit = $GLOBALS['ISC_CLASS_CUSTOMER']->GetCustomerStoreCredit($order['ordcustid']);
            $newCredit = $currentCredit - $orderStoreCredit;
            if ($newCredit < 0) {
                $newCredit = 0;
            }
            $updatedCustomer = array('custstorecredit' => $newCredit);
            $GLOBALS['ISC_CLASS_DB']->UpdateQuery('customers', $updatedCustomer, "customerid='" . (int) $order['ordcustid'] . "'");
            $processedStoreCredit = true;
        }
        $extraInfo = @unserialize($order['extrainfo']);
        if (!is_array($extraInfo)) {
            $extraInfo = array();
        }
        // If one or more gift certificates were used we need to apply them to this order and subtract the total
        if ($order['ordgiftcertificateamount'] > 0 && isset($extraInfo['giftcertificates']) && !empty($extraInfo['giftcertificates']) && $processedGiftCertificates == false) {
            $usedCertificates = array();
            $GLOBALS['ISC_CLASS_GIFT_CERTIFICATES'] = GetClass('ISC_GIFTCERTIFICATES');
            $GLOBALS['ISC_CLASS_GIFT_CERTIFICATES']->ApplyGiftCertificatesToOrder($order['orderid'], $orderTotalAmount, $extraInfo['giftcertificates'], $usedCertificates);
            unset($extraInfo['giftcertificates']);
            $processedGiftCertificates = true;
        }
        // If there are one or more digital products in this order then we need to create a record in the order_downloads table
        // for each of them and set the expiry dates
        $query = "\n\t\t\tSELECT ordprodid, ordprodqty\n\t\t\tFROM [|PREFIX|]order_products\n\t\t\tWHERE orderorderid='" . $order['orderid'] . "' AND ordprodtype='digital'\n\t\t";
        $result = $GLOBALS['ISC_CLASS_DB']->Query($query);
        $digitalProductIds = array();
        while ($digitalProduct = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
            $digitalProductIds[$digitalProduct['ordprodid']] = $digitalProduct;
        }
        if (!empty($digitalProductIds)) {
            $query = "\n\t\t\t\tSELECT downloadid, productid, downexpiresafter, downmaxdownloads\n\t\t\t\tFROM [|PREFIX|]product_downloads\n\t\t\t\tWHERE productid IN (" . implode(',', array_keys($digitalProductIds)) . ")\n\t\t\t";
            $result = $GLOBALS['ISC_CLASS_DB']->Query($query);
            while ($digitalDownload = $GLOBALS['ISC_CLASS_DB']->Fetch($result)) {
                $expiryDate = 0;
                // If this download has an expiry date, set it to now + expiry time
                if ($digitalDownload['downexpiresafter'] > 0) {
                    $expiryDate = time() + $digitalDownload['downexpiresafter'];
                }
                // If they've purchased more than one, we need to give them max downloads X quantity downloads
                $quantity = $digitalProductIds[$digitalDownload['productid']]['ordprodqty'];
                $newDownload = array('orderid' => $order['orderid'], 'downloadid' => $digitalDownload['downloadid'], 'numdownloads' => 0, 'downloadexpires' => $expiryDate, 'maxdownloads' => $digitalDownload['downmaxdownloads'] * $quantity);
                $GLOBALS['ISC_CLASS_DB']->InsertQuery('order_downloads', $newDownload);
            }
        }
        // Now update the order and set the status
        $updatedOrder = array("ordstatus" => $status, "extrainfo" => serialize($extraInfo));
        $GLOBALS['ISC_CLASS_DB']->UpdateQuery("orders", $updatedOrder, "orderid='" . $order['orderid'] . "'");
    }
    return true;
}
	/**
	 * 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;
	}