<?php
/**
* @version	$Id: usps.php 13262 2010-03-17 17:53:25Z alex $
* @package	In-Commerce
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license	Commercial License
* This software is protected by copyright law and international treaties.
* Unauthorized reproduction or unlicensed usage of the code of this program,
* or any portion of it may result in severe civil and criminal penalties,
* and will be prosecuted to the maximum extent possible under the law
* See http://www.in-portal.org/commercial-license for copyright notices and details.
*/

defined('FULL_PATH') or die('restricted access!');

define('MODULE_SHIPPING_USPS_TEXT_TITLE', 'United States Postal Service');
define('MODULE_SHIPPING_USPS_TEXT_DESCRIPTION', 'You will need to have registered an account with USPS. Click <a target="_blank" href="https://secure.shippingapis.com/registration/"><strong>HERE</strong></a> for registration details. USPS expects you to use pounds as weight measure for your products.');
define('MODULE_SHIPPING_USPS_TEXT_ERROR', 'An error occured with the USPS shipping calculations.<br>If you prefer to use USPS as your shipping method, please contact the store owner.');
define('MODULE_SHIPPING_USPS_TEXT_DAY', 'Day');
define('MODULE_SHIPPING_USPS_TEXT_DAYS', 'Days');
define('MODULE_SHIPPING_USPS_TEXT_WEEKS', 'Weeks');

define('MODULE_SHIPPING_USPS_STATUS', 'True'); // Do you want to offer USPS shipping?
define('MODULE_SHIPPING_USPS_SERVER', 'production'); // An account at USPS is needed to use the Production server // production  othervise value may be 'test'
define('MODULE_SHIPPING_USPS_HANDLING', '0'); // Handling fee for this shipping method
define('MODULE_SHIPPING_USPS_TAX_CLASS', '0'); // Use the following tax class on the shipping fee
define('MODULE_SHIPPING_USPS_ZONE', '0'); // If a zone is selected, only enable this shipping method for that zone.
define('MODULE_SHIPPING_USPS_SORT_ORDER', '0'); // Sort order of display.
define('MODULE_SHIPPING_USPS_TYPES', 'PRIORITY, PARCEL'); //  EXPRESS, FIRST CLASS, BMP, MEDIA  'Select the domestic services to be offered:
define('MODULE_SHIPPING_USPS_TYPES_INTL', 'EXPRESS MAIL INTERNATIONAL (EMS), EXPRESS MAIL INT, EXPRESS MAIL INT FLAT RATE ENV, PRIORITY MAIL INT, PRIORITY MAIL INT FLAT RATE ENV, PRIORITY MAILINT FLAT RATE BOX, FIRST-CLASS MAIL INT');//  'GLOBAL EXPRESS, GLOBAL EXPRESS NON-DOC RECT, GLOBAL EXPRESS NON-DOC NON-RECT,  Select the international services to be offered:
define('MODULE_SHIPPING_USPS_OPTIONS', 'Display weight, Display transit time'); //

//configuration values for insurance
define('MODULE_SHIPPING_USPS_INS1', '1.65');// 'US/Canada insurance for totals $.01-$50.00
define('MODULE_SHIPPING_USPS_INS2', '2.05');// 'US/Canada insurance for totals $50.01-$100
define('MODULE_SHIPPING_USPS_INS3', '2.45');// 'US/Canada insurance for totals $100.01-$200
define('MODULE_SHIPPING_USPS_INS4', '4.60');// 'US/Canada insurance for totals $200.01-$300
define('MODULE_SHIPPING_USPS_INS5', '.90');// 'US/Canada insurance for every $100 over $300 (add)
define('MODULE_SHIPPING_USPS_INS6', '2.40');//  'International insurance for totals $.01-$50.00
define('MODULE_SHIPPING_USPS_INS7', '3.30');// 'International insurance for totals $50.01-$100
define('MODULE_SHIPPING_USPS_INS8', '4.20');// 'International insurance for totals $100.01-$200
define('MODULE_SHIPPING_USPS_INS9', '5.10');// 'International insurance for totals $200.01-$300
define('MODULE_SHIPPING_USPS_INS10', '.90');// 'International insurance for every $100 over $300 (add)
define('MODULE_SHIPPING_USPS_INSURE', 'True');// 'Insure packages shipped by USPS?
define('MODULE_SHIPPING_USPS_INSURE_TAX', 'True');// 'Insure tax on packages shipped by USPS?

define('USPS_LOG_FILE', WRITEABLE .'/user_files/usps.log');

class USPS extends ShippingQuoteEngine
{
	var $countries, $pounds, $ounces, $insurance_cost = 0, $shipping_origin_country, $store_first_name, $store_last_name, $company_name, $store_name, $store_address1, $store_address2, $store_city, $store_state, $store_zip5, $store_zip4, $store_phone, $usps_userid;
	var $order = Array();
	var $types = Array();
	var $intl_types = Array();


