<?php

require_once("lib-action/action.php");
require_once("lib-billing/include/config/internal/cycles.php");

use Billing\Manager\AdminManager;
use Billing\Manager\CompanyManager;
use Billing\Manager\InvoiceFacturaManager;
use Billing\Manager\StoreManager;
use Billing\Manager\SubscriptionManager;
use Billing\Manager\TaxZoneManager;
use Billing\Object\Plan;
use Billing\Object\Subscription;
use Billing\Object\SubscriptionAddon;

class UpgradeDowngradeActionResponse extends MB_Action
{
    const INSTANT_UPGRADE_DOWNGRADE_WITH_PRORATION = 0;
    const INSTANT_UPGRADE_DOWNGRADE_WITHOUT_PRORATION = 1;
    const DFFERED_UPGRADE_DOWNGRADE = 2;

	public function execute()
	{
		parent::execute();
		$this->packageInfoCache[(int)$_REQUEST["packageID"]] = $this->packageInfo = correctArray(grabFirstResultFromMBAPIArray(array(
			"mbapi" => array(
				"command" => "GetPackages",
				"params" => array(
					"packageID" => (int)$_REQUEST["packageID"],
					"getPackageAttributeData" => 1,
					"getPackageDatabaseData" => 1,
					"getDomainData" => 1,
					"getCertData" => 1,
					"getPackageAddonData" => 1,
				),
			),
		)));
		$this->_ajustAddonsOnly = (int)bgpc("productID") == $this->packageInfo["productID"];

		$this->processFullUpgradeDowngrade($this->packageInfo);
	}

