A Qcodo based CMS/ecommerce framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
21 KiB

12 years ago
  1. <?php
  2. /**
  3. * This file is a part of Quasi CMS
  4. *@package Quasi
  5. */
  6. if(!defined('QUASICMS') ) die('No Quasi.');
  7. if (!defined("USPSREQUEST.CLASS.PHP")){
  8. define("USPSREQUEST.CLASS.PHP",1);
  9. /**
  10. * Class USPSRequest - provides shipping requests for USPS via web services
  11. * Note: This class provides access to the EndiciaRequest as a child request object for
  12. * some services.
  13. *
  14. *Service must be Express, First Class, Priority, Parcel, Library, BPM, Media or ALL for domestic (in US)
  15. *
  16. *@author Erik Winn <erikwinnmail@yahoo.com>
  17. *
  18. * $Id: USPSRequest.class.php 502 2009-02-10 22:09:53Z erikwinn $
  19. *@version 0.1
  20. *
  21. *@copyright (C) 2008 by Erik Winn
  22. *@license GPL v.2
  23. This program is free software; you can redistribute it and/or modify
  24. it under the terms of the GNU General Public License as published by
  25. the Free Software Foundation; either version 2 of the License, or
  26. (at your option) any later version.
  27. This program is distributed in the hope that it will be useful,
  28. but WITHOUT ANY WARRANTY; without even the implied warranty of
  29. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  30. GNU General Public License for more details.
  31. You should have received a copy of the GNU General Public License
  32. along with this program; if not, write to the Free Software
  33. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
  34. *
  35. *@package Quasi
  36. * @subpackage Classes
  37. */
  38. class USPSRequest extends ShippingRequest
  39. {
  40. /**
  41. *@var string Indicates enumeration value for size (REGULAR, LARGE, OVERSIZE)
  42. */
  43. protected $strSize = "REGULAR";
  44. /**
  45. *@var string Indicates enumeration value for FIRST CLASS "type" [LETTER | FLAT | PARCEL]
  46. */
  47. protected $strFirstClassMailType = "FLAT";
  48. /**
  49. *@var ShippingRequest
  50. */
  51. protected $objEndiciaRequest;
  52. /**
  53. * USPSRequest Constructor
  54. *
  55. * @param ShippingMethod objShippingMethod - the method for which to obtain estimate
  56. */
  57. public function __construct(ShippingMethod $objShippingMethod)
  58. {
  59. try {
  60. parent::__construct($objShippingMethod);
  61. } catch (QCallerException $objExc) {
  62. $objExc->IncrementOffset();
  63. throw $objExc;
  64. }
  65. ///@todo this is currently defined in quasi_config - fixme!!
  66. $this->RemoteAccountId = USPS_USERID;
  67. $this->RemotePassword = USPS_PASSWORD;
  68. if($this->blnTestMode)
  69. {
  70. $this->strRemoteDomainName = 'testing.shippingapis.com';
  71. $this->strRemoteCgiUrl = '/ShippingAPITest.dll?';
  72. }
  73. else
  74. {
  75. $this->strRemoteDomainName = 'production.shippingapis.com';
  76. $this->strRemoteCgiUrl = '/ShippingAPI.dll?';
  77. }
  78. $this->UseSsl = false;
  79. }
  80. //Public interface ..
  81. /**
  82. * Returns a shipping label for this method to the order address
  83. * Note that we use the Endicia label server for this - it must be enabled
  84. * and you must have a configured account with Endicia for this to work!
  85. *@return resource gd image object containing label image
  86. */
  87. public function GetLabel()
  88. {
  89. $this->objEndiciaRequest = new EndiciaRequest($this->objShippingMethod);
  90. $this->objShippingLabelImage = $this->objEndiciaRequest->GetLabel();
  91. $this->blnHasErrors = $this->objEndiciaRequest->HasErrors;
  92. $this->strErrors = $this->objEndiciaRequest->Errors;
  93. $this->aryExtraDocumentImages = $this->objEndiciaRequest->ExtraDocumentImages;
  94. $this->aryCustomsFormImages = $this->objEndiciaRequest->CustomsFormImages;
  95. return $this->objShippingLabelImage;
  96. }
  97. /**
  98. * Returns a shipping rate for the order for this method
  99. *@return image object containing the image code
  100. */
  101. public function GetRate()
  102. {
  103. $this->createRequest(ShippingRequestType::Rate, WebRequestType::GET);
  104. $this->submitRequest();
  105. return $this->Rate;
  106. }
  107. //Request string creators
  108. /**
  109. * Creates a rate request - if the shipping is international this call createIntlRateRequest instead ..
  110. */
  111. protected function createRateRequest()
  112. {
  113. if($this->Order->IsInternational)
  114. return $this->createIntlRateRequest();
  115. $this->strApiName = 'API=RateV3';
  116. if($this->blnTestMode)
  117. {
  118. $this->OriginZip = '10022';
  119. $this->DestinationZip = '20008';
  120. $this->Ounces = 5;
  121. $this->Pounds = 10;
  122. $this->Container = 'Flat Rate Box';
  123. }
  124. $this->strRequest = '&XML=';
  125. $strXml = '<RateV3Request USERID="' .$this->strRemoteAccountId . '">';
  126. $strXml .= '<Package ID="0">';
  127. $strXml .= '<Service>' . $this->ServiceType . '</Service>';
  128. $strXml .= '<FirstClassMailType>' . $this->strFirstClassMailType . '</FirstClassMailType>';
  129. $strXml .= '<ZipOrigination>' . $this->OriginZip . '</ZipOrigination>';
  130. $strXml .= '<ZipDestination>' . $this->DestinationZip . '</ZipDestination>';
  131. $strXml .= '<Pounds>' . $this->Pounds . '</Pounds>';
  132. $strXml .= '<Ounces>' . round($this->Ounces, 2) . '</Ounces>';
  133. $strXml .= '<Container>' . $this->Container .'</Container>';
  134. $strXml .= '<Size>' . $this->strSize . '</Size>';
  135. $strXml .= '<Machinable>' . ($this->IsMachinable ? 'True' : 'False') . '</Machinable>';
  136. $strXml .= '</Package></RateV3Request>';
  137. $strXml = urlencode($strXml);
  138. $this->strRequest .= $strXml;
  139. }
  140. /**
  141. * Creates an international shipping rate request
  142. */
  143. protected function createIntlRateRequest()
  144. {
  145. //USPS testing servers do not support international - force to production ..
  146. $this->strRemoteDomainName = 'production.shippingapis.com';
  147. $this->strRemoteCgiUrl = '/ShippingAPI.dll?';
  148. //neato - USPS has decided that it prefers "Great Britain" and not "United Kingdom" ..
  149. $strCountry = strtoupper($this->DestinationCountry);
  150. if($strCountry == "UNITED KINGDOM")
  151. $strCountry = 'GREAT BRITAIN';
  152. $this->strApiName = 'API=IntlRate';
  153. $this->strRequest = '&XML=';
  154. $strXml = '<IntlRateRequest USERID="' . $this->strRemoteAccountId . '">';
  155. $strXml .= '<Package ID="0">';
  156. $strXml .= '<Pounds>' . $this->Pounds . '</Pounds>';
  157. $strXml .= '<Ounces>' . round($this->Ounces, 2) . '</Ounces>';
  158. $strXml .= '<MailType>Package</MailType>';
  159. $strXml .= '<Country>' . $strCountry . '</Country>';
  160. $strXml .= '</Package></IntlRateRequest>';
  161. $strXml = urlencode($strXml);
  162. $this->strRequest .= $strXml;
  163. }
  164. //Response handlers
  165. /**
  166. * Handles a rate request response
  167. */
  168. protected function handleRateResponse()
  169. {
  170. if ($this->objShippingMethod->Order->IsInternational)
  171. $objDomDoc = $this->getDomDocument('IntlRateResponse');
  172. else
  173. $objDomDoc = $this->getDomDocument('RateV3Response');
  174. if($objDomDoc)
  175. {
  176. $strErrorMessage = $this->requestErrors($objDomDoc);
  177. if($strErrorMessage)
  178. {
  179. $this->blnIsAvailable = false;
  180. $this->blnHasErrors = true;
  181. $this->strErrors = $strErrorMessage;
  182. $this->Rate = 0;
  183. } else {
  184. $this->blnIsAvailable = true;
  185. if( $this->objShippingMethod->Order->IsInternational)
  186. return $this->handleIntlRateResponse($objDomDoc);
  187. $nodeList = $objDomDoc->getElementsByTagName('Postage');
  188. $nodeList = $nodeList->item(0)->getElementsByTagName('Rate');
  189. if($nodeList->length > 0)
  190. $this->Rate = $nodeList->item(0)->nodeValue;
  191. else
  192. $this->Rate = 0;
  193. }
  194. } else {
  195. $this->blnIsAvailable = false;
  196. $this->HasErrors = true;
  197. $this->Errors = 'Unknown USPS error ..Request:' . $this->strRequest . ' Response:' . $this->strResponse;
  198. $this->Rate = 0;
  199. }
  200. }
  201. /**
  202. * Handles an International rate request response.
  203. * USPS returns multiple rates for some areas (eg. Australia) and we need to parse the DOM to
  204. * return the correct rate for the method (service type).
  205. * @todo - more sophisticated and configurable matching by package type; currently we mostly grab the first match ..
  206. * Here is a typical response for the Service nodes:
  207. <Service id="4"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  208. <Postage>79.95</Postage><SvcCommitments>1 - 3 Days</SvcCommitments>\
  209. <SvcDescription>Global Express Guaranteed</SvcDescription><MaxDimensions>Max. length 46", width 35", height 46" and max. length plus girth 108"</MaxDimensions><MaxWeight>70</MaxWeight>
  210. </Service>
  211. <Service id="6"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  212. <Postage>79.95</Postage><SvcCommitments>1 - 3 Days</SvcCommitments>
  213. <SvcDescription>Global Express Guaranteed Non-Document Rectangular</SvcDescription><MaxDimensions>Max. length 46", width 35", height 46" and max. length plus girth 108"</MaxDimensions><MaxWeight>70</MaxWeight>
  214. </Service>
  215. <Service id="7"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  216. <Postage>79.95</Postage><SvcCommitments>1 - 3 Days</SvcCommitments>
  217. <SvcDescription>Global Express Guaranteed Non-Document Non-Rectangular</SvcDescription><MaxDimensions>Max. length 46", width 35", height 46" and max. length plus girth 108"</MaxDimensions><MaxWeight>70</MaxWeight>
  218. </Service>
  219. <Service id="12"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  220. <Postage>79.95</Postage><SvcCommitments>1 - 3 Days</SvcCommitments>
  221. <SvcDescription>USPS GXG Envelopes</SvcDescription><MaxDimensions>Cardboard envelope has a dimension of 9 1/2" X 12 1/2" and GXG tyvek envelope has a dimension of 12 1/2" X 15 1/2"</MaxDimensions><MaxWeight>70</MaxWeight>
  222. </Service>
  223. <Service id="1"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  224. <Postage>25.95</Postage><SvcCommitments>8 Days</SvcCommitments>
  225. <SvcDescription>Express Mail International (EMS)</SvcDescription><MaxDimensions>Max.length 36", max. length plus girth 79"</MaxDimensions><MaxWeight>70</MaxWeight>
  226. </Service>
  227. <Service id="10"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  228. <Postage>25.95</Postage><SvcCommitments>8 Days</SvcCommitments>
  229. <SvcDescription>Express Mail International (EMS) Flat-Rate Envelope</SvcDescription><MaxDimensions>9 1/2" X 12 1/2"</MaxDimensions><MaxWeight>70</MaxWeight>
  230. </Service>
  231. <Service id="2"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  232. <Postage>21.50</Postage><SvcCommitments>6 - 10 Days</SvcCommitments>
  233. <SvcDescription>Priority Mail International</SvcDescription><MaxDimensions>Max. length 42", Max length plus girth combined 79"</MaxDimensions><MaxWeight>70</MaxWeight>
  234. </Service>
  235. <Service id="8"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  236. <Postage>11.95</Postage><SvcCommitments>6 - 10 Days</SvcCommitments>
  237. <SvcDescription>Priority Mail International Flat-Rate Envelope</SvcDescription><MaxDimensions>USPS-supplied Priority Mail flat-rate envelope 9 1/2" x 12 1/2." Maximum weight 4 pounds.</MaxDimensions><MaxWeight>4</MaxWeight>
  238. </Service>
  239. <Service id="9"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  240. <Postage>38.95</Postage><SvcCommitments>6 - 10 Days</SvcCommitments>
  241. <SvcDescription>Priority Mail International Flat-Rate Box</SvcDescription><MaxDimensions>USPS-supplied Priority Mail flat-rate box. Maximum weight 20 pounds.</MaxDimensions><MaxWeight>20</MaxWeight>
  242. </Service>
  243. <Service id="11"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  244. <Postage>49.95</Postage><SvcCommitments>6 - 10 Days</SvcCommitments>
  245. <SvcDescription>Priority Mail International Large Flat-Rate Box</SvcDescription><MaxDimensions>USPS-supplied Priority Mail Large flat-rate box. Maximum weight 20 pounds.</MaxDimensions><MaxWeight>20</MaxWeight>
  246. </Service>
  247. <Service id="13"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  248. <Postage>0.94</Postage><SvcCommitments>Varies</SvcCommitments>
  249. <SvcDescription>First Class Mail International Letters</SvcDescription><MaxDimensions>Max. length 11.5", height 6 1/8" or more than 1/4" thick</MaxDimensions><MaxWeight>0.2188</MaxWeight>
  250. </Service>
  251. <Postage>1.20</Postage><SvcCommitments>Varies</SvcCommitments>
  252. <SvcDescription>First Class Mail International Large Envelope</SvcDescription><MaxDimensions>Max. length 15", height 12 or more than 3/4" thick</MaxDimensions><MaxWeight>4</MaxWeight>
  253. </Service>
  254. <Service id="15"><Pounds>0</Pounds><Ounces>0.07</Ounces><MailType>Package</MailType><Country>ROMANIA</Country>
  255. <Postage>1.40</Postage><SvcCommitments>Varies</SvcCommitments>
  256. <SvcDescription>First Class Mail International Package</SvcDescription><MaxDimensions>Max. length 24", max length, height and depth (thickness) combined 36"</MaxDimensions><MaxWeight>4</MaxWeight>
  257. </Service>
  258. *
  259. */
  260. protected function handleIntlRateResponse($objDomDoc)
  261. {
  262. $this->Rate = 0;
  263. $nodeList = $objDomDoc->getElementsByTagName('Service');
  264. if($nodeList->length > 0)
  265. {
  266. foreach($nodeList as $objNode)
  267. {
  268. $nodeSvcDescription = $objNode->getElementsByTagName('SvcDescription');
  269. $nodePostage = $objNode->getElementsByTagName('Postage');
  270. if($nodeSvcDescription->length > 0)
  271. {
  272. $strSvcDesc = $nodeSvcDescription->item(0)->nodeValue;
  273. if(false !== stripos($strSvcDesc , $this->ServiceType ))
  274. {
  275. //these are examples - not written in stone (they just happen to work for me now ..):
  276. if('First Class Mail International' == $this->ServiceType )
  277. {
  278. if(false === stripos($strSvcDesc , 'Large Envelope' ))
  279. continue;
  280. else
  281. {
  282. $this->Rate = $nodePostage->item(0)->nodeValue;
  283. break;
  284. }
  285. }
  286. if('Priority Mail International' == $this->ServiceType )
  287. {
  288. if(false === stripos($strSvcDesc , 'Flat-Rate Envelope' ))
  289. continue;
  290. else
  291. {
  292. $this->Rate = $nodePostage->item(0)->nodeValue;
  293. break;
  294. }
  295. }
  296. }
  297. }
  298. }
  299. }
  300. }
  301. /**
  302. * Utility function to check for request errors - returns either a string containing
  303. * server error messages or false if there were none.
  304. *@param DOMDocument objDomDoc - the server response ..
  305. *@return string | boolean error messages or false if request succeeded.
  306. */
  307. private function requestErrors($objDomDoc)
  308. {
  309. $mixToReturn = false;
  310. $nodeListErrors = $objDomDoc->getElementsByTagName('Error');
  311. if( $nodeListErrors->length > 0 )
  312. {
  313. $this->blnHasErrors = true;
  314. $mixToReturn = 'Request: ' . $this->strRequest;
  315. $nodeListErrorMessages = $objDomDoc->getElementsByTagName('Description');
  316. if( $nodeListErrorMessages->length)
  317. $mixToReturn .= ' Message: ' . $nodeListErrorMessages->item(0)->nodeValue;
  318. }
  319. return $mixToReturn;
  320. }
  321. /*************************************************************************************/
  322. ///@todo - implement me:
  323. /**
  324. * Returns an account status report
  325. *@return string containing the status report
  326. */
  327. public function GetAccountStatus()
  328. {
  329. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  330. ShippingRequestType::ToString($this->ShippingRequestType)) );
  331. }
  332. /**
  333. * Returns whether this method is available for the order address
  334. *@return boolean true if method is available
  335. */
  336. public function GetAvailability()
  337. {
  338. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  339. ShippingRequestType::ToString($this->ShippingRequestType)) );
  340. }
  341. /**
  342. * Submits an account credit payment
  343. *@return boolean true on success
  344. */
  345. public function CreditAccount()
  346. {
  347. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  348. ShippingRequestType::ToString($this->ShippingRequestType)) );
  349. }
  350. //Request string creators
  351. /**
  352. * Creates a method available request
  353. */
  354. protected function createAvailabilityRequest()
  355. {
  356. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  357. ShippingRequestType::ToString($this->ShippingRequestType)) );
  358. }
  359. /**
  360. * Creates a label printing request
  361. */
  362. protected function createLabelRequest()
  363. {
  364. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  365. ShippingRequestType::ToString($this->ShippingRequestType)) );
  366. }
  367. /**
  368. * Creates a request submitting an account credit payment
  369. */
  370. protected function createCreditAccountRequest()
  371. {
  372. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  373. ShippingRequestType::ToString($this->ShippingRequestType)) );
  374. }
  375. /**
  376. * Creates an account status request
  377. */
  378. protected function createAccountStatusRequest()
  379. {
  380. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  381. ShippingRequestType::ToString($this->ShippingRequestType)) );
  382. }
  383. /**
  384. * Handles an account status request
  385. */
  386. protected function handleAccountStatusResponse()
  387. {
  388. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  389. ShippingRequestType::ToString($this->ShippingRequestType)) );
  390. }
  391. /**
  392. * Handles a request submitting an account credit payment
  393. */
  394. protected function handleCreditAccountResponse()
  395. {
  396. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  397. ShippingRequestType::ToString($this->ShippingRequestType)) );
  398. }
  399. /**
  400. * Handles a method available request
  401. */
  402. protected function handleAvailabilityResponse()
  403. {
  404. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  405. ShippingRequestType::ToString($this->ShippingRequestType)) );
  406. }
  407. /**
  408. * Handles a label request
  409. */
  410. protected function handleLabelResponse()
  411. {
  412. throw new QCallerException(sprintf('USPSRequest: Shipping request type %s unsupported! ',
  413. ShippingRequestType::ToString($this->ShippingRequestType)) );
  414. }
  415. /*************************************************************************************/
  416. }//end class
  417. }//end define
  418. ?>