	function USPS()
	{
		parent::kBase();

        // EXPRESS, FIRST CLASS, PRIORITY, PARCEL, BMP, MEDIA
		$this->types = Array(
			'EXPRESS' => 'Express Mail',
			'FIRST CLASS' => 'First Class Mail',
			'PRIORITY' => 'Priority Mail',
			'PARCEL' => 'Parcel Post',
			'BPM' => 'Bound Printed Matter',
			'MEDIA' => 'Media Mail'
		);

		$this->intl_types = Array(
			// 'GLOBAL EXPRESS' => 'Global Express Guaranteed',
			// 'GLOBAL EXPRESS NON-DOC RECT' => 'Global Express Guaranteed Non-Document Rectangular',
			// 'GLOBAL EXPRESS NON-DOC NON-RECT' => 'Global Express Guaranteed Non-Document Non-Rectangular',
			'EXPRESS MAIL INT' => 'Express Mail International (EMS)',
			'EXPRESS MAIL INT FLAT RATE ENV' => 'Express Mail International (EMS) Flat Rate Envelope',
			'PRIORITY MAIL INT' => 'Priority Mail International',
			'PRIORITY MAIL INT FLAT RATE ENV' => 'Priority Mail International Flat Rate Envelope',
			'PRIORITY MAIL INT FLAT RATE BOX' => 'Priority Mail International Flat Rate Box',
			'FIRST-CLASS MAIL INT' => 'First-Class Mail International'
		);

		// get 2-symbol country code
		$country = $this->Application->ConfigValue('Comm_Shipping_Country');

		if ($country != '') {
			$db =& $this->Application->GetADODBConnection();
			$this->shipping_origin_country = $db->GetOne(
				'SELECT DestAbbr2
					FROM '.TABLE_PREFIX.'StdDestinations
					WHERE
						DestAbbr = '.$db->qstr($country).'
						AND destType = 1
				'
			);
		}
		$contact_name = trim($this->_prepare_xml_param($this->Application->ConfigValue('Comm_Contacts_Name')));
		$split_pos = strpos($contact_name, ' ');
		if ($split_pos === false) {
			$this->store_first_name = $contact_name;
			$this->store_last_name = '';
		} else {
			$this->store_first_name = substr($contact_name, 0, $split_pos);
			$this->store_last_name = trim(substr($contact_name, $split_pos));
		}
		$this->company_name = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_CompanyName'));
		$this->store_name = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_StoreName'));
		$this->store_address1 = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_AddressLine1'));
		$this->store_address2 = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_AddressLine2'));

		if ($this->store_address2 == '') {
			$this->store_address2 = $this->store_address1;
			$this->store_address1 = '';
		}

		$this->store_city = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_City'));
		$this->store_state = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_State'));
		$zip = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Shipping_ZIP'));
		$this->store_zip5 = substr($zip, 0, 5);
		$this->store_zip4 = trim(substr($zip, 6), '-');
		$this->store_phone = $this->_prepare_xml_param($this->Application->ConfigValue('Comm_Contacts_Phone'));

		// get username and password fron config.
		$a_params = $this->LoadParams();
		$this->usps_userid = $a_params['AccountLogin'];

		// Note by Erik: DO NOT CHANGE THIS ARRAY. It's values are sent to USPS service and any changes may impact class main functionality.
		$this->countries = array(
			'AF' => 'Afghanistan',
            'AL' => 'Albania',
            'DZ' => 'Algeria',
            'AD' => 'Andorra',
            'AO' => 'Angola',
            'AI' => 'Anguilla',
            'AG' => 'Antigua and Barbuda',
            'AR' => 'Argentina',
            'AM' => 'Armenia',
            'AW' => 'Aruba',
            'AU' => 'Australia',
            'AT' => 'Austria',
            'AZ' => 'Azerbaijan',
            'BS' => 'Bahamas',
        	'BH' => 'Bahrain',
            'BD' => 'Bangladesh',
            'BB' => 'Barbados',
            'BY' => 'Belarus',
            'BE' => 'Belgium',
            'BZ' => 'Belize',
            'BJ' => 'Benin',
            'BM' => 'Bermuda',
            'BT' => 'Bhutan',
            'BO' => 'Bolivia',
            'BA' => 'Bosnia-Herzegovina',
            'BW' => 'Botswana',
            'BR' => 'Brazil',
            'VG' => 'British Virgin Islands',
            'BN' => 'Brunei Darussalam',
            'BG' => 'Bulgaria',
            'BF' => 'Burkina Faso',
            'MM' => 'Burma',
            'BI' => 'Burundi',
            'KH' => 'Cambodia',
            'CM' => 'Cameroon',
            'CA' => 'Canada',
            'CV' => 'Cape Verde',
            'KY' => 'Cayman Islands',
            'CF' => 'Central African Republic',
            'TD' => 'Chad',
            'CL' => 'Chile',
            'CN' => 'China',
            'CX' => 'Christmas Island (Australia)',
            'CC' => 'Cocos Island (Australia)',
            'CO' => 'Colombia',
            'KM' => 'Comoros',
            'CG' => 'Congo (Brazzaville),Republic of the',
            'ZR' => 'Congo, Democratic Republic of the',
            'CK' => 'Cook Islands (New Zealand)',
            'CR' => 'Costa Rica',
            'CI' => 'Cote d\'Ivoire (Ivory Coast)',
            'HR' => 'Croatia',
            'CU' => 'Cuba',
            'CY' => 'Cyprus',
            'CZ' => 'Czech Republic',
            'DK' => 'Denmark',
            'DJ' => 'Djibouti',
            'DM' => 'Dominica',
            'DO' => 'Dominican Republic',
            'TP' => 'East Timor (Indonesia)',
            'EC' => 'Ecuador',
            'EG' => 'Egypt',
            'SV' => 'El Salvador',
            'GQ' => 'Equatorial Guinea',
            'ER' => 'Eritrea',
            'EE' => 'Estonia',
            'ET' => 'Ethiopia',
            'FK' => 'Falkland Islands',
            'FO' => 'Faroe Islands',
            'FJ' => 'Fiji',
            'FI' => 'Finland',
            'FR' => 'France',
            'GF' => 'French Guiana',
            'PF' => 'French Polynesia',
            'GA' => 'Gabon',
            'GM' => 'Gambia',
            'GE' => 'Georgia, Republic of',
            'DE' => 'Germany',
            'GH' => 'Ghana',
            'GI' => 'Gibraltar',
            'GB' => 'Great Britain and Northern Ireland',
            'GR' => 'Greece',
            'GL' => 'Greenland',
            'GD' => 'Grenada',
            'GP' => 'Guadeloupe',
            'GT' => 'Guatemala',
            'GN' => 'Guinea',
            'GW' => 'Guinea-Bissau',
            'GY' => 'Guyana',
            'HT' => 'Haiti',
            'HN' => 'Honduras',
            'HK' => 'Hong Kong',
            'HU' => 'Hungary',
            'IS' => 'Iceland',
            'IN' => 'India',
            'ID' => 'Indonesia',
            'IR' => 'Iran',
            'IQ' => 'Iraq',
            'IE' => 'Ireland',
            'IL' => 'Israel',
            'IT' => 'Italy',
            'JM' => 'Jamaica',
            'JP' => 'Japan',
            'JO' => 'Jordan',
            'KZ' => 'Kazakhstan',
            'KE' => 'Kenya',
            'KI' => 'Kiribati',
            'KW' => 'Kuwait',
            'KG' => 'Kyrgyzstan',
            'LA' => 'Laos',
            'LV' => 'Latvia',
            'LB' => 'Lebanon',
            'LS' => 'Lesotho',
            'LR' => 'Liberia',
            'LY' => 'Libya',
            'LI' => 'Liechtenstein',
            'LT' => 'Lithuania',
            'LU' => 'Luxembourg',
            'MO' => 'Macao',
            'MK' => 'Macedonia, Republic of',
            'MG' => 'Madagascar',
            'MW' => 'Malawi',
            'MY' => 'Malaysia',
            'MV' => 'Maldives',
            'ML' => 'Mali',
            'MT' => 'Malta',
            'MQ' => 'Martinique',
            'MR' => 'Mauritania',
            'MU' => 'Mauritius',
            'YT' => 'Mayotte (France)',
            'MX' => 'Mexico',
            'MD' => 'Moldova',
            'MC' => 'Monaco (France)',
            'MN' => 'Mongolia',
            'MS' => 'Montserrat',
            'MA' => 'Morocco',
            'MZ' => 'Mozambique',
            'NA' => 'Namibia',
            'NR' => 'Nauru',
            'NP' => 'Nepal',
            'NL' => 'Netherlands',
            'AN' => 'Netherlands Antilles',
            'NC' => 'New Caledonia',
            'NZ' => 'New Zealand',
            'NI' => 'Nicaragua',
            'NE' => 'Niger',
            'NG' => 'Nigeria',
            'KP' => 'North Korea (Korea, Democratic People\'s Republic of)',
            'NO' => 'Norway',
            'OM' => 'Oman',
            'PK' => 'Pakistan',
            'PA' => 'Panama',
            'PG' => 'Papua New Guinea',
            'PY' => 'Paraguay',
            'PE' => 'Peru',
            'PH' => 'Philippines',
            'PN' => 'Pitcairn Island',
            'PL' => 'Poland',
            'PT' => 'Portugal',
            'QA' => 'Qatar',
            'RE' => 'Reunion',
            'RO' => 'Romania',
            'RU' => 'Russia',
            'RW' => 'Rwanda',
            'SH' => 'Saint Helena',
            'KN' => 'Saint Kitts (St. Christopher and Nevis)',
            'LC' => 'Saint Lucia',
            'PM' => 'Saint Pierre and Miquelon',
            'VC' => 'Saint Vincent and the Grenadines',
            'SM' => 'San Marino',
            'ST' => 'Sao Tome and Principe',
            'SA' => 'Saudi Arabia',
            'SN' => 'Senegal',
            'YU' => 'Serbia-Montenegro',
            'SC' => 'Seychelles',
            'SL' => 'Sierra Leone',
            'SG' => 'Singapore',
            'SK' => 'Slovak Republic',
            'SI' => 'Slovenia',
            'SB' => 'Solomon Islands',
            'SO' => 'Somalia',
            'ZA' => 'South Africa',
            'GS' => 'South Georgia (Falkland Islands)',
            'KR' => 'South Korea (Korea, Republic of)',
            'ES' => 'Spain',
            'LK' => 'Sri Lanka',
            'SD' => 'Sudan',
            'SR' => 'Suriname',
            'SZ' => 'Swaziland',
            'SE' => 'Sweden',
            'CH' => 'Switzerland',
            'SY' => 'Syrian Arab Republic',
            'TW' => 'Taiwan',
            'TJ' => 'Tajikistan',
            'TZ' => 'Tanzania',
            'TH' => 'Thailand',
            'TG' => 'Togo',
            'TK' => 'Tokelau (Union) Group (Western Samoa)',
            'TO' => 'Tonga',
            'TT' => 'Trinidad and Tobago',
            'TN' => 'Tunisia',
            'TR' => 'Turkey',
            'TM' => 'Turkmenistan',
            'TC' => 'Turks and Caicos Islands',
            'TV' => 'Tuvalu',
            'UG' => 'Uganda',
            'UA' => 'Ukraine',
            'AE' => 'United Arab Emirates',
            'UY' => 'Uruguay',
            'UZ' => 'Uzbekistan',
            'VU' => 'Vanuatu',
            'VA' => 'Vatican City',
            'VE' => 'Venezuela',
            'VN' => 'Vietnam',
            'WF' => 'Wallis and Futuna Islands',
            'WS' => 'Western Samoa',
            'YE' => 'Yemen',
            'ZM' => 'Zambia',
            'ZW' => 'Zimbabwe'
        );

		$this->countryinsure = array(
			'AF' => 0,
            'AL' => 0,
            'DZ' => 2185,
            'AD' => 5000,
            'AO' => 0,
            'AI' => 415,
            'AG' => 60,
            'AR' => 5000,
            'AM' => 1350,
            'AW' => 830,
            'AU' => 3370,
            'AT' => 5000,
            'AZ' => 5000,
            'BS' => 2795,
            'BH' => 0,
            'BD' => 5000,
            'BB' => 220,
            'BY' => 1323,
            'BE' => 5000,
            'BZ' => 1600,
            'BJ' => 170,
            'BM' => 440,
            'BT' => 440,
            'BO' => 0,
            'BA' => 5000,
            'BW' => 145,
            'BR' => 5000,
            'VG' => 165,
            'BN' => 4405,
            'BG' => 1030,
            'BF' => 530,
            'MM' => 4045,
            'BI' => 790,
            'KH' => 0,
            'CM' => 5000,
            'CA' => 675,
            'CV' => 0,
            'KY' => 0,
            'CF' => 4405,
            'TD' => 440,
            'CL' => 0,
            'CN' => 1130,
            'CX' => 3370,
            'CC' => 3370,
            'CO' => 0,
            'KM' => 690,
            'CG' => 1685,
            'ZR' => 0,
            'CK' => 980,
            'CR' => 0,
            'CI' => 5000,
            'HR' => 5000,
            'CU' => 0,
            'CY' => 5000,
            'CZ' => 5000,
            'DK' => 5000,
            'DJ' => 880,
            'DM' => 0,
            'DO' => 0,
            'TP' => 0,
            'EC' => 0,
            'EG' => 1685,
            'SV' => 0,
            'GQ' => 0,
            'ER' => 0,
            'EE' => 2020,
            'ET' => 1000,
            'FK' => 510,
            'FO' => 5000,
            'FJ' => 600,
            'FI' => 5000,
            'FR' => 5000,
            'GF' => 5000,
            'PF' => 1015,
            'GA' => 485,
            'GM' => 2575,
            'GE' => 1350,
            'DE' => 5000,
            'GH' => 5000,
            'GI' => 5000,
            'GB' => 857,
            'GR' => 5000,
            'GL' => 5000,
            'GD' => 350,
            'GP' => 5000,
            'GT' => 0,
            'GN' => 875,
            'GW' => 21,
            'GY' => 10,
            'HT' => 0,
            'HN' => 0,
            'HK' => 5000,
            'HU' => 5000,
            'IS' => 5000,
            'IN' => 2265,
            'ID' => 0,
            'IR' => 0,
            'IQ' => 0,
            'IE' => 5000,
            'IL' => 0,
            'IT' => 5000,
            'JM' => 0,
            'JP' => 5000,
            'JO' => 0,
            'KZ' => 5000,
            'KE' => 815,
            'KI' => 0,
            'KW' => 1765,
            'KG' => 1350,
            'LA' => 0,
            'LV' => 1350,
            'LB' => 440,
            'LS' => 440,
            'LR' => 440,
            'LY' => 0,
            'LI' => 5000,
            'LT' => 5000,
            'LU' => 5000,
            'MO' => 4262,
            'MK' => 2200,
            'MG' => 675,
            'MW' => 50,
            'MY' => 1320,
            'MV' => 0,
            'ML' => 950,
            'MT' => 5000,
            'MQ' => 5000,
            'MR' => 635,
            'MU' => 270,
            'YT' => 5000,
            'MX' => 0,
            'MD' => 1350,
            'MC' => 5000,
            'MN' => 440,
            'MS' => 2200,
            'MA' => 5000,
            'MZ' => 0,
            'NA' => 4405,
            'NR' => 220,
            'NP' => 0,
            'NL' => 5000,
            'AN' => 830,
            'NC' => 1615,
            'NZ' => 980,
            'NI' => 440,
            'NE' => 810,
            'NG' => 205,
            'KP' => 0,
            'NO' => 0,
            'OM' => 575,
            'PK' => 270,
            'PA' => 0,
            'PG' => 445,
            'PY' => 0,
            'PE' => 0,
            'PH' => 270,
            'PN' => 0,
            'PL' => 1350,
            'PT' => 5000,
            'QA' => 2515,
            'RE' => 5000,
            'RO' => 5000,
            'RU' => 5000,
            'RW' => 0,
            'SH' => 170,
            'KN' => 210,
            'LC' => 400,
            'PM' => 5000,
            'VC' => 130,
            'SM' => 5000,
            'ST' => 440,
            'SA' => 0,
            'SN' => 865,
            'YU' => 5000,
            'SC' => 0,
            'SL' => 0,
            'SG' => 4580,
            'SK' => 5000,
            'SI' => 4400,
            'SB' => 0,
            'SO' => 440,
            'ZA' => 1760,
            'GS' => 510,
            'KR' => 5000,
            'ES' => 5000,
            'LK' => 35,
            'SD' => 0,
            'SR' => 535,
            'SZ' => 560,
            'SE' => 5000,
            'CH' => 5000,
            'SY' => 3080,
            'TW' => 1350,
            'TJ' => 1350,
            'TZ' => 230,
            'TH' => 1350,
            'TG' => 2190,
            'TK' => 295,
            'TO' => 515,
            'TT' => 930,
            'TN' => 2200,
            'TR' => 880,
            'TM' => 675,
            'TC' => 0,
            'TV' => 4715,
            'UG' => 0,
            'UA' => 5000,
            'AE' => 5000,
            'UY' => 0,
            'UZ' => 5000,
            'VU' => 0,
            'VA' => 5000,
            'VE' => 0,
            'VN' => 0,
            'WF' => 1615,
            'WS' => 295,
            'YE' => 0,
            'ZM' => 540,
            'ZW' => 600,
            'US' => 5000
    	);
	}