	private function processFullUpgradeDowngrade($packageInfo)
	{
        $this->_upgradeDowngradeType = bgpc("packageUpgradeDowngradeType");
		$copyAttributeNames = array(
			"packageUsername",
			"domainUserName",
			"ftpUsername",
			"ip",
			"ipv6",
			"externalID",
			"clientID",
			"domainID",
			"guid",
			"subscriptionGUID",
		);

		$billingShareInfo = correctArray(grabResultsFromMBAPIArray(array(
			"mbapi" => array(
				"command" => "GetBillingAccountShares",
				"params" => array(
					"packageID" => $packageInfo["packageID"],
				),
			),
		)));

		$productInfo = oneByID("products", (int)$_REQUEST["productID"], array(
			"getProductAttributeData" => 1,
			"getCouponData" => 1,
			"getProductVariantData" => 1,
			"getProductVariantPriceData" => 1,
			"getTLDData" => 1,
			"getCycleData" => 1));

		$rawaddons = arrayFor("products", array(
			"parentProductID" => (int)$_REQUEST["productID"],
			"productActive" => 1,
			"getProductAttributeData" => 1,
			"getCouponData" => 1,
			"getProductVariantData" => 1,
			"getProductVariantPriceData" => 1,
			"getTLDData" => 1,
			"getCycleData" => 1,
            "getAddons" => 1
        ));

		$keyedAddons = array();
		$addonGroupIDs = array();
		foreach ((array)$rawaddons as $k=>$addon){
			$addonGroupIDs[] = $addon["productAddonGroupID"];
			$keyedAddons[$addon["productID"]] =& $rawaddons[$k];
		}

		$addonGroupNames = optionsFor("productAddonGroups", array("productAddonGroupID" => $addonGroupIDs));

		// lets get the tax zone group id's of the variants we are going to use
		$taxZoneGroupIDs = array();
		foreach ((array)$productInfo["productVariants"] as $variant){
			$taxZoneGroupIDs[$variant["productVariantID"]] = $variant["taxZoneGroupID"];
		}
		foreach ((array)$_REQUEST["addons"] as $addonID){
			foreach ((array)$keyedAddons[$addonID]["productVariants"] as $variant){
				$taxZoneGroupIDs[$variant["productVariantID"]] = $variant["taxZoneGroupID"];
				$keyedAddons[$addonID]["taxZoneGroupID"] = $variant["taxZoneGroupID"];
			}
		}
		// ok, now we have tax zone group id's keyed on the variant id's
		// lets figure out the tax zone group id of the package now: (there is an easier way)
		foreach ((array)$productInfo["productVariantPrices"] as $price){
			if ($price["productVariantID"] == (int)$_REQUEST["productVariantID"]){
				$packageTaxZoneGroupID = $taxZoneGroupIDs[$price["productVariantID"]];
			}
		}

/*		// now lets get the tax zone group ids for the addons
		foreach ((array)$_REQUEST["addons"] as $addonID){
			foreach ((array)$keyedAddons[$addonID]["productVariantPrices"] as $price){
				if ($price["currencyID"]==$packageInfo["currencyID"] && $price["cycleID"]==$packageInfo["cycleID"]){
					$keyedAddons[$addonID]["taxZoneGroupID"] = $price["taxZoneGroupID"];
				}
			}
		}*/


		// first lets make the attributes we need for the new package, which will include some of the old ones...
		$packageAttributes = $this->getPackageAttributesByName($packageInfo);
		$serverGroupMap = array();
		foreach ((array)$packageAttributes as $attr){
			$serverGroupMap[$attr["serverGroupID"]] = $attr["configGroupID"];
		}
		$this->serverGroupIDs =& $serverGroupMap;
		$productAttributes = $this->getProductAttributesByName($productInfo, $serverGroupMap);
		$addProvisioning = (is_array($serverGroupMap) && count($serverGroupMap))?1:0;

		foreach ($packageAttributes as $attribute){
			if (in_array($attribute["packageAttributeName"], $copyAttributeNames)){
				$row = array(
					"serverGroupID" => $attribute["serverGroupID"],
					"packageAttributeName" => $attribute["packageAttributeName"],
					"packageAttributeValue" => $attribute["packageAttributeValue"],
					"packageAttributeDateCreated" => $attribute["packageAttributeDateCreated"],
					"configGroupID" => $attribute["configGroupID"],
				);
				$productAttributes[$attribute["packageAttributeName"]] = $row;
			}
		}


		//dbg(toXML(array("packageAttributes"=>array("packageAttribute"=>array_values($productAttributes)))));
		// done making the attributes, just add into query when needed...


		// ok, lets make the addon array:
		$tempaddons = array();
		foreach ((array)$_REQUEST["addons"] as $addons){
			foreach ((array)$addons as $addon){
				$tempaddons[] = $addon;
			}
		}
		$_REQUEST["addons"] = $tempaddons;

		$packageAddons = array();
		foreach ((array)$_REQUEST["addons"] as $addonID){
			if ($keyedAddons[$addonID]){
				$packageAddons[] = array(
					"copyLedgerEntries" => 1,
					"packageAddonDisplayName" => $addonGroupNames[$keyedAddons[$addonID]["productAddonGroupID"]].": ".$keyedAddons[$addonID]["productName"],
					"packageAddonProductID" => $keyedAddons[$addonID]["productID"],
					"taxZoneGroupID" => $keyedAddons[$addonID]["productVariants"][0]["taxZoneGroupID"],
					"packageAddonCycleDiscountGroupID" => $keyedAddons[$addonID]["productVariants"][0]["cycleDiscountGroupID"],
					"packageAddonPriceLock" => 1,
					"packageAddonActive" => 1,
					"packageAddonDiscountable" => $keyedAddons[$addonID]["productDiscountable"],
					"couponID" => 0,
					"couponNumCycles" => (int)$_REQUEST["prices"][$addonID]["couponcycles"],
					"couponPrice" => BillingLocale::getNumberFromInput($_REQUEST["prices"][$addonID]["coupon"]),
					"packageAddonOriginalPrice" => BillingLocale::getNumberFromInput($_REQUEST["prices"][$addonID]["general"]),
					"packageAddonPrice" => BillingLocale::getNumberFromInput($_REQUEST["prices"][$addonID]["general"]),
					// if we are upgrading at the end of the cycle we add the fee as the setup price
					"packageAddonSetup" => ((int)$this->_upgradeDowngradeType) ? BillingLocale::getNumberFromInput($_REQUEST["prices"][$addonID]["fee"]) : 0,
				);
			}
		}

		$packageArray = array(
			"packageName" => $productInfo["productName"],
			"packagePriorID" => $packageInfo["packageID"],
			"clientID" => $packageInfo["clientID"],
			"packageStatus" => $packageInfo["packageStatus"],
			"packageActive" => 1,
			"productID" => $productInfo["productID"],
			"productVariantID" => bgpc("productVariantID"),
			"primaryDomainID" => $packageInfo["primaryDomainID"],
			"taxZoneGroupID" => $packageTaxZoneGroupID,
			"packageProratable" => $productInfo["productProratable"],
			"packageDateCreated" => time(),
			"packageDateBeginRenewal" => $packageInfo["packageDateNextRenewal"],
			"packageDateLastRenewal" => 0,
			"packageDateNextRenewal" => $packageInfo["packageDateNextRenewal"],
			"packageDateStopRenewal" => $packageInfo["packageDateStopRenewal"],
			"packageComments" => $_REQUEST["packageComments"],
			"packageType" => $productInfo["productType"],
			"cycleID" => $packageInfo["cycleID"],
			"packagePriceLock" => $packageInfo["packagePriceLock"],
			"usageCycleID" => $packageInfo["usageCycleID"],
			"usageCalculateOnly" => $packageInfo["usageCalculateOnly"],
			"orderFormID" => $packageInfo["orderFormID"],
		);
		if ($this->_ajustAddonsOnly) {
			$packageArray["packageOriginalPrice"] = $packageInfo["packageOriginalPrice"];
			$packageArray["packagePrice"] = $packageInfo["packagePrice"];
			$packageArray["packageSetup"] = 0;
			$packageArray["couponPrice"] = $packageInfo["couponPrice"];
			$packageArray["couponNumCycles"] = $packageInfo["couponNumCycles"];
		} else {
			$packagePrices = bgpc("packagePrices");
			$packageArray["packageOriginalPrice"] = BillingLocale::getNumberFromInput($packagePrices["original"]);
			$packageArray["packagePrice"] = BillingLocale::getNumberFromInput($packagePrices["general"]);
			// if we are upgrading at the end of the cycle we add the fee as the setup price
			$packageArray["packageSetup"] = ((int)$this->_upgradeDowngradeType ) ? BillingLocale::getNumberFromInput($packagePrices["fee"]) : 0;
			$packageArray["couponPrice"] = BillingLocale::getNumberFromInput($packagePrices["coupon"]);
			$packageArray["couponNumCycles"] = BillingLocale::getNumberFromInput($packagePrices["couponcycles"]);
		}
		if(is_array($packageInfo["domains"])) {
			$packageArray["domainIDs"] = array("domainID"=> array());
			foreach ($packageInfo["domains"] as $domain){
				$packageArray["domainIDs"]["domainID"][] = $domain["domainID"];
			}
		}

		$packageArray["packageAddons"] = array("packageAddon"=>$packageAddons);
		$packageArray["packageAttributes"] = array("packageAttribute"=>array_values($productAttributes));

		// lets get the percentage of the cycle we are through here.
		$feeFromDate = $now = time()-(time()%(24*60*60))+(24*60*60);

		$additionalCycles = 0;
		$feeToDate = $cycleEnd = (int)$packageInfo["packageDateNextRenewal"];
		while ( ($cycleStart = getLastBillingTimestamp($cycleEnd, $packageInfo["cycleID"])) > $now ){
			$additionalCycles++;
			$cycleEnd = getLastBillingTimestamp($cycleEnd, $packageInfo["cycleID"]);
			dbg("taking cycle end backwards one step ....");
		}
		$cycleLength = $cycleEnd-$cycleStart;
		if ((int)$cycleLength){
			$through = $now-$cycleStart;
			$percentageThrough = $through/$cycleLength;
			$percentageLeft = 1-$percentageThrough;
			if ($percentageThrough>1 || $percentageLeft<0){
                $this->_upgradeDowngradeType = self::DFFERED_UPGRADE_DOWNGRADE; // we didn't calculate a valid percentage
			}
		} else {
            $this->_upgradeDowngradeType = self::DFFERED_UPGRADE_DOWNGRADE; // we didnt have a cycle length and couldn't get a percentage
			// so we can't do any proration
		}

		dbg("packageUpgradeDowngradeType");
		dbg($this->_upgradeDowngradeType );
		dbg("cycleLength");
		dbg($cycleLength);
		dbg("percentageLeftUpgrade");
		dbg($percentageLeft);

		switch ((int)$this->_upgradeDowngradeType)
		{
		case 0: // need to add proration credits
			$doProrationAndCredits = 1;
			$systemQueueDateToRun = time();
			break;
		case 1: // don't need to add proration credits per request
			$systemQueueDateToRun = time();
			break;
		default:
			$systemQueueDateToRun = $packageInfo["packageDateNextRenewal"];
		}
		dbg("systemQueueDateToRun");
		dbg($systemQueueDateToRun);

		$packageArray["upgradeLockingDate"] = Billing\sql_date($systemQueueDateToRun);

		$previousSubscription = Factory()->Subscription($packageInfo["packageID"]);
		if (!$previousSubscription->canUpgrade()) {
			billing_put_error(blmsg("panelUpgrade" == $this->subCommand ? "TRANS_SUBSCRIPTION_CANNOT_UPGRADE" : "TRANS_SUBSCRIPTION_CANNOT_DOWNGRADE"));
			redirect("packages.php", getActionID("ShowPackages"), array("clientID" => $packageInfo["clientID"]));
			return $this->hasExecutedSuccessfully();
		}

		if (self::DFFERED_UPGRADE_DOWNGRADE == $this->_upgradeDowngradeType) {
			$packageArray["packageStatus"] = Subscription::STATUS_PENDING;
			$packageArray["packageStatusReason"] = blmsg(
				"TRANS_SUBSCRIPTION_STATUS_REASON_PRIOR_UPGRADE_SCHEDULED",
				array(
					"DATE" => BillingLocale::getDateTimeShort($systemQueueDateToRun),
					"ID" => $previousSubscription->id,
					"NAME" => $previousSubscription->package_name,
				),
				AdminManager::getMasterAdmin()->getLocale()
			);
			$packageArray["packageCustomerStatusReason"] = blmsg(
				"TRANS_SUBSCRIPTION_STATUS_REASON_PRIOR_UPGRADE_SCHEDULED",
				array(
					"DATE" => BillingLocale::getDateTimeShort($systemQueueDateToRun),
					"ID" => $previousSubscription->id,
					"NAME" => $previousSubscription->package_name,
				),
				$previousSubscription->Customer->PrimaryContact->getLocale()
			);
			dbg('Make to pending cancellation previous subscription #' . $previousSubscription->id);
			$previousSubscription->package_pending_cancellation = Subscription::PENDING_CANCELLATION_TYPE_UPGRADE;
			$previousSubscription->package_cancellation_type = Plan::CANCELLATION_POLICY_CAN_ADMIN_AND_NOTHING_ON_SERVER;
			$previousSubscription->package_status_reason =  blmsg(
				"TRANS_SUBSCRIPTION_UPGRADE_DOWNGRADE_SUCCESSFULLY_NEXT_CYCLE",
				array("DATE" => BillingLocale::getDateTimeShort($systemQueueDateToRun)),
				AdminManager::getMasterAdmin()->getLocale()
			);
			$previousSubscription->package_customer_status_reason =  blmsg(
				"TRANS_SUBSCRIPTION_UPGRADE_DOWNGRADE_SUCCESSFULLY_NEXT_CYCLE",
				array("DATE" => BillingLocale::getDateTimeShort($systemQueueDateToRun)),
				$previousSubscription->Customer->PrimaryContact->getLocale()
			);
			$previousSubscription->update();
		} else {
			dbg('Cancel previous subscription #' . $previousSubscription->id);
			$packageArray["guid"] = $previousSubscription->guid; // for capability with PP 10.x
			$packageArray["externalID"] = $previousSubscription->external_id; // inherit external_id
			$previousSubscription->guid = null; // for capability with PP 10.x
			$previousSubscription->external_id = null;
			$previousSubscription->package_active = 0;
			$previousSubscription->package_status = Subscription::STATUS_CANCELED;
			$previousSubscription->package_status_reason = NULL;
			$previousSubscription->update();
		}

		$packageResult = dispatchMBAPI(array(
			"mbapi" => array(
				"command" => "SetPackages",
				"subCommand" => "insert",
				"params" => $packageArray,
			),
		), 1);
		if (!(int)$packageResult["mbapi"][0]["header"][0]["lastInsertID"][0]) {
			$message = (isset($packageResult["mbapi"][0]["header"][0]["errors"]) && isset($packageResult["mbapi"][0]["header"][0]["errors"][0]["error"])) ?
				$packageResult["mbapi"][0]["header"][0]["errors"][0]["error"][0]["message"][0] : "";
			billing_put_error(blmsg("TRANS_SUBSCRIPTION_UPGRADE_DOWNGRADE_ERROR_MSG", array("ERROR" => $message)));
			redirect("packages.php", getActionID("ShowPackages"), array("clientID" => $packageInfo["clientID"]));
			return $this->hasExecutedSuccessfully();
		}
		$newPackageID = $packageResult["mbapi"][0]["header"][0]["lastInsertID"][0];
		if (self::DFFERED_UPGRADE_DOWNGRADE != $this->_upgradeDowngradeType) {
			SubscriptionManager::onAfterUpgrade(Factory()->Subscription($newPackageID));
		}

		$invoicePackages = array();
		$invoicePackages[] = $newPackageID;
		$currencyID = $packageInfo["currencyID"];
		if ((int)$doProrationAndCredits){

			if (!$this->packageInfoCache[$newPackageID]){
				$this->packageInfoCache[$newPackageID] = $newPackageInfo = correctArray(grabFirstResultFromMBAPIArray(array(
					"mbapi" => array(
						"command" => "GetPackages",
						"params" => array(
							"packageID" => (int)$newPackageID,
							"getPackageAttributeData" => 1,
							"getPackageDatabaseData" => 1,
							"getDomainData" => 1,
							"getCertData" => 1,
							"getPackageAddonData" => 1,
						),
					),
				)));
			} else {
				$newPackageInfo = $this->packageInfoCache[$newPackageID];
			}

			$endCreditDescription = array();
			$totalCreditsAmount = 0;

			$paidAmounts = $this->getPackageLastLineItemTotal((int)$_REQUEST["packageID"], 0, $now);
			$paidAmountForProration = round($paidAmounts[0], 2);
			$paidAmountNotProrated = array_sum($allPaidAmounts = array_merge($paidAmounts[1], $paidAmounts[2]));
			list($newAmount, $discountPercentage, $taxes, $amount, $discountAmount, $taxZones, $additionalCyclesAmount, $additionalCycleAmounts, $thisTaxZoneGroupID) = array_values($this->getPackageDiscountedAndTaxedAmount($newPackageID, 0, $additionalCycles));

			$refundAmount = round($paidAmountForProration*$percentageLeft, 2)+$paidAmountNotProrated;
			$upgradeDowngradeFeeAmount = $this->_ajustAddonsOnly ? 0 : BillingLocale::getNumberFromInput($_REQUEST["packagePrices"]["fee"]);
			$chargeAmount = round($newAmount*$percentageLeft, 2)+$additionalCyclesAmount+$upgradeDowngradeFeeAmount;

			$proratedAmts = array(
				round($paidAmountForProration*$percentageLeft, 2),
				round($newAmount*$percentageLeft, 2),
			);

			if ($refundAmount>0){
				$refundFinalAmount = round($refundAmount, 2);

				$description = array();

				$description[] = array(
					"name" => TRANS_PREVIOUS_PACKAGE.": ".$packageInfo["packageID"]." ".TRANS_FOR." ".round($percentageLeft*100, 2)."% ".TRANS_OF." ".@constant(getCycleName($packageInfo["cycleID"]))." @ ".$currencyID." ".number_format($paidAmountForProration, 2, ".", ""),
					"packageName" => $packageInfo["packageName"],
					"op" => "+",
					"value" => $proratedAmts[0],
				);

				if ($paidAmountNotProrated>0){
					foreach ($allPaidAmounts as $amt){
						$description[] = array(
							"name" => TRANS_PAID_FOR_CYCLE,
							"op" => "+",
							"value" => round($amt, 2),
						);
					}
				}

				$totalCreditsAmount += round($refundFinalAmount, 2);
				$endCreditDescription = $description;

				$description[] = array(
					"name" => "",
					"op" => "=",
					"value" => round($refundFinalAmount, 2),
				);


				dbg("setting credit of: ".$refundFinalAmount);
				dispatchMBAPI(toXML(array(
					"mbapi" => array(
						"command" => "SetCreditAccounting",
						"params" => array(
							"clientID" => $packageInfo["clientID"],
							"ledgerAccountName" => "revenue",
							"amount" => round($refundFinalAmount, 2),
							"currencyID" => $packageInfo["currencyID"],
							"description" => $this->formatTextProrationDescription($description, $currencyID),
						),
					),
				)));
			}
			if ($chargeAmount>0){
				$chargeFinalAmount = round($chargeAmount, 2);

				$description = array();

				$description[] = array(
					"name" => TRANS_NEW_PACKAGE.": ".$newPackageID." ".TRANS_FOR." ".round($percentageLeft*100, 2)."% ".TRANS_OF." ".@constant(getCycleName($packageArray["cycleID"]))." @ ".$currencyID." ".number_format($newAmount, 2, ".", ""),
					"packageName" => $newPackageInfo["packageName"],
					"op" => "+",
					"value" => $proratedAmts[1],
				);
				if ($upgradeDowngradeFeeAmount>0){
					$description[] = array(
						"name" => blmsg('TRANS_X_FEE'),
						"packageName" => $newPackageInfo["packageName"],
						"op" => "+",
						"value" => $upgradeDowngradeFeeAmount,
					);
				}
				if ($additionalCyclesAmount>0){
					foreach ($additionalCycleAmounts as $amt){
						$description[] = array(
							"name" => TRANS_ADDITIONAL_CYCLE,
							"op" => "+",
							"value" => round($amt, 2),
						);
					}
				}

				$description[] = array(
					"name" => "",
					"op" => "=",
					"value" => round($chargeFinalAmount, 2),
				);

				dbg("setting fee of: ".$chargeFinalAmount);

				$tPackageInfo = array_merge(array(), $packageInfo);
				dbg("using tax zone group id: ".$thisTaxZoneGroupID." for package id: ".$tPackageInfo["packageID"]);

				$invoicePackages[] = $this->addProrationFee($tPackageInfo, blmsg('TRANS_PRORATED_X_FEE'), $this->formatHTMLProrationDescription($description, $currencyID), $chargeFinalAmount, $feeFromDate, $feeToDate, $newPackageID);
			}

##############################################################################
## Here are make the fees for the new addons that were just created
##############################################################################
			foreach ((array)$newPackageInfo["packageAddons"] as $addon){
				if (!$addon["packageAddonActive"]) continue;

				list($newAmount, $discountPercentage, $taxes, $amount, $discountAmount, $taxZones, $additionalCyclesAmount, $additionalCycleAmounts, $thisTaxZoneGroupID) = array_values($this->getPackageDiscountedAndTaxedAmount($newPackageID, $addon["packageAddonID"], $additionalCycles));
				$packageAddonUpgradeDowngradeFee = BillingLocale::getNumberFromInput($_REQUEST["prices"][$addon["packageAddonProductID"]]["fee"]);
				$chargeAmount = ($newAmount*$percentageLeft)+$additionalCyclesAmount+$packageAddonUpgradeDowngradeFee;
				if ($chargeAmount>0) {
					$chargeFinalAmount = round($chargeAmount, 2);

					$proratedAmts = array(
						round($newAmount*$percentageLeft, 2),
					);

					$description = array();

					$description[] = array(
						"name" => TRANS_NEW_PACKAGE_ADDON.": ".$addon["packageAddonID"]." ".TRANS_FOR." ".round($percentageLeft*100, 2)."% ".TRANS_OF." ".@constant(getCycleName($packageArray["cycleID"]))." @ ".$currencyID." ".number_format($newAmount, 2, ".", ""),
						"packageName" => $addon["packageAddonDisplayName"],
						"op" => "+",
						"value" => $proratedAmts[0],
					);
					if ($packageAddonUpgradeDowngradeFee>0){
						$description[] = array(
							"name" => TRANS_X_FEE,
							"packageName" => $addon["packageAddonDisplayName"],
							"op" => "+",
							"value" => $packageAddonUpgradeDowngradeFee,
						);
					}
					if ($additionalCyclesAmount>0){
						foreach ($additionalCycleAmounts as $amt){
							$description[] = array(
								"name" => TRANS_ADDITIONAL_CYCLE,
								"op" => "+",
								"value" => round($amt, 2),
							);
						}
					}

					$description[] = array(
						"name" => "",
						"op" => "=",
						"value" => $chargeFinalAmount,
					);

					dbg("adding fee package of: ".$chargeFinalAmount);

					$tPackageInfo = array_merge(array(), $newPackageInfo);
					$tPackageInfo["taxZoneGroupID"] = $addon["taxZoneGroupID"];
					$tPackageInfo["packageDiscountable"] = $addon["packageDiscountable"];
					dbg("using tax zone group id: ".$thisTaxZoneGroupID." for package id: ".$addon["packageID"]." package addon id: ".$addon["packageAddonID"]);

					$invoicePackages[] = $this->addProrationFee($tPackageInfo, blmsg('TRANS_PRORATED_X_FEE'), $this->formatHTMLProrationDescription($description, $currencyID), $chargeFinalAmount, $feeFromDate, $feeToDate, $addon["packageID"], $addon["packageAddonID"]);
				}
			}
##############################################################################
## End new addon fees
##############################################################################

##############################################################################
## Here are make the credits for the existing addons that are being canceled
##############################################################################
			foreach ((array)$packageInfo["packageAddons"] as $addon){
				if (!$addon["packageAddonActive"]) continue;

				$paidAmounts = $this->getPackageLastLineItemTotal($packageInfo["packageID"], $addon["packageAddonID"], $now);
				$paidAmountForProration = $paidAmounts[0];
				$paidAmountNotProrated = array_sum($allPaidAmounts = array_merge($paidAmounts[1], $paidAmounts[2]));

				$refundAmount = round($paidAmountForProration*$percentageLeft, 2)+$paidAmountNotProrated;

				$proratedAmts = array(
					round($paidAmountForProration*$percentageLeft, 2),
				);

				if ($refundAmount>0){
					$refundFinalAmount = round($refundAmount, 2);

					$description = array();

					$description[] = array(
						"name" => TRANS_PREVIOUS_PACKAGE_ADDON.": ".$addon["packageAddonID"]." ".TRANS_FOR." ".round($percentageLeft*100, 2)."% ".TRANS_OF." ".@constant(getCycleName($packageInfo["cycleID"]))." @ ".$currencyID." ".number_format($paidAmountForProration, 2, ".", ""),
						"packageName" => $addon["packageAddonDisplayName"],
						"op" => "+",
						"value" => $proratedAmts[0],
					);

					if ($paidAmountNotProrated>0){
						foreach ($allPaidAmounts as $amt){
							$description[] = array(
								"name" => TRANS_PAID_FOR_CYCLE,
								"op" => "+",
								"value" => round($amt, 2),
							);
						}
					}

					$totalCreditsAmount += round($refundFinalAmount, 2);
					$endCreditDescription = array_merge($endCreditDescription, $description);

					$description[] = array(
						"name" => "",
						"op" => "=",
						"value" => $refundFinalAmount,
					);

					dbg("setting addon credit of: ".$refundFinalAmount);

					dispatchMBAPI(toXML(array(
						"mbapi" => array(
							"command" => "SetCreditAccounting",
							"params" => array(
								"clientID" => $packageInfo["clientID"],
								"ledgerAccountName" => "revenue",
								"amount" => round($refundFinalAmount, 2),
								"currencyID" => $packageInfo["currencyID"],
								"description" => $this->formatTextProrationDescription($description, $currencyID),
							),
						),
					)));
				}
			}

##############################################################################
## End existing addon credits
##############################################################################

			if ($totalCreditsAmount>0){
				$endCreditDescription[] = array(
					"name" => TRANS_TOTAL,
					"op" => "=",
					"value" => round($totalCreditsAmount, 2),
				);
				$invoiceComments = blmsg('TRANS_ITEMIZED_X_CREDITS')." ".blmsg('TRANS_FOR')." ".blmsg('TRANS_PACKAGE')." ".$packageInfo["packageID"]." (".$packageInfo["packageName"].")"."<br />".$this->formatHTMLProrationDescription($endCreditDescription, $currencyID);
			}

		} else {
			// set package expiration date for subscription and addons
			$previousSubscription->package_expiration_date = $previousSubscription->package_date_next_renewal;
			$previousSubscription->update();
		}

		foreach ($billingShareInfo as $share){
			foreach ($invoicePackages as $pkgID){
				if ((int)$pkgID){
					dispatchMBAPI(toXML(array(
						"mbapi" => array(
							"command" => "SetBillingAccountShare",
							"subCommand" => "insert",
							"params" => array(
								"packageID" => $pkgID,
								"billingAccountID" => $share["billingAccountID"],
								"billingAccountFallbackGroup" => $share["billingAccountFallbackGroup"],
								"billingAccountShareGroup" => $share["billingAccountShareGroup"],
								"billingAccountShareValue" => $share["billingAccountShareValue"],
							),
						),
					)));
					dbg("added share for package: ".$pkgID);
				}
			}
		}

		$generateOptions = array(
			"getClients" =>  array("clientID" => $packageInfo["clientID"]),
			"getPackages" =>  array("packageID" => (array)$invoicePackages),
			"setBillingMode" =>  "anniversary",
			"packageDateNextRenewalLessThan" => time() +10,
			"domainDateNextRenewalLessThan" => -1,
		);
		if ($invoiceComments) $generateOptions["invoiceComments"] = secureInput($invoiceComments);
		//if upgrade/downgrade not in the end of billing cycle we should change the resources_usage_date_next_renewal field value
		if (self::DFFERED_UPGRADE_DOWNGRADE != $this->_upgradeDowngradeType) {
			$generateOptions["resourceOveruseSubscriptionID"] = $previousSubscription->id;
		}

		$invoiceInfo = grabResultsFromMBAPIArray(array(
			"mbapi" => array(
				"command" => "ProcessInvoices",
				"params" => $generateOptions,
			),
		));
		dbg("invoice Info:");
		dbg($invoiceInfo);

        $subscription = Factory()->Subscription($newPackageID);
        if (self::DFFERED_UPGRADE_DOWNGRADE != $this->_upgradeDowngradeType) {
            // create invoice-factura now and prorate
            $addonIDs = array();
            foreach ((array)$packageInfo["packageAddons"] as $addon){
                $addonIDs[] = $addon["packageAddonID"];

            }
            $prorate = (self::INSTANT_UPGRADE_DOWNGRADE_WITH_PRORATION == $this->_upgradeDowngradeType) ? true : false ;
            InvoiceFacturaManager::createSingleFactura($packageInfo["packageID"], $addonIDs, $prorate);
            $subscription->resources_usage_date_last_renewal = date("Y-m-d");
            $subscription->resources_usage_date_next_renewal = Billing\firstDayOfNextMonth();
        } else {
            $subscription->resources_usage_date_last_renewal = date("Y-m-d", $subscription->package_date_next_renewal);
            $subscription->resources_usage_date_next_renewal = Billing\firstDayOfNextMonth(new DateTime(date("Y-m-d", $subscription->package_date_next_renewal)));
        }
        $subscription->update();

		if ((int)$_REQUEST["packageProvision"] && $addProvisioning){
			$timeData = explode(";", date("i;G;j;n;Y", $systemQueueDateToRun));
			foreach ((array)$this->serverGroupIDs as $serverGroupID=>$configGroupID){
				if ((int)$serverGroupID){
					dispatchMBAPI(toXML(array(
						"mbapi" => array(
							"command" => "SetSystemQueue",
							"subCommand" => "insert",
							"params" => array(
								"systemQueueCommand" => "ProcessPanel",
								"systemQueueSubCommand" => $this->subCommand,
								"systemQueueCommandInput" => htmlentities(toXML(array(
									"mbapi" => array(
										"command" => "ProcessPanel",
										"subCommand" => $this->subCommand,
										"params" => array(
											"subCommand" => $this->subCommand,
											"serverGroupID" => (int)$serverGroupID,
											"configGroupID" => (int)$configGroupID,
											"packageID" => $newPackageID,
											"oldPackageID" => $packageInfo["packageID"],
										),
									),
								))),
								"serverGroupID" => (int)$serverGroupID,
								"configGroupID" => (int)$configGroupID,
								"systemQueueMinute" => $timeData[0],
								"systemQueueHour" => $timeData[1],
								"systemQueueDay" => $timeData[2],
								"systemQueueMonth" => $timeData[3],
								"systemQueueYear" => $timeData[4],
								"systemQueueIsRecurring" => 0,
								"systemQueueDateCreated" => time(),
								"systemQueueDateToRun" => $systemQueueDateToRun,
								"systemQueueDateStarted" => 0,
								"systemQueueDateFinished" => 0,
								"packageID" => $newPackageID,
							),
						),
					)));
				}
			}
		} else {
			$this->setTodo(blmsg('TRANS_PACKAGE_X').": ".$newPackageID, blmsg('TRANS_PACKAGE_X_DESC'), $newPackageID);
		}

		$this->addInfoMessage($newPackageID);
		redirect("packages.php", getActionID("ShowPackages"), array("clientID" => $packageInfo["clientID"]));
		return $this->hasExecutedSuccessfully();
	}

