A QCodo powered CMS
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.

395 lines
17 KiB

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