	function SetInsurance()
	{
		$this->insurance_cost = 0;

		// Insurance module by Kevin Shelton
		// divide the value of the order among the packages based on the order total or subtotal depending on whether or not you have configured to insure tax
		$shipping_weight = $this->order['ShippingWeight'];
		$shipping_num_boxes = $this->order['ShippingNumBoxes'];
		$costperpkg = $this->order['SubTotal'] / $shipping_num_boxes;

		// retrieve the maximum allowed insurance for the destination country and if the package value exceeds it then set package value to the maximum allowed

		$maxins = $this->countryinsure[$this->order['ShippingCountry']];
		if ($costperpkg > $maxins) $costperpkg = $maxins;

		// if insurance not allowed for destination or insurance is turned off add nothing to shipping cost

		if (($maxins == 0) || (MODULE_SHIPPING_USPS_INSURE == 'False')) {
			$insurance = 0;
		}
		// US and Canada share the same insurance calculation (though not the same maximum)
		else if (($this->order['ShippingCountry'] == 'US') || ($this->order['ShippingCountry'] == 'CA'))
		{
			if ($costperpkg<=50) {
				$insurance=MODULE_SHIPPING_USPS_INS1;
			}
			else if ($costperpkg<=100) {
				$insurance=MODULE_SHIPPING_USPS_INS2;
			}
			else if ($costperpkg<=200) {
				$insurance=MODULE_SHIPPING_USPS_INS3;
			}
			else if ($costperpkg<=300) {
				$insurance=MODULE_SHIPPING_USPS_INS4;
			}
			else {
				$insurance = MODULE_SHIPPING_USPS_INS4 + ((ceil($costperpkg/100) -3) * MODULE_SHIPPING_USPS_INS5);
			}
		}
		// if insurance allowed and is not US or Canada then calculate international insurance
		else {
			if ($costperpkg<=50) {
			    $insurance=MODULE_SHIPPING_USPS_INS6;
			}
			else if ($costperpkg<=100) {
			    $insurance=MODULE_SHIPPING_USPS_INS7;
			}
			else if ($costperpkg<=200) {
			    $insurance=MODULE_SHIPPING_USPS_INS8;
			}
			else if ($costperpkg<=300) {
			    $insurance=MODULE_SHIPPING_USPS_INS9;
			}
			else {
			    $insurance = MODULE_SHIPPING_USPS_INS9 + ((ceil($costperpkg/100) - 3) * MODULE_SHIPPING_USPS_INS10);
			}
		}
		// usps doesnt accept zero weight
		$shipping_weight = ($shipping_weight < 0.1 ? 0.1 : $shipping_weight);
		$shipping_pounds = floor ($shipping_weight);
		$shipping_ounces = round(16 * ($shipping_weight - floor($shipping_weight)));
		$this->_setWeight($shipping_pounds, $shipping_ounces);
		// Added by Kevin Chen (kkchen@uci.edu); Fixes the Parcel Post Bug July 1, 2004
		// Refer to http://www.usps.com/webtools/htm/Domestic-Rates.htm documentation
		// Thanks Ryan
		if($shipping_pounds > 35 || ($shipping_pounds == 0 && $shipping_ounces < 6)){
			$this->_setMachinable('False');
		}
		else{
			$this->_setMachinable('True');
		}

		$this->insurance_cost = $insurance;
		// End Kevin Chen July 1, 2004
	}