    private function addInfoMessage($packageId)
    {
        switch ($this->_upgradeDowngradeType) {
            case self::INSTANT_UPGRADE_DOWNGRADE_WITH_PRORATION:
            case self::INSTANT_UPGRADE_DOWNGRADE_WITHOUT_PRORATION:
                getCurrentSession()->Storage->infoMessages[] = blmsg("TRANS_SUBSCRIPTION_UPGRADE_DOWNGRADE_SUCCESSFULLY");
                break;
            default:
                $subscription = new Subscription($packageId);
                getCurrentSession()->Storage->infoMessages[] = blmsg("TRANS_SUBSCRIPTION_UPGRADE_DOWNGRADE_SUCCESSFULLY_NEXT_CYCLE", array("DATE" => $subscription->upgrade_locking_date));
                break;
        }

    }

	private function addProrationFee($packageInfo, $name, $description, $amount, $from=false, $to=false, $packageID=0, $packageAddonID=0)
	{
		if (!$from)$from = time();
		if (!$to)$to = $packageInfo["packageDateNextRenewal"];

		dbg("adding fee package with info:");
		dbg("name: ".$name);
		dbg("description: ".$description);
		dbg("amount: ".$amount);
		dbg("package info");
		dbg($packageInfo);

		$adminLocale = AdminManager::getMasterAdmin()->getLocale();
		if (!$this->feeProduct){
			$this->feeProduct = correctArray(grabFirstResultFromMBAPIArray(array(
				"mbapi" => array(
					"command" => "GetProducts",
					"params" => array(
						"productType" => Plan::TYPE_FEE,
                        "productName" => blmsg("TRANS_PRORATION_FEE", array(), $adminLocale),
					),
				),
			)));
		}

		if (!$this->feeProduct["productID"]){
			$result = dispatchMBAPI(toXML(array(
				"mbapi" => array(
					"command" => "SetProducts",
					"subCommand" => "insert",
					"params" => array(
						"productName" => blmsg("TRANS_PRORATION_FEE", array(), $adminLocale),
						"productComments" => blmsg("TRANS_ADDITIONAL_CHARGES_DUE_TO_PARTIAL_CYCLE_PRORATION", array(), $adminLocale),
						"productActive" => 0,
						"productType" => Plan::TYPE_FEE,
						"productProratable" => 0,
						"productVariants" => array(
							"productVariant" => array(
								"productVariantDisplayName" => blmsg("TRANS_PRORATION_FEE", array(), $adminLocale),
								"productVariantActive" => 0,
							),
						),
					),
				),
			)), 1);
			$this->feeProductID = $result["mbapi"][0]["header"][0]["lastInsertID"][0];
		} else {
			$this->feeProductID = $this->feeProduct["productID"];
		}

		if (!$this->feeProductVariant){
			$this->feeProductVariant = grabFirstResultFromMBAPIArray(array(
				"mbapi" => array(
					"command" => "GetProductVariants",
					"params" => array(
						"productID" => $this->feeProductID
					),
				),
			));

			$this->feeProductVariantID = $this->feeProductVariant["productVariantID"];
		} else {
			$this->feeProductVariantID = $this->feeProductVariant["productVariantID"];
		}

		$this->feePackageCmd = array(
			"mbapi" => array(
				"command" => "SetPackages",
				"subCommand" => "insert",
				"params" => array(
					"clientID" => $packageInfo["clientID"],
					"productID" => $this->feeProductID,
					"productVariantID" => $this->feeProductVariantID,
					"packageName" => utf8_html_entity_encode($name),
					"packageComments" => utf8_html_entity_encode($description),
					"packageDateNextRenewal" => $t = time(),
					"packageDateStopRenewal" => $t+1,
					"cycleID" => $packageInfo["cycleID"],
                    "packageActive" => 0,
                    "packageStatus" => Subscription::STATUS_COMPLETED,
					"taxZoneGroupID" => $packageInfo["taxZoneGroupID"],
					"cycleDiscountGroupID" => $packageInfo["cycleDiscountGroupID"],
                    "packageDiscountable" => $packageInfo["packageDiscountable"],
					"packageProratable" => 0,
					"packagePriceLock" => $packageInfo["packagePriceLock"],
					"packageDateCreated" => $t,
					"packageDateBeginRenewal" => $t,
					"packageType" => getAPITypeID("products", "fee"),
					"packagePrice" => $amount,
					"packageOriginalPrice" => $amount,
					"currencyID" => $packageInfo["currencyID"],
					"feeForPackageID" => $packageID,
					"feeForPackageAddonID" => $packageAddonID,
					"orderFormID" => $packageInfo["orderFormID"],
					"packageAttributes" => array(
						"packageAttribute" => array(
							array(
								"packageAttributeName" => "fromDate",
								"packageAttributeValue" => $from,
								"packageAttributeDateCreated" => time(),
							),
							array(
								"packageAttributeName" => "toDate",
								"packageAttributeValue" => $to,
								"packageAttributeDateCreated" => time(),
							),
						),
					),
				),
			),
		);
		$packageResult = dispatchMBAPI(toXML($this->feePackageCmd), 1);
		dbg("fee package addition result:");
		dbg($packageResult);
		return $packageResult["mbapi"][0]["header"][0]["lastInsertID"][0];
	}

