uc_ups.module

Tracking 5.x-1.x branch
  1. drupal
    1. 5 contributions/ubercart/shipping/uc_ups/uc_ups.module
    2. 6 contributions/ubercart/shipping/uc_ups/uc_ups.module
    3. 7 contributions/ubercart/shipping/uc_ups/uc_ups.module

Shipping quote module that interfaces with www.ups.com to get rates for small package shipments.

Coded by Lyle Mantooth

Functions & methods

NameDescription
theme_uc_ups_confirm_shipmentDisplay final shipment information for review.
theme_uc_ups_label_imageDisplay the shipping label for printing.
uc_ups_access_requestReturn XML access request to be prepended to all requests to the UPS webservice.
uc_ups_admin_settingsUPS Online Tool settings.
uc_ups_admin_settings_validateValidation handler for uc_ups_admin_settings.
uc_ups_configurationImplementation of hook_configuration().
uc_ups_confirm_shipmentLast chance for user to review shipment.
uc_ups_confirm_shipment_submitSubmit handler for uc_ups_confirm_shipment().
uc_ups_form_alterImplementation of hook_form_alter().
uc_ups_fulfill_orderShipment creation callback.
uc_ups_fulfill_order_submitSubmit handler for uc_ups_fulfill_order().
uc_ups_fulfill_order_validateValidation handler for uc_ups_fulfill_order().
uc_ups_markupModify the rate received from UPS before displaying to the customer.
uc_ups_menuImplementation of hook_menu().
uc_ups_nodeapiImplementation of hook_nodeapi().
uc_ups_product_alter_validateValidation handler for UPS product fields.
uc_ups_quoteCallback for retrieving a UPS shipping quote.
uc_ups_request_pickupConstruct an XML label and pickup request.
uc_ups_shipment_requestConstruct an XML shippment request.
uc_ups_shipping_methodImplementation of Übercart's hook_shipping_method().
uc_ups_shipping_quoteConstruct an XML quote request.
uc_ups_shipping_typeImplementation of Übercart's hook_shipping_type().
uc_ups_store_statusImplementation of Übercart's hook_store_status().
uc_ups_void_shipment
uc_ups_void_shipment_request
_uc_ups_new_package
_uc_ups_pkg_typesConvenience function to get UPS codes for their package types.
_uc_ups_service_listConvenience function to get UPS codes for their services.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Shipping quote module that interfaces with www.ups.com to get rates for small
  5. * package shipments.
  6. *
  7. * Coded by Lyle Mantooth
  8. */
  9. /******************************************************************************
  10. * Drupal Hooks *
  11. ******************************************************************************/
  12. /**
  13. * Implementation of hook_menu().
  14. */
  15. function uc_ups_menu($may_cache) {
  16. $items = array();
  17. if ($may_cache) {
  18. $items[] = array('path' => 'admin/store/settings/quotes/methods/ups',
  19. 'access' => user_access('configure quotes'),
  20. 'title' => t('UPS'),
  21. 'callback' => 'drupal_get_form',
  22. 'callback arguments' => 'uc_ups_admin_settings',
  23. 'type' => MENU_LOCAL_TASK,
  24. );
  25. }
  26. else {
  27. $items[] = array('path' => 'admin/store/orders/'. arg(3) .'/shipments/ups',
  28. 'access' => user_access('fulfill orders'),
  29. 'title' => t('UPS shipment'),
  30. 'callback' => 'drupal_get_form',
  31. 'callback arguments' => array('uc_ups_confirm_shipment', arg(3)),
  32. 'type' => MENU_CALLBACK,
  33. );
  34. $items[] = array('path' => 'admin/store/orders/'. arg(3) .'/shipments/labels/ups',
  35. 'access' => user_access('fulfill orders'),
  36. 'callback' => 'theme',
  37. 'callback arguments' => array('uc_ups_label_image'),
  38. 'type' => MENU_CALLBACK,
  39. );
  40. drupal_add_css(drupal_get_path('module', 'uc_ups') .'/uc_ups.css');
  41. }
  42. return $items;
  43. }
  44. /**
  45. * Implementation of hook_form_alter().
  46. *
  47. * Add package type to products.
  48. *
  49. * @see uc_product_form
  50. * @see uc_ups_product_alte_validate
  51. */
  52. function uc_ups_form_alter($form_id, &$form) {
  53. $node = $form['#node'];
  54. if (is_object($node) && $form_id == $node->type .'_node_form' && isset($form['base']['dimensions']) && in_array($node->type, module_invoke_all('product_types'))) {
  55. $enabled = variable_get('uc_quote_enabled', array());
  56. $weight = variable_get('uc_quote_method_weight', array('ups' => 0));
  57. $ups = array('#type' => 'fieldset',
  58. '#title' => t('UPS product description'),
  59. '#collapsible' => true,
  60. '#collapsed' => ($enabled['ups'] == false || uc_product_get_shipping_type($node) != 'small_package'),
  61. '#weight' => $weight['ups'],
  62. '#tree' => true,
  63. );
  64. $ups['pkg_type'] = array('#type' => 'select',
  65. '#title' => t('Package type'),
  66. '#options' => _uc_ups_pkg_types(),
  67. '#default_value' => $node->ups['pkg_type'] ? $node->ups['pkg_type'] : '02',
  68. );
  69. $form['shipping']['ups'] = $ups;
  70. if ($enabled['ups']) {
  71. $form['#validate']['uc_ups_product_alter_validate'] = array();
  72. }
  73. }
  74. }
  75. /**
  76. * Validation handler for UPS product fields.
  77. */
  78. function uc_ups_product_alter_validate($form_id, $form_values) {
  79. $enabled = variable_get('uc_quote_enabled', array());
  80. if ($form_values['shippable'] && ($form_values['shipping_type'] == 'small_package' || (empty($form_values['shipping_type']) && variable_get('uc_store_shipping_type', 'small_package') == 'small_package'))) {
  81. if ($form_values['ups']['pkg_type'] == '02' && (empty($form_values['length']) || empty($form_values['width']) || empty($form_values['height']))) {
  82. form_set_error('base][dimensions', t('Dimensions are required for custom packaging.'));
  83. }
  84. }
  85. }
  86. /**
  87. * Implementation of hook_nodeapi().
  88. */
  89. function uc_ups_nodeapi(&$node, $op) {
  90. if (in_array($node->type, module_invoke_all('product_types'))) {
  91. switch ($op) {
  92. case 'insert':
  93. case 'update':
  94. if (isset($node->ups)) {
  95. $ups_values = $node->ups;
  96. if (!$node->revision) {
  97. db_query("DELETE FROM {uc_ups_products} WHERE vid = %d", $node->vid);
  98. }
  99. db_query("INSERT INTO {uc_ups_products} (vid, nid, pkg_type) VALUES (%d, %d, '%s')",
  100. $node->vid, $node->nid, $ups_values['pkg_type']);
  101. }
  102. break;
  103. case 'load':
  104. if (uc_product_get_shipping_type($node) == 'small_package') {
  105. return array('ups' => db_fetch_array(db_query("SELECT * FROM {uc_ups_products} WHERE vid = %d", $node->vid)));
  106. }
  107. break;
  108. case 'delete':
  109. db_query("DELETE FROM {uc_ups_products} WHERE nid = %d", $node->nid);
  110. break;
  111. case 'delete revision':
  112. db_query("DELETE FROM {uc_ups_products} WHERE vid = %d", $node->vid);
  113. break;
  114. }
  115. }
  116. }
  117. /******************************************************************************
  118. * Workflow-ng Hooks *
  119. ******************************************************************************/
  120. /**
  121. * Implementation of hook_configuration().
  122. *
  123. * Connect the UPS quote action and event.
  124. */
  125. function uc_ups_configuration() {
  126. $enabled = variable_get('uc_quote_enabled', array());
  127. $configurations = array(
  128. 'uc_ups_get_quote' => array(
  129. '#label' => t('Shipping quote from UPS'),
  130. '#event' => 'get_quote_from_ups',
  131. '#module' => 'uc_ups',
  132. '#active' => $enabled['ups'],
  133. ),
  134. );
  135. $action = workflow_ng_use_action('uc_quote_action_get_quote', array(
  136. '#label' => t('Fetch a shipping quote'),
  137. ));
  138. $configurations['uc_ups_get_quote'] = workflow_ng_configure($configurations['uc_ups_get_quote'], $action);
  139. return $configurations;
  140. }
  141. /******************************************************************************
  142. * Übercart Hooks *
  143. ******************************************************************************/
  144. /**
  145. * Implementation of Übercart's hook_shipping_type().
  146. */
  147. function uc_ups_shipping_type() {
  148. $weight = variable_get('uc_quote_type_weight', array('small_package' => 0));
  149. $types = array();
  150. $types['small_package'] = array(
  151. 'id' => 'small_package',
  152. 'title' => t('Small packages'),
  153. 'weight' => $weight['small_package'],
  154. );
  155. return $types;
  156. }
  157. /**
  158. * Implementation of Übercart's hook_shipping_method().
  159. */
  160. function uc_ups_shipping_method() {
  161. $methods = array();
  162. $enabled = variable_get('uc_quote_enabled', array());
  163. $weight = variable_get('uc_quote_method_weight', array('ups' => 0));
  164. $methods['ups'] = array(
  165. 'id' => 'ups',
  166. 'module' => 'uc_ups',
  167. 'title' => t('UPS'),
  168. 'enabled' => $enabled['ups'],
  169. 'quote' => array(
  170. 'type' => 'small_package',
  171. 'callback' => 'uc_ups_quote',
  172. 'accessorials' => _uc_ups_service_list(),
  173. ),
  174. 'ship' => array(
  175. 'type' => 'small_package',
  176. 'callback' => 'uc_ups_fulfill_order',
  177. 'pkg_types' => _uc_ups_pkg_types(),
  178. ),
  179. 'cancel' => 'uc_ups_void_shipment',
  180. 'weight' => $weight['ups'],
  181. );
  182. return $methods;
  183. }
  184. /**
  185. * Implementation of Übercart's hook_store_status().
  186. *
  187. * Let the administrator know that the UPS account information has not been
  188. * filled out.
  189. */
  190. function uc_ups_store_status() {
  191. $messages = array();
  192. $access = variable_get('uc_ups_access_license', '') != '';
  193. $account = variable_get('uc_ups_shipper_number', '') != '';
  194. $user = variable_get('uc_ups_user_id', '') != '';
  195. $password = variable_get('uc_ups_password', '') != '';
  196. if ($access && $account && $user && $password) {
  197. $messages[] = array('status' => 'ok', 'title' => t('UPS Online Tools'),
  198. 'desc' => t('Information needed to access UPS Online Tools has been entered.'),
  199. );
  200. }
  201. else {
  202. $messages[] = array('status' => 'error', 'title' => t('UPS Online Tools'),
  203. 'desc' => t('More information is needed to access UPS Online Tools. Please enter it <a href="!url">here</a>.', array('!url' => url('admin/store/settings/quotes/methods/ups'))),
  204. );
  205. }
  206. return $messages;
  207. }
  208. /******************************************************************************
  209. * Menu Callbacks *
  210. ******************************************************************************/
  211. /**
  212. * UPS Online Tool settings.
  213. *
  214. * Record UPS account information neccessary to use the service. Allow testing
  215. * or production mode. Configure which UPS services are quoted to customers.
  216. *
  217. * @ingroup forms
  218. * @see uc_admin_settings_validate
  219. */
  220. function uc_ups_admin_settings() {
  221. $form = array();
  222. $form['uc_ups_access_license'] = array('#type' => 'textfield',
  223. '#title' => t('UPS OnLine Tools XML Access Key'),
  224. '#default_value' => variable_get('uc_ups_access_license', ''),
  225. '#required' => true,
  226. );
  227. $form['uc_ups_shipper_number'] = array('#type' => 'textfield',
  228. '#title' => t('UPS Shipper #'),
  229. '#description' => t('The 6-character string identifying your UPS account as a shipper.'),
  230. '#default_value' => variable_get('uc_ups_shipper_number', ''),
  231. '#required' => true,
  232. );
  233. $form['uc_ups_user_id'] = array('#type' => 'textfield',
  234. '#title' => t('UPS.com user ID'),
  235. '#default_value' => variable_get('uc_ups_user_id', ''),
  236. '#required' => true,
  237. );
  238. $form['uc_ups_password'] = array('#type' => 'password',
  239. '#title' => t('Password'),
  240. '#default_value' => variable_get('uc_ups_password', ''),
  241. );
  242. $form['uc_ups_connection_address'] = array('#type' => 'select',
  243. '#title' => t('Mode'),
  244. '#description' => t('Quotes and shipments requested in Testing mode will not be picked up or charged to your account.'),
  245. '#options' => array('https://wwwcie.ups.com/ups.app/xml/' => t('Testing'),
  246. 'https://www.ups.com/ups.app/xml/' => t('Production'),
  247. ),
  248. '#default_value' => variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/'),
  249. );
  250. $form['uc_ups_services'] = array('#type' => 'checkboxes',
  251. '#title' => t('UPS services'),
  252. '#default_value' => variable_get('uc_ups_services', _uc_ups_service_list()),
  253. '#options' => _uc_ups_service_list(),
  254. '#description' => t('Select the UPS services that are available to customers.'),
  255. );
  256. $form['uc_ups_pickup_type'] = array('#type' => 'select',
  257. '#title' => t('Pickup type'),
  258. '#options' => array(
  259. '01' => 'Daily Pickup',
  260. '03' => 'Customer Counter',
  261. '06' => 'One Time Pickup',
  262. '07' => 'On Call Air',
  263. '11' => 'Suggested Retail Rates',
  264. '19' => 'Letter Center',
  265. '20' => 'Air Service Center',
  266. ),
  267. '#default_value' => variable_get('uc_ups_pickup_type', '01'),
  268. );
  269. $form['uc_ups_classification'] = array('#type' => 'select',
  270. '#title' => t('UPS Customer classification'),
  271. '#options' => array(
  272. '01' => t('Wholesale'),
  273. '03' => t('Occasional'),
  274. '04' => t('Retail'),
  275. ),
  276. '#default_value' => variable_get('uc_ups_classification', '04'),
  277. '#description' => t('The kind of customer you are to UPS. For daily pickups the default is wholesale; for customer counter pickups the default is retail; for other pickups the default is occasional.'),
  278. );
  279. $form['uc_ups_negotiated_rates'] = array('#type' => 'radios',
  280. '#title' => t('Negotiated rates'),
  281. '#default_value' => variable_get('uc_ups_negotiated_rates', 0),
  282. '#options' => array(1 => t('Yes'), 0 => t('No')),
  283. '#description' => t('Is your UPS account receiving negotiated rates on shipments?'),
  284. );
  285. $form['uc_ups_residential_quotes'] = array('#type' => 'radios',
  286. '#title' => t('Assume UPS shipping quotes will be delivered to'),
  287. '#default_value' => variable_get('uc_ups_residential_quotes', 0),
  288. '#options' => array(
  289. 0 => t('Business locations'),
  290. 1 => t('Residential locations (extra fees)'),
  291. ),
  292. );
  293. $form['uc_ups_markup_type'] = array('#type' => 'select',
  294. '#title' => t('Markup type'),
  295. '#default_value' => variable_get('uc_ups_markup_type', 'percentage'),
  296. '#options' => array(
  297. 'percentage' => t('Percentage (%)'),
  298. 'multiplier' => t('Multiplier (×)'),
  299. 'currency' => t('Addition (!currency)', array('!currency' => variable_get('uc_currency_sign', '$'))),
  300. ),
  301. );
  302. $form['uc_ups_markup'] = array('#type' => 'textfield',
  303. '#title' => t('Shipping rate markup'),
  304. '#default_value' => variable_get('uc_ups_markup', '0'),
  305. '#description' => t('Markup shipping rate quote by currency amount, percentage, or multiplier.'),
  306. );
  307. $form['uc_ups_all_in_one'] = array('#type' => 'radios',
  308. '#title' => t('Product packages'),
  309. '#default_value' => variable_get('uc_ups_all_in_one', 1),
  310. '#options' => array(
  311. 0 => t('Each in its own package'),
  312. 1 => t('All in one'),
  313. ),
  314. '#description' => t('Indicate whether each product is quoted as shipping separately or all in one package.'),
  315. );
  316. $form['uc_ups_unit_system'] = array('#type' => 'select',
  317. '#title' => t('System of measurement'),
  318. '#default_value' => variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in')),
  319. '#options' => array(
  320. 'in' => t('British'),
  321. 'cm' => t('Metric'),
  322. ),
  323. '#description' => t('Choose the standard system of measurement for your country.'),
  324. );
  325. $form['uc_ups_insurance'] = array(
  326. '#type' => 'checkbox',
  327. '#title' => t('Package insurance'),
  328. '#default_value' => variable_get('uc_ups_insurance', TRUE),
  329. '#description' => t('When enabled, products are insured for their full value.'),
  330. );
  331. return system_settings_form($form);
  332. }
  333. /**
  334. * Validation handler for uc_ups_admin_settings.
  335. *
  336. * Require password only if it hasn't been set.
  337. */
  338. function uc_ups_admin_settings_validate($form_id, $form_values, $form) {
  339. $old_password = variable_get('uc_ups_password', '');
  340. if (!$form_values['uc_ups_password']) {
  341. if ($old_password) {
  342. form_set_value($form['uc_ups_password'], $old_password);
  343. }
  344. else {
  345. form_set_error('uc_ups_password', t('Password field is required.'));
  346. }
  347. }
  348. }
  349. /******************************************************************************
  350. * Module Functions *
  351. ******************************************************************************/
  352. /**
  353. * Return XML access request to be prepended to all requests to the UPS webservice.
  354. */
  355. function uc_ups_access_request() {
  356. $access = variable_get('uc_ups_access_license', '');
  357. $user = variable_get('uc_ups_user_id', '');
  358. $password = variable_get('uc_ups_password', '');
  359. return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  360. <AccessRequest xml:lang=\"en-US\">
  361. <AccessLicenseNumber>$access</AccessLicenseNumber>
  362. <UserId>$user</UserId>
  363. <Password>$password</Password>
  364. </AccessRequest>
  365. ";
  366. }
  367. /**
  368. * Construct an XML quote request.
  369. *
  370. * @param $packages
  371. * Array of packages received from the cart.
  372. * @param $origin
  373. * Delivery origin address information.
  374. * @param $destination
  375. * Delivery destination address information.
  376. * @param $ups_service
  377. * UPS service code (refers to UPS Ground, Next-Day Air, etc.).
  378. * @return
  379. * RatingServiceSelectionRequest XML document to send to UPS
  380. */
  381. function uc_ups_shipping_quote($packages, $origin, $destination, $ups_service) {
  382. $store['name'] = variable_get('uc_store_name', NULL);
  383. $store['owner'] = variable_get('uc_store_owner', NULL);
  384. $store['email'] = variable_get('uc_store_email', NULL);
  385. $store['email_from'] = variable_get('uc_store_email', NULL);
  386. $store['phone'] = variable_get('uc_store_phone', NULL);
  387. $store['fax'] = variable_get('uc_store_fax', NULL);
  388. $store['street1'] = variable_get('uc_store_street1', NULL);
  389. $store['street2'] = variable_get('uc_store_street2', NULL);
  390. $store['city'] = variable_get('uc_store_city', NULL);
  391. $store['zone'] = variable_get('uc_store_zone', NULL);
  392. $store['postal_code'] = variable_get('uc_store_postal_code', NULL);
  393. $store['country'] = variable_get('uc_store_country', 840);
  394. $account = variable_get('uc_ups_shipper_number', '');
  395. $ua = explode(' ', $_SERVER['HTTP_USER_AGENT']);
  396. $user_agent = $ua[0];
  397. $services = _uc_ups_service_list();
  398. $service = array('code' => $ups_service, 'description' => $services[$ups_service]);
  399. $pkg_types = _uc_ups_pkg_types();
  400. $shipper_zone = uc_get_zone_code($store['zone']);
  401. $shipper_country = uc_get_country_data(array('country_id' => $store['country']));
  402. $shipper_country = $shipper_country[0]['country_iso_code_2'];
  403. $shipper_zip = $store['postal_code'];
  404. $shipto_zone = uc_get_zone_code($destination->zone);
  405. $shipto_country = uc_get_country_data(array('country_id' => $destination->country));
  406. $shipto_country = $shipto_country[0]['country_iso_code_2'];
  407. $shipto_zip = $destination->postal_code;
  408. $shipfrom_zone = uc_get_zone_code($origin->zone);
  409. $shipfrom_country = uc_get_country_data(array('country_id' => $origin->country));
  410. $shipfrom_country = $shipfrom_country[0]['country_iso_code_2'];
  411. $shipfrom_zip = $origin->postal_code;
  412. $ups_units = variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'));
  413. switch ($ups_units) {
  414. case 'in':
  415. $units = 'LBS';
  416. $unit_name = 'Pounds';
  417. break;
  418. case 'cm':
  419. $units = 'KGS';
  420. $unit_name = 'Kilograms';
  421. break;
  422. }
  423. $shipment_weight = 0;
  424. $package_schema = '';
  425. foreach ($packages as $package) {
  426. $qty = $package->qty;
  427. for ($i = 0; $i < $qty; $i++) {
  428. $package_type = array('code' => $package->pkg_type, 'description' => $pkg_types[$package->pkg_type]);
  429. $package_schema .= "<Package>";
  430. $package_schema .= "<PackagingType>";
  431. $package_schema .= "<Code>". $package_type['code'] ."</Code>";
  432. //$package_schema .= "<Description>". $package_type['description'] ."</Description>";
  433. $package_schema .= "</PackagingType>";
  434. if ($package->pkg_type == '02' && $package->length && $package->width && $package->height) {
  435. if ($package->length < $package->width) {
  436. list($package->length, $package->width) = array($package->width, $package->length);
  437. }
  438. $package_schema .= "<Dimensions>";
  439. $package_schema .= "<UnitOfMeasurement>";
  440. $conversion = uc_weight_conversion($package->length_units, variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in')));
  441. $package_schema .= "<Code>". strtoupper(variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))) ."</Code>";
  442. $package_schema .= "</UnitOfMeasurement>";
  443. $package_schema .= "<Length>". number_format($package->length * $conversion, 2, '.', '') ."</Length>";
  444. $package_schema .= "<Width>". number_format($package->width * $conversion, 2, '.', '') ."</Width>";
  445. $package_schema .= "<Height>". number_format($package->height * $conversion, 2, '.', '') ."</Height>";
  446. $package_schema .= "</Dimensions>";
  447. }
  448. $size = $package->length * $conversion + 2 * $conversion * ($package->width + $package->height);
  449. switch ($ups_units) {
  450. case 'in':
  451. $conversion = uc_weight_conversion($package->weight_units, 'lb');
  452. break;
  453. case 'cm':
  454. $conversion = uc_weight_conversion($package->weight_units, 'kg');
  455. break;
  456. }
  457. $weight = max(1, $package->weight * $conversion);
  458. $shipment_weight += $weight;
  459. $package_schema .= "<PackageWeight>";
  460. $package_schema .= "<UnitOfMeasurement>";
  461. $package_schema .= "<Code>$units</Code>";
  462. $package_schema .= "<Description>$unit_name</Description>";
  463. $package_schema .= "</UnitOfMeasurement>";
  464. $package_schema .= "<Weight>". number_format($weight, 1, '.', '') ."</Weight>";
  465. $package_schema .= "</PackageWeight>";
  466. if ($size > 130 && $size <= 165) {
  467. $package_schema .= "<LargePackageIndicator/>";
  468. }
  469. if (variable_get('uc_ups_insurance', TRUE)) {
  470. $package_schema .= "<PackageServiceOptions>";
  471. $package_schema .= "<InsuredValue>";
  472. $package_schema .= "<CurrencyCode>". variable_get('uc_currency_code', 'USD') ."</CurrencyCode>";
  473. $package_schema .= "<MonetaryValue>". $package->price ."</MonetaryValue>";
  474. $package_schema .= "</InsuredValue>";
  475. $package_schema .= "</PackageServiceOptions>";
  476. }
  477. $package_schema .= "</Package>";
  478. }
  479. }
  480. $schema = uc_ups_access_request() ."
  481. <?xml version=\"1.0\" encoding=\"UTF-8\"?>
  482. <RatingServiceSelectionRequest xml:lang=\"en-US\">
  483. <Request>
  484. <TransactionReference>
  485. <CustomerContext>Complex Rate Request</CustomerContext>
  486. <XpciVersion>1.0001</XpciVersion>
  487. </TransactionReference>
  488. <RequestAction>Rate</RequestAction>
  489. <RequestOption>rate</RequestOption>
  490. </Request>
  491. <PickupType>
  492. <Code>". variable_get('uc_ups_pickup_type', '01') ."</Code>
  493. </PickupType>
  494. <CustomerClassification>
  495. <Code>". variable_get('uc_ups_classification', '04') ."</Code>
  496. </CustomerClassification>
  497. <Shipment>
  498. <Shipper>
  499. <ShipperNumber>". variable_get('uc_ups_shipper_number', '') ."</ShipperNumber>
  500. <Address>
  501. <City>". $store['city'] ."</City>
  502. <StateProvinceCode>$shipper_zone</StateProvinceCode>
  503. <PostalCode>$shipper_zip</PostalCode>
  504. <CountryCode>$shipper_country</CountryCode>
  505. </Address>
  506. </Shipper>
  507. <ShipTo>
  508. <Address>
  509. <StateProvinceCode>$shipto_zone</StateProvinceCode>
  510. <PostalCode>$shipto_zip</PostalCode>
  511. <CountryCode>$shipto_country</CountryCode>
  512. ";
  513. if (variable_get('uc_ups_residential_quotes', 0)) {
  514. $schema .= "<ResidentialAddressIndicator/>
  515. ";
  516. }
  517. $schema .= "</Address>
  518. </ShipTo>
  519. <ShipFrom>
  520. <Address>
  521. <StateProvinceCode>$shipfrom_zone</StateProvinceCode>
  522. <PostalCode>$shipfrom_zip</PostalCode>
  523. <CountryCode>$shipfrom_country</CountryCode>
  524. </Address>
  525. </ShipFrom>
  526. <ShipmentWeight>
  527. <UnitOfMeasurement>
  528. <Code>$units</Code>
  529. <Description>$unit_name</Description>
  530. </UnitOfMeasurement>
  531. <Weight>". number_format($shipment_weight, 1, '.', '') ."</Weight>
  532. </ShipmentWeight>
  533. <Service>
  534. <Code>{$service[code]}</Code>
  535. <Description>{$service[description]}</Description>
  536. </Service>
  537. ";
  538. $schema .= $package_schema;
  539. if (variable_get('uc_ups_negotiated_rates', false)) {
  540. $schema .= "<RateInformation>
  541. <NegotiatedRatesIndicator/>
  542. </RateInformation>";
  543. }
  544. $schema .= "</Shipment>
  545. </RatingServiceSelectionRequest>";
  546. return $schema;
  547. }
  548. /**
  549. * Construct an XML shippment request.
  550. *
  551. * @param $packages
  552. * Array of packages received from the cart.
  553. * @param $origin
  554. * Delivery origin address information.
  555. * @param $destination
  556. * Delivery destination address information.
  557. * @param $ups_service
  558. * UPS service code (refers to UPS Ground, Next-Day Air, etc.).
  559. * @return
  560. * ShipConfirm XML document to send to UPS
  561. */
  562. function uc_ups_shipment_request($packages, $origin, $destination, $ups_service) {
  563. $store['name'] = variable_get('uc_store_name', NULL);
  564. $store['owner'] = variable_get('uc_store_owner', NULL);
  565. $store['email'] = variable_get('uc_store_email', NULL);
  566. $store['email_from'] = variable_get('uc_store_email', NULL);
  567. $store['phone'] = variable_get('uc_store_phone', NULL);
  568. $store['fax'] = variable_get('uc_store_fax', NULL);
  569. $store['street1'] = variable_get('uc_store_street1', NULL);
  570. $store['street2'] = variable_get('uc_store_street2', NULL);
  571. $store['city'] = variable_get('uc_store_city', NULL);
  572. $store['zone'] = variable_get('uc_store_zone', NULL);
  573. $store['postal_code'] = variable_get('uc_store_postal_code', NULL);
  574. $store['country'] = variable_get('uc_store_country', 840);
  575. $account = variable_get('uc_ups_shipper_number', '');
  576. $ua = explode(' ', $_SERVER['HTTP_USER_AGENT']);
  577. $user_agent = $ua[0];
  578. $services = _uc_ups_service_list();
  579. $service = array('code' => $ups_service, 'description' => $services[$ups_service]);
  580. $pkg_types = _uc_ups_pkg_types();
  581. $shipper_zone = uc_get_zone_code($store['zone']);
  582. $shipper_country = uc_get_country_data(array('country_id' => $store['country']));
  583. $shipper_country = $shipper_country[0]['country_iso_code_2'];
  584. $shipper_zip = $store['postal_code'];
  585. $shipto_zone = uc_get_zone_code($destination->zone);
  586. $shipto_country = uc_get_country_data(array('country_id' => $destination->country));
  587. $shipto_country = $shipto_country[0]['country_iso_code_2'];
  588. $shipto_zip = $destination->postal_code;
  589. $shipfrom_zone = uc_get_zone_code($origin->zone);
  590. $shipfrom_country = uc_get_country_data(array('country_id' => $origin->country));
  591. $shipfrom_country = $shipfrom_country[0]['country_iso_code_2'];
  592. $shipfrom_zip = $origin->postal_code;
  593. $ups_units = variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'));
  594. $package_schema = '';
  595. foreach ($packages as $package) {
  596. $qty = $package->qty;
  597. for ($i = 0; $i < $qty; $i++) {
  598. $package_type = array('code' => $package->pkg_type, 'description' => $pkg_types[$package->pkg_type]);
  599. $package_schema .= "<Package>";
  600. $package_schema .= "<PackagingType>";
  601. $package_schema .= "<Code>". $package_type['code'] ."</Code>";
  602. $package_schema .= "</PackagingType>";
  603. if ($package->pkg_type == '02' && $package->length && $package->width && $package->height) {
  604. if ($package->length < $package->width) {
  605. list($package->length, $package->width) = array($package->width, $package->length);
  606. }
  607. $package_schema .= "<Dimensions>";
  608. $package_schema .= "<UnitOfMeasurement>";
  609. $conversion = constant(strtoupper($package->length_units) .'_TO_'. strtoupper(variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))));
  610. $package_schema .= "<Code>". strtoupper(variable_get('uc_ups_unit_system', variable_get('uc_length_unit', 'in'))) ."</Code>";
  611. $package_schema .= "</UnitOfMeasurement>";
  612. $package_schema .= "<Length>". (floor($package->length * $conversion) + 1) ."</Length>";
  613. $package_schema .= "<Width>". (floor($package->width * $conversion) + 1) ."</Width>";
  614. $package_schema .= "<Height>". (floor($package->height * $conversion) + 1) ."</Height>";
  615. $package_schema .= "</Dimensions>";
  616. }
  617. $size = $package->length * $conversion + 2 * $conversion * ($package->width + $package->height);
  618. switch ($ups_units) {
  619. case 'in':
  620. $conversion = uc_weight_conversion($package->weight_units, 'lb');
  621. break;
  622. case 'cm':
  623. $conversion = uc_weight_conversion($package->weight_units, 'kg');
  624. break;
  625. }
  626. $weight = $package->weight * $conversion;
  627. $package_schema .= "<PackageWeight>";
  628. $package_schema .= "<UnitOfMeasurement>";
  629. $package_schema .= "<Code>$units</Code>";
  630. $package_schema .= "<Description>$unit_name</Description>";
  631. $package_schema .= "</UnitOfMeasurement>";
  632. $package_schema .= "<Weight>". number_format($weight, 1, '.', '') ."</Weight>";
  633. $package_schema .= "</PackageWeight>";
  634. if ($size > 130 && $size <= 165) {
  635. $package_schema .= "<LargePackageIndicator/>";
  636. }
  637. $package_schema .= "<PackageServiceOptions>";
  638. $package_schema .= "<InsuredValue>";
  639. $package_schema .= "<CurrencyCode>". variable_get('uc_currency_code', 'USD') ."</CurrencyCode>";
  640. $package_schema .= "<MonetaryValue>". number_format($package->price, 2, '.', '') ."</MonetaryValue>";
  641. $package_schema .= "</InsuredValue>";
  642. $package_schema .= "</PackageServiceOptions>";
  643. $package_schema .= "</Package>";
  644. }
  645. }
  646. $schema = uc_ups_access_request() ."
  647. <?xml version=\"1.0\" encoding=\"UTF-8\"?>
  648. <ShipmentConfirmRequest xml:lang=\"en-US\">
  649. <Request>
  650. <TransactionReference>
  651. <CustomerContext>Complex Rate Request</CustomerContext>
  652. <XpciVersion>1.0001</XpciVersion>
  653. </TransactionReference>
  654. <RequestAction>ShipConfirm</RequestAction>
  655. <RequestOption>validate</RequestOption>
  656. </Request>
  657. <Shipment>";
  658. $schema .= "<Shipper>";
  659. $schema .= "<Name>". $store['name'] ."</Name>";
  660. $schema .= "<ShipperNumber>". variable_get('uc_ups_shipper_number', '') ."</ShipperNumber>";
  661. if ($store['phone']) {
  662. $schema .= "<PhoneNumber>". $store['phone'] ."</PhoneNumber>";
  663. }
  664. if ($store['fax']) {
  665. $schema .= "<FaxNumber>". $store['fax'] ."</FaxNumber>";
  666. }
  667. if ($store['email']) {
  668. $schema .= "<EMailAddress>". $store['email'] ."</EMailAddress>";
  669. }
  670. $schema .= "<Address>";
  671. $schema .= "<AddressLine1>". $store['street1'] ."</AddressLine1>";
  672. if ($store['street2']) {
  673. $schema .= "<AddressLine2>". $store['street2'] ."</AddressLine2>";
  674. }
  675. $schema .= "<City>". $store['city'] ."</City>";
  676. $schema .= "<StateProvinceCode>$shipper_zone</StateProvinceCode>";
  677. $schema .= "<PostalCode>$shipper_zip</PostalCode>";
  678. $schema .= "<CountryCode>$shipper_country</CountryCode>";
  679. $schema .= "</Address>";
  680. $schema .= "</Shipper>";
  681. $schema .= "<ShipTo>";
  682. $schema .= "<CompanyName>". $destination->company ."</CompanyName>";
  683. $schema .= "<AttentionName>". $destination->first_name .' '. $destination->last_name ."</AttentionName>";
  684. $schema .= "<PhoneNumber>". $destination->phone ."</PhoneNumber>";
  685. $schema .= "<EMailAddress>". $destination->email ."</EMailAddress>";
  686. $schema .= "<Address>";
  687. $schema .= "<AddressLine1>". $destination->street1 ."</AddressLine1>";
  688. if ($destination->street2) {
  689. $schema .= "<AddressLine2>". $destination->street2 ."</AddressLine2>";
  690. }
  691. $schema .= "<City>". $destination->city ."</City>";
  692. $schema .= "<StateProvinceCode>$shipto_zone</StateProvinceCode>";
  693. $schema .= "<PostalCode>$shipto_zip</PostalCode>";
  694. $schema .= "<CountryCode>$shipto_country</CountryCode>";
  695. if ($destination->residence) {
  696. $schema .= "<ResidentialAddressIndicator/>";
  697. }
  698. $schema .= "</Address>";
  699. $schema .= "</ShipTo>";
  700. $schema .= "<ShipFrom>";
  701. $schema .= "<CompanyName>". $origin->company ."</CompanyName>";
  702. $schema .= "<AttentionName>". $origin->first_name .' '. $origin->last_name ."</AttentionName>";
  703. $schema .= "<PhoneNumber>". $origin->phone ."</PhoneNumber>";
  704. $schema .= "<EMailAddress>". $origin->email ."</EMailAddress>";
  705. $schema .= "<Address>";
  706. $schema .= "<AddressLine1>". $origin->street1 ."</AddressLine1>";
  707. if ($origin->street2) {
  708. $schema .= "<AddressLine2>". $origin->street2 ."</AddressLine2>";
  709. }
  710. $schema .= "<City>". $origin->city ."</City>";
  711. $schema .= "<StateProvinceCode>$shipfrom_zone</StateProvinceCode>";
  712. $schema .= "<PostalCode>$shipfrom_zip</PostalCode>";
  713. $schema .= "<CountryCode>$shipfrom_country</CountryCode>";
  714. $schema .= "</Address>";
  715. $schema .= "</ShipFrom>";
  716. $schema .= "<PaymentInformation>";
  717. $schema .= "<Prepaid>";
  718. $schema .= "<BillShipper>";
  719. $schema .= "<AccountNumber>$account</AccountNumber>";
  720. $schema .= "</BillShipper>";
  721. $schema .= "</Prepaid>";
  722. $schema .= "</PaymentInformation>";
  723. if (variable_get('uc_ups_negotiated_rates', false)) {
  724. $schema .= "<RateInformation>
  725. <NegotiatedRatesIndicator/>
  726. </RateInformation>";
  727. }
  728. $schema .= "<Service>";
  729. $schema .= "<Code>{$service[code]}</Code>";
  730. $schema .= "<Description>{$service[description]}</Description>";
  731. $schema .= "</Service>";
  732. $schema .= $package_schema;
  733. $schema .= "</Shipment>";
  734. $schema .= "<LabelSpecification>";
  735. $schema .= "<LabelPrintMethod>";
  736. $schema .= "<Code>GIF</Code>";
  737. $schema .= "</LabelPrintMethod>";
  738. $schema .= "<LabelImageFormat>";
  739. $schema .= "<Code>GIF</Code>";
  740. $schema .= "</LabelImageFormat>";
  741. $schema .= "</LabelSpecification>";
  742. $schema .= "</ShipmentConfirmRequest>";
  743. return $schema;
  744. }
  745. /**
  746. * Callback for retrieving a UPS shipping quote.
  747. *
  748. * Request a quote for each enabled UPS Service. Therefore, the quote will
  749. * take longer to display to the user for each option the customer has available.
  750. *
  751. * @param $products
  752. * Array of cart contents.
  753. * @param $details
  754. * Order details other than product information.
  755. * @return
  756. * JSON object containing rate, error, and debugging information.
  757. */
  758. function uc_ups_quote($products, $details) {
  759. include_once(drupal_get_path('module', 'uc_store') .'/includes/simplexml.php');
  760. $quotes = array();
  761. $method = uc_ups_shipping_method();
  762. $addresses = array((array)variable_get('uc_quote_store_default_address', new stdClass()));
  763. $key = 0;
  764. $last_key = 0;
  765. $packages = array();
  766. if (variable_get('uc_ups_all_in_one', true) && count($products) > 1) {
  767. foreach ($products as $product) {
  768. if ($product->nid) {
  769. // Packages are grouped by the address from which they will be
  770. // shipped. We will keep track of the different addresses in an array
  771. // and use their keys for the array of packages.
  772. $address = (array)uc_quote_get_default_shipping_address($product->nid);
  773. $key = array_search($address, $addresses);
  774. if ($key === false) {
  775. // This is a new address. Increment the address counter $last_key
  776. // instead of using [] so that it can be used in $packages and
  777. // $addresses.
  778. $addresses[++$last_key] = $address;
  779. $key = $last_key;
  780. }
  781. }
  782. // Add this product to the last package from the found address or start
  783. // a new package.
  784. if (isset($packages[$key]) && count($packages[$key])) {
  785. $package = array_pop($packages[$key]);
  786. }
  787. else {
  788. $package = _uc_ups_new_package();
  789. }
  790. $weight = $product->weight * $product->qty * uc_weight_conversion($product->weight_units, 'lb');
  791. $package->weight += $weight;
  792. $package->price += $product->price * $product->qty;
  793. $conversion = uc_weight_conversion($product->length_units, 'in');
  794. $package->length = max($product->length * $conversion, $package->length);
  795. $package->width = max($product->width * $conversion, $package->width);
  796. $package->height = max($product->height * $conversion, $package->height);
  797. $packages[$key][] = $package;
  798. }
  799. foreach ($packages as $addr_key => $shipment) {
  800. foreach ($shipment as $key => $package) {
  801. if (!$package->weight) {
  802. unset($packages[$addr_key][$key]);
  803. continue;
  804. }
  805. elseif ($package->weight > 150) {
  806. // UPS has a weight limit on packages of 150 lbs. Pretend the
  807. // products can be divided into enough packages.
  808. $qty = floor($package->weight / 150) + 1;
  809. $package->qty = $qty;
  810. $package->weight /= $qty;
  811. $package->price /= $qty;
  812. }
  813. }
  814. }
  815. }
  816. else {
  817. foreach ($products as $product) {
  818. $key = 0;
  819. if ($product->nid) {
  820. $address = (array)uc_quote_get_default_shipping_address($product->nid);
  821. $key = array_search($address, $addresses);
  822. if ($key === false) {
  823. $addresses[++$last_key] = $address;
  824. $key = $last_key;
  825. }
  826. }
  827. if (!$product->pkg_qty) {
  828. $product->pkg_qty = 1;
  829. }
  830. $num_of_pkgs = (int)($product->qty / $product->pkg_qty);
  831. if ($num_of_pkgs) {
  832. $package = drupal_clone($product);
  833. $package->description = $product->model;
  834. $package->weight = $product->weight * $product->pkg_qty;
  835. $package->price = $product->price * $product->pkg_qty;
  836. $package->qty = $num_of_pkgs;
  837. $package->pkg_type = $product->ups ? $product->ups['pkg_type'] : '02';
  838. if ($package->weight) {
  839. $packages[$key][] = $package;
  840. }
  841. }
  842. $remaining_qty = $product->qty % $product->pkg_qty;
  843. if ($remaining_qty) {
  844. $package = drupal_clone($product);
  845. $package->description = $product->model;
  846. $package->weight = $product->weight * $remaining_qty;
  847. $package->price = $product->price * $remaining_qty;
  848. $package->qty = 1;
  849. $package->pkg_type = $product->ups ? $product->ups['pkg_type'] : '02';
  850. if ($package->weight) {
  851. $packages[$key][] = $package;
  852. }
  853. }
  854. }
  855. }
  856. if (!count($packages)) {
  857. return array();
  858. }
  859. $dest = (object)$details;
  860. foreach ($packages as $key => $ship_packages) {
  861. $orig = (object)$addresses[$key];
  862. $orig->email = variable_get('uc_store_email', '');
  863. foreach (array_keys(array_filter(variable_get('uc_ups_services', array()))) as $ups_service) {
  864. $request = uc_ups_shipping_quote($ship_packages, $orig, $dest, $ups_service);
  865. $resp = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') .'Rate', array(), 'POST', $request);
  866. if (user_access('configure quotes') && variable_get('uc_quote_display_debug', false)) {
  867. $quotes[$ups_service]['debug'] .= /* '<pre>'. print_r($ship_packages, true) .'</pre>' . */ htmlentities($request) .' <br /><br /> '. htmlentities($resp->data);
  868. }
  869. $response = new JSimpleXML();
  870. $response->loadString($resp->data);
  871. if (isset($response->document->response[0]->error)) {
  872. foreach ($response->document->response[0]->error as $error) {
  873. if (user_access('configure quotes') && variable_get('uc_quote_display_debug', false)) {
  874. $quotes[$ups_service]['error'][] = $error->errorseverity[0]->data() .' '. $error->errorcode[0]->data() .': '. $error->errordescription[0]->data();
  875. }
  876. if ($error->errorseverity[0]->data() == 'HardError') {
  877. // All or nothing quote. If some products can't be shipped by
  878. // a certain service, no quote is given for that service. If
  879. // that means no quotes are given at all, they'd better call in.
  880. unset($quotes[$ups_service]['rate']);
  881. }
  882. }
  883. }
  884. // if NegotiatedRates exist, quote based on those, otherwise, use TotalCharges
  885. if (isset($response->document->ratedshipment)) {
  886. $charge = $response->document->ratedshipment[0]->totalcharges[0];
  887. if (isset($response->document->ratedshipment[0]->negotiatedrates)) {
  888. $charge = $response->document->ratedshipment[0]->negotiatedrates[0]->netsummarycharges[0]->grandtotal[0];
  889. }
  890. if (!isset($charge->currencycode) || $charge->currencycode[0]->data() == variable_get('uc_currency_code', "USD")) {
  891. $rate = uc_ups_markup($charge->monetaryvalue[0]->data());
  892. $quotes[$ups_service]['rate'] += $rate;
  893. }
  894. }
  895. }
  896. }
  897. uasort($quotes, 'uc_quote_price_sort');
  898. foreach ($quotes as $key => $quote) {
  899. if (isset($quote['rate'])) {
  900. $quotes[$key]['format'] = uc_currency_format($quote['rate']);
  901. $quotes[$key]['option_label'] = '<img class="ups_logo" src="'. base_path() . drupal_get_path('module', 'uc_ups') .'/uc_ups_logo.gif" /> '. $method['ups']['quote']['accessorials'][$key] . t(' Rate');
  902. }
  903. }
  904. /**
  905. * Ugly hack to work around PHP bug, details here:
  906. * http://bugs.php.net/bug.php?id=23220
  907. * We strip out errors that look something like:
  908. * warning: fread() [function.fread]: SSL fatal protocol error in...
  909. * Copied from http://drupal.org/node/70915 and then improved by Lyle.
  910. */
  911. $messages = drupal_set_message();
  912. $errors = $messages['error'];
  913. $total = count($errors);
  914. for ($i = 0; $i <= $total; $i++) {
  915. if (strpos($errors[$i], 'SSL: fatal protocol error in')) {
  916. unset($_SESSION['messages']['error'][$i]);
  917. }
  918. }
  919. if (empty($_SESSION['messages']['error'])) {
  920. unset($_SESSION['messages']['error']);
  921. }
  922. db_query("DELETE FROM {watchdog} WHERE type = 'php' AND message LIKE '%%SSL: fatal protocol error%%'");
  923. // End of ugly hack.
  924. return $quotes;
  925. }
  926. /**
  927. * Shipment creation callback.
  928. *
  929. * Confirm shipment data before requesting a shipping label.
  930. *
  931. * @param $order_id
  932. * The order id for the shipment.
  933. * @param $package_ids
  934. * Array of package ids to shipped.
  935. * @ingroup forms
  936. * @see uc_ups_fulfill_order_submit
  937. */
  938. function uc_ups_fulfill_order($order_id, $package_ids) {
  939. $form = array();
  940. $pkg_types = _uc_ups_pkg_types();
  941. if ($order = uc_order_load($order_id)) {
  942. $form['order_id'] = array('#type' => 'value', '#value' => $order_id);
  943. $packages = array();
  944. $addresses = array();
  945. $form['packages'] = array('#tree' => true);
  946. foreach ($package_ids as $id) {
  947. $package = uc_shipping_package_load($id);
  948. if ($package) {
  949. foreach ($package->addresses as $address) {
  950. if (!in_array($address, $addresses)) {
  951. $addresses[] = $address;
  952. }
  953. }
  954. // Create list of products and get a representative product for default values
  955. $product_list = array();
  956. $declared_value = 0;
  957. foreach ($package->products as $product) {
  958. $product_list[] = $product->qty .' x '. $product->model;
  959. $declared_value += $product->qty * $product->price;
  960. }
  961. $ups_data = db_fetch_array(db_query("SELECT pkg_type FROM {uc_ups_products} WHERE nid = %d", $product->nid));
  962. $product->ups = $ups_data;
  963. $pkg_form = array('#type' => 'fieldset',
  964. '#title' => t('Package !id', array('!id' => $id)),
  965. );
  966. $pkg_form['products'] = array('#value' => theme('item_list', $product_list));
  967. $pkg_form['package_id'] = array('#type' => 'hidden', '#value' => $id);
  968. $pkg_form['pkg_type'] = array('#type' => 'select',
  969. '#title' => t('Package type'),
  970. '#options' => $pkg_types,
  971. '#default_value' => $product->ups['pkg_type'],
  972. '#required' => true,
  973. );
  974. $pkg_form['declared_value'] = array('#type' => 'textfield',
  975. '#title' => t('Declared value'),
  976. '#default_value' => $declared_value,
  977. '#required' => true,
  978. );
  979. $pkg_type['dimensions'] = array('#type' => 'fieldset',
  980. '#title' => t('Dimensions'),
  981. '#description' => t('Physical dimensions of the package.'),
  982. '#theme' => 'uc_ups_dimensions',
  983. );
  984. $pkg_form['dimensions']['units'] = array('#type' => 'select',
  985. '#title' => t('Units of measurement'),
  986. '#options' => array(
  987. 'in' => t('Inches'),
  988. 'ft' => t('Feet'),
  989. 'cm' => t('Centimeters'),
  990. 'mm' => t('Millimeters'),
  991. ),
  992. '#default_value' => $product->length_units ? $product->length_units : variable_get('uc_length_unit', 'in'),
  993. );
  994. $pkg_form['dimensions']['length'] = array('#type' => 'textfield',
  995. '#title' => t('Length'),
  996. '#default_value' => $product->length,
  997. );
  998. $pkg_form['dimensions']['width'] = array('#type' => 'textfield',
  999. '#title' => t('Width'),
  1000. '#default_value' => $product->width,
  1001. );
  1002. $pkg_form['dimensions']['height'] = array('#type' => 'textfield',
  1003. '#title' => t('Height'),
  1004. '#default_value' => $product->height,
  1005. );
  1006. $form['packages'][$id] = $pkg_form;
  1007. }
  1008. }
  1009. $form = array_merge($form, uc_shipping_address_form($addresses, $order));
  1010. foreach (array('delivery_email', 'delivery_last_name', 'delivery_company', 'delivery_street1', 'delivery_city', 'delivery_zone', 'delivery_country', 'delivery_postal_code') as $field) {
  1011. $form['destination'][$field]['#required'] = true;
  1012. }
  1013. $ups_services = _uc_ups_service_list();
  1014. $services = array_filter(variable_get('uc_ups_services', array()));
  1015. foreach ($services as $ups_id => $service) {
  1016. $services[$ups_id] = $ups_services[$ups_id];
  1017. }
  1018. if (count($services)) {
  1019. $form['service'] = array('#type' => 'select',
  1020. '#title' => t('UPS service'),
  1021. '#options' => $services,
  1022. );
  1023. }
  1024. $today = getdate();
  1025. $form['ship_date'] = array('#type' => 'date',
  1026. '#title' => t('Ship date'),
  1027. '#default_value' => array('year' => $today['year'], 'month' => $today['mon'], 'day' => $today['mday']),
  1028. );
  1029. $form['expected_delivery'] = array('#type' => 'date',
  1030. '#title' => t('Expected delivery'),
  1031. '#default_value' => array('year' => $today['year'], 'month' => $today['mon'], 'day' => $today['mday']),
  1032. );
  1033. $form['submit'] = array('#type' => 'submit', '#value' => t('Review shipment'));
  1034. }
  1035. else {
  1036. drupal_set_message(t("What? That's not an order id. You can't create a shipment without an order."));
  1037. drupal_goto('admin/store/orders');
  1038. }
  1039. return $form;
  1040. }
  1041. /**
  1042. * Validation handler for uc_ups_fulfill_order().
  1043. *
  1044. * Pass final information into shipment object.
  1045. *
  1046. * @see uc_ups_confirm_shipment
  1047. */
  1048. function uc_ups_fulfill_order_validate($form_id, $form_values) {
  1049. include_once(drupal_get_path('module', 'uc_store') .'/includes/simplexml.php');
  1050. $origin = new stdClass();
  1051. $destination = new stdClass();
  1052. $packages = array();
  1053. foreach ($form_values as $key => $value) {
  1054. if (substr($key, 0, 7) == 'pickup_') {
  1055. $field = substr($key, 7);
  1056. $origin->$field = $value;
  1057. }
  1058. else if (substr($key, 0, 9) == 'delivery_') {
  1059. $field = substr($key, 9);
  1060. $destination->$field = $value;
  1061. }
  1062. }
  1063. $_SESSION['ups'] = array();
  1064. $_SESSION['ups']['origin'] = $origin;
  1065. $_SESSION['ups']['destination'] = $destination;
  1066. foreach ($form_values['packages'] as $id => $pkg_form) {
  1067. $package = uc_shipping_package_load($id);
  1068. $package->pkg_type = $pkg_form['pkg_type'];
  1069. $package->value = $pkg_form['declared_value'];
  1070. $package->length = $pkg_form['dimensions']['length'];
  1071. $package->width = $pkg_form['dimensions']['width'];
  1072. $package->height = $pkg_form['dimensions']['height'];
  1073. $package->length_units = $pkg_form['dimensions']['units'];
  1074. $package->qty = 1;
  1075. $_SESSION['ups']['packages'][$id] = $package;
  1076. }
  1077. $_SESSION['ups']['service'] = $form_values['service'];
  1078. $_SESSION['ups']['ship_date'] = $form_values['ship_date'];
  1079. $_SESSION['ups']['expected_delivery'] = $form_values['expected_delivery'];
  1080. $_SESSION['ups']['order_id'] = $form_values['order_id'];
  1081. $request = uc_ups_shipment_request($_SESSION['ups']['packages'], $origin, $destination, $form_values['service']);
  1082. //print htmlentities($request);
  1083. $response_obj = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') .'ShipConfirm', array(), 'POST', $request);
  1084. $response = new JSimpleXML();
  1085. $response->loadString($response_obj->data);
  1086. //drupal_set_message('<pre>'. htmlentities($response->document->asXML()) .'</pre>');
  1087. if (is_array($response->document->response[0]->error)) {
  1088. $error = $response->document->response[0]->error[0];
  1089. $error_msg = $error->errorseverity[0]->data() .' Error '. $error->errorcode[0]->data() .': '. $error->errordescription[0]->data();
  1090. drupal_set_message($error_msg, 'error');
  1091. //drupal_set_message('<pre>'. print_r($_SESSION['ups']['packages'], true) .'</pre>' . htmlentities($request) .' <br /><br /> '. htmlentities($response->data));
  1092. if ($error->errorseverity[0]->data() == 'Hard') {
  1093. return null;
  1094. }
  1095. }
  1096. $charge = new stdClass();
  1097. // if NegotiatedRates exist, quote based on those, otherwise, use TotalCharges
  1098. if (is_array($response->document->shipmentcharges)) {
  1099. $charge = $response->document->shipmentcharges[0]->totalcharges[0];
  1100. $_SESSION['ups']['rate']['type'] = t('Total Charges');
  1101. if (is_array($response->document->shipmentcharges[0]->negotiatedrates)) {
  1102. $charge = $response->document->shipmentcharges[0]->negotiatedrates[0]->netsummarycharges[0]->grandtotal[0];
  1103. $_SESSION['ups']['rate']['type'] = t('Negotiated Rates');
  1104. }
  1105. }
  1106. $_SESSION['ups']['rate']['currency'] = $charge->currencycode[0]->data();
  1107. $_SESSION['ups']['rate']['amount'] = $charge->monetaryvalue[0]->data();
  1108. $_SESSION['ups']['digest'] = $response->document->shipmentdigest[0]->data();
  1109. }
  1110. /**
  1111. * Submit handler for uc_ups_fulfill_order().
  1112. *
  1113. * Pass final information into shipment object.
  1114. *
  1115. * @see uc_ups_confirm_shipment
  1116. */
  1117. function uc_ups_fulfill_order_submit($form_id, $form_values) {
  1118. return 'admin/store/orders/'. $form_values['order_id'] .'/shipments/ups';
  1119. }
  1120. /**
  1121. * Last chance for user to review shipment.
  1122. *
  1123. * @ingroup forms
  1124. * @see theme_uc_ups_confirm_shipment
  1125. * @see uc_ups_confirm_shipment_submit
  1126. */
  1127. function uc_ups_confirm_shipment($order_id) {
  1128. $form = array();
  1129. $form['digest'] = array('#type' => 'hidden', '#value' => $_SESSION['ups']['digest']);
  1130. $form['submit'] = array('#type' => 'submit', '#value' => t('Request Pickup'));
  1131. return $form;
  1132. }
  1133. /**
  1134. * Display final shipment information for review.
  1135. */
  1136. function theme_uc_ups_confirm_shipment($form) {
  1137. $output = '';
  1138. $output .= '<div class="shipping_address"><b>'. t('Ship from:') .'</b><br />';
  1139. $output .= uc_address_format(
  1140. check_plain($_SESSION['ups']['origin']->first_name),
  1141. check_plain($_SESSION['ups']['origin']->last_name),
  1142. check_plain($_SESSION['ups']['origin']->company),
  1143. check_plain($_SESSION['ups']['origin']->street1),
  1144. check_plain($_SESSION['ups']['origin']->street2),
  1145. check_plain($_SESSION['ups']['origin']->city),
  1146. check_plain($_SESSION['ups']['origin']->zone),
  1147. check_plain($_SESSION['ups']['origin']->postal_code),
  1148. check_plain($_SESSION['ups']['origin']->country)
  1149. );
  1150. $output .= '<br />'. check_plain($_SESSION['ups']['origin']->email);
  1151. $output .= '</div>';
  1152. $output .= '<div class="shipping_address"><b>'. t('Ship to:') .'</b><br />';
  1153. $output .= uc_address_format(
  1154. check_plain($_SESSION['ups']['destination']->first_name),
  1155. check_plain($_SESSION['ups']['destination']->last_name),
  1156. check_plain($_SESSION['ups']['destination']->company),
  1157. check_plain($_SESSION['ups']['destination']->street1),
  1158. check_plain($_SESSION['ups']['destination']->street2),
  1159. check_plain($_SESSION['ups']['destination']->city),
  1160. check_plain($_SESSION['ups']['destination']->zone),
  1161. check_plain($_SESSION['ups']['destination']->postal_code),
  1162. check_plain($_SESSION['ups']['destination']->country)
  1163. );
  1164. $output .= '<br />'. check_plain($_SESSION['ups']['destination']->email);
  1165. $output .= '</div>';
  1166. $output .= '<div class="shipment_data">';
  1167. $method = uc_ups_shipping_method();
  1168. $output .= '<b>'. $method['ups']['quote']['accessorials'][$_SESSION['ups']['service']] .'</b><br />';
  1169. $output .= '<i>'. check_plain($_SESSION['ups']['rate']['type']) .'</i>: '. uc_currency_format($_SESSION['ups']['rate']['amount']) .' ('. check_plain($_SESSION['ups']['rate']['currency']) .')<br />';
  1170. $ship_date = $_SESSION['ups']['ship_date'];
  1171. $output .= 'Ship date: '. format_date(gmmktime(12, 0, 0, $ship_date['month'], $ship_date['day'], $ship_date['year']), 'custom', variable_get('uc_date_format_default', 'm/d/Y'));
  1172. $exp_delivery = $_SESSION['ups']['expected_delivery'];
  1173. $output .= '<br />Expected delivery: '. format_date(gmmktime(12, 0, 0, $exp_delivery['month'], $exp_delivery['day'], $exp_delivery['year']), 'custom', variable_get('uc_date_format_default', 'm/d/Y'));
  1174. $output .= "</div>\n<br style=\"clear: both;\" />";
  1175. $output .= drupal_render($form);
  1176. return $output;
  1177. }
  1178. /**
  1179. * Submit handler for uc_ups_confirm_shipment().
  1180. *
  1181. * Generate label and schedule pickup of the shipment.
  1182. */
  1183. function uc_ups_confirm_shipment_submit($form_id, $form_values) {
  1184. include_once(drupal_get_path('module', 'uc_store') .'/includes/simplexml.php');
  1185. // Request pickup using parameters in form.
  1186. $order_id = $_SESSION['ups']['order_id'];
  1187. $packages = array_keys($_SESSION['ups']['packages']);
  1188. $request = uc_ups_request_pickup($form_values['digest'], $order_id, $packages);
  1189. $result = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') .'ShipAccept', array(), 'POST', $request);
  1190. $response = new JSimpleXML();
  1191. $response->loadString($result->data);
  1192. $code = $response->document->response[0]->responsestatuscode[0]->data();
  1193. if ($code == 0) { // failed request
  1194. $error = $response->document->response[0]->error[0];
  1195. $error_severity = $error->errorseverity[0]->data();
  1196. $error_code = $error->errorcode[0]->data();
  1197. $error_description = $error->errordescription[0]->data();
  1198. drupal_set_message(t('(@severity error @code) @description', array('@severity' => $error_severity, '@code' => $error_code, '@description' => $error_description)), 'error');
  1199. if ($error_severity == 'HardError') {
  1200. return 'admin/store/orders/'. $order_id .'/shipments/ups/'. implode('/', $packages);
  1201. }
  1202. }
  1203. $shipment = new stdClass();
  1204. $shipment->order_id = $order_id;
  1205. $shipment->origin = drupal_clone($_SESSION['ups']['origin']);
  1206. $shipment->destination = drupal_clone($_SESSION['ups']['destination']);
  1207. $shipment->packages = $_SESSION['ups']['packages'];
  1208. $shipment->shipping_method = 'ups';
  1209. $shipment->accessorials = $_SESSION['ups']['service'];
  1210. $shipment->carrier = t('UPS');
  1211. // if NegotiatedRates exist, quote based on those, otherwise, use TotalCharges
  1212. if (is_array($response->document->shipmentresults[0]->shipmentcharges)) {
  1213. $charge = $response->document->shipmentresults[0]->shipmentcharges[0]->totalcharges[0];
  1214. if (is_array($response->document->shipmentresults[0]->negotiatedrates)) {
  1215. $charge = $response->document->shipmentresults[0]->negotiatedrates[0]->netsummarycharges[0]->grandtotal[0];
  1216. }
  1217. }
  1218. $cost = $charge->monetaryvalue[0]->data();
  1219. $shipment->cost = $cost;
  1220. $shipment->tracking_number = $response->document->shipmentresults[0]->shipmentidentificationnumber[0]->data();
  1221. $ship_date = $_SESSION['ups']['ship_date'];
  1222. $shipment->ship_date = gmmktime(12, 0, 0, $ship_date['month'], $ship_date['day'], $ship_date['year']);
  1223. $exp_delivery = $_SESSION['ups']['expected_delivery'];
  1224. $shipment->expected_delivery = gmmktime(12, 0, 0, $exp_delivery['month'], $exp_delivery['day'], $exp_delivery['year']);
  1225. foreach ($response->document->shipmentresults[0]->packageresults as $package_results) {
  1226. $package =& current($shipment->packages);
  1227. $package->tracking_number = $package_results->trackingnumber[0]->data();
  1228. $label_image = $package_results->labelimage[0]->graphicimage[0]->data();
  1229. if (file_check_directory(file_create_path('ups_labels'), FILE_CREATE_DIRECTORY)) {
  1230. $label_path = file_create_path('ups_labels') .'/label'. $package->tracking_number .'.gif';
  1231. if ($label_file = fopen($label_path, 'wb')) {
  1232. fwrite($label_file, base64_decode($label_image));
  1233. fclose($label_file);
  1234. $package->label_image = $label_path;
  1235. }
  1236. else {
  1237. drupal_set_message(t('Could not open a file to save the label image.'), 'error');
  1238. }
  1239. }
  1240. else {
  1241. drupal_set_message(t('Could not find or create the directory "ups_labels" in the file system path.'), 'error');
  1242. }
  1243. unset($package);
  1244. next($shipment->packages);
  1245. }
  1246. uc_shipping_shipment_save($shipment);
  1247. unset($_SESSION['ups']);
  1248. return 'admin/store/orders/'. $order_id .'/shipments';
  1249. }
  1250. /**
  1251. * Construct an XML label and pickup request.
  1252. *
  1253. * @param $digest
  1254. * Base-64 encoded shipment request.
  1255. * @param $order_id
  1256. * The order id of the shipment.
  1257. * @param $packages
  1258. * An array of package ids to be shipped.
  1259. * @return
  1260. * ShipmentAcceptRequest XML document to send to UPS.
  1261. */
  1262. function uc_ups_request_pickup($digest, $order_id = 0, $packages = array()) {
  1263. $packages = (array)$packages;
  1264. $schema = uc_ups_access_request();
  1265. $schema .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  1266. <ShipmentAcceptRequest>
  1267. <Request>
  1268. <RequestAction>ShipAccept</RequestAction>";
  1269. if ($order_id || count($packages)) {
  1270. $schema .= "\n<TransactionReference>
  1271. <CustomerContext>";
  1272. if ($order_id) {
  1273. $schema .= "<OrderId>". $order_id ."</OrderId>\n";
  1274. }
  1275. foreach ($packages as $pkg_id) {
  1276. $schema .= "<PackageId>". $pkg_id ."</PackageId>\n";
  1277. }
  1278. $schema .= "</CustomerContext>\n</TransactionReference>\n";
  1279. }
  1280. $schema .= " </Request>
  1281. <ShipmentDigest>". $digest ."</ShipmentDigest>
  1282. </ShipmentAcceptRequest>";
  1283. //drupal_set_message('<pre>'. htmlentities($schema) .'</pre>');
  1284. return $schema;
  1285. }
  1286. /**
  1287. * Display the shipping label for printing.
  1288. *
  1289. * Each argument is a component of the file path to the image.
  1290. *
  1291. * @ingroup themeable
  1292. */
  1293. function theme_uc_ups_label_image() {
  1294. $args = func_get_args();
  1295. $image_path = implode('/', $args);
  1296. print '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 3.2//EN">
  1297. <html><head><title>
  1298. View/Print Label</title></head><style>
  1299. .small_text {font-size: 80%;}
  1300. .large_text {font-size: 115%;}
  1301. </style>
  1302. <body bgcolor="#FFFFFF">
  1303. <table border="0" cellpadding="0" cellspacing="0" width="600"><tr>
  1304. <td height="410" align="left" valign="top">
  1305. <b class="large_text">View/Print Label</b>
  1306. &nbsp;<br />
  1307. <ol class="small_text"> <li><b>Print the label:</b> &nbsp;
  1308. Select Print from the File menu in this browser window to print the label below.<br /><br /><li><b>
  1309. Fold the printed label at the dotted line.</b> &nbsp;
  1310. Place the label in a UPS Shipping Pouch. If you do not have a pouch, affix the folded label using clear plastic shipping tape over the entire label.<br /><br /><li><b>GETTING YOUR SHIPMENT TO UPS<br />
  1311. Customers without a Daily Pickup</b><ul><li>Ground, 3 Day Select, and Standard to Canada shipments must be dropped off at an authorized UPS location, or handed to a UPS driver. Pickup service is not available for these services. To find the nearest drop-off location, select the Drop-off icon from the UPS tool bar.<li>
  1312. Air shipments (including Worldwide Express and Expedited) can be picked up or dropped off. To schedule a pickup, or to find a drop-off location, select the Pickup or Drop-off icon from the UPS tool bar. </ul> <br />
  1313. <b>Customers with a Daily Pickup</b><ul><li>
  1314. Your driver will pickup your shipment(s) as usual. </ul>
  1315. </ol></td></tr></table><table border="0" cellpadding="0" cellspacing="0" width="600">
  1316. <tr>
  1317. <td class="small_text" align="left" valign="top">
  1318. &nbsp;&nbsp;&nbsp;
  1319. FOLD HERE</td>
  1320. </tr>
  1321. <tr>
  1322. <td align="left" valign="top"><hr />
  1323. </td>
  1324. </tr>
  1325. </table>
  1326. <table>
  1327. <tr>
  1328. <td height="10">&nbsp;
  1329. </td>
  1330. </tr>
  1331. </table>
  1332. <table border="0" cellpadding="0" cellspacing="0" width="650" ><tr>
  1333. <td align="left" valign="top">
  1334. <img src="'. base_path() . $image_path .'" height="392" width="672">
  1335. </td>
  1336. </tr></table>
  1337. </body>
  1338. </html>';
  1339. exit();
  1340. }
  1341. function uc_ups_void_shipment_request($shipment_number, $tracking_numbers = array()) {
  1342. $schema = uc_ups_access_request();
  1343. $schema .= '<?xml version="1.0"?>';
  1344. $schema .= '<VoidShipmentRequest>';
  1345. $schema .= '<Request>';
  1346. $schema .= '<RequestAction>Void</RequestAction>';
  1347. $schema .= '<TransactionReference>';
  1348. $schema .= '<CustomerContext>';
  1349. $schema .= t('Void shipment @ship_number and tracking numbers @track_list', array('@ship_number' => $shipment_number, '@track_list' => implode(', ', $tracking_numbers)));
  1350. $schema .= '</CustomerContext>';
  1351. $schema .= '<XpciVersion>1.0</XpciVersion>';
  1352. $schema .= '</TransactionReference>';
  1353. $schema .= '</Request>';
  1354. $schema .= '<ExpandedVoidShipment>';
  1355. $schema .= '<ShipmentIdentificationNumber>'. $shipment_number .'</ShipmentIdentificationNumber>';
  1356. foreach ($tracking_numbers as $number) {
  1357. $schema .= '<TrackingNumber>'. $number .'</TrackingNumber>';
  1358. }
  1359. $schema .= '</ExpandedVoidShipment>';
  1360. $schema .= '</VoidShipmentRequest>';
  1361. return $schema;
  1362. }
  1363. function uc_ups_void_shipment($shipment_number, $tracking_numbers = array()) {
  1364. include_once(drupal_get_path('module', 'uc_store') .'/includes/simplexml.php');
  1365. $success = false;
  1366. $request = uc_ups_void_shipment_request($shipment_number, $tracking_numbers);
  1367. $resp = drupal_http_request(variable_get('uc_ups_connection_address', 'https://wwwcie.ups.com/ups.app/xml/') .'Void', array(), 'POST', $request);
  1368. $response = new JSimpleXML();
  1369. $response->loadString($resp->data);
  1370. if (is_array($response->document->response)) {
  1371. if (is_array($response->document->response[0]->responsestatuscode)) {
  1372. $success = $response->document->response[0]->responsestatuscode[0]->data();
  1373. }
  1374. if (is_array($response->document->response[0]->error)) {
  1375. foreach ($response->document->response[0]->error as $error) {
  1376. drupal_set_message($error->errorseverity[0]->data() .' '. $error->errorcode[0]->data() .': '. $error->errordescription[0]->data(), 'error');
  1377. }
  1378. }
  1379. }
  1380. if (is_array($response->document->status)) {
  1381. if (is_array($response->document->status[0]->statustype)) {
  1382. $success = $response->document->status[0]->statustype[0]->code[0]->data();
  1383. }
  1384. }
  1385. return (bool)$success;
  1386. }
  1387. /**
  1388. * Modify the rate received from UPS before displaying to the customer.
  1389. */
  1390. function uc_ups_markup($rate) {
  1391. $markup = variable_get('uc_ups_markup', '0');
  1392. $type = variable_get('uc_ups_markup_type', 'percentage');
  1393. if (is_numeric(trim($markup))) {
  1394. switch ($type) {
  1395. case 'percentage':
  1396. return $rate + $rate * floatval(trim($markup)) / 100;
  1397. case 'multiplier':
  1398. return $rate * floatval(trim($markup));
  1399. case 'currency':
  1400. return $rate + floatval(trim($markup));
  1401. }
  1402. }
  1403. else {
  1404. return $rate;
  1405. }
  1406. }
  1407. /**
  1408. * Convenience function to get UPS codes for their services.
  1409. */
  1410. function _uc_ups_service_list() {
  1411. return array(
  1412. '03' => t('UPS Ground'),
  1413. '11' => t('UPS Standard'),
  1414. '01' => t('UPS Next Day Air'),
  1415. '13' => t('UPS Next Day Air Saver'),
  1416. '14' => t('UPS Next Day Early A.M.'),
  1417. '02' => t('UPS 2nd Day Air'),
  1418. '59' => t('UPS 2nd Day Air A.M.'),
  1419. '12' => t('UPS 3-Day Select'),
  1420. );
  1421. }
  1422. /**
  1423. * Convenience function to get UPS codes for their package types.
  1424. */
  1425. function _uc_ups_pkg_types() {
  1426. return array(
  1427. '01' => t('UPS Letter'),
  1428. '02' => t('Customer Supplied Package'),
  1429. '03' => t('Tube'),
  1430. '04' => t('PAK'),
  1431. '21' => t('UPS Express Box'),
  1432. '24' => t('UPS 25KG Box'),
  1433. '25' => t('UPS 10KG Box'),
  1434. '30' => t('Pallet'),
  1435. );
  1436. }
  1437. function _uc_ups_new_package() {
  1438. $package = new stdClass();
  1439. $package->weight = 0;
  1440. $package->price = 0;
  1441. $package->length = 0;
  1442. $package->width = 0;
  1443. $package->height = 0;
  1444. $package->length_units = 'in';
  1445. $package->weight_units = 'lb';
  1446. $package->qty = 1;
  1447. $package->pkg_type = '02';
  1448. return $package;
  1449. }