	function _setService($service)
	{
		$this->service = $service;
	}

	function _setWeight($pounds, $ounces=0)
	{
		$this->pounds = $pounds;
		$this->ounces = $ounces;
	}

	function _setContainer($container)
	{
		$this->container = $container;
	}

	function _setSize($size)
	{
		$this->size = $size;
	}

	function _setMachinable($machinable)
	{
		$this->machinable = $machinable;
	}

	function PhoneClean($phone)
	{
		$res = preg_replace('/[(]|[)]|[\-]|[ ]|[#]|[\.]|[a-z](.*)|[A-Z](.*)/g', '', $phone);
		if ( strlen($res) > 10 ) {
			$res = substr($res, 0, 10);
		} 
		return $res != '' ? $res : $phone;
	}

	function GetQuote($method = '')
	{
		if ( isset($this->types[$method]) || in_array($method, $this->intl_types)) {
			$this->_setService($method);
		}
		$this -> _setContainer('None');
		$this -> _setSize('REGULAR');
		$this -> SetInsurance(); // ???
		if ($this->order['ShippingCountry'] == $this->shipping_origin_country) {
			$request='<?xml version="1.0"?>';
			// PASSWORD="'.$this->usps_password.'"
			$request.= '<RateV3Request USERID="'.$this->usps_userid.'">';
			$services_count = 0;
			if (isset($this->service)) {
				$this->types = array($this->service => $this->types[$this->service]);
			}
			$dest_zip = str_replace(' ', '', $this->order['ShippingZip']);
			$dest_zip = substr($dest_zip, 0, 5);
			reset($this->types);
			$allowed_types = explode(", ", MODULE_SHIPPING_USPS_TYPES);

	    	while (list($key, $value) = each($this->types))
	    	{
	 		 	if ( !in_array($key, $allowed_types) ) continue;
				$request .= '<Package ID="'.$services_count.'">'.
				          '<Service>'.$key.'</Service>'.
				          '<ZipOrigination>'.$this->store_zip5.'</ZipOrigination>'.
				          '<ZipDestination>'.$dest_zip.'</ZipDestination>'.
				          '<Pounds>'.$this->pounds.'</Pounds>'.
				          '<Ounces>'.$this->ounces.'</Ounces>'.
				          '<Size>'.$this->size.'</Size>'.
				          '<Machinable>'.$this->machinable.'</Machinable>'.
				          '</Package>';
	     	 	$services_count++;
			}
			$request .= '</RateV3Request>';
			$api_query = 'RateV3';
		}
	  	else {
			$request  = '<IntlRateRequest USERID="'.$this->usps_userid.'">'.
			            '<Package ID="0">'.
			            '<Pounds>'.$this->pounds.'</Pounds>'.
			            '<Ounces>'.$this->ounces.'</Ounces>'.
			            '<MailType>Package</MailType>'.
			            '<Country>'.$this->countries[$this->order['ShippingCountry']].'</Country>'.
			            '</Package>'.
			            '</IntlRateRequest>';
			$api_query = 'IntlRate';
		}
		$request = 'API='.$api_query.'&XML=' . urlencode($request);
		$body = $this->PostQuery($request);
		$body = str_replace(chr(146), '', $body); // for bad `

		// check for errors
		if (strpos($body, '<Error>') !== false) {
			$errors = Array ();
			preg_match_all('/<Number>(.*?)<\/Number>/s', $body, $error_numbers);
			preg_match_all('/<Description>(.*?)<\/Description>/s', $body, $error_descriptions);

			foreach ($error_numbers[1] as $index => $error_number) {
				$errors[$index] = $error_descriptions[1][$index];
				if ($this->Application->isDebugMode()) {
					$errors[$index] .= ' (' . $error_number . ')';
				}
			}

			$errors = array_unique($errors); // we may have same errors on many packages, so don't show duplicates
			return Array('error' => implode('<br/>', $errors));
		}

		// parse response


		$xml_helper =& $this->Application->recallObject('kXMLHelper');
		/* @var $xml_helper kXMLHelper */

		$root_node =& $xml_helper->Parse($body);
		/* @var $root_node kXMLNode */

		$rates = Array();
		// Domestic shipping
		if ($this->order['ShippingCountry'] == $this->shipping_origin_country) {
			$i = 0;
			$postage_node =& $root_node->FindChild('Package');
			do {
				// $parcel_node =& $postage_node->firstChild;
				$service = $postage_node->FindChildValue('MailService');
				if ( $service != '' ) {
					$i++;
					$rates[$i] = Array();
					$rates[$i]['Title'] = $service;
					$rates[$i]['Rate'] = $this->insurance_cost + $postage_node->FindChildValue('Rate');
				}
			}
			while ( $postage_node =& $postage_node->NextSibling());
		}
		else {
			// for International Rates !!!
		    $allowed_types = array();
			foreach( explode(", ", MODULE_SHIPPING_USPS_TYPES_INTL) as $value ) {
				$allowed_types[$value] = $this->intl_types[$value];
			}
			$i = 0;
			$service_node =& $root_node->FindChild('Service');
			do {
				$service = trim($service_node->FindChildValue('SvcDescription'));
				if( !in_array($service, $allowed_types) ) continue;
				$i++;
				if ( $service_node->FindChildValue('MaxWeight') >= $this->pounds ) {
					$rates[$i] = Array();
					$rates[$i]['Title'] = $service;
					$rates[$i]['MaxDimensions'] = $service_node->FindChildValue('MaxDimensions');
					$rates[$i]['MaxWeight'] = $service_node->FindChildValue('MaxWeight');
					$rates[$i]['SvcCommitments'] = $service_node->FindChildValue('SvcCommitments');
					$rates[$i]['Rate'] = $this->insurance_cost + $service_node->FindChildValue('Postage');
				}
			}
			while ( $service_node =& $service_node->NextSibling());
	  	}
	  	// print_r($rates);
	  	// die('here');
		return $rates;
	}