	private function getPackageLastLineItemTotal($packageID, $packageAddonID=0, $now=false)
	{
		dbg("running get package last line item total");
		dbg("package id: ".$packageID);
		dbg("package addon id: ".$packageAddonID);
		dbg("now: ".$now);
		$now = ($now)?$now:time();
		// lets get the fees for this addon or product
		$feeForName = ((int)$packageAddonID)?"getFeesForPackageAddonID":"getFeesForPackageID";
		$feeForData = ((int)$packageAddonID)?$packageAddonID:$packageID;
		$feePackages = correctArray(grabResultsFromMBAPIArray($x = array(
			"mbapi" => array(
				"command" => "GetPackages",
				"params" => array(
					$feeForName => $feeForData,
					"getPackageAttributeData" => 1,
				),
			),
		)));
		dbg(toXML($x));
		dbg($feePackages);
		$feeReturns = array();
		foreach ((array)$feePackages as $fpackage){
			$attributes = $this->getPackageAttributesByName($fpackage);
			dbg($attributes);
			dbg(date("m/d/Y g:i a", $attributes["fromDate"]["packageAttributeValue"]));
			dbg(date("m/d/Y g:i a", $now));
			dbg(date("m/d/Y g:i a", $attributes["toDate"]["packageAttributeValue"]));
			if ($attributes["toDate"]["packageAttributeValue"]>$now){
				if ($attributes["fromDate"]["packageAttributeValue"]<$now){
					$period = $attributes["toDate"]["packageAttributeValue"]-$attributes["fromDate"]["packageAttributeValue"];
					$perThrough = ($now-$attributes["fromDate"]["packageAttributeValue"])/$period;
					$perLeft = 1-$perThrough;
					dbg("percentage through and left:");
					dbg($perThrough);
					dbg($perLeft);
					if ($perLeft>1 || $perLeft<0)$perLeft=1;
					$feeReturns[] = round($fpackage["packagePrice"]*$perLeft, 2);
				} else {
					$feeReturns[] = $fpackage["packagePrice"];
				}
			}
		}

		$lineItems = arrayFor("lineItems", array(
			"packageID" => (int)$packageID,
			"sortColumn" => "lineItemDateCurrentRenewal",
			"sortDir" => "ASC",
			"lineItemDateNextRenewalGreaterThan" => $now-1,
			"packageAddonID" => (int)$packageAddonID,
		));

		dbg("got line items:");
		dbg($lineItems);

		$firstReturn = 0;
		$return = array();
		foreach ($lineItems as $li){
			if (!(int)$li["lineItemIsUsage"]){
				if ($firstReturn>0){
					$return[] = (float)($li["lineItemTotalAmount"]-$li["lineItemSetupAmount"]);
				} else {
					$firstReturn = (float)($li["lineItemTotalAmount"]-$li["lineItemSetupAmount"]);
				}
			}
		}

		dbg("returning: ".$firstReturn." -- ");
		dbg($return);

		return array($firstReturn, $return, $feeReturns);
	}

