6401065; #--------------------------------------------------------------- my $pMessageList = [ 'The shipping price is formatted incorrectly. It should be formatted like %s.', 'The shipping price is too large. The price must be less than %s.', 'The shipping price is too small. The price must be greater than or equal to %s.', 'The class/location combination you selected were invalid. Please check and re-enter your selection.', 'The catalog shipping database does not have any shipping options defined for this location. Please contact us directly with your order.', 'Free Shipping', 'Standard Shipping', 'Your order has exceeded the shipping tables defined by the supplier, therefore it is not possible to calculate the shipping cost. Please contact the supplier with this information as they will be happy to process your order and will then be able to correct the shipping tables.

Thank you.', 'Please enter a shipping cost.', 'Please select a state or province.', ]; my %ZoneTable = ( "UK" => { "UndefinedRegion" => [13], }, "US" => { "UndefinedRegion" => [10], }, "CA" => { "UndefinedRegion" => [10], }, "AT" => { "UndefinedRegion" => [11], }, "BE" => { "UndefinedRegion" => [11], }, "BG" => { "UndefinedRegion" => [11], }, "CY" => { "UndefinedRegion" => [11], }, "CZ" => { "UndefinedRegion" => [11], }, "DK" => { "UndefinedRegion" => [11], }, "EE" => { "UndefinedRegion" => [11], }, "FI" => { "UndefinedRegion" => [11], }, "FR" => { "UndefinedRegion" => [11], }, "DE" => { "UndefinedRegion" => [11], }, "GR" => { "UndefinedRegion" => [11], }, "HU" => { "UndefinedRegion" => [11], }, "IE" => { "UndefinedRegion" => [11], }, "IT" => { "UndefinedRegion" => [11], }, "LV" => { "UndefinedRegion" => [11], }, "LT" => { "UndefinedRegion" => [11], }, "LU" => { "UndefinedRegion" => [11], }, "MT" => { "UndefinedRegion" => [11], }, "NL" => { "UndefinedRegion" => [11], }, "PL" => { "UndefinedRegion" => [11], }, "PT" => { "UndefinedRegion" => [11], }, "RO" => { "UndefinedRegion" => [11], }, "SK" => { "UndefinedRegion" => [11], }, "SI" => { "UndefinedRegion" => [11], }, "ES" => { "UndefinedRegion" => [11], }, "SE" => { "UndefinedRegion" => [11], }, ); my %ClassTable = ( 6 => ['Royal Mail Intl Standard', 0, ''], 8 => ['Example Class - 2 Day', 0, ''], 10 => ['Royal Mail First Class', 0, ''] ); my $phashDefinedCategories = { 'Normal' => 1, }; my $sDefaultCategory = 'Normal'; my %ShippingTable = ( 10 => { 13 => [ {'CalculationBasis' => 2, 'WeightFactor' => 1.000000, 'AltWeightFactor' => 1.000000, 'TaxAppliesToShipping' => 1, 'ShippingCostsIncludeTax' => 0, 'ExcessAction' => 'AddFurther', 'IncrementalWeight' => 100000.000000, 'IncrementalCharge' => 6000}, { "wt" => 100000, "cost" => 4500}, { "wt" => 300000, "cost" => 8500}, { "wt" => 500000, "cost" => 12500}, ], }, 6 => { 10 => [ {'CalculationBasis' => 2, 'WeightFactor' => 1.000000, 'AltWeightFactor' => 1.000000, 'TaxAppliesToShipping' => 1, 'ShippingCostsIncludeTax' => 0, 'ExcessAction' => 'AddFurther', 'IncrementalWeight' => 100000.000000, 'IncrementalCharge' => 8500}, { "wt" => 100000, "cost" => 8500}, { "wt" => 300000, "cost" => 12500}, { "wt" => 500000, "cost" => 17500}, ], }, 8 => { 11 => [ {'CalculationBasis' => 2, 'WeightFactor' => 1.000000, 'AltWeightFactor' => 1.000000, 'TaxAppliesToShipping' => 1, 'ShippingCostsIncludeTax' => 0, 'ExcessAction' => 'AddFurther', 'IncrementalWeight' => 100000.000000, 'IncrementalCharge' => 8500}, { "wt" => 100000, "cost" => 8500}, { "wt" => 300000, "cost" => 12500}, { "wt" => 500000, "cost" => 18500}, ], }, ); my $phashWeightConfiguration = { 0 => {'UseWeightIfUndefined' => 0, 'DefaultWeight' => '0.25' ,'OptimalWeight' => '' ,}, 4 => {'UseWeightIfUndefined' => 0, 'DefaultWeight' => '0.25' ,'OptimalWeight' => '' ,}, 5 => {'UseWeightIfUndefined' => 0, 'DefaultWeight' => '' ,'OptimalWeight' => '' ,}, }; my ($ShippingBasis, $SimpleCost, $UnknownRegion, $UnknownRegionCost, $WaiveCharges, $WaiveThreshold); $ShippingBasis = 'ByZoneClass'; $UnknownRegion = 'Default'; $UnknownRegionCost = 12500; $WaiveCharges = 'No'; $WaiveThreshold = 100000.000000; my $bPricesIncludesTax = 0; my $dTaxInclusiveMultiplier = 1.000000; my $nHandlingCharge = 1000; my $nHandlingProportion = 0; my %ParentZoneTable = ( "US" => [10, ], "CA" => [10, ], ); use strict; my $UNDEFINED = 'UndefinedRegion'; my $sOnlineError = ''; $::UPS_XPCI_VERSION = '1.0001'; $::UPS_SUCCESSFUL = '1'; $::UPS_FAILED = '0'; $::XML_HEADER = ""; $::UPS_XML_RESPONSE = 'Response'; $::UPS_XML_RESPONSE_STATUS_CODE = 'ResponseStatusCode'; $::UPS_XML_RESPONSE_STATUS_DESCRIPTION = 'ResponseStatusDescription'; $::UPS_XML_ERROR = 'Error'; $::UPS_XML_ERROR_DESCRIPTION = 'ErrorDescription'; $::UPS_XML_ERROR_SEVERITY = 'ErrorSeverity'; $::UPS_XML_ADDRESS_VALIDATION_RESULT = 'AddressValidationResult'; $::UPS_XML_RATED_SHIPMENT = 'RatedShipment'; $::UPS_XML_SERVICE = 'Service'; $::UPS_XML_SERVICE_CODE = 'Code'; $::UPS_XML_TOTAL_CHARGES = 'TotalCharges'; $::UPS_XML_CURRENCY_CODE = 'CurrencyCode'; $::UPS_XML_MONETARY_VALUE = 'MonetaryValue'; $::UPS_XML_RANK = 'Rank'; $::UPS_XML_QUALITY = 'Quality'; $::UPS_XML_ADDRESS = 'Address'; $::UPS_XML_STATE_PROVINCE_CODE = 'StateProvinceCode'; $::UPS_XML_CITY = 'City'; $::UPS_XML_POSTAL_CODE_LOW_END = 'PostalCodeLowEnd'; $::UPS_XML_POSTAL_CODE_HIGH_END = 'PostalCodeHighEnd'; $::UPS_ERROR_SEVERITY_TRANSIENT_ERROR = 'Transient'; $::UPS_ERROR_SEVERITY_HARD_ERROR = 'Hard'; $::UPS_ERROR_SEVERITY_WARNING = 'Warning'; my $ssl_socket; %::s_Ship_nShippingStatus = (); %::s_Ship_sShippingError = (); %::s_Ship_PreliminaryInfoVariables = (); %::s_Ship_ShippingVariables = (); $::s_Ship_bPrelimIsHidden = $::FALSE; $::s_Ship_bShipPhaseIsHidden = $::FALSE; $::s_Ship_sShippingDescription = ''; $::s_Ship_sHandlingDescription = ''; # not used in this plug-in $::s_Ship_sShippingCountryName = ''; $::s_Ship_nShipCharges = 0; $::s_Ship_nShipOptions = 0; $::s_Ship_nShippingStatus{GetHandlingDescription} = $::SUCCESS; $::s_Ship_sShippingError{GetHandlingDescription} = ''; $::s_Ship_bDisplayExtraCartInformation = $::FALSE; %::s_Ship_hShippingClassProviderIDs = (); %::s_Ship_hBasePlusPerProviderIDs = (); $::s_Ship_nSSPProviderID = -1; $::s_Ship_bTaxAppliesToShipping = $::FALSE; $::s_Ship_sGFSCarrierAndService = ''; $::s_Ship_bInternationalShippingZone = $::FALSE; $::UPS_CLASSES_NOT_USED = 0; $::UPS_CLASSES_USED = 1; $::UPS_BASEPLUSPER_CLASSES_USED = 2; my %hSSPUsed; my $bUPS_Available = $::TRUE; my $sCONFIRM_BY_EMAIL = 'Actinic:ConfirmByEmail'; @::s_arrSortedShippingHashes; $::SimpleCost = $SimpleCost; $::ShippingBasis = $ShippingBasis; $::UnknownRegion = $UnknownRegion; $::UnknownRegionCost = $UnknownRegionCost; $::UnknownRegionLabel = $$pMessageList[6]; $::FreeShippingLabel = $$pMessageList[5]; local %::s_hashShipData; local %::s_hashClassToWeightCost; my $c_nWeight = 0; my $c_nQuantity = 1; my $c_nPrice = 2; my $c_nSimple = 3; my $c_nAlternateWeight = 4; my $c_nMaximumWeight = 5; my $c_nPerItemShipping = 6; $::dShippingSupplements = 0; $::dHandlingSupplements = 0; $::s_Ship_nAdjustedTotalQuantity = undef; $::DPD_HOST = "api.dpd.co.uk"; $::DPD_GROUP_HOST = "api.dpdgroup.co.uk"; $::DPD_SSL_PORT = 443; $::DPD_LOGIN_URL = "/user/?action=login"; $::DPD_GET_SERVICES_URL = "/shipping/network/?"; $::DPD_GET_PICKUP_LOCATIONS = "/organisation/pickuplocation/?"; $::DPD_NETWORK_CODE_SHIP_TO_SHOP = "1^91"; # Ship to Shop network service, 1^91 escaped $::DPD_SHIP_TO_SHOP_POSTFIX = "_DPDShipToShop"; $::DPD_MAX_RESULTS = 20; $::DPD_ADD_ALWAYS = "add-always"; %::hashPickupLocations; $::sGeoSessionID; $::DPD_WEEKDAY = 1; $::DPD_SATURDAY = 2; $::DPD_SUNDAY = 4; $::DPD_ALL = $::DPD_WEEKDAY | $::DPD_SATURDAY | $::DPD_SUNDAY; $::g_DPDFilters = { 'w' => $::DPD_WEEKDAY, 'st' => $::DPD_SATURDAY, 'sn' => $::DPD_SUNDAY }; local %::s_hashZoneMinFailed; if ($::bAjaxCall) { return($::SUCCESS); } my @arrFuncns = ( [\&ValidatePreliminaryInput, 'ValidatePreliminaryInput'], [\&ValidateFinalInput, 'ValidateFinalInput'], [\&RestoreFinalUI, 'RestoreFinalUI'], [\&CalculateShipping, 'CalculateShipping'], [\&IsFinalPhaseHidden, 'IsFinalPhaseHidden'], [\&GetShippingDescription, 'GetShippingDescription'], [\&CalculateHandling, 'CalculateHandling'], ); OpaqueToHash(); my ($parrFunction, $nReturnCode, $sError); $nReturnCode = $::SUCCESS; foreach $parrFunction (@arrFuncns) { my $pFunction = $$parrFunction[0]; ($nReturnCode, $sError) = &$pFunction(); $::s_Ship_nShippingStatus{$$parrFunction[1]} = $nReturnCode; $::s_Ship_sShippingError{$$parrFunction[1]} = $sError; } if(defined $::s_hashShipData{InternationalShipping}) { $::s_Ship_bInternationalShippingZone = $::s_hashShipData{InternationalShipping}; } else { $::s_Ship_bInternationalShippingZone = $::FALSE; } SaveSelectionToOpaqueData(); my $nClassID; foreach $nClassID (keys(%ClassTable)) { push (@::s_ShipClassList, $ClassTable{$nClassID}[0]); } return($::SUCCESS); sub ValidatePreliminaryInput { if ($ShippingBasis eq 'Simple') { return($::SUCCESS, undef); } if ($WaiveCharges eq 'Value' && CalculatePrice() > $WaiveThreshold) { return(SetFreeShipping()); } if($::s_sDeliveryCountryCode eq '') { return(SetUndefinedShipping()); } if($::s_sDeliveryCountryCode eq $ActinicOrder::REGION_NOT_SUPPLIED) { return(SetDefaultCharge()); } if ($::s_sDeliveryRegionCode eq "" || $::s_sDeliveryRegionCode eq $UNDEFINED) { if (defined $ParentZoneTable{$::s_sDeliveryCountryCode} && $#{$ParentZoneTable{$::s_sDeliveryCountryCode}} == -1) { return ($::FAILURE, $$pMessageList[9]); } } my $pProviderList = GetSSPProviderList($::s_sDeliveryCountryCode); if (keys %ZoneTable == 0 && @$pProviderList == 0 ) { return(SetDefaultCharge()); } if($::g_pSSPSetupBlob && $$::g_pSSPSetupBlob{1}{'AVSEnabled'} && (exists $::g_InputHash{'LocationDeliveryCountry'} || exists $::g_InputHash{DELIVERADDRESSSELECT})) { my $sCity = $::g_ShipContact{'ADDRESS3'}; my ($Result, $sSSPError) = DoUPSAddressValidation(ActinicLocations::GetISODeliveryCountryCode(), ActinicLocations::GetISODeliveryRegionCode(), $sCity, $::g_LocationInfo{DELIVERPOSTALCODE}); if($Result == $::BADDATA) { if($sCity eq '') { SetUndefinedShipping(); } return($::FAILURE, $sSSPError); } } return($::SUCCESS, undef); } sub ValidateFinalInput { if ($ShippingBasis eq 'Simple') { return(SimpleValidateFinalInput()); } if(@::s_arrSortedShippingHashes > 0) { return($::SUCCESS, undef); } ActinicOrder::GetExtrasettings(); my ($nReturnCode, $sError); if(@::s_arrSortedShippingHashes == 0) { ($nReturnCode, $sError) = CalculateMultiPackageShipping(); if($nReturnCode != $::SUCCESS) { return($nReturnCode, $sError); } } SaveSelectionToOpaqueData(); return($::SUCCESS, undef); } sub RestoreFinalUI { if ($ShippingBasis eq 'Simple') { return(SimpleRestoreFinalUI()); } my ($phashShipping, $sClassLabel, $sClassID, $sSelectHTML); my $sPriceLabelFormat = ' (%s)'; $::s_Ship_nShipOptions = @::s_arrSortedShippingHashes; my (%hashDPDServices, %hashCodesToServices); my ($bDPDVarDefined, $bDPDEnabled) = ACTINIC::IsCustomVarDefined("IsDPDEnabled"); my ($bDPDS2SDefined, $bDPDShipToShopEnabled) = ACTINIC::IsCustomVarDefined("IsDPDShipToShopEnabled"); my ($bDPDDateDefined, $bDPDDeliveryDateEnabled) = ACTINIC::IsCustomVarDefined("IsDPDDeliveryDateEnabled"); my $sCountryCode = $::g_LocationInfo{DELIVERY_COUNTRY_CODE}; if ($sCountryCode ne 'UK') { $bDPDEnabled = $::FALSE; } if ($bDPDEnabled) {{ my ($phashWeightToQuantity, $parrSortedWeightKeys, $sWeightList, $parrShipSeparatePackages, $parrMixedPackages, $sOptimalWeight) = DivideIntoPackages($c_nWeight, undef); my $nPackages = scalar @$parrShipSeparatePackages + scalar @$parrMixedPackages; my $dSumOfWeights = 0.0; foreach my $dWeight (@$parrSortedWeightKeys) { $dSumOfWeights += $$phashWeightToQuantity{$dWeight} * $dWeight; } ACTINIC::LoadJsonLib(); my @Response = GetAvailableDPDServices($nPackages, $dSumOfWeights); if ($Response[0] != $::SUCCESS) { if ($Response[4] =~ /^validation/i) { return (@Response); } ACTINIC::RecordErrors($Response[1], ACTINIC::GetPath()); $bDPDEnabled = $::FALSE; } last if (!$bDPDEnabled); %hashDPDServices = %{$Response[2]}; %hashCodesToServices = %{$Response[3]}; my $sFilename = $::Session->GetSessionFileFolder() . "dpdservices.fil"; @Response = ACTINIC::ReadAndVerifyFileNoChecksum($sFilename); if ($Response[0] != $::SUCCESS) { ACTINIC::RecordErrors($Response[1], ACTINIC::GetPath()); $bDPDEnabled = $::FALSE; } last if (!$bDPDEnabled); my ($sScript) = $Response[2]; eval($sScript); }} if (@::s_arrSortedShippingHashes == 1) { $phashShipping = $::s_arrSortedShippingHashes[0]; $sClassLabel = $$phashShipping{ShippingLabel}; $bDPDDeliveryDateEnabled = $::FALSE; my $sDPDName = $$::g_pShippingToDPDClass{$sClassLabel}; if ($sDPDName ne $hashCodesToServices{$::DPD_NETWORK_CODE_SHIP_TO_SHOP}) # ship 2 shop not enabled? { $bDPDShipToShopEnabled = $::FALSE; } if ($::s_Ship_bDisplayPrices) { my (@PriceResponse) = ActinicOrder::FormatPrice($$phashShipping{Cost}, $::TRUE, \%::s_Ship_PriceFormatBlob); $sClassLabel .= sprintf($sPriceLabelFormat, $PriceResponse[2]); } $sSelectHTML = sprintf("%s\n", ACTINIC::HTMLEncode($sClassLabel), $$phashShipping{ShippingClass}); push(@::s_ShipClassDetailList, GetClassHash($phashShipping->{'ShippingClass'}, $phashShipping->{'ShippingLabel'}, $phashShipping->{'Cost'})); } elsif (@::s_arrSortedShippingHashes > 1) { $sSelectHTML = "\n"; if (!$bDPDDateClassesAvailable) { $bDPDDeliveryDateEnabled = $::FALSE; } if (!$bDPDS2SClassesAvailable) { $bDPDShipToShopEnabled = $::FALSE; } } if (!$bDPDShipToShopEnabled && !$bDPDDeliveryDateEnabled) { $bDPDEnabled = $::FALSE; } if($hSSPUsed{$::UPS_CLASSES_USED} == $::TRUE) { $::s_Ship_hShippingClassProviderIDs{1} = $::TRUE; } elsif ($hSSPUsed{$::UPS_BASEPLUSPER_CLASSES_USED} == $::TRUE) { $::s_Ship_hBasePlusPerProviderIDs{1} = $::TRUE; } $::s_Ship_ShippingVariables{$::VARPREFIX . 'SHIPPINGSELECT'} = $sSelectHTML; if ($bDPDEnabled) { my $sHTML = ""; if ($::bPPConfirmationPage) { $::g_ShipInfo{'DPDSHIPPINGTYPE'} = $::g_InputHash{'DPDShippingType'}; $::g_ShipInfo{'DPDPICKUPLOCATION'} = $::g_InputHash{'DPDPickupLocation'}; $::g_ShipInfo{'DPDDELIVERYDATE'} = $::g_InputHash{'DPDDeliveryDate'}; my ($sLocation, $sLocationCode) = split(/\|/, $::g_ShipInfo{'DPDPICKUPLOCATION'}); $sHTML .= sprintf("\n", $sLocationCode); $sHTML .= sprintf("\n", $::g_ShipInfo{'DPDDELIVERYDATE'}); } my $IDsToShippingTypes = { '1' => 'Standard Delivery', '2' => 'Specified Day', '3' => 'Collection Point Pickup' }; $sHTML .= "\n"; my ($sID, $sChecked); foreach $sID (sort keys %$IDsToShippingTypes) { if (($::DPD_SPECIFIED_DAY eq $sID) && !$bDPDDeliveryDateEnabled) { next; } if (($::DPD_COLLECTION_POINT_PICKUP eq $sID) && !$bDPDShipToShopEnabled) { next; } if ($::g_ShipInfo{'DPDSHIPPINGTYPE'} eq "") { $sChecked = ($sID eq "1") ? " checked" : ""; } else { $sChecked = ($::g_ShipInfo{'DPDSHIPPINGTYPE'} eq $sID) ? " checked" : ""; } $sHTML .= sprintf("\n", $sID, $sChecked, $$IDsToShippingTypes{$sID}); } $sHTML .= "
%s
\n"; $::s_Ship_ShippingVariables{$::VARPREFIX . 'SHIPPINGTYPE'} = $sHTML; } else { $::s_Ship_ShippingVariables{$::VARPREFIX . 'SHIPPINGTYPE'} = ""; $::g_ShipInfo{'DPDSHIPPINGTYPE'} = ""; $::g_ShipInfo{'DPDPICKUPLOCATION'} = ""; $::g_ShipInfo{'DPDDELIVERYDATE'} = ""; } if ($::bPPConfirmationPage) { $::g_sShippingDump = ActinicOrder::GetShippingDump(); } return($::SUCCESS, undef); } sub CalculateShipping { if ($ShippingBasis eq 'Simple') { return(SimpleCalculateShipping()); } if(@::s_arrSortedShippingHashes == 0) { return($::SUCCESS, undef); } if($::s_hashShipData{'ShippingClass'} =~ /^(\d+)_(.+)/) { $::s_Ship_nSSPProviderID = $1; my $bSSPError = $2 eq $sCONFIRM_BY_EMAIL; my $pSSPProvider = GetUPSSetup(); $::s_Ship_sSSPOpaqueShipData = sprintf("SSPID=%d;SSPClassRef=%s;OrigZip=%s;OrigCntry=%s;OrigCntryDesc=%s;Pack=%s;Rate=%s;Weight=%.03f;DestCntry=%s;DestPost=%s;Residential=%s;", $::s_Ship_nSSPProviderID, $2, $$pSSPProvider{ShipperPostalCode}, $$pSSPProvider{ShipperCountry}, ACTINIC::GetCountryName($$pSSPProvider{ShipperCountry}), $$pSSPProvider{'PackagingType'}, $$pSSPProvider{'RateChart'}, $::s_hashShipData{BasisTotal}, $::s_sDeliveryCountryCode, $::g_ShipContact{'POSTALCODE'}, $::g_ShipContact{'RESIDENTIAL'} ne '' ? 1 : 0 ); if($::s_Ship_nSSPProviderID == 1) { if(!$bSSPError) { $::s_Ship_bDisplayExtraCartInformation = $::TRUE; } } } return($::SUCCESS, undef); } sub IsFinalPhaseHidden { if ($ShippingBasis eq 'Simple') { return($::SUCCESS, undef); } if ((@::s_arrSortedShippingHashes < 1) || (scalar @::s_Ship_sShipProducts == 0)) { $::s_Ship_bShipPhaseIsHidden = $::TRUE; } return($::SUCCESS, undef); } sub GetShippingDescription { if(defined $::s_hashShipData{ShippingLabel}) { $::s_Ship_sShippingDescription = $::s_hashShipData{ShippingLabel}; } else { $::s_Ship_sShippingDescription = ''; } if(defined $::s_hashShipData{GFSCarrierAndService}) { $::s_Ship_sGFSCarrierAndService = $::s_hashShipData{GFSCarrierAndService}; } else { $::s_Ship_sGFSCarrierAndService = ''; } return($::SUCCESS, undef); } sub CalculateHandling { $::s_Ship_nHandlingCharges = $nHandlingCharge + int (GetTaxExclusiveShipping() * $nHandlingProportion / $ActinicOrder::PERCENTOFFSET); $::s_Ship_nHandlingCharges += $::dHandlingSupplements; $::s_Ship_sOpaqueHandleData = sprintf("Handling;%d;", $::s_Ship_nHandlingCharges); return ($::SUCCESS, undef); } sub GetTaxExclusiveShipping { my ($phashShipping, $phashSelected); $phashSelected = undef; foreach $phashShipping (@::s_arrSortedShippingHashes) { if($$phashShipping{ShippingClass} eq $::s_hashShipData{ShippingClass}) { $phashSelected = $phashShipping; last; } } if(!defined $phashSelected && @::s_arrSortedShippingHashes > 0) { $phashSelected = $::s_arrSortedShippingHashes[0]; } if (defined $phashSelected) { %::s_hashShipData = %$phashSelected; $::s_Ship_nShipCharges = $$phashSelected{Cost}; } return ($::s_Ship_nShipCharges); } sub SimpleValidateFinalInput { my (@Response); if(!defined $::g_InputHash{SHIPPING}) { return($::SUCCESS, undef); } if ($::g_InputHash{SHIPPING}) { $::g_InputHash{SHIPPING} =~ s/^\s*(.*?)\s*$/$1/gs; } if (defined $::g_InputHash{SHIPPING}) { my $sText = (0 == length $::g_InputHash{SHIPPING}) ? ' ' : $::g_InputHash{SHIPPING}; $::s_Ship_sOpaqueShipData = sprintf("Simple;Error-%s;", $sText); } if (!defined $::g_InputHash{'SHIPPING'} ||# if the shipping is undefined, error out length $::g_InputHash{'SHIPPING'} == 0) { return($::FAILURE, $$pMessageList[8]); } @Response = ActinicOrder::ReadPrice($::g_InputHash{SHIPPING}, \%::s_Ship_PriceFormatBlob); if ($Response[0] != $::SUCCESS || $Response[2] != int $Response[2]) { @Response = ActinicOrder::FormatSinglePrice(10000, $::FALSE, \%::s_Ship_PriceFormatBlob); if ($Response[0] != $::SUCCESS) { return($Response[0], $Response[1]); } return($::FAILURE, sprintf($$pMessageList[0], $Response[2])); } my ($nMaxShipping) = 99999999; if ($Response[2] >= $nMaxShipping) { @Response = ActinicOrder::FormatPrice($nMaxShipping, $::TRUE, \%::s_Ship_PriceFormatBlob); if ($Response[0] != $::SUCCESS) { return($Response[0], $Response[1]); } return($::FAILURE, sprintf($$pMessageList[1], $Response[2])); } my ($nMinShipping) = 0; if ($Response[2] < $nMinShipping) { @Response = ActinicOrder::FormatPrice($nMinShipping, $::TRUE, \%::s_Ship_PriceFormatBlob); if ($Response[0] != $::SUCCESS) { return($Response[0], $Response[1]); } return($::FAILURE, sprintf($$pMessageList[2], $Response[2])); } if (defined $::g_InputHash{SHIPPING}) { $::s_Ship_sOpaqueShipData = sprintf("Simple;%s;", $Response[2]); if ($bPricesIncludesTax) { $::s_Ship_sOpaqueShipData .= sprintf('TaxApplies;%d;', $::s_sShip_bLocationTaxable); } OpaqueToHash(); } return($::SUCCESS, undef); } sub SimpleRestoreFinalUI { my (@Response); $::s_Ship_nShipOptions = -1; my $ePosOrder = $::s_Ship_PriceFormatBlob{"ICURRENCY"}; if ($ePosOrder == 0) { $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL1"} = $::s_Ship_PriceFormatBlob{"SCURRENCY"}; $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL2"} = ''; } elsif ($ePosOrder == 1) { $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL1"} = ''; $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL2"} = $::s_Ship_PriceFormatBlob{"SCURRENCY"}; } elsif ($ePosOrder == 2) { $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL1"} = $::s_Ship_PriceFormatBlob{"SCURRENCY"} . ' '; $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL2"} = ''; } elsif ($ePosOrder == 3) { $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL1"} = ''; $::s_Ship_ShippingVariables{"NETQUOTEVAR:CURRENCYSYMBOL2"} = $::s_Ship_PriceFormatBlob{"SCURRENCY"} . ' '; } if (!defined $::s_hashShipData{'Simple'}) { @Response = ActinicOrder::FormatSinglePrice($SimpleCost, $::FALSE, \%::s_Ship_PriceFormatBlob); if ($Response[0] != $::SUCCESS) { return($Response[0], $Response[1]); } $::s_Ship_ShippingVariables{"NETQUOTEVAR:SHIPPINGVALUE"} = $Response[2]; $::s_hashShipData{'Simple'} = $SimpleCost; $::s_Ship_sOpaqueShipData = sprintf("Simple;%s;", $SimpleCost); if ($bPricesIncludesTax) { $::s_Ship_sOpaqueShipData .= sprintf('TaxApplies;%d;', $::s_sShip_bLocationTaxable); } } elsif($::s_hashShipData{'Simple'} =~ /Error-/) { $::s_hashShipData{'Simple'} =~ s/^Error-\s*(.*?)\s*$/$1/g; $::s_Ship_ShippingVariables{"NETQUOTEVAR:SHIPPINGVALUE"} = $::s_hashShipData{'Simple'}; } else { $::s_hashShipData{'Simple'} =~ s/^\s*(.*?)\s*$/$1/g; @Response = ActinicOrder::FormatSinglePrice($::s_hashShipData{'Simple'}, $::FALSE, \%::s_Ship_PriceFormatBlob); if ($Response[0] != $::SUCCESS) { return($Response[0], $Response[1]); } $::s_Ship_ShippingVariables{"NETQUOTEVAR:SHIPPINGVALUE"} = $Response[2]; } if ($bPricesIncludesTax) { $::s_Ship_bTaxAppliesToShipping = ActinicOrder::IsTaxApplicableForLocation('TAX_1'); } else { $::s_Ship_bTaxAppliesToShipping = $::TRUE; } AddShippingHash({ 'ShippingLabel' => 'Delivery', 'ShippingClass' => 'Default', 'ShippingZone' => -1, 'Cost' => $::s_hashShipData{'Simple'}, 'TaxAppliesToShipping' => $::s_sShip_bLocationTaxable, }); return($::SUCCESS, undef); } sub SimpleCalculateShipping { if (!defined $::s_hashShipData{'Simple'} || $::s_hashShipData{'Simple'} =~ /Error-/) { $::s_Ship_nShipCharges = 0; } else { $::s_Ship_nShipCharges = $::s_hashShipData{'Simple'}; } return($::SUCCESS, undef); } sub CalculateQuantity { return($::s_Ship_nTotalQuantity); } sub CalculateAdjustedQuantity { if (defined $::s_Ship_nAdjustedTotalQuantity) { return ($::s_Ship_nAdjustedTotalQuantity); } $::s_Ship_nAdjustedTotalQuantity = 0; $::s_Ship_nNonExcludedCount = 0; my $i; for $i (0 .. $#::s_Ship_sShipProducts) { if($::s_Ship_sShipProducts[$i] =~ /_/) { next; } if ($::s_Ship_nExcludeFromShipping[$i] == 1 && $::s_Ship_sGFSCarrierAndService eq "") { next; } if ($::s_Ship_bProduct == 0 && $::s_Ship_bUseAssociatedShip[$i] == 0) { next; } $::s_Ship_nNonExcludedCount++; $::s_Ship_nAdjustedTotalQuantity += ($::s_Ship_nShipShipQuantities[$i] * $::s_Ship_nShipQuantities[$i]); } return($::s_Ship_nAdjustedTotalQuantity); } sub CalculatePrice { my $j; if (defined $::s_Ship_nTotalPrice) { return ($::s_Ship_nTotalPrice); } if (defined $::s_Ship_nSubTotalEx) { return ($::s_Ship_nSubTotalEx); } if (defined $::s_Ship_nSubTotal) { return ($::s_Ship_nSubTotal); } $::s_Ship_nTotalPrice = 0; for $j (0 .. $#::s_Ship_sShipProducts) { $::s_Ship_nTotalPrice += ($::s_Ship_nShipPrices[$j] * $::s_Ship_nShipQuantities[$j]); } return($::s_Ship_nTotalPrice); } sub GetBands { if ($::s_sDeliveryRegionCode eq "" || $::s_sDeliveryRegionCode eq $UNDEFINED) { if ($#{$ParentZoneTable{$::s_sDeliveryCountryCode}} != -1) { return (@{$ParentZoneTable{$::s_sDeliveryCountryCode}}); # return this list (has invalid entries stripped) } } if(defined $ZoneTable{$::s_sDeliveryCountryCode}) { if(defined $ZoneTable{$::s_sDeliveryCountryCode}{$::s_sDeliveryRegionCode}) { return(@{ $ZoneTable{$::s_sDeliveryCountryCode}{$::s_sDeliveryRegionCode} }); } my $sParentState = ActinicLocations::GetDeliveryParentRegionCode(); if($sParentState ne '' && $sParentState ne $::s_sDeliveryRegionCode && defined $ZoneTable{$::s_sDeliveryCountryCode}{$sParentState}) { return(@{ $ZoneTable{$::s_sDeliveryCountryCode}{$sParentState} }); } if(defined $ZoneTable{$::s_sDeliveryCountryCode}{$UNDEFINED}) { return(@{ $ZoneTable{$::s_sDeliveryCountryCode}{$UNDEFINED} }); } } my @listEmpty = (); return(@listEmpty); } sub GetSSPProviderList { my ($sCountryCode) = @_; my @arrReturn; if(defined $$::g_pSSPSetupBlob{SupportedRegions} && defined $$::g_pSSPSetupBlob{SupportedRegions}{$sCountryCode}) { my $nProviderID; foreach $nProviderID ($$::g_pSSPSetupBlob{SupportedRegions}{$sCountryCode}) { push(@arrReturn, $nProviderID); } } return (\@arrReturn); } sub GetUS5DigitZipCode { my ($sZipCode) = @_; if($sZipCode !~ /^\d{5}$/ && $sZipCode !~ /^\d{5}-\d{4}$/ && $sZipCode !~ /^\d{9}$/) { return($::FAILURE, ACTINIC::GetPhrase(-1, 2150)); } $sZipCode = substr($sZipCode, 0, 5); return($::SUCCESS, '', $sZipCode); } sub CalculatePackageShipping { my ($nZoneID, $nClassID, $objBasis, $nCalculationBasis) = @_; if ($nCalculationBasis == $c_nPerItemShipping) { return (CalculatePerItemShipping($nZoneID, $nClassID, $objBasis)); } my $nCost = 0; my $bWeightOK = $::TRUE; my $dMaxWeight = 0.0; if ($::s_hashZoneMinFailed{$nZoneID}) { return($::FALSE, $nCost); } my $nMinValue; if (defined $$::g_ZonesToMinValues{$nZoneID}) { my $nMinValue = $$::g_ZonesToMinValues{$nZoneID}; my $nTotal = CalculatePrice(); if ($nMinValue > $nTotal) { $::s_hashZoneMinFailed{$nZoneID} = $::TRUE; ACTINIC::LogData("Cart value ($nTotal) lower than min value $nMinValue for the zone $nZoneID, goods cannot be shipped.", $::DC_ORDERSCRIPT); return($::FALSE, $nCost); } else { ACTINIC::LogData("Cart value ($nTotal) higher than or equal to min value $nMinValue for the zone $nZoneID, goods can be shipped.", $::DC_ORDERSCRIPT); } } my $nHighestCost = 0; my $sCostKey = 'cost'; my $parrBandEntries = $ShippingTable{$nClassID}{$nZoneID}; my $nEntryCount = @$parrBandEntries; my $phashBandEntry; if($nEntryCount > 1) { $phashBandEntry = $$parrBandEntries[$nEntryCount - 1]; $dMaxWeight = $$phashBandEntry{wt}; if (defined $phashBandEntry->{'costIncTax'}) { $sCostKey = 'costIncTax'; } $nHighestCost = $$phashBandEntry{$sCostKey}; } if($objBasis > $dMaxWeight) { my $phashExcessAction = $$parrBandEntries[0]; if($$phashExcessAction{ExcessAction} eq 'Highest') { $nCost = $nHighestCost; } elsif($$phashExcessAction{ExcessAction} eq 'AddFurther') { my $dExtraWeight = $objBasis - $dMaxWeight; my $sCostSuffix = defined $phashExcessAction->{'IncrementalChargeIncTax'} ? 'IncTax' : ''; my ($dWeightIncrement, $nChargeIncrement) = ($$phashExcessAction{'IncrementalWeight'}, $$phashExcessAction{'IncrementalCharge' . $sCostSuffix}); my $nExtraUnits = int ($dExtraWeight / $dWeightIncrement + 0.999); $nCost = $nHighestCost + ($nExtraUnits * $nChargeIncrement); } elsif($$phashExcessAction{ExcessAction} eq 'Error') { $bWeightOK = $::FALSE; } } else { my $i; for($i = 1; $i < $nEntryCount; $i++) { $phashBandEntry = $$parrBandEntries[$i]; if($$phashBandEntry{wt} >= $objBasis) { $nCost = $$phashBandEntry{$sCostKey}; last; } } } return($bWeightOK, $nCost); } sub GetPerItemQuantities { my ($phashCategoryQuantities) = @_; my $i; for $i (0 .. $#::s_Ship_sShipProducts) { if($::s_Ship_sShipProducts[$i] =~ /_/) { next; } if ($::s_Ship_nExcludeFromShipping[$i] == 1 && $::s_Ship_sGFSCarrierAndService eq "") { next; } if ($::s_Ship_bUseAssociatedShip[$i] == 0) { next; } my $sCategory = $::s_Ship_sShipCategories[$i]; if (!defined $phashDefinedCategories->{$sCategory}) { $sCategory = $sDefaultCategory; } my $nTotalQuantity = $::s_Ship_nShipQuantities[$i] * $::s_Ship_nShipShipQuantities[$i]; if (defined $phashCategoryQuantities->{$sCategory}) { $phashCategoryQuantities->{$sCategory} += $nTotalQuantity; } else { $phashCategoryQuantities->{$sCategory} = $nTotalQuantity; } } } sub CalculateSupplements { my %hashShippingSupplementApplied; my %hashHandlingSupplementApplied; my $i; for $i (0 .. $#::s_Ship_sShipProducts) { if ($::s_Ship_sShipProducts[$i] =~ /_/) { next; } if ($::s_Ship_bProduct[$i] || ($::s_Ship_bUseAssociatedShip[$i] == 1)) { my $nQuantity = $::s_Ship_nShipQuantities[$i]; if ($::s_Ship_dShipSupplementOnce[$i] == 1) { if (defined $hashShippingSupplementApplied{$::s_Ship_sShipProducts[$i]}) { $nQuantity = 0; } else { $hashShippingSupplementApplied{$::s_Ship_sShipProducts[$i]} = 1; $nQuantity = 1; } } $::dShippingSupplements += $nQuantity * $::s_Ship_dShipSupplements[$i]; $nQuantity = $::s_Ship_nShipQuantities[$i]; if ($::s_Ship_dHandSupplementOnce[$i] == 1) { if (defined $hashHandlingSupplementApplied{$::s_Ship_sShipProducts[$i]}) { $nQuantity = 0; } else { $hashHandlingSupplementApplied{$::s_Ship_sShipProducts[$i]} = 1; $nQuantity = 1; } } $::dHandlingSupplements += $nQuantity * $::s_Ship_dHandSupplements[$i]; } } } sub CalculatePerItemShipping { my ($nZoneID, $nClassID, $phashCategoryQuantities) = @_; my $nMaxFixedCost = 0; my $dPerItemCharges = 0; my $parrBandEntries = $ShippingTable{$nClassID}{$nZoneID}; my $phashZoneClassPerItemCharges = $parrBandEntries->[1]; my $sKeySuffix = ''; if ($bPricesIncludesTax && $parrBandEntries->[0]->{'TaxAppliesToShipping'} && !$parrBandEntries->[0]->{'ShippingCostsIncludeTax'}) { $sKeySuffix = 'IncTax'; } my $sCategory; foreach $sCategory (keys %$phashCategoryQuantities) { my $phashCategory = $phashZoneClassPerItemCharges->{$sCategory}; if ($phashCategory->{'Fixed' . $sKeySuffix} > $nMaxFixedCost) { $nMaxFixedCost = $phashCategory->{'Fixed' . $sKeySuffix}; } my $nQuantity = $phashCategoryQuantities->{$sCategory}; $dPerItemCharges += $phashCategory->{'PerItem' . $sKeySuffix} * $nQuantity; } return ($::TRUE, $nMaxFixedCost + $dPerItemCharges); } sub CalculateMultiPackageShipping { my $dWeightRemainder = 0.0; my $bNonSeparateShipFound = $::FALSE; my ($i); my $dWeight; my @arrShippingHashes; my $parrZonesClasses = GetZoneClassCombinations(); my $pProviderList = GetSSPProviderList($::s_sDeliveryCountryCode); if(@$parrZonesClasses == 0 && @$pProviderList == 0) { return(SetDefaultCharge()); } CalculateAdjustedQuantity(); CalculateSupplements(); my %hashCalculationBases = {}; GetZoneClassesByBasis(\%hashCalculationBases, $parrZonesClasses, \@arrShippingHashes); my $nCalculationBasis; foreach $nCalculationBasis (keys %hashCalculationBases) { my $parrBasisZoneClasses = $hashCalculationBases{$nCalculationBasis}; my $parrZoneClass; foreach $parrZoneClass (@$parrBasisZoneClasses) { CalculateZoneClassShipping($nCalculationBasis, $parrZoneClass, \@arrShippingHashes); } } if (@$pProviderList > 0) { my ($phashWeightToQuantity, $parrSortedWeightKeys, $sWeightList, $parrShipSeparatePackages, $parrMixedPackages, $sOptimalWeight) = DivideIntoPackages($c_nWeight, undef); my $dSumOfWeights = 0.0; foreach $dWeight (@$parrSortedWeightKeys) { $dSumOfWeights += $$phashWeightToQuantity{$dWeight} * $dWeight; } my $nProviderID; foreach $nProviderID (@$pProviderList) { my $bWeightThresholdExceeded = IsWeightThresholdExceeded($nProviderID, $dSumOfWeights); if($::g_pSSPSetupBlob && $$::g_pSSPSetupBlob{$nProviderID}{'RSSEnabled'} && $bWeightThresholdExceeded == $::FALSE) { my ($nReturnCode, $sSSPError, $parrShippingHashes, $nRateType) = GetUPSRates(); $hSSPUsed{$nRateType} = $::TRUE; if($nReturnCode != $::SUCCESS) { return($nReturnCode, $sSSPError); } else { push @arrShippingHashes, @$parrShippingHashes; } } } } if(@$parrZonesClasses == 0 && @arrShippingHashes == 0) { return(SetDefaultCharge()); } if (@arrShippingHashes == 0 && scalar @::s_Ship_sShipProducts != 0) { return ($::FAILURE, $$pMessageList[7]); } @arrShippingHashes = sort{$$a{Cost} <=> $$b{Cost}} @arrShippingHashes; my @arrLastClasses; my $phashClass; foreach $phashClass (@arrShippingHashes) { my $bLastClass = 0; my $nClassID = $phashClass->{'ShippingClass'}; if (defined $ClassTable{$nClassID}) { $bLastClass = $ClassTable{$nClassID}->[1]; } if ($bLastClass) { push @arrLastClasses, $phashClass; } else { push @::s_arrSortedShippingHashes, $phashClass; } } push @::s_arrSortedShippingHashes, @arrLastClasses; return($::SUCCESS, ''); } sub CalculateZoneClassShipping { my ($nCalculationBasis, $parrZoneClass, $parrShippingHashes) = @_; my ($phashWeightToQuantity, $parrSortedWeightKeys, $sWeightList, $parrShipSeparatePackages, $parrMixedPackages, $sOptimalWeight) = DivideIntoPackages($nCalculationBasis, $parrZoneClass); my $nTotalCost = 0; my ($nZoneID, $nClassID) = @$parrZoneClass; my ($bBasisOK, $nPackageCost); $bBasisOK = $::TRUE; my $dBasisTotal = 0; my $dBasis; foreach $dBasis (@$parrSortedWeightKeys) { ($bBasisOK, $nPackageCost) = CalculatePackageShipping($nZoneID, $nClassID, $dBasis, $nCalculationBasis); if ($bBasisOK) { $nTotalCost += $$phashWeightToQuantity{$dBasis} * $nPackageCost; my $sKey = sprintf('%0.03f', $dBasis); $::s_hashClassToWeightCost{$nClassID}{$sKey} = $nPackageCost; $dBasisTotal += $dBasis; } else { last; } } if ($bBasisOK) { if ($::s_Ship_nNonExcludedCount == 0 && $dBasisTotal == 0) { $nTotalCost = 0.0; } my $nCost = ActinicOrder::RoundScientific($nTotalCost + $::dShippingSupplements); my $phashBandDefinition = GetBandDefinition(@$parrZoneClass); if (defined $phashBandDefinition->{'FreeOver'} && CalculatePrice() > $phashBandDefinition->{'FreeOver'}) { $nCost = 0; } my $bInternational = $::FALSE; if (defined $phashBandDefinition->{'InternationalZone'}) { $bInternational = $phashBandDefinition->{'InternationalZone'}; } push @$parrShippingHashes, { 'ShippingLabel' => $ClassTable{$nClassID}[0], 'ShippingClass' => $nClassID, 'ShippingZone' => $nZoneID, 'Cost' => $nCost, 'BasisTotal' => $dBasis, 'ShipSeparatePackages' => $parrShipSeparatePackages, 'MixedPackages' => $parrMixedPackages, 'OptimalWeight' => $sOptimalWeight, 'TaxAppliesToShipping' => $phashBandDefinition->{'TaxAppliesToShipping'}, 'ShippingCostsIncludeTax' => $phashBandDefinition->{'ShippingCostsIncludeTax'}, 'GFSCarrierAndService' => $ClassTable{$nClassID}[2], 'InternationalShipping' => $bInternational }; } } sub GetZoneClassesByBasis { my ($phashCalculationBases, $parrZonesClasses, $parrShippingHashes) = @_; my $parrZoneClass; foreach $parrZoneClass (@$parrZonesClasses) { my ($nZoneID, $nClassID) = @$parrZoneClass; my $phashBandDefinition = GetBandDefinition(@$parrZoneClass); if (defined $phashBandDefinition->{'FreeClass'}) { my $bInternational = $::FALSE; if (defined $phashBandDefinition->{'InternationalZone'}) { $bInternational = $phashBandDefinition->{'InternationalZone'}; } push @$parrShippingHashes, { 'ShippingLabel' => $ClassTable{$nClassID}[0], 'ShippingClass' => $nClassID, 'ShippingZone' => $nZoneID, 'Cost' => 0, 'BasisTotal' => 0, 'GFSCarrierAndService' => $ClassTable{$nClassID}[2], 'InternationalShipping' => $bInternational }; } else { my $nCalculationBasis = $phashBandDefinition->{'CalculationBasis'}; if (!defined $phashCalculationBases->{$nCalculationBasis}) { $phashCalculationBases->{$nCalculationBasis} = []; } my $parrBasisZoneClasses = $phashCalculationBases->{$nCalculationBasis}; push @$parrBasisZoneClasses, $parrZoneClass; } } return (scalar(keys %$phashCalculationBases) > 0); } sub GetBandDefinition { my ($nZoneID, $nClassID) = @_; my $parrBandEntries = $ShippingTable{$nClassID}{$nZoneID}; my $phashBandDefinition = $$parrBandEntries[0]; return ($phashBandDefinition); } sub IsWeightThresholdExceeded { my $nProviderID = shift; my $dSumOfWeights = shift; my $bWeightThresholdExceeded = $::FALSE; if($::g_pSSPSetupBlob && $$::g_pSSPSetupBlob{$nProviderID}{'WEIGHTTHRESHOLD'}) { my $dWeightThreshold = $$::g_pSSPSetupBlob{$nProviderID}{'WEIGHTTHRESHOLD'}; if (($dWeightThreshold ne '') && ($dWeightThreshold =~ /^[+]?[\d]*(\.[\d]+)?$/)) { if ($dWeightThreshold < $dSumOfWeights) { $bWeightThresholdExceeded = $::TRUE; } } } return $bWeightThresholdExceeded; } sub DivideIntoPackages { my ($nCalculationBasis, $parrZoneClass, $bUseIntegralWeights) = @_; my $dWeightRemainder = 0.0; my $nNonSeparateShipCount = 0; my $dExcludeFromShippingWeight = 0.0; my (%hashWeightToQuantity, @arrSortedWeightKeys); my ($i); my (@arrShipSeparatePackages, @arrMixedPackages, $parrPackage); if (($::s_Ship_sGFSCarrierAndService eq "" || !defined $::s_Ship_sGFSCarrierAndService) && defined $parrZoneClass) { my ($nZoneID, $nClassID) = @$parrZoneClass; $::s_Ship_sGFSCarrierAndService = $ClassTable{$nClassID}[2]; } my $nBasisTotal = -1; if ($nCalculationBasis == $c_nQuantity) { $nBasisTotal = CalculateAdjustedQuantity(); } elsif ($nCalculationBasis == $c_nPrice) { $nBasisTotal = CalculatePrice(); } elsif ($nCalculationBasis == $c_nPerItemShipping) { $nBasisTotal = {}; GetPerItemQuantities($nBasisTotal); } if (ref($nBasisTotal) ne '' || $nBasisTotal != -1) { $hashWeightToQuantity{$nBasisTotal} = 1; @arrSortedWeightKeys = ($nBasisTotal); return(\%hashWeightToQuantity, \@arrSortedWeightKeys, $nBasisTotal); } my $dWeightDivisor = 1; my $dAltWeightDivisor = 1; my $sOptimalWeight = ''; if (defined $parrZoneClass) { my ($nZoneID, $nClassID) = @$parrZoneClass; my $parrBandEntries = $ShippingTable{$nClassID}{$nZoneID}; my $phashBandDefinition = $$parrBandEntries[0]; $dWeightDivisor = $phashBandDefinition->{'WeightFactor'}; $dAltWeightDivisor = $phashBandDefinition->{'AltWeightFactor'}; $sOptimalWeight = $phashWeightConfiguration->{$nCalculationBasis}->{'OptimalWeight'}; } else { $sOptimalWeight = $phashWeightConfiguration->{$c_nWeight}->{'OptimalWeight'}; } my $dUnitWeight; for $i (0 .. $#::s_Ship_sShipProducts) { my $sProdRef = $::s_Ship_sShipProducts[$i]; if($::s_Ship_sShipProducts[$i] =~ /_/) { next; } if ($::s_Ship_nExcludeFromShipping[$i] == 1 && $::s_Ship_sGFSCarrierAndService eq "") { next; } if ($nCalculationBasis == $c_nWeight) { $dUnitWeight = GetWeight($i, $phashWeightConfiguration, $dWeightDivisor); } elsif ($nCalculationBasis == $c_nAlternateWeight) { $dUnitWeight = GetAltWeight($i, $phashWeightConfiguration, $dWeightDivisor, $dAltWeightDivisor); } elsif ($nCalculationBasis == $c_nMaximumWeight) { $dUnitWeight = GetMaxWeight($i, $phashWeightConfiguration, $dWeightDivisor, $dAltWeightDivisor); } if($::s_Ship_nShipSeparately[$i] == 1 || ($sOptimalWeight > 0 && $dUnitWeight >= $sOptimalWeight)) { if($bUseIntegralWeights) { $dUnitWeight = int($dUnitWeight + 0.9999); } if ($::s_Ship_nExcludeFromShipping[$i] == 0) { $hashWeightToQuantity{$dUnitWeight} += $::s_Ship_nShipQuantities[$i]; } my @arrTemp = ($::s_Ship_sShipProducts[$i], $::s_Ship_nShipQuantities[$i], $dUnitWeight); push @arrShipSeparatePackages, \@arrTemp; } else { $nNonSeparateShipCount += $::s_Ship_nShipQuantities[$i]; $dWeightRemainder += $dUnitWeight * $::s_Ship_nShipQuantities[$i]; if ($::s_Ship_nExcludeFromShipping[$i] == 1) { $dExcludeFromShippingWeight += $dUnitWeight * $::s_Ship_nShipQuantities[$i]; } my @arrTemp = ($::s_Ship_sShipProducts[$i], $::s_Ship_nShipQuantities[$i], $dUnitWeight); push @arrMixedPackages, \@arrTemp; } } my $dBasis = 0.0; if($nNonSeparateShipCount > 0) { my $nQuantity = 1; if($sOptimalWeight ne '' && $dWeightRemainder > $sOptimalWeight) { my $nCalculatedPackages = int(($dWeightRemainder / $sOptimalWeight) + 0.9999); if($nCalculatedPackages == $nNonSeparateShipCount) { foreach $parrPackage (@arrMixedPackages) { $dUnitWeight = $$parrPackage[2]; if($bUseIntegralWeights) { $dUnitWeight = int($dUnitWeight + 0.9999); } $dBasis = $dUnitWeight; if ($dExcludeFromShippingWeight > 0) { $dBasis -= $dExcludeFromShippingWeight; } $hashWeightToQuantity{$dBasis} += $$parrPackage[1]; push @arrShipSeparatePackages, $parrPackage; } @arrMixedPackages = (); } else { $nQuantity = ($nCalculatedPackages < $nNonSeparateShipCount) ? $nCalculatedPackages : $nNonSeparateShipCount; $dWeightRemainder = $dWeightRemainder / $nQuantity; $dExcludeFromShippingWeight = $dExcludeFromShippingWeight / $nQuantity; if($bUseIntegralWeights) { $dWeightRemainder = int($dWeightRemainder + 0.9999); if ($dExcludeFromShippingWeight > 0) { $dExcludeFromShippingWeight = int($dExcludeFromShippingWeight + 0.9999); } } $dBasis = $dWeightRemainder; if ($dExcludeFromShippingWeight > 0) { $dBasis -= $dExcludeFromShippingWeight; } if ($dBasis != 0.0 || $dBasis == $dWeightRemainder) { $hashWeightToQuantity{$dBasis} += $nQuantity; } my @arrTemp = ('', $nQuantity, $dWeightRemainder); push @arrMixedPackages, \@arrTemp; } } else { if($bUseIntegralWeights) { $dWeightRemainder = int($dWeightRemainder + 0.9999); if ($dExcludeFromShippingWeight > 0) { $dExcludeFromShippingWeight = int($dExcludeFromShippingWeight + 0.9999); } } $dBasis = $dWeightRemainder; if ($dExcludeFromShippingWeight > 0) { $dBasis -= $dExcludeFromShippingWeight; } if ($dBasis != 0.0 || $dBasis == $dWeightRemainder) { $hashWeightToQuantity{$dBasis} += $nQuantity; } my @arrTemp = ('', $nQuantity, $dWeightRemainder); push @arrMixedPackages, \@arrTemp; } } @arrSortedWeightKeys = sort {$b <=> $a} keys %hashWeightToQuantity; my ($dWeight, $sWeightList); foreach $dWeight (@arrSortedWeightKeys) { $sWeightList .= sprintf("%d@%.03f,", $hashWeightToQuantity{$dWeight}, $dWeight); } $sWeightList =~ s/,$//; return(\%hashWeightToQuantity, \@arrSortedWeightKeys, $sWeightList, \@arrShipSeparatePackages, \@arrMixedPackages, $sOptimalWeight); } sub GetWeight { my ($nIndex, $phashWeightConfiguration, $dWeightDivisor) = @_; my $dUnitWeight = $::s_Ship_OpaqueDataTables{$::s_Ship_sShipProducts[$nIndex]}; if ($dUnitWeight eq "") { $dUnitWeight = $phashWeightConfiguration->{$c_nWeight}->{'DefaultWeight'}; } if ($dWeightDivisor != 0) { $dUnitWeight /= $dWeightDivisor; } return ($dUnitWeight); } sub GetAltWeight { my ($nIndex, $phashWeightConfiguration, $dWeightDivisor, $dAltWeightDivisor) = @_; my $dUnitWeight = $::s_Ship_dShipAltWeights[$nIndex]; if ($::s_Ship_dShipAltWeights[$nIndex] eq "") { my $phashWeightDetails = $phashWeightConfiguration->{$c_nAlternateWeight}; if ($phashWeightDetails->{'UseWeightIfUndefined'}) { return (GetWeight($nIndex, $phashWeightConfiguration, $dWeightDivisor)); } $dUnitWeight = $phashWeightConfiguration->{$c_nAlternateWeight}->{'DefaultWeight'}; } if ($dAltWeightDivisor != 0) { $dUnitWeight /= $dAltWeightDivisor; } return ($dUnitWeight); } sub GetMaxWeight { my ($nIndex, $phashWeightConfiguration, $dWeightDivisor, $dAltWeightDivisor) = @_; my $dUnitWeight = GetWeight($nIndex, $phashWeightConfiguration, $dWeightDivisor); my $dAltWeight = GetAltWeight($nIndex, $phashWeightConfiguration, $dWeightDivisor, $dAltWeightDivisor); if ($dAltWeight > $dUnitWeight) { $dUnitWeight = $dAltWeight; } return ($dUnitWeight); } sub GetZoneClassCombinations { my @arrZones = GetBands(); my (%hashZones, $nZoneID, $nClassID, @arrZonesClasses); foreach $nZoneID (@arrZones) { $hashZones{$nZoneID} = 1; } foreach $nClassID (keys %ShippingTable) { my $phashClass = $ShippingTable{$nClassID}; foreach $nZoneID (keys %$phashClass) { if(defined $hashZones{$nZoneID}) { my @arrClassZone = ($nZoneID, $nClassID); push @arrZonesClasses, \@arrClassZone; } } } return(\@arrZonesClasses); } sub AddShippingHash { my ($phashShipping) = @_; push @::s_arrSortedShippingHashes, $phashShipping; push(@::s_ShipClassDetailList, GetClassHash($phashShipping->{'ShippingClass'}, $phashShipping->{'ShippingLabel'}, $phashShipping->{'Cost'})); } sub SetDefaultCharge { if ($UnknownRegion eq 'Default') { AddShippingHash({ 'ShippingLabel' => $$pMessageList[6], 'ShippingClass' => 'Default', 'ShippingZone' => -1, 'Cost' => $UnknownRegionCost, 'TaxAppliesToShipping' => $::s_sShip_bLocationTaxable, }); return($::SUCCESS, ''); } return($::FAILURE, $$pMessageList[4]); } sub SetFreeShipping { AddShippingHash(GetFreeShippingHash()); return($::SUCCESS, ''); } sub GetFreeShippingHash { return({ 'ShippingLabel' => $$pMessageList[5], 'ShippingClass' => '-1', 'ShippingZone' => -1, 'Cost' => 0, 'BasisTotal' => 0 }); } sub SetUndefinedShipping { AddShippingHash({ 'ShippingLabel' => '', 'ShippingClass' => -1, 'ShippingZone' => -1, 'Cost' => 0, }); return($::SUCCESS, ''); } sub OpaqueToHash { if(defined $::g_InputHash{ShippingClass}) { $::s_hashShipData{ShippingClass} = $::g_InputHash{ShippingClass}; } else { %::s_hashShipData = split (';', $::s_Ship_sOpaqueShipData); } } sub SaveSelectionToOpaqueData { if($ShippingBasis eq 'Simple') { return; } my ($phashShipping, $phashSelected); $phashSelected = undef; foreach $phashShipping (@::s_arrSortedShippingHashes) { HashToOpaque($phashShipping); $$phashShipping{'OpaqueData'} = $::s_Ship_sOpaqueShipData; if($$phashShipping{ShippingClass} eq $::s_hashShipData{ShippingClass}) { $phashSelected = $phashShipping; } } if(!defined $phashSelected && @::s_arrSortedShippingHashes > 0) { $phashSelected = $::s_arrSortedShippingHashes[0]; } if (defined $phashSelected) { %::s_hashShipData = %$phashSelected; } HashToOpaque($phashSelected); if (!$phashSelected || $$phashSelected{ShippingClass} !~ /^\d+_/) { $::s_Ship_sSSPOpaqueShipData = ''; } } sub HashToOpaque { my $phashSelected = shift; if (defined $phashSelected) { $::s_Ship_sOpaqueShipData = sprintf("ShippingClass;%s;ShippingZone;%d;BasisTotal;%s;Cost;%d;", $$phashSelected{ShippingClass}, $$phashSelected{ShippingZone}, $$phashSelected{BasisTotal}, $$phashSelected{Cost}); if ($bPricesIncludesTax) { $::s_Ship_sOpaqueShipData .= sprintf("TaxApplies;%s;TaxIncluded;%d;TaxMultiplier;%0.06f;", $$phashSelected{'TaxAppliesToShipping'}, $$phashSelected{'ShippingCostsIncludeTax'}, $dTaxInclusiveMultiplier); } if(defined $$phashSelected{OnlineError} && $$phashSelected{OnlineError} ne '') { $::s_Ship_sOpaqueShipData .= sprintf('OnlineError;%s;', $$phashSelected{OnlineError}); } my $sOptimalWeight = $phashSelected->{'OptimalWeight'}; if($sOptimalWeight ne '' && $sOptimalWeight > 0) { $::s_Ship_sOpaqueShipData .= sprintf('OptimalWeight;%s;', $sOptimalWeight); } $::s_Ship_nShipCharges = $$phashSelected{Cost}; if ($bPricesIncludesTax) { $::s_Ship_bTaxAppliesToShipping = $$phashSelected{TaxAppliesToShipping}; } else { $::s_Ship_bTaxAppliesToShipping = $::TRUE; } my $sClassID = $$phashSelected{ShippingClass}; my $parrShipSeparatePackages = $phashSelected->{'ShipSeparatePackages'}; my $parrMixedPackages = $phashSelected->{'MixedPackages'}; if(defined $parrShipSeparatePackages && defined $parrMixedPackages) { my $phashWeightToCost = (defined $::s_hashClassToWeightCost{$sClassID}) ? $::s_hashClassToWeightCost{$sClassID} : undef; $::s_Ship_sSeparatePackageDetails = ''; $::s_Ship_sMixedPackageDetails = ''; my $parrPackage; foreach $parrPackage (@$parrShipSeparatePackages) { my $sUnitWeight = ($sClassID =~ /^1_/) ? sprintf('%0.03f', int($$parrPackage[2] + 0.9999)) : sprintf('%0.03f', $$parrPackage[2]); my $nUnitCost = (defined $phashWeightToCost) ? $$phashWeightToCost{$sUnitWeight} : 0; $::s_Ship_sSeparatePackageDetails .= sprintf("%s\t%d\t%0.03f\t%d\n", $$parrPackage[0], $$parrPackage[1], $$parrPackage[2], $nUnitCost); } my $parrSummary = (@$parrMixedPackages > 0) ? $$parrMixedPackages[-1] : undef; foreach $parrPackage (@$parrMixedPackages) { my $sUnitWeight = ($sClassID =~ /^1_/) ? sprintf('%0.03f', int($$parrPackage[2] + 0.9999)) : sprintf('%0.03f', $$parrPackage[2]); my $nUnitCost = (defined $phashWeightToCost && $parrSummary == $parrPackage) ? $$phashWeightToCost{$sUnitWeight} : 0; $::s_Ship_sMixedPackageDetails .= sprintf("%s\t%d\t%0.03f\t%d\n", $$parrPackage[0], $$parrPackage[1], $$parrPackage[2], $nUnitCost); } } } else { $::s_Ship_sOpaqueShipData = ''; $::s_Ship_nShipCharges = 0; $::s_Ship_sSSPOpaqueShipData = ''; } } sub ClearUnusedSSPShippingEntries { if (CalculateQuantity() == 0) { my $sShipKey; foreach $sShipKey (keys %::g_ShipInfo) { if($sShipKey =~ /^\d+_/) { delete $::g_ShipInfo{$sShipKey}; } } return; } } sub GetUPSRates { my @arrShippingHashes; my (%hashValidClasses, %hashClassToTotal, $sClassID); my $sShipKey; foreach $sShipKey (keys %::g_ShipInfo) { if($sShipKey =~ /^1_/) { delete $::g_ShipInfo{$sShipKey}; } } my $pSSPProvider = GetUPSSetup(); my ($nReturnCode, $sError, $sServiceLevelCode, $sRateChart, $sShipperPostalCode, $sShipperCountry, $sConsigneePostalCode, $sConsigneeCountry, $nResidential, $sPackagingType) = GetShipmentDetails(); if($nReturnCode != $::SUCCESS) { return($nReturnCode, $sError); } my $sRSSRequestDataFormat; $sRSSRequestDataFormat = $::XML_HEADER; $sRSSRequestDataFormat .= GetUPSAccessRequestNode($pSSPProvider); $sRSSRequestDataFormat .= $::XML_HEADER; $sRSSRequestDataFormat .= ""; $sRSSRequestDataFormat .= GetUPSRequestNode('Rate', 'Shop'); $sRSSRequestDataFormat .= ""; $sRSSRequestDataFormat .= " $sRateChart"; $sRSSRequestDataFormat .= ""; $sRSSRequestDataFormat .= ""; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= "

"; $sRSSRequestDataFormat .= " $sShipperPostalCode"; $sRSSRequestDataFormat .= " $sShipperCountry"; $sRSSRequestDataFormat .= "
"; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= "
"; $sRSSRequestDataFormat .= " $sConsigneePostalCode"; $sRSSRequestDataFormat .= " $sConsigneeCountry"; $sRSSRequestDataFormat .= ($nResidential == 1) ? '' : ''; $sRSSRequestDataFormat .= "
"; $sRSSRequestDataFormat .= "
"; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " $sServiceLevelCode"; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " $sPackagingType"; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " %d"; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= " "; $sRSSRequestDataFormat .= ""; $sRSSRequestDataFormat .= ""; my ($phashWeightToQuantity, $parrSortedWeightKeys, $sWeightList, $parrShipSeparatePackages, $parrMixedPackages, $sOptimalWeight) = DivideIntoPackages($c_nWeight, undef, $::TRUE); my $nWeight; foreach $nWeight (@$parrSortedWeightKeys) { if ($nWeight == 0) { next; } my $sRSSRequestData = sprintf($sRSSRequestDataFormat, $nWeight); my $parrShippingHashes; my $pXmlRoot; ($nReturnCode, $sError, $pXmlRoot) = GetUPSPackageShipping($sRSSRequestData); if($nReturnCode == $::SUCCESS) { my $pXmlRatedShipments = $pXmlRoot->GetChildNodes($::UPS_XML_RATED_SHIPMENT); my $pXmlRatedShipment; foreach $pXmlRatedShipment (@{$pXmlRatedShipments}) { my $sServiceCode = $pXmlRatedShipment->GetChildNode($::UPS_XML_SERVICE)->GetChildNode($::UPS_XML_SERVICE_CODE)->GetNodeValue(); my $sClassID = "1_$sServiceCode"; if(defined $$pSSPProvider{ServiceLevelCode}{$sServiceCode}) { my $pXmlTotalCharges = $pXmlRatedShipment->GetChildNode($::UPS_XML_TOTAL_CHARGES); my $sCurrencyCode = $pXmlTotalCharges->GetChildNode($::UPS_XML_CURRENCY_CODE)->GetNodeValue(); my $sMonetaryValue = $pXmlTotalCharges->GetChildNode($::UPS_XML_MONETARY_VALUE)->GetNodeValue(); my $nIntegralCost = int($sMonetaryValue * 100 + 0.999); $hashClassToTotal{$sClassID} += $$phashWeightToQuantity{$nWeight} * $nIntegralCost; if(!defined $hashValidClasses{$sClassID}) { $hashValidClasses{$sClassID} = { 'ShippingLabel' => GetUPSServiceName($sServiceCode), 'ShippingClass' => $sClassID, 'ShippingZone' => -1, 'ShipSeparatePackages' => $parrShipSeparatePackages, 'MixedPackages' => $parrMixedPackages, 'OptimalWeight' => $sOptimalWeight, 'GFSCarrierAndService' => '', 'InternationalShipping' => $::FALSE }; } $::s_hashClassToWeightCost{$sClassID}{sprintf('%0.03f', $nWeight)} = $nIntegralCost; } } } elsif ($nReturnCode == $::FAILURE) { return(HandleUPSOnlineError($sError, $parrSortedWeightKeys, $phashWeightToQuantity, $sWeightList)); } else { @arrShippingHashes = (); return($::SUCCESS, '', \@arrShippingHashes, $::UPS_CLASSES_NOT_USED); } } my $nRatingType = $::UPS_CLASSES_NOT_USED; foreach $sClassID (keys %hashValidClasses) { my $phashShipping = $hashValidClasses{$sClassID}; $$phashShipping{BasisTotal} = 0; $$phashShipping{Cost} = $hashClassToTotal{$sClassID} + $::dShippingSupplements; push @arrShippingHashes, $phashShipping; my $dUPSCost = $hashClassToTotal{$sClassID} / 100; $::g_ShipInfo{$sClassID} = "UPSOnLine%1.2\%0000%0000Success%4%$sServiceLevelCode%$sShipperPostalCode%US%$sConsigneePostalCode%$sConsigneeCountry%000%1%$dUPSCost%0.00%$dUPSCost%-1"; $nRatingType = $::UPS_CLASSES_USED; } return($::SUCCESS, '', \@arrShippingHashes, $nRatingType); } sub GetUPSPackageShipping { my ($sRequestData, $nWeight) = @_; my (@arrShippingHashes); my $nRetries = 2; return(UPS_SendAndReceive('/ups.app/xml/Rate', $sRequestData, $nRetries, 2253)); } sub HandleUPSOnlineError { my ($sResponse, $parrSortedWeightKeys, $phashWeightToQuantity, $sWeightList) = @_; my ( $sRateChart, $sShipperPostalCode, $sConsigneePostalCode, $sConsigneeCountry, $sPackagingType); my (@arrShippingHashes); my $pSSPProvider = GetUPSSetup(); my $nRatingType = $::UPS_CLASSES_NOT_USED; $sRateChart = $$pSSPProvider{'RateChart'}; $sShipperPostalCode = $$pSSPProvider{'ShipperPostalCode'}; $sPackagingType = $$pSSPProvider{'PackagingType'}; $sConsigneePostalCode = $::g_ShipContact{'POSTALCODE'}; $sConsigneeCountry = ActinicLocations::GetISODeliveryCountryCode(); my $sErrorText = ACTINIC::GetPhrase(-1, 2292, $sResponse); ACTINIC::RecordErrors($sErrorText, ACTINIC::GetPath()); if($$::g_pSSPSetupBlob{NotifyMerchantOfFailure}) { my ($Status, $Message) = ACTINIC::SendMail($::g_sSmtpServer, $$::g_pSSPSetupBlob{FailureEmailAddress}, ACTINIC::GetPhrase(-1, 2291), $sErrorText, $$::g_pSSPSetupBlob{FailureEmailAddress}); if ($Status != $::SUCCESS) { ACTINIC::RecordErrors("$sErrorText:\n sending$Message" , ACTINIC::GetPath()); } } if($$::g_pSSPSetupBlob{ConfirmShippingByEmail}) { $::g_ShipInfo{"1_$sCONFIRM_BY_EMAIL"} = "UPSOnLine%1.2\%0000%0000Success%4%000%$sShipperPostalCode%US%$sConsigneePostalCode%$sConsigneeCountry%000%1%0.00%0.00%0.00%-1"; $sOnlineError = 'Email'; push @arrShippingHashes, { 'ShippingLabel' => GetUPSServiceName($sCONFIRM_BY_EMAIL), 'ShippingClass' => "1_$sCONFIRM_BY_EMAIL", 'ShippingZone' => -1, 'Cost' => 0, 'BasisTotal' => 0, 'OnlineError' => 'Email', 'GFSCarrierAndService' => '', 'InternationalShipping' => $::FALSE }; } elsif($$::g_pSSPSetupBlob{UseClassDefaultFormula}) { $sOnlineError = 'BasePlusIncrement'; my @arrServiceLevelCodes; if($::s_sDeliveryCountryCode eq 'CA') { push @arrServiceLevelCodes, '11', '07', '08'; } elsif($::s_sDeliveryCountryCode eq 'US') { push @arrServiceLevelCodes, '14', '01', '13', '59', '02', '12', '03'; } else { push @arrServiceLevelCodes, '07', '08'; } my $sServiceLevelCode; foreach $sServiceLevelCode (@arrServiceLevelCodes) { if(defined $$pSSPProvider{'ServiceLevelCode'}{$sServiceLevelCode}) { my ($nWeight, $nTotalCost); foreach $nWeight (@$parrSortedWeightKeys) { my $nIncrementalUnits = int(($nWeight / $$pSSPProvider{'ServiceLevelCode'}{$sServiceLevelCode}[3]) + 0.999); my $nIntegralCost = $$pSSPProvider{'ServiceLevelCode'}{$sServiceLevelCode}[1] + ($$pSSPProvider{'ServiceLevelCode'}{$sServiceLevelCode}[2] * $nIncrementalUnits); $nTotalCost += $$phashWeightToQuantity{$nWeight} * $nIntegralCost; my $dUPSCost = $nIntegralCost / 100; $::g_ShipInfo{"1_$sServiceLevelCode" . "_$nWeight"} = "UPSOnLine%1.2\%0000%0000Success%4%$sServiceLevelCode%$sShipperPostalCode%US%$sConsigneePostalCode%$sConsigneeCountry%000%1%$dUPSCost%0.00%$dUPSCost%-1"; $::s_hashClassToWeightCost{"1_$sServiceLevelCode"}{sprintf('%0.03f', $nWeight)} = $nIntegralCost; } my $dUPSCost = $nTotalCost / 100; $::g_ShipInfo{"1_$sServiceLevelCode"} = "UPSOnLine%1.2\%0000%0000Success%4%$sServiceLevelCode%$sShipperPostalCode%US%$sConsigneePostalCode%$sConsigneeCountry%000%1%$dUPSCost%0.00%$dUPSCost%-1"; push @arrShippingHashes, { 'ShippingLabel' => GetUPSServiceName($sServiceLevelCode), 'ShippingClass' => "1_$sServiceLevelCode", 'ShippingZone' => -1, 'Cost' => $nTotalCost, 'BasisTotal' => 0, 'OnlineError' => 'BasePlusIncrement', 'GFSCarrierAndService' => '', 'InternationalShipping' => $::FALSE }; $nRatingType = $::UPS_BASEPLUSPER_CLASSES_USED; } } } return($::SUCCESS, '', \@arrShippingHashes, $nRatingType); } sub GetShipmentDetails { my ($nReturnCode, $sError, $sServiceLevelCode, $sRateChart, $sShipperPostalCode, $sShipperCountry, $sConsigneePostalCode, $sConsigneeCountry, $nResidential, $sPackagingType); my $pSSPProvider = GetUPSSetup(); $sRateChart = $$pSSPProvider{'RateChart'}; $sShipperPostalCode = $$pSSPProvider{'ShipperPostalCode'}; $sShipperCountry = $$pSSPProvider{'ShipperCountry'}; $sPackagingType = $$pSSPProvider{'PackagingType'}; $sConsigneePostalCode = $::g_ShipContact{'POSTALCODE'}; $sConsigneeCountry = ActinicLocations::GetISODeliveryCountryCode(); if($sConsigneeCountry eq 'CA') { if($sConsigneePostalCode !~ /^(\w\d\w)\s{0,1}(\d\w\d)$/) { return($::FAILURE, ACTINIC::GetPhrase(-1, 2149)); } $sConsigneePostalCode =~ s/\s*//g; $sServiceLevelCode = '11'; } elsif($sConsigneeCountry eq 'US') { my ($nStatus, $sError); ($nStatus, $sError, $sConsigneePostalCode) = GetUS5DigitZipCode($sConsigneePostalCode); if($nStatus == $::FAILURE) { return($nStatus, $sError); } if($sRateChart eq '07' or $sRateChart eq '19') { $sServiceLevelCode = '02'; } else { $sServiceLevelCode = '03'; } } else { $sServiceLevelCode = '07'; } $nResidential = $::g_ShipContact{'RESIDENTIAL'} ne '' ? 1 : 0; return($::SUCCESS, '', $sServiceLevelCode, $sRateChart, $sShipperPostalCode, $sShipperCountry, $sConsigneePostalCode, $sConsigneeCountry, $nResidential, $sPackagingType); } sub GetUPSSetup { return($$::g_pSSPSetupBlob{1}); } sub GetUPSServiceName { my ($sServiceLevelCode) = @_; if($sServiceLevelCode eq $sCONFIRM_BY_EMAIL) { return(ACTINIC::GetPhrase(-1, 2100)); } return($$::g_pSSPSetupBlob{1}{ServiceLevelCode}{$sServiceLevelCode}[0]); } sub DoUPSAddressValidation { my ($sConsigneeCountry, $sConsigneeState, $sConsigneeCity, $sConsigneePostalCode) = @_; if($sConsigneeCountry ne 'US' || $sConsigneeCountry eq '' || $sConsigneeCountry eq '---') { return($::SUCCESS, ''); } my $pSSPProvider = GetUPSSetup(); my @arrStates = ( 'AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'GA', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VT', 'WA', 'WI', 'WV', 'WY', ); my $sStatesString = join('|', @arrStates); if($sStatesString !~ $sConsigneeState) { { if(defined $$pSSPProvider{'RSSEnabled'} && $$pSSPProvider{'RSSEnabled'}) { my $sErrorText = sprintf(ACTINIC::GetPhrase(-1, 2099), ACTINIC::GetCountryName("US.$sConsigneeState")); return($::FAILURE, $sErrorText); } return($::SUCCESS, ''); } } my ($nStatus, $sError); ($nStatus, $sError, $sConsigneePostalCode) = GetUS5DigitZipCode($sConsigneePostalCode); if($nStatus == $::FAILURE) { return($::FAILURE, $sError); } my (@Response); my $pSSPProvider = GetUPSSetup(); my $sAVRequestData = ''; $sAVRequestData = $::XML_HEADER; $sAVRequestData .= GetUPSAccessRequestNode($pSSPProvider); $sAVRequestData .= $::XML_HEADER; $sAVRequestData .= ""; $sAVRequestData .= GetUPSRequestNode('AV'); $sAVRequestData .= "
"; if($sConsigneeState ne '') { $sConsigneeState =~ s/^\w\w\.//; $sAVRequestData .= "$sConsigneeState"; } if($sConsigneeCity ne '') { $sAVRequestData .= "$sConsigneeCity"; } if($sConsigneePostalCode ne '') { $sAVRequestData .= "$sConsigneePostalCode"; } $sAVRequestData .= "
"; $sAVRequestData .= "
"; my $nRetries = 2; my ($Result, $sMessage, $pXmlRoot) = UPS_SendAndReceive('/ups.app/xml/AV', $sAVRequestData, $nRetries, 2305); if ($Result != $::SUCCESS) { return ($Result, $sMessage); } my $bValidationFailed = $::TRUE; my $raAddressValidationResults = $pXmlRoot->GetChildNodes($::UPS_XML_ADDRESS_VALIDATION_RESULT); my $pXmlAddressValidationResult; foreach $pXmlAddressValidationResult (@{$raAddressValidationResults}) { my $sRank = $pXmlAddressValidationResult->GetChildNode($::UPS_XML_RANK)->GetNodeValue(); my $sQuality = $pXmlAddressValidationResult->GetChildNode($::UPS_XML_QUALITY)->GetNodeValue(); my $sState = $pXmlAddressValidationResult->GetChildNode($::UPS_XML_ADDRESS)->GetChildNode($::UPS_XML_STATE_PROVINCE_CODE)->GetNodeValue(); my $sCity = $pXmlAddressValidationResult->GetChildNode($::UPS_XML_ADDRESS)->GetChildNode($::UPS_XML_CITY)->GetNodeValue(); my $sPostalCodeLow = $pXmlAddressValidationResult->GetChildNode($::UPS_XML_POSTAL_CODE_LOW_END)->GetNodeValue(); my $sPostalCodeHigh = $pXmlAddressValidationResult->GetChildNode($::UPS_XML_POSTAL_CODE_HIGH_END)->GetNodeValue(); if($sState eq $sConsigneeState && ( (lc($sCity) eq lc($sConsigneeCity) || $sConsigneeCity eq '')) && ($sConsigneePostalCode eq $sPostalCodeLow || ($sConsigneePostalCode gt $sPostalCodeLow && $sPostalCodeHigh ne '' && $sConsigneePostalCode le $sPostalCodeHigh))) { $bValidationFailed = $::FALSE; } } if($bValidationFailed) { my $sErrorText = ACTINIC::GetPhrase(-1, 2305, ACTINIC::GetPhrase(-1, 2072)); return($::BADDATA, $sErrorText); } return($::SUCCESS, ''); } sub UPS_SendAndReceive { my ($sPath, $sRequestData, $nRetries, $nErrorTitlePhrase) = @_; my (@Response, $sHTTPResponse, $sHTTPHeader, $sHTTPContent, $phashHeader); while($nRetries && $bUPS_Available) { @Response = ACTINIC::HTTPS_SendAndReceive('onlinetools.ups.com', 443, $sPath, $sRequestData, 'POST', $::FALSE, $ssl_socket); if($Response[0] != $::SUCCESS || $Response[2] eq '') { $nRetries--; } else { $sHTTPResponse = $Response[2]; $ssl_socket = $Response[3]; last; } } unless ($sHTTPResponse) { $bUPS_Available = $::FALSE; return($::FAILURE, $Response[1]); } @Response = ACTINIC::HTTP_SplitHeaderAndContent($sHTTPResponse); if($Response[0] != $::TRUE) { return($::FAILURE, $Response[1]); } $sHTTPHeader = $Response[3]; $sHTTPContent = $Response[4]; $phashHeader = $Response[5]; my $sContentType = $$phashHeader{'Content-Type'}; unless($sContentType) { return($::FAILURE, ACTINIC::GetPhrase(-1, 2293)); } if($sContentType =~ /application\/xml/) { my $pParser = new PXML(); my ($sParsedText, $pXmlRoot) = $pParser->Parse($sHTTPContent); $pXmlRoot = $pXmlRoot->[0]; my ($Result, $sMessage) = ParseUPSResponseNode($pXmlRoot->GetChildNode($::UPS_XML_RESPONSE), $nErrorTitlePhrase); return($Result, $sMessage, $pXmlRoot); } return($::FAILURE, ACTINIC::GetPhrase(-1, 2293)); } sub GetUPSAccessRequestNode { my ($pSSPProvider) = @_; my $sAccessKey = ACTINIC::DecodeXOREncryption($$pSSPProvider{AccessKey}, $::UPS_ENCRYPT_PASSWORD); my $sUserName = ACTINIC::DecodeXOREncryption($$pSSPProvider{UserName}, $::UPS_ENCRYPT_PASSWORD); my $sPassword = ACTINIC::DecodeXOREncryption($$pSSPProvider{Password}, $::UPS_ENCRYPT_PASSWORD); my $sAccessRequestNode = ''; $sAccessRequestNode .= ""; $sAccessRequestNode .= "$sAccessKey"; $sAccessRequestNode .= "$sUserName"; $sAccessRequestNode .= "$sPassword"; $sAccessRequestNode .= ""; return $sAccessRequestNode; } sub GetUPSRequestNode { my ($sAction, $sOption) = @_; my $sRequestNode = ''; $sRequestNode .= ""; $sRequestNode .= ""; $sRequestNode .= "$::UPS_XPCI_VERSION"; $sRequestNode .= ""; $sRequestNode .= "$sAction"; if (defined $sOption) { $sRequestNode .= "$sOption"; } $sRequestNode .= ""; return $sRequestNode; } sub ParseUPSResponseNode { my ($pXmlResponse, $nErrorTitlePhrase) = @_; my $pXmlStatusCode = $pXmlResponse->GetChildNode($::UPS_XML_RESPONSE_STATUS_CODE); if (!defined($pXmlStatusCode)) { return ($::FAILURE, ACTINIC::GetPhrase(-1, $nErrorTitlePhrase, ACTINIC::GetPhrase(-1, 2294))); } if ($pXmlStatusCode->GetNodeValue() eq $::UPS_SUCCESSFUL) { return($::SUCCESS, '') } my $paXmlErrors = $pXmlResponse->GetChildNodes($::UPS_XML_ERROR); my $pXmlError; foreach $pXmlError (@$paXmlErrors) { my $pXmlErrorSeverity = $pXmlError->GetChildNode($::UPS_XML_ERROR_SEVERITY); if (!defined($pXmlErrorSeverity)) { return ($::FAILURE, ACTINIC::GetPhrase(-1, $nErrorTitlePhrase, ACTINIC::GetPhrase(-1, 2294))); } my $sSeverity = $pXmlErrorSeverity->GetNodeValue(); my $pXmlErrorDescription = $pXmlError->GetChildNode($::UPS_XML_ERROR_DESCRIPTION); if (!defined($pXmlErrorDescription)) { return ($::FAILURE, ACTINIC::GetPhrase(-1, $nErrorTitlePhrase, ACTINIC::GetPhrase(-1, 2294))); } my $sErrorDescription = $pXmlErrorDescription->GetNodeValue(); if ($sSeverity eq $::UPS_ERROR_SEVERITY_HARD_ERROR) { return ($::BADDATA, ACTINIC::GetPhrase(-1, $nErrorTitlePhrase, $sErrorDescription)); } elsif ($sSeverity eq $::UPS_ERROR_SEVERITY_TRANSIENT_ERROR) # temporary server problem - failure and not bad data { return ($::FAILURE, ACTINIC::GetPhrase(-1, $nErrorTitlePhrase, $sErrorDescription)); } elsif ($sSeverity eq $::UPS_ERROR_SEVERITY_WARNING) { } else { return ($::FAILURE, ACTINIC::GetPhrase(-1, $nErrorTitlePhrase, ACTINIC::GetPhrase(-1, 2294))); } } return($::SUCCESS, '') } sub GetGeoSession { my $SSLConnection = SSLConnection->new($::DPD_HOST, $::DPD_SSL_PORT, $::DPD_LOGIN_URL); $SSLConnection->SetRequestMethod("POST"); $SSLConnection->SetHeaderValue("Content-Type", "application/json"); $SSLConnection->SetHeaderValue("Accept", "application/json"); $SSLConnection->SetHeaderValue("Authorization", "Basic $::sAuthorization"); $SSLConnection->SetHeaderValue("GEOClient", "account/$::sAccount"); $SSLConnection->SetRequestTimeout(5); $SSLConnection->SendRequest(""); if ($SSLConnection->GetResponseCode() != 200) { my $nRetCode = $SSLConnection->GetResponseCode(); if (401 == $nRetCode) { return ($::FAILURE, "DPD authorization error: $nRetCode", ""); } else { return ($::FAILURE, "DPD integration error: $nRetCode", ""); } } if ($SSLConnection->GetConnectStatus() == $::FALSE) { return ($::FAILURE, sprintf("%s (%s) %s", "DPD connection failed:", $SSLConnection->GetResponseCode(), $SSLConnection->GetConnectErrorMessage(), "")); } my $sGeoSession = ""; if (ref($SSLConnection->GetResponseJSON()) eq 'HASH') { my ($sError, $sType) = GetServerError($SSLConnection->GetResponseJSON()); if ($sError ne "") { return ($::FAILURE, "DPD returned an error. $sError", "", "", $sType); } $sGeoSession = $SSLConnection->GetResponseJSON()->{'data'}->{'geoSession'}; if ($sGeoSession eq "") { $sGeoSession = $SSLConnection->GetResponseJSON()->{'data'}->{'dpdsession'}; } } else { return ($::FAILURE, "Response format error for the DPD request", ""); } return ($::SUCCESS, "", $sGeoSession); } sub GetAvailableDPDServices { my ($nParcels, $dTotalWeight) = @_; my @Response = GetGeoSession(); if ($Response[0] != $::SUCCESS) { return ($::FAILURE, $Response[1], ""); } $::sGeoSessionID = $Response[2]; my $sCountryCode = $::g_LocationInfo{DELIVERY_COUNTRY_CODE}; if ($sCountryCode eq 'UK') { $sCountryCode =~ s/^UK$/GB/; } my $sGetParams = "deliveryDirection=1"; $sGetParams .= "&numberOfParcels=$nParcels"; $sGetParams .= "&shipmentType=0"; $sGetParams .= "&totalWeight=$dTotalWeight"; $sGetParams .= "&deliveryDetails.address.countryCode=$sCountryCode"; $sGetParams .= "&deliveryDetails.address.countryName=" . ACTINIC::EncodeText2($::g_ShipContact{'COUNTRY'}, $::FALSE); $sGetParams .= "&deliveryDetails.address.locality=" . ACTINIC::EncodeText2($::g_ShipContact{'ADDRESS2'}, $::FALSE); $sGetParams .= "&deliveryDetails.address.organisation=" . ACTINIC::EncodeText2($::g_ShipContact{'COMPANY'}, $::FALSE); $sGetParams .= "&deliveryDetails.address.postcode=" . ACTINIC::EncodeText2($::g_ShipContact{'POSTALCODE'}, $::FALSE); $sGetParams .= "&deliveryDetails.address.property="; $sGetParams .= "&deliveryDetails.address.street=" . ACTINIC::EncodeText2($::g_ShipContact{'ADDRESS1'}, $::FALSE); $sGetParams .= "&deliveryDetails.address.town=" . ACTINIC::EncodeText2($::g_ShipContact{'ADDRESS3'}, $::FALSE); $sGetParams .= "&deliveryDetails.address.county=" . ACTINIC::EncodeText2($::g_ShipContact{'ADDRESS4'}, $::FALSE); $sCountryCode = $$::g_pSetupBlob{'MERCHANT_COUNTRY_CODE'}; if ($sCountryCode eq 'UK') { $sCountryCode =~ s/^UK$/GB/; } $sGetParams .= "&collectionDetails.address.countryCode=$sCountryCode"; $sGetParams .= "&collectionDetails.address.countryName=" . ACTINIC::EncodeText2($$::g_pSetupBlob{'COUNTRY'}, $::FALSE); $sGetParams .= "&collectionDetails.address.locality=" . ACTINIC::EncodeText2($$::g_pSetupBlob{'ADDRESS_2'}, $::FALSE); $sGetParams .= "&collectionDetails.address.organisation=" . ACTINIC::EncodeText2($$::g_pSetupBlob{'COMPANY_NAME'}, $::FALSE); $sGetParams .= "&collectionDetails.address.postcode=" . ACTINIC::EncodeText2($$::g_pSetupBlob{'POSTAL_CODE'}, $::FALSE); $sGetParams .= "&collectionDetails.address.property="; $sGetParams .= "&collectionDetails.address.street=" . ACTINIC::EncodeText2($$::g_pSetupBlob{'ADDRESS_1'}, $::FALSE); $sGetParams .= "&collectionDetails.address.town=" . ACTINIC::EncodeText2($$::g_pSetupBlob{'ADDRESS_3'}, $::FALSE); $sGetParams .= "&collectionDetails.address.county=" . ACTINIC::EncodeText2($$::g_pSetupBlob{'ADDRESS_4'}, $::FALSE); my $SSLConnection = SSLConnection->new($::DPD_HOST, $::DPD_SSL_PORT, $::DPD_GET_SERVICES_URL . $sGetParams); $SSLConnection->SetRequestMethod("GET"); $SSLConnection->SetHeaderValue("Content-Type", "application/json"); $SSLConnection->SetHeaderValue("Accept", "application/json"); $SSLConnection->SetHeaderValue("GEOClient", "account/$::sAccount"); $SSLConnection->SetHeaderValue("GeoSession", "$Response[2]"); $SSLConnection->SetRequestTimeout(5); $SSLConnection->SendRequest(""); if ($SSLConnection->GetResponseCode() != 200) { my $RetCode = $SSLConnection->GetResponseCode(); my ($sMsg, $sType) = GetServerError($SSLConnection->GetResponseJSON()); return ($::FAILURE, "DPD integration error: $RetCode." . $sMsg, ""); } if ($SSLConnection->GetConnectStatus() == $::FALSE) { return ($::FAILURE, sprintf("%s (%s) %s", "DPD connection failed:", $SSLConnection->GetResponseCode(), $SSLConnection->GetConnectErrorMessage(), "")); } my (%hashServiceCodes, %hashServices); if (ref($SSLConnection->GetResponseJSON()) eq 'HASH') { my ($sError, $sType) = GetServerError($SSLConnection->GetResponseJSON()); if ($sError ne "") { return ($::FAILURE, "DPD returned an error. Error message:$sError", "", "", $sType); } my $ServiceItem; if (ref($SSLConnection->GetResponseJSON()->{'data'}) eq 'ARRAY') { foreach $ServiceItem(@{$SSLConnection->GetResponseJSON()->{'data'}}) { if (!exists $ServiceItem->{'network'}) { return ($::FAILURE, "Response format error for the DPD request", ""); } my ($sDesc, $sCode); $sDesc = $ServiceItem->{'network'}->{'networkDescription'}; $sCode = $ServiceItem->{'network'}->{'networkCode'}; $hashServices{$sDesc} = $sCode; $hashServiceCodes{$sCode} = $sDesc; } } } else { return ($::FAILURE, "Response format error for the DPD request", ""); } return ($::SUCCESS, "", \%hashServices, \%hashServiceCodes, ""); } sub GetServerError { my ($hashErrors) = shift; my ($sErrorMessage, $sType); if ((ref($hashErrors) eq 'HASH') && (defined $hashErrors->{'error'}) && (ref($hashErrors->{'error'}) eq 'HASH')) { my $sError = sprintf("%s error, %s (code %s). Field: %s.", $hashErrors->{'error'}->{'errorType'}, $hashErrors->{'error'}->{'errorMessage'}, $hashErrors->{'error'}->{'errorCode'}, $hashErrors->{'error'}->{'obj'}); $sErrorMessage .= $sError; $sType = $hashErrors->{'error'}->{'errorType'}; } return ($sErrorMessage, $sType); } sub GetPickupLocations { my ($sGeoSession, $sPostalCode, $sCountryCode) = @_; my $sGetParams = "filter=nearAddress"; if ($sCountryCode eq 'UK') { $sCountryCode =~ s/^UK$/GB/; } $sGetParams .= "&countryCode=$sCountryCode"; $sGetParams .= "&searchPageSize=$::DPD_MAX_RESULTS"; $sGetParams .= "&searchPage=1&searchCriteria=&maxDistance=10"; $sGetParams .= "&searchAddress=" . ACTINIC::EncodeText2($sPostalCode); my $SSLConnection = SSLConnection->new($::DPD_GROUP_HOST, $::DPD_SSL_PORT, $::DPD_GET_PICKUP_LOCATIONS . $sGetParams); $SSLConnection->SetRequestMethod("GET"); $SSLConnection->SetHeaderValue("Content-Type", "application/json"); $SSLConnection->SetHeaderValue("Accept", "application/json"); $SSLConnection->SetHeaderValue("GEOClient", "account/$::sAccount"); $SSLConnection->SetHeaderValue("GeoSession", "$sGeoSession"); $SSLConnection->SetRequestTimeout(5); $SSLConnection->SendRequest(""); if ($SSLConnection->GetResponseCode() != 200) { my $RetCode = $SSLConnection->GetResponseCode(); my ($sMsg, $sType) = GetServerError($SSLConnection->GetResponseJSON()); my $sError = "DPD integration error: $RetCode" . $sMsg; ACTINIC::RecordErrors($sError, ACTINIC::GetPath()); return ($::FAILURE, $sError); } if ($SSLConnection->GetConnectStatus() == $::FALSE) { my $sError = sprintf("%s (%s) %s", "DPD connection failed:", $SSLConnection->GetResponseCode(), $SSLConnection->GetConnectErrorMessage()); ACTINIC::RecordErrors($sError, ACTINIC::GetPath()); return ($::FAILURE, $sError); } my $sJsonText = ""; if (ref($SSLConnection->GetResponseJSON()) eq 'HASH') { $sJsonText = $SSLConnection->GetResponseContent(); } else { return ($::FAILURE, "DPD response format error"); } return ($::SUCCESS, $sJsonText); } return ($::SUCCESS); sub GetClassHash { my ($sClassID, $sClassTitle, $sAmount, $sDetail) = @_; if (!defined $sDetail) { $sDetail = ''; } my $hResult = {'identifier' => $sClassID, 'label' => $sClassTitle, 'detail' => $sDetail, 'amount' => ActinicOrder::FormatPriceWithoutSymbol($sAmount), }; return ($hResult); }