	function PostOrder()
	{
		$request='';
		$base_request = '';
		$this->SetInsurance();
		// $this->order['ShippingCountry'] = $this->GetUSPSCountry($this->order['ShippingCountry']);

		// Domestic Order
		if ($this->order['ShippingCountry'] == $this->shipping_origin_country) {

			// $dest_zip = str_replace(' ', '', $this->order['ShippingZip5']);
			$this->order['ShippingZip5'] = substr($this->order['ShippingZip5'], 0, 5);
			$WeightInOunces = floor($this->pounds * 16 + $this->ounces);

			$base_request ='
			<Option>1</Option>
			<ImageParameters></ImageParameters>
			<FromName>'.$this->store_name.'</FromName>
			<FromFirm>'.$this->company_name.'</FromFirm>
			<FromAddress1>'.$this->store_address1.'</FromAddress1>
			<FromAddress2>'.$this->store_address2.'</FromAddress2>
			<FromCity>'.$this->store_city.'</FromCity>
			<FromState>'.$this->store_state.'</FromState>
			<FromZip5>'.$this->store_zip5.'</FromZip5>
			<FromZip4>'.$this->store_zip4.'</FromZip4>
			<ToName>'.$this->order['FirstName'].' '.$this->order['LastName'].'</ToName>
			<ToFirm>'.$this->order['ShippingCompany'].'</ToFirm>
			<ToAddress1>'.$this->order['ShippingAddress2'].'</ToAddress1>
			<ToAddress2>'.$this->order['ShippingAddress1'].'</ToAddress2>
			<ToCity>'.$this->order['ShippingCity'].'</ToCity>
			<ToState>'.$this->order['ShippingState'].'</ToState>
			<ToZip5>'.$this->order['ShippingZip5'].'</ToZip5>
			<ToZip4>'.$this->order['ShippingZip4'].'</ToZip4>
			<WeightInOunces>'.$WeightInOunces.'</WeightInOunces>
			<ServiceType>'.$this->order['ShippingService'].'</ServiceType>
			<ImageType>PDF</ImageType>
			<LabelDate>'.date('m/d/Y',time()).'</LabelDate>
			<CustomerRefNo></CustomerRefNo>
			<AddressServiceRequested></AddressServiceRequested>
			<SenderName></SenderName><SenderEMail></SenderEMail>
			<RecipientName></RecipientName>
			<RecipientEMail></RecipientEMail>
			';

			$api_query = 'DeliveryConfirmationV3';
			$xml_request = 'DeliveryConfirmationV3.0Request';
		}
		else {

			// International Order(s)
			$shipping_service = strtolower($this->order['ShippingService']);

		    $base_request = '<Option/>
		    <ImageParameters/>
		    <FromFirstName>'.$this->store_first_name.'</FromFirstName>
		    <FromLastName>'.$this->store_last_name.'</FromLastName>
		    <FromFirm>'.$this->company_name.'</FromFirm>
		    <FromAddress1>'.$this->store_address1.'</FromAddress1>
		    <FromAddress2>'.$this->store_address2.'</FromAddress2>
		    <FromCity>'.$this->store_city.'</FromCity>
		    <FromState>'.$this->store_state.'</FromState>
		    <FromZip5>'.$this->store_zip5.'</FromZip5>
		    <FromPhone>'.$this->PhoneClean($this->store_phone).'</FromPhone>
		    <ToName>'.$this->order['FirstName'].' '.$this->order['LastName'].'</ToName>
		    <ToFirm>'.$this->order['ShippingCompany'].'</ToFirm>
		    <ToAddress1></ToAddress1>
		    <ToAddress2>'.$this->order['ShippingAddress2'].'</ToAddress2>
		    <ToAddress3>'.$this->order['ShippingAddress1'].'</ToAddress3>
		    <ToCity>'.$this->order['ShippingCity'].'</ToCity>';
		    if ( $this->order['ShippingProvince'] != '' ) {
		    	$base_request.='
		    	<ToProvince>'.$this->order['ShippingProvince'].'</ToProvince>';
		    }
		    $base_request.='
		    <ToCountry>'.$this->countries[$this->order['ShippingCountry']].'</ToCountry>
		    <ToPostalCode>'.$this->order['ShippingZip'].'</ToPostalCode>
		    <ToPOBoxFlag>N</ToPOBoxFlag>
		    <ToPhone>'.$this->PhoneClean($this->order['ShippingPhone']).'</ToPhone>
		    <ToFax>'.$this->PhoneClean($this->order['ShippingFax']).'</ToFax>
		    <ToEmail>'.$this->order['Email'].'</ToEmail>
		    <ShippingContents>';

		    // add items
			foreach ( $this->order['Items'] as $k => $value ) {
				 $base_request.='
				 <ItemDetail>
					<Description>Computer Parts</Description>
					<Quantity>'.$value['Qty'].'</Quantity>
					<Value>'.($value['Price'] * $value['Qty']).'</Value>
					<NetPounds>'.$value['NetPounds'].'</NetPounds>
					<NetOunces>'.$value['NetOunces'].'</NetOunces>
					<HSTariffNumber>123456</HSTariffNumber>
					<CountryOfOrigin>United States</CountryOfOrigin>
				 </ItemDetail>';
			}
		    // end add items

		    $base_request.='
		    </ShippingContents>
		    <GrossPounds>'.$this->pounds.'</GrossPounds>
		    <GrossOunces>'.$this->ounces.'</GrossOunces>
		    <ContentType>MERCHANDISE</ContentType>
		    <Agreement>Y</Agreement>
		    <InvoiceNumber>'.$this->order['InvoiceNumber'].'</InvoiceNumber>
		    <ImageType>PDF</ImageType>
		    <ImageLayout>ALLINONEFILE</ImageLayout>
		    <LabelDate>'.date('m/d/Y',time()).'</LabelDate>
		    ';

			if (strpos($shipping_service, 'express') !== false) {
				$xml_request = 'ExpressMailIntlRequest';
				$api_query = 'ExpressMailIntl';
			}
			elseif (strpos($shipping_service, 'priority') !== false) {
				$xml_request = 'PriorityMailIntlRequest';
				$api_query = 'PriorityMailIntl';
			}
			else {
				$xml_request = 'FirstClassMailIntlRequest';
				$api_query = 'FirstClassMailIntl';
			}
		}

		$request.= '<'.$xml_request.' USERID="'.$this->usps_userid.'">';
		$request.= $base_request;
		$request.= '</'.$xml_request.'>';

		// die($request);

		$request = 'API='.$api_query.'&XML='.urlencode($request);

		$body = $this->PostQuery($request, 1);

		// check for errors
		if (strpos($body, '<Error>') !== false) {
			$errors = Array ();
			preg_match_all('/<Number>(.*?)<\/Number>/s', $body, $error_numbers);
			preg_match_all('/<Description>(.*?)<\/Description>/s', $body, $error_descriptions);

			foreach ($error_numbers[1] as $index => $error_number) {
				$errors[$index] = Array ('error_number' => $error_number, 'error_description' => $error_descriptions[1][$index]);
			}

			// TODO: find a way to return other error messages in same package as well
			return $errors[0];
		}

		// parse response
		$xml_helper =& $this->Application->recallObject('kXMLHelper');
		$root_node =& $xml_helper->Parse($body);
		/* @var $root_node kXMLNode */
		$Postage = 0;

		$label_file = $TrackingNumber = $PostnetBarCode = '';

		// Domestic shipping
		if ($this->order['ShippingCountry'] == $this->shipping_origin_country ) {
			$delivery_node =& $root_node->FindChild('DeliveryConfirmationV3.0Response');
			do {
				$TrackingNumber = $delivery_node->FindChildValue('DeliveryConfirmationNumber');
				$PostnetBarCode = $delivery_node->FindChildValue('Postnet');
				$DeliveryConfirmationLabel = base64_decode($delivery_node->FindChildValue('DeliveryConfirmationLabel'));
			}
			while ( $delivery_node =& $delivery_node->NextSibling());
		}
		else {

			if (strpos($shipping_service, 'express') !== false) {
				$node_title = 'ExpressMailIntlResponse';
			}
			elseif (strpos($shipping_service, 'priority') !== false) {
				$node_title = 'PriorityMailIntlResponse';
			}
			else {
 				$node_title = 'FirstClassMailIntlResponse';
			}

			$delivery_node =& $root_node->FindChild($node_title);
			$PostnetBarCode = $delivery_node->FindChildValue('BarcodeNumber');
			$Postage = $delivery_node->FindChildValue('Postage');
			$DeliveryConfirmationLabel = base64_decode($delivery_node->FindChildValue('LabelImage'));

		}

		if ( $TrackingNumber != '' ) {
			$label_file = USPS_LABEL_FOLDER.$TrackingNumber.".pdf";
		}
		elseif ( $PostnetBarCode != '' ) {
			$label_file = USPS_LABEL_FOLDER.$PostnetBarCode.".pdf";
		}

		if ( $label_file != '' ) {
			if (!$handle = fopen($label_file, 'a')) echo "Cannot open file ($label_file)";
			if ( @fwrite($handle, $DeliveryConfirmationLabel) === FALSE) echo "Cannot write to file ($label_file)";
		}

		return array('TrackingNumber' => $TrackingNumber, 'PostnetBarCode' => $PostnetBarCode, 'Postage' => $Postage);
	}