	private function formatHTMLProrationDescription($description, $currencyID)
	{
		$out = "<table border=\"0\" width=\"100%\" align=\"right\">\n";
		foreach ((array)$description as $x=>$row){
			$out .= "	<tr>\n";
			$out .= "		<td valign=\"top\" style=\"text-align= right;\">".$row["name"].(($row["packageName"])?"<br />".$row["packageName"]:"")."</td>\n";
			$out .= "		<td valign=\"top\" style=\"text-align= right;\" width=\"25\">".$row["op"]."</td>\n";
			$out .= "		<td valign=\"top\" width=\"100\" style=\"text-align= right;\">".$currencyID." ".number_format($row["value"], 2, ".", "")."</td>\n";
			$out .= "	</tr>\n";
		}
		$out .= "</table>";
		return $out;
	}

	private function formatTextProrationDescription($description, $currencyID)
	{
		$out = "";
		foreach ((array)$description as $row){
			$out .= $row["op"]." ".$row["name"]." ".$currencyID." ".$row["value"]." ";
		}
		return $out;
	}

	private function getPackageDiscountedAndTaxedAmount($packageID, $packageAddonID=0, $additionalCycles=0)
	{
		dbg("getting package discounted and taxed");
		dbg("package id: ".$packageID);
		dbg("package addon id: ".$packageAddonID);
		dbg("additional cycles: ".$additionalCycles);

		if (!$this->cycleDiscountsCache)$this->generateCycleDiscountGroupCache();

		if (!$this->packageInfoCache[$packageID]){
			$this->packageInfoCache[$packageID] = $packageInfo = correctArray(grabFirstResultFromMBAPIArray(array(
				"mbapi" => array(
					"command" => "GetPackages",
					"params" => array(
						"packageID" => (int)$packageID,
						"getPackageAttributeData" => 1,
						"getPackageDatabaseData" => 1,
						"getDomainData" => 1,
						"getCertData" => 1,
						"getPackageAddonData" => 1,
					),
				),
			)));
		} else {
			$packageInfo = $this->packageInfoCache[$packageID];
		}
		$this->populateClientInfo($packageInfo["clientID"]);

		if ($packageAddonID){
			// if we need to do this for an addon, add in the info...
			$packageAddonInfo = oneByID("packageAddons", $packageAddonID);
			$packageInfo["packageName"] = $packageAddonInfo["packageAddonDisplayName"];
			$packageInfo["couponNumCycles"] = $packageAddonInfo["couponNumCycles"];
			$packageInfo["couponPrice"] = $packageAddonInfo["couponPrice"];
			$packageInfo["packageDiscount"] = $packageAddonInfo["packageAddonDiscount"];
			$packageInfo["packageDiscountable"] = $packageAddonInfo["packageAddonDiscountable"];
			$packageInfo["packageAddonID"] = $packageAddonInfo["packageAddonID"];
			$packageInfo["packagePrice"] = $packageAddonInfo["packageAddonPrice"];
			$packageInfo["packageOriginalPrice"] = $packageAddonInfo["packageAddonOriginalPrice"];
			$packageInfo["packageSetup"] = $packageAddonInfo["packageAddonSetup"];
			$packageInfo["taxZoneGroupID"] = (int)$packageAddonInfo["taxZoneGroupID"];
		}

		$volumnDiscountAmount = (isset($this->clientInfo["clientDiscountLevel"]) && $this->clientInfo["clientDiscountLevel"][0]>$this->clientAttributes["minimumdiscount"])
			? (float)$this->clientInfo["clientDiscountLevel"]
			: (float)$this->clientAttributes["minimumdiscount"];
		dbg("volumn discount amt: ".$volumnDiscountAmount, 0, 1);
		$cycleBasedDiscount = 0;
		if ((int)$packageInfo["cycleDiscountGroupID"]){
			foreach ((array)$this->cycleDiscountsCache[$packageInfo["cycleDiscountGroupID"]][$packageInfo["cycleID"]] as $cDisc){
				$cycleBasedDiscount += $cDisc["cycleDiscountAmount"];
			}
		}

		$packageInfo["packageDiscount"] = ($packageInfo["packageDiscount"]>$volumnDiscountAmount+$cycleBasedDiscount)?$packageInfo["packageDiscount"]:$volumnDiscountAmount+$cycleBasedDiscount;

		if (isset($this->clientAttributes["maximumdiscount"])){
			if ($packageInfo["packageDiscount"]>(float)$this->clientAttributes["maximumdiscount"]){
				$packageInfo["packageDiscount"] = (float)$this->clientAttributes["maximumdiscount"];
			}
		} else {
			if ($packageInfo["packageDiscount"]>DEFAULT_MAXIMUM_DISCOUNT){
				$packageInfo["packageDiscount"] = DEFAULT_MAXIMUM_DISCOUNT;
			}
		}
		$thisPackageDiscount = (!(int)$packageInfo["packageDiscountable"])?0:doubleval($packageInfo["packageDiscount"])/100;

		dbg("total discount: ".$thisPackageDiscount, 0, 1);

		$thisPackageAmount = ($packageInfo["couponNumCycles"]>0)?$packageInfo["couponPrice"]:$packageInfo["packagePrice"];
		$thisPackageDiscountAmount -= ($thisPackageAmount*$thisPackageDiscount);
        $store = Factory()->OrderForm($packageInfo["orderFormID"]);
		$currentTaxZones = $this->getApplicableTaxZones($store, $packageInfo["taxZoneGroupID"]);
		dbg("got applicable tax zones:");
		dbg($currentTaxZones);

		if (!(int)$this->clientInfo["applyTax"]){
			dbg("I DONT THINK I SHOULD APPLY TAX!");
			$currentTaxZones = null;
			$currentTaxZones = array();
		}

		$taxReturn = $this->calculateTaxes($thisPackageAmount-$thisPackageDiscountAmount, $currentTaxZones);
		$thisPackageTaxAmount = $taxReturn["amount"];

		$additionalCyclesAmount = 0;
		$additionalCycleAmounts = array();
		if ((int)$additionalCycles){
			$couponNumCycles = $packageInfo["couponNumCycles"]--;
			for ($i=0; $i<$additionalCycles; $i++){
				$thisPrice = (--$couponNumCycles<0)?$packageInfo["packagePrice"]:$packageInfo["couponPrice"];
				$thisPrice -= ($thisPackageDiscount*$thisPrice);
				$thisTaxReturn = $this->calculateTaxes($thisPrice, $currentTaxZones);
				$thisPrice += $thisTaxReturn["amount"];
				$additionalCycleAmounts[] = round($thisPrice, 2);
				$additionalCyclesAmount += round($thisPrice, 2);
			}
		}


		return array(
			"totalAmount" => round($thisPackageAmount, 2),
			"discount" => $thisPackageDiscount,
			"taxes" => $thisPackageTaxAmount,
			"amount" => $thisPackageAmount,
			"discountAmount" => $thisPackageDiscountAmount,
			"taxZones" => $taxReturn["zones"],
			"additionalCyclesAmount" => $additionalCyclesAmount,
			"additionalCycleAmounts" => $additionalCycleAmounts,
			"taxZoneGroupID" => $packageInfo["taxZoneGroupID"],
		);
	}

	private function populateClientInfo($clientID)
	{
		dbg("getting client info for id: ".$clientID);
		if (!(int)$clientID) return dbg("BAD CLIENT ID").dbg(debug_backtrace());
		if (!$this->clientInfo){
			$this->clientInfo = correctArray(grabFirstResultFromMBAPIArray(array(
				"mbapi" => array(
					"command" => "GetClients",
					"params" => array(
						"clientID" => $clientID,
						"mergeDefaultBillingAccount" => 1,
						"getContactData" => 1,
						"getCompanyData" => 1,
						"getAttributeData" => 1,
						"hooks" => array(
							"hook" => array(
								"hookFunctionName" => "calculateResellerVolumnDiscount",
							),
						),
					),
				),
			)));
			$attributes = array();
			if ( isset($this->clientInfo["groupAttributes"]) && isset($this->clientInfo["groupAttributes"][0]["groupAttribute"]) ) {
				$groupAttributes = correctArray($this->clientInfo["groupAttributes"][0]["groupAttribute"]);
				foreach ( $groupAttributes as $groupAttribute ) {
					if ( 1 ==$groupAttribute["groupActive"] ) {
						$attributes[$groupAttribute["groupAttributeName"]] = $groupAttribute["groupAttributeValue"];
					}
				}
			}
			dbg("client attributes based on group:");
			dbg($attributes);
			if ( isset($this->clientInfo["attributes"]) && isset($this->clientInfo["attributes"][0]["attribute"]) ) {
				$clientAttributes = correctArray($this->clientInfo["attributes"][0]["attribute"]);
				foreach ($clientAttributes as $clientAttribute){
					$attributes[$clientAttribute["attributeName"]] = $clientAttribute["clientAttributeValue"];
				}
			}
			dbg("client attributes after manual overrides:", 0, 1);
			dbg($attributes, 0, 1);
			$this->clientAttributes = $attributes;
		}
	}