	function GetUSPSCountry($country)
	{
		$country = $this->Application->Conn->GetOne('SELECT DestAbbr2 FROM '.TABLE_PREFIX.'StdDestinations WHERE DestAbbr = '.$this->Application->Conn->qstr($country));
		if ( $country == '' ) $country = 'US';
		return $country;
	}

	function GetShippingQuotes($params = null)
	{
		$weights = Kg2Pounds($params['packages']['0']['weight']);
		$weight = '';
		$weight = $weights[0];
		if ( $weights[1] != '' ) {
			$weight.='.'.$weights[1];
		}
		$country = $this->GetUSPSCountry($params['dest_country']);

		$this->order = Array();
		$this->order['ShippingWeight'] = $weight;
		$this->order['ShippingNumBoxes'] = 1;
		$this->order['ShippingZip'] = $params['dest_postal'];
		$this->order['SubTotal'] = $params['amount'];
		$this->order['ShippingCountry'] = $country;

		$shipping_types = Array();
		$rates = $this->GetQuote();

		if ( !isset($rates['error']) ) {
			$this->Application->RemoveVar('usps_errors');

			$i = 1;
			foreach ($rates as $k => $rate ) {
				 $shipping_types['USPS_'.$i] = Array(
		            'ShippingId' => 'USPS_'.$i,
		            'TotalCost' => $rate['Rate'],
		            'ShippingName' => $rate['Title'],
		            'Type' => '1',
		            'CODFlat' => '0',
		            'CODPercent' => '0',
		            'PortalGroups' => ',15,',
		            'InsuranceFee' => '',
		            'COD' => '0',
		            'SelectedOnly' => '0',
		            'Code' => $rate['Title']
				 );
				 $i++;
			}
		}
		else {
			// for Front-End (shipping screen) and Admin (Shipping Tab on editing order)
			$this->Application->StoreVar('usps_errors', $rates['error']);
		}

		$this->Application->StoreVar('current_usps_shipping_types', serialize($shipping_types));
		return $shipping_types;
	}

	function TrackOrder($TrackingNumber='')
	{
		if ( $TrackingNumber != '' ) {
			// http://testing.shippingapis.com/ShippingAPITest.dll?API=TrackV2&XML=<TrackFieldRequest USERID="402INTEC7634"><TrackID ID="EJ958083578US"></TrackID></TrackFieldRequest>

			$request = '<TrackRequest USERID="'.$this->usps_userid.'"><TrackID ID="'.$TrackingNumber.'"></TrackID></TrackRequest>';
			$api_query = 'TrackV2';
			$request = 'API='.$api_query.'&XML='.urlencode($request);
			$body = $this->PostQuery($request);

			// check for errors
			if (strpos($body, '<Error>') !== false) {
				$errors = Array ();
				preg_match_all('/<Number>(.*?)<\/Number>/s', $body, $error_numbers);
				preg_match_all('/<Description>(.*?)<\/Description>/s', $body, $error_descriptions);

				foreach ($error_numbers[1] as $index => $error_number) {
					$errors[$index] = $error_descriptions[1][$index];
					if ($this->Application->isDebugMode()) {
						$errors[$index] .= ' (' . $error_number . ')';
					}
				}

				$errors = array_unique($errors); // we may have same errors on many packages, so don't show duplicates
				return Array('error' => implode('<br/>', $errors));
			}





			$xml_helper =& $this->Application->recallObject('kXMLHelper');
			$root_node =& $xml_helper->Parse($body);
			/* @var $root_node kXMLNode */

			// Tracking Shipping
			$delivery_node =& $root_node->FindChild('TrackInfo');
			$TrackSummary = $delivery_node->FindChildValue('TrackSummary');
			// echo ' TrackSummary ('.$TrackingNumber.') = '.$TrackSummary.'<br>';

			return strpos($TrackSummary, 'delivered') !== false ? 1 : 0;
		}
		else
			return false;
	}