	/**
	 * generates the cycle discount cache
	 *
	 * @return	nothing
	 */
	private function generateCycleDiscountGroupCache()
	{
		dbg("getting cycle discount group data");
		$cycleDiscountData = arrayFor("cycleDiscountGroups", array(
			"cycleDiscounts" => "",
		));
		dbg("cycle discount groups with discounts");
		dbg($cycleDiscountData);

		$this->cycleDiscountsCache = array();
		foreach ((array)$cycleDiscountData as $discountGroup){
			if (isset($discountGroup["cycleDiscounts"])){
				foreach ((array)$discountGroup["cycleDiscounts"] as $discount){
					$this->cycleDiscountsCache[$discount["cycleDiscountGroupID"]][$discount["cycleID"]][] = $discount;
				}
			}
		}
	}

	/**
	 * Calculates the taxes for this invoice amount
	 *
	 * @param amount	the amount to get taxes on
	 * @param taxzones  the array of tax zones to use to calculate taxes with
	 * @return the tax amount
	 */
	private function calculateTaxes($amount, $taxZones)
	{
		dbg("calculating taxes for amount: ".$amount);
		dbg("with tax zones:");
		dbg($taxZones);
		$ret = array();

		$tax = 0;
		for ($i = 0; $i < count($taxZones); $i++){
			$taxZone =& $taxZones[$i];
			$ret[$i]["taxZoneID"] = $taxZone["taxZoneID"];
			$ret[$i]["countriesISO2"] = $taxZone["countriesISO2"];
			$ret[$i]["taxZoneName"] = $taxZone["taxZoneName"];
			$ret[$i]["taxZoneAmountType"] = $taxZone["taxZoneAmountType"];
			$ret[$i]["taxZoneAmount"] = $taxZone["taxZoneAmount"];
			$ret[$i]["taxZoneDescription"] = $taxZone["taxZoneDescription"];
			$ret[$i]["ledgerAccountID"] = $taxZone["ledgerAccountID"];
			$ret[$i]["taxZoneActive"] = $taxZone["taxZoneActive"];

			if ($taxZone["taxZoneAmountType"]==0){ // if 0 we use as percentage
				dbg("calculating tax on $amount using percentage ".$taxZone["taxZoneAmount"]."");
				$cTax = $this->getLocalAmount($amount*($taxZone["taxZoneAmount"]/100));
				dbg("tax was $cTax");
				$ret[$i]["amount"] = $cTax;
				$tax = $this->getLocalAmount($tax+$cTax);
			} else { // else  we use as set amount
				dbg("calculating tax on $amount using set amount ".$taxZone["taxZoneAmount"]."");
				$cTax = $this->getLocalAmount($taxZone["taxZoneAmount"]);
				$ret[$i]["amount"] = $cTax;
				$tax = $this->getLocalAmount($tax+$cTax);
			}
		}
		dbg("amount is: ".$tax);
		return array("amount"=>$tax, "zones"=>$ret);
	}

	/**
	 * generates the tax zone cache from db calls
	 *
	 * @return	nothing
	 */
	private function generateTaxZoneCache()
	{
		dbg("generating the tax zone cache");

		$taxData = correctArray(grabResultsFromMBAPIArray(array(
			"mbapi" => array(
				"command" => "GetTaxZoneGroups",
				"params" => array(
					"getTaxZoneData" => 1,
				),
			),
		)));

		dbg("tax data array:");
		dbg($taxData);

		$this->taxZoneGroupCache = array();
		foreach ((array)$taxData as $row){
			$this->taxZoneGroupCache[$row["taxZoneGroupID"]] = $row;
		}

		dbg("tax zone groups:");
		dbg($this->taxZoneGroupCache);

	}

	/**
	 * gets an array of tax zones that apply to this package based on the group id and client info
	 *
     * @param $store	online store
	 * @param taxZoneGroupID	The group id of the tax zone group we want to test the zones for.
	 * @param clientInfo	An array return from GetClients mbapi command we use to test country and
	 *						other things for tax application.
	 */
	private function getApplicableTaxZones($store, $taxZoneGroupID)
	{
		dbg("getting applicable tax zones for group: ".$taxZoneGroupID);
		if (!$this->taxZoneGroupCache)$this->generateTaxZoneCache();

		$clientInfo = $this->clientInfo;
		dbg("tax zone group id: ".$taxZoneGroupID);
		if (!(int)$taxZoneGroupID)return array(); // 0 group id

		$applicableTaxes = $this->taxZoneGroupCache[(int)$taxZoneGroupID]["taxZones"];

		dbg("applicable tax zoneS: ");
		dbg($applicableTaxes);

		if (!count($applicableTaxes))return array(); // no zones

		dbg("client info we were passed:");
		dbg($clientInfo);

		$match1 = $clientInfo["contacts"][0]["contactState"];
		$match2 = $clientInfo["contacts"][0]["contactState"]."-".strtolower($clientInfo["contacts"][0]["contactCity"]);

		dbg("match 1 and 2");
		dbg($match1);
		dbg($match2);
        if (is_null($store->vendor_id)) {
            $country = CompanyManager::getProviderCompany()->Country;
        } else {
            $reseller = Factory()->Reseller($store->vendor_id);
            $country = Factory()->Company($reseller->company_id)->Country;
        }
		if ( StoreManager::isVatCountry($country->countries_iso_2) && !TaxZoneManager::isAppliesCountryTaxes($country->countries_iso_2, $countriesISO2, $clientType) ) {
			return  array();
		}

		foreach ($applicableTaxes as $k=>$v){
			if ((int)$v["taxZoneID"]){
		 		dbg("TAX COUNTRY TESTING: ".$v["countriesISO2"]." | ".$clientInfo["countriesISO2"]);
				if ($v["countries_iso_2"]==$clientInfo["countriesISO2"][0]){
					if ((preg_replace("/\s*/", "", $v["taxZoneName"])=="*" || // if its a star apply it
						preg_replace("/\s*/", "", $v["taxZoneName"])=="" || // if its nothing apply it
						strtolower(preg_replace("/\s*/", "", $v["taxZoneName"]))==strtolower($match1) || // if it matches match1 (state) apply it
						strtolower(preg_replace("/\s*/", "", $v["taxZoneName"]))==strtolower($match2))){ // if it matches match2 (state-city) apply it
						// if we get here we will apply the tax
					} else { // dont apply it
						unset($applicableTaxes[$k]);
					}
				} else {
					unset($applicableTaxes[$k]);
				}
			} else {
				unset($applicableTaxes[$k]);
			}
		}

		return $applicableTaxes;
	}

	private function getProductAttributesByName($productInfo, $serverGroupMap)
	{
		$attributes = (array)$productInfo["productAttributes"];
		$ret = array();
		foreach ($attributes as $row){
			unset($row["productAttributeID"]);
			$row["packageAttributeName"] = $row["productAttributeName"];
			unset($row["productAttributeName"]);
			$row["packageAttributeValue"] = $row["productAttributeValue"];
			unset($row["productAttributeValue"]);
			$row["packageAttributeDateCreated"] = $row["productAttributeDateCreated"];
			unset($row["productAttributeDateCreated"]);
			$row["configGroupID"] = $serverGroupMap[$row["serverGroupID"]]; // add in the correct config group id
			$ret[$row["packageAttributeName"]] = $row;
		}
		return $ret;
	}

	private function getPackageAttributesByName($packageInfo)
	{
		$attributes = (array)$packageInfo["packageAttributes"];
		$ret = array();
		foreach ($attributes as $row){
			$ret[$row["packageAttributeName"]] = $row;
		}
		return $ret;
	}

	private $_ajustAddonsOnly;
    private $_upgradeDowngradeType;
}