	function ProcessTrackOrders()
	{
		$sql = sprintf('SELECT `OrderId`, `ShippingTracking` FROM %s WHERE `Status` > 3 AND `Delivered` = 0', TABLE_PREFIX.'Orders');
		$orders = $this->Application->Conn->Query($sql);
		foreach ( $orders as $k => $order ) {
			// try to track order
			if ( $order['ShippingTracking'] != '' && $this->TrackOrder($order['ShippingTracking']) ) {
				$update_order = sprintf("UPDATE %s SET `Delivered` = 1 WHERE %s = %s",
					TABLE_PREFIX.'Orders',
					$this->Application->getUnitOption('ord', 'IDField'),
					$order[$this->Application->getUnitOption('ord', 'IDField')]
				);
				$this->Application->Conn->Query($update_order);
			}
		}
	}

	function PostQuery($request, $secure=0)
	{
		switch (MODULE_SHIPPING_USPS_SERVER) {
			case 'production':
				$usps_server = $secure > 0 ? 'https://secure.shippingapis.com' : 'http://production.shippingapis.com' ;
			    $api_dll = 'ShippingAPI.dll';
			break;

			case 'test':
				$usps_server = $secure > 0 ? 'https://secure.shippingapis.com' : 'http://testing.shippingapis.com';
				$api_dll = 'ShippingAPITest.dll';
			break;
		}

		$curl = curl_init();
		curl_setopt($curl, CURLOPT_URL, $usps_server.'/'.$api_dll.'?'.$request);
		curl_setopt($curl, CURLOPT_HEADER, 0);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		$body = curl_exec($curl);
		curl_close($curl);

		if (defined('USPS_LOG_FILE')) {
			$filename = USPS_LOG_FILE;
	      	if ( !$fp = fopen($filename, "a") ) echo("Failed opening file $filename");
		}
	   	$request_url = sprintf("Date %s : IP %s\n\nPost\n\n%s\n\nReplay\n\n%s\n\n",
	   		date("m/d/Y H:i:s",time()),
	   		$_SERVER['REMOTE_ADDR'],
	   		$usps_server.'/'.$api_dll.'?'.urldecode($request),
	   		$body
	   	);
		if (defined('USPS_LOG_FILE')) {
	        if (!fwrite($fp, $request_url)) echo("Failed writing to file $filename");
	        fclose($fp);
		}
		return $body;
	}

	function GetAvailableTypes()
	{
		return array();
		$conn =& $this->Application->GetADODBConnection();
		$types = $conn->Query('SELECT * FROM '.TABLE_PREFIX.'ShippingType');

		$ret = array();
		foreach ($types as $a_type) {
			$a_type['_ClassName'] = get_class($this);
			$a_type['_Id'] = 'CUST_'.$a_type['ShippingID'];
			$a_type['_Name'] = '(Custom) '.$a_type['Name'];
			$ret[] = $a_type;
		}
		return $ret;

	}

	function LoadParams()
	{
		$sql = 'SELECT Properties FROM '.$this->Application->getUnitOption('sqe', 'TableName').'
				WHERE ClassName="USPS"';
		$db =& $this->Application->GetADODBConnection();
		return unserialize($db->GetOne($sql));
	}

	function _prepare_xml_param($value) {
		return strip_tags($value);
	}

}