Hi (Gordon?)

I've moved on to a site where I need to calculate shipping costs for delivery inside Australia. I'd love some hints about the steps I would need to take.

In zen-cart there is an module for calculating Australia Post delivery, I don't know what the equivalent would be in drupal, or if there is one. I've had a fruitless search on Drupal and Google.

Thanks in advance.

Comments

Andy_Lowe’s picture

I don't think there is a module to calculate Australian Post yet.
If you are willing to write some code (or pay someone else to), there is a module called shipcalc here:
http://cvs.drupal.org/viewcvs/drupal/contributions/modules/ecommerce/con...
which will get you off to a start. It is a module for calculated shipping via UPS, so you will still have some work to do to make it with Australian Post, but it is a start.
Cheers,
Andy

sime’s picture

Status: Active » Closed (fixed)

Thanks ajlowe for the quick response! This is the pointer I needed.

wellsy’s picture

Status: Closed (fixed) » Needs review

there is a module which works very well for Australia Post for oScommerce.

Perhaps it can be ported to drupal?

http://www.oscommerce.com/community/contributions,648

sime’s picture

Well by the time I'm done with it, it will be more like a "hack" than a "port". :-)

But that's my intention, either that or the zen-cart equiv which I have used.

Andy_Lowe’s picture

Status: Needs review » Closed (fixed)

zen cart is a (IMHO improvement on and) fork of oscommerce. Last time I looked, they used the same shipping api, so the modules should be nearly identical. Note that the shipcalc module uses both the checkoutapi and the shippingapi. You can get a good idea of what is going on here:
http://cvs.drupal.org/viewcvs/drupal/contributions/modules/ecommerce/doc...
and in the shipping.module file.

sime’s picture

Title: australia post » Aussie Post shipping calcs
Assigned: Unassigned » sime
Category: support » task
Status: Closed (fixed) » Active

alrighty, I will have a go at this. but this will be a drupalian baptism of fire for me. remember, even Bender gets scared sometimes.

Ludwig’s picture

We will put our hands up for testings if you need that.
We are Australian developers (previously using OsCommerce for ecommerce sites.)
We "need" the AusPost calcs for Drupal which we are slooowly migrating to...

sime’s picture

Thankyou Ludwig

I appreciate the support. My progress so far. I have applied for cvs access, I have setup a CVS client on my windows machine. I have set up a virtual domain on my Linux test server and on that I am installing a clean HEAD installation of Drupal with e-commercie. I am now just reviewing Drupal (API, hooks, whatever) before I am ready have a close look at the shipcalc/UPS contribs.

I hope with the existing contribs to compare against, that my path will be easier. That said, it's good to know who is interested in this, especially if I get stuck and need some urgent advice from someone with a bit of dev experience.

gordon’s picture

Sime: Thanks for looking at this.

You do not need cvs access to do this mode. You will not be commiting this change even if you have cvs access as it needs to be reviewed to make sure that everything is going. Checkout the ecommerce module as an anonymous user and do the development. Once you have finished create a patch and submit it here to be reviewed.

I think there are a number of Aussie people in Melbourne, I think that we should arrange a Meetup for Melbourne.

sime’s picture

Sure, it would help me a lot to know a couple of people.

Ludwig’s picture

Just checking in.
how is the development going? Is it? are you looking at it now with 4.7 in mind?
still ready, willing and able to do testing :-)

neclimdul’s picture

Status: Active » Postponed

This is important but we can't start work on it till we finish revamping the shipping module. Going to mark think postponed contingent on the shipping updates.

mrmachine’s picture

Version: 4.6.x-1.x-dev » 4.7.x-1.x-dev
Component: shipping.module » shipcalc

Please - anything happened with this yet? I've been waiting months to be able to get my mum's ecommerce site online, but can't really do anything till australia post shipcalc is implemented. I'm about to try and do it myself, but it's gonna take me at least another month to understand how.

sime’s picture

Sorry, no. My own customers went with basic shipping which is being added in EC5.

mrmachine’s picture

is it possible that i would also be able to use basic shipping? i'm using drupal 4.7.4 and latest ecommerce package (not cvs). if not, what versions of drupal and ecommerce do i need to use it? how do i go about getting the .inc file?

my only other option is to create about 100 non-shippable products for all of the possible postages, and have customers add the "postage product" to their shopping cart ... obviously not desirable option :)

sime’s picture

I'll be putting this in 5.0

You're free to try some sort of simple shipping in 4.7 by jumping on this http://drupal.org/node/71718

mrmachine’s picture

Well, I'm going to have to try to build an australiapost.inc, and fast ;)

In the interests of getting help from others who also don't have much PHP expertise, I'll post a work-in-progress australiapost.inc that we can build off. I've taken the canadapost.inc and started to modify it ... however, I haven't gotten far, as I am learning as I go ... I will update the code as I learn.

I have also altered the PHP comments to include reference to stuff I don't understand what to do with, so any PHP experts, can you have a look?

For anyone who wants to help, here is the relevant documentation from Australia Post:

INSTRUCTIONS : PHP

These instructions are for web sites built using PHP scripting language versions 3 and 4.

1 ACTIVATING THE DRC

Send the following code to the DRC :

$myfile=file('http://drc.edeliver.com.au/ratecalc.asp?Pickup_Postcode='.$var_pickup.'&Destination_Postcode='.$var_destination.'&Country='.$var_country.'&Weight='.$var_weight.'&Service_Type='.$var_service.'&Length='$var_length.'&Width='.$var_width.'&Height='.$var_height.'&Quantity='.$var_quantity);

Note : Code must be built as a single line.

Example of PHP query to obtain data for above code

$qs .= 'Height='.$HTTP_POST_VARS["Height"].'&';
$qs .= 'Length='.$HTTP_POST_VARS["Length"].'&';
$qs .= 'Width='.$HTTP_POST_VARS["Width"].'&';
$qs .= 'Weight='.$HTTP_POST_VARS["Weight"].'&'; 
$qs .= 'Pickup_Postcode='.$HTTP_POST_VARS["Pickup_Postcode"].'&';
$qs .= 'Destination_Postcode='.$HTTP_POST_VARS["Destination_Postcode"].'&';
$qs .= 'Country='.$HTTP_POST_VARS["Country"].'&'; 
$qs .= 'Service_Type='.$HTTP_POST_VARS["Service_Type"].'&';
$qs .= 'Quantity='.$HTTP_POST_VARS["Quantity"]; . 

Please note

It is recommended that Merchants with domestic deliveries only preset the Country Code to “AU” (Australia).

2 INTERPRETING THE RESULTS

When the $myfile=file('http://drc.edeliver.com.au/ratecalc.asp?'.$qs) enquiry is executed by the DRC the results are placed into an array called $myfile. The name/value pairs of the results are returned as per this example:

charge=2.74 days=2 err_msg=OK

Where:

charge = the estimated delivery charge in $AUS;

days = the estimated transit time in business days through the Australia Post delivery network from time of lodgement with Australia Post to time of delivery;

err_msg = a message verifying that the data sent is correct (“OK”) or notifying data errors detected (Eg “Item girth outside valid ranges”).

3 DISPLAYING THE RESULTS

PHP’s “split()” function is used to retrieve the values returned by the DRC.

Merchants may manipulate the results to suit the needs of Consumers and the requirements of their Web page design. Ie Merchants can select which parts of the results to display (Eg delivery time may be displayed and delivery charge ignored), alter the data returned (Eg to add a margin to the delivery charge estimate or to add order processing time in the Merchant’s warehouse to the delivery time estimate) and set the format used to display the results.

Example

$x = split('=',$myfile[0]) splits the data pair line into items separated by the “=” sign, resulting in an array called $x with values, which can be displayed. Using the example above, the first data pair could be split, manipulated and displayed as follows:

$x[0] = "charge" and $x[1] = 2.74

echo "The charge is $".$x[1];

The next two data pairs ( $myfile[1] and $myfile[2] ) can be split, manipulated and displayed in the same way.

And, just below, is the work-in-progress australiapost.inc ... theres' a few additional code comments in there, plus a few more comments below the code:

<?php
// $Id: australiapostaustraliapost.inc,v 1.3 2006/09/07 19:11:47 nedjo Exp $

/**
 * @file
 * Functions to communicate with AustraliaPost API.
 *
 * Derived from the CanadaPost document titled:
 *    Canada Post - Sell Online Developer's Site
 *    Version: 12 (Sept. 15, 2003)
 *
 * Additional information:
 *  http://206.191.4.228/
 *
 * Created by 
 */

/**
 * Shipcalc _shipping_methods hook.
 *
 * Define the AustraliaPost shipping methods.
 */
function australiapost_shipping_methods($type = 'domestic') {
  // TODO: Add descriptions of various shipping methods.
  $methods = array();

  $methods['australiapost'] = array(
    '#title' => t('AustraliaPost'),
    '#description' => t('AustraliaPost')
  );
  $methods['australiapost']['STANDARD'] = array(
    '#title' => t('Standard delivery within Australia'),
  );
  $methods['australiapost']['EXPRESS'] = array(
    '#title' => t('Express Post delivery within Australia'),
  );
  $methods['australiapost']['AIR'] = array(
    '#title' => t('Airmail for International delivery'),
  );
  $methods['australiapost']['SEA'] = array(
    '#title' => t('Shipping via Sea for International Delivery'),
  );
  $methods['australiapost']['ECI_M'] = array(
    '#title' => t('Express Courier International'),
  );

  return $methods;
}

/**
 * Shipcalc _settings_form hook.
 *
 * Create a form for AUSTRALIAPOST-specific configuration.
 * 
 * You don't need an account or merchant id to use the Australia Post postage calculator, so I removed those options.
 */
function australiapost_settings_form(&$form) {
  $form['australiapost'] = array(
    '#type' => 'fieldset',
    '#title' => t('AustraliaPost settings')
  );
  $form['australiapost']['australiapost_postalcode'] = array(
    '#type' => 'textfield',
    '#title' => t('Postal Code'),
    '#description' => t('This is the Postal Code of your business and is used for calculating shipping costs.'),
    '#default_value' => variable_get('shipcalc_australiapost_postalcode', ''),
    '#required' => TRUE
  );
  $form['australiapost']['australiapost_turnaround'] = array(
    '#type' => 'textfield',
    '#title' => t('Turn Around Time'),
    '#description' => t('This is the turn around time between receiving the order and shipping the product. (ie. enter 24 for a 24 hour turn around time.)'),
    '#default_value' => variable_get('shipcalc_australiapost_turnaround', ''),
    '#required' => TRUE
  );
/**
 *Not sure if this is necessary, can probably be hardcoded
 */
  $form['australiapost']['australiapost_url'] = array(
    '#type' => 'textfield',
    '#title' => t('AustraliaPost Server URL'),
    '#description' => t('Enter the fully qualified URL of the AustraliaPost shipping rate server, as provided by AustraliaPost.'),
    '#default_value' => (variable_get('shipcalc_australiapost_url', '') ? variable_get('shipcalc_australiapost_url', '') : 'http://drc.edeliver.com.au/ratecalc.asp'),
    '#required' => TRUE
  );

  // TODO: Testing to help admin set up site.  Not fully implemented yet.
  $form['australiapost']['test'] = array(
    '#type' => 'fieldset',
    '#title' => t('Testing'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE
  );
  $form['australiapost']['test']['australiapost_test_url'] = array(
    '#type' => 'textfield',
    '#title' => t('AustraliaPost Test Server URL'),
    '#description' => t('AustraliaPost provides a test server to test your site configuration prior to launch. Clicking <em>Test configuration</em> below will use your Access Key, User ID and Password to test several transactions against the AustraliaPost Test Server URL.'),
    '#default_value' => (variable_get('shipcalc_australiapost_test_url', '') ? variable_get('shipcalc_australiapost_test_url', '') : 'http://drc.edeliver.com.au/ratecalc.asp'),
    '#required' => TRUE
  );

  $form['australiapost']['test']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Test Australia Post configuration')
  );
}

/**
 * Shipcalc _settings_form_submit hook.
 *
 * Save data from our AustraliaPost-specific configuration form.
 */
function australiapost_settings_form_submit(&$form) {
  global $form_values;
  $op = $_POST['op'];

  if ($form_values['shipping_partner'] == 'australiapost') {
    variable_set('shipcalc_australiapost_postalcode', $form_values['australiapost_postalcode']);
    variable_set('shipcalc_australiapost_turnaround', $form_values['australiapost_turnaround']);
    variable_set('shipcalc_australiapost_url', $form_values['australiapost_url']);
    variable_set('shipcalc_australiapost_test_url', $form_values['australiapost_test_url']);
  }

  if ($op == t('Test Australia Post configuration')) {
    // Populate a fake transfer.
    // Find the first 'shippable' product type.
    $ptypes = product_get_ptypes();
    foreach (array_keys($ptypes) as $ptype) {
      if (product_is_shippable(NULL, $ptype)) {
        // Load the first product of this type.
        $nid = db_result(db_query(db_rewrite_sql('SELECT n.nid FROM {node} n INNER JOIN {ec_product} p ON n.nid = p.nid WHERE p.ptype = "%s" LIMIT 1', $ptype)));
        if ($nid) {
          $node = node_load($nid);
        }
        break;
      }
    }
    if (!$node) {
      drupal_set_message(t('To test UPS configuration you must first <a href="/node/add/product/%ptype">create a %ptype_nice</a> and assign it shippable properties.', array('%ptype' => $ptype, '%ptype_nice' => $ptypes[$ptype])));
      return;
    }
    $txn = new StdClass();
    $txn->items[] = $node;
    $txn->address['shipping']->zip = 33068;
    $txn->address['shipping']->country = 'AU';
    $rates = australiapost_get_rates($txn, 'http://206.191.4.228:30000', TRUE);
    drupal_set_message(theme('shipcalc_testing_results', $rates));
  }
}

/**
 * Shipcalc _product_attributes hook.
 *
 * Update the product form with fields that we need.  It is possible for 
 * multiple carriers to define the same field -- that is fine.  So long as
 * the field as the same name (e.g. 'weight'), it will only be displayed
 * once, and the data will be saved and restored for use by all shipping
 * partners that define it.
 */
function australiapost_product_attributes($form) {
  $fields = array();
  $fields['weight'] = array(
    '#type' => 'textfield',
    '#title' => t('Product Weight'),
    '#description' => t('The weight of the product (in Kilograms)'),
    '#default_value' => $form['#node']->product_attributes['weight']
  );
  
  $fields['width'] = array(
    '#type' => 'textfield',
    '#title' => t('Product Width'),
    '#description' => t('The width of the product (in Centimeters)'),
    '#default_value' => $form['#node']->product_attributes['width']
  );
  
  $fields['height'] = array(
    '#type' => 'textfield',
    '#title' => t('Product Height'),
    '#description' => t('The height of the product (in Centimeters)'),
    '#default_value' => $form['#node']->product_attributes['height']
  );
  
  $fields['length'] = array(
    '#type' => 'textfield',
    '#title' => t('Product Length'),
    '#description' => t('The length of the product (in Centimeters).'),
    '#default_value' => $form['#node']->product_attributes['length']
  );
  
  return $fields;
}

/**
 * Shipcalc _get_rates_form hook.
 *
 * Request rates from Australia Post for the current transaction.  Return a form of all
 * shipping options that the shipcalc module will display during the checkout 
 * process.
 * 
 * This is where things start to get tough ... a URL must be built that contains the variables to submit to AusPost. I get the feeling that the other partners require you to submit shipping rate requests via XML files? And that they return the results as XML, too? This isn't the case, though, with AusPost, so i'm a bit stumped ... I get the feeling it shouldn't be as complicated, though.
 */
function australiapost_get_rates($txn, $testing = FALSE) {
$myfile=file('http://drc.edeliver.com.au/ratecalc.asp?Pickup_Postcode='.$var_pickup.'&Destination_Postcode='.$var_destination.'&Country='.$var_country.'&Weight='.$var_weight.'&Service_Type='.$var_service.'&Length='$var_length.'&Width='.$var_width.'&Height='.$var_height.'&Quantity='.$var_quantity);


  $xml = australiapost_AccessRequest($txn);

  // We're doing a POST, so no need for libcurl.
  $result = _australiapost_http_request("http://206.191.4.228:30000", array('Content-type' => 'text/xml'), 'POST', $xml);  

  /**
   * Ugly hack to work around PHP bug, details here:
   *   http://bugs.php.net/bug.php?id=23220
   * We strip out errors that look something like:
   *  warning: fread() [function.fread]: SSL fatal protocol error in...
   */
  $messages = drupal_set_message();
  $errors = $messages['error'];
  $count = 0;
  for ($i = 0; $i <= sizeof($errors); $i++) {
    if (strpos($errors[$i], 'SSL: fatal protocol error in')) {
      unset($errors[$i]);
      unset($_SESSION['messages']['error'][$i]);
    }
    else {
      $count++;
    }
  }
  if (!$count) {
    unset($_SESSION['messages']['error']);
  }
  // End of ugly hack.

  $response = _parse_xml($result->data, '<eparcel>');
  $code = _parse_xml($response, '<statusCode>');
  $rates = array();
  if ($code == 0) { // failed request 
    $error = _parse_xml($response, '<statusMessage>');
    drupal_set_message(t($error), 'error');
    
    return -1; // negative charges indicates an error
  } else { // success, build form
  	$xml = $result->data;
    $loop = TRUE;
    $options = array();
    
    $xml = _parse_xml($response, '<ratesAndServicesResponse>');
    
    $xml = ereg_replace (" id=\"[0-9]{4}\"", "", $xml);
    $xml = ereg_replace (" sequence=\"[0-9]{1}\"", "", $xml);
        
    while ($loop == TRUE) {
      if (strpos($xml, '<product>')) {
      	$product = _parse_xml($xml, '<product>');
        // See if this is a supported shipping method.
        //TODO: make service checker work
        $service = _parse_xml($product, '<name>');
        if ($method = _australiapost_valid_service_code($service, $txn, $testing)) {
          $total = _parse_xml($product, '<rate>');
          //$currency = _parse_xml($total, '<CurrencyCode>');
          $currency = "CAD";
          //$value = _parse_xml($total, '<MonetaryValue>');
          $shipping_date = _parse_xml($product, '<shippingDate>');
          $delivery_date = _parse_xml($product, '<deliveryDate>');
          $delivery_day = _parse_xml($product, '<deliveryDayOfWeek>');
          $next_day_am = _parse_xml($product, '<nextDayAM>');
          $packing_id = _parse_xml($product, '<packingID>');

          $rates[] = array(
            '#service' => 'australiapost',
            '#key' => key($method),
            '#cost' => $total,
            '#currency' => $currency,
            '#method' => current($method),
            '#shipping_date' => $shipping_date,
            '#delivery_date' => $delivery_date,
            '#delivery_day' => $delivery_day,
            '#next_day_am' => $next_day_am,
            '#packing_id' => $packing_id
          );
        }
        $xml = substr($xml, strpos($xml, '</product>') + 1);
      }
      else {
        $loop = FALSE;
      }
    }
  }

  return $rates;
}

/**
 * Build the XML AccessRequest used to login to the UPS shipping server.
 */
function australiapost_AccessRequest($txn) {
  $xml = "<?xml version=\"1.0\"?>\n";
  $xml .= "<!DOCTYPE eparcel SYSTEM \"eParcel.dtd\">\n";
  $xml .= "<eparcel>\n";
  $xml .= "<language>en</language>\n";
  $xml .= "<ratesAndServicesRequest>\n";
  $xml .= "<merchantCPCID>".variable_get('shipcalc_australiapost_merchantid', '')."</merchantCPCID>\n";
  $xml .= "<fromPostalCode>".variable_get('shipcalc_australiapost_postalcode', '')."</fromPostalCode>\n";
  $xml .= "<turnAroundTime>".variable_get('shipcalc_australiapost_turnaround', '')."</turnAroundTime>\n";
  
  //TODO: this field is optional
  //<itemsPrice>  {Insert items $ here} </itemsPrice>
    
  $xml .= "<lineItems>\n";
  
  $weight = 0;
  if (is_array($txn->items) && $txn->items != array()) {
    foreach ($txn->items as $item) {
      // Load product_weight into $item.
      shipping_nodeapi($item, 'load', NULL);
      if ($item->product_attributes['weight']) {
        $weight = $item->product_attributes['weight'];
        $length = $item->product_attributes['length'];
        $height = $item->product_attributes['height'];
        $width = $item->product_attributes['width'];
        
        $xml .= "<item>\n";
      	$xml .= "<quantity>1</quantity>\n"; //FIX THIS
        $xml .= "<weight>$weight</weight>\n";
        $xml .= "<length>$length</length>\n";
        $xml .= "<width>$width</width>\n";
        $xml .= "<height>$height</height>\n";
        $xml .= "<description>Item</description>\n"; //FIX THIS
        $xml .= "</item>\n";
        
      }
    }
  }
  
  $xml .= "</lineItems>\n";

	$xml .= "<city>".$txn->address['shipping']->city."</city>\n";
	$xml .= "<provOrState>".$txn->address['shipping']->state."</provOrState>\n";
  $xml .= "<country>".$txn->address['shipping']->country."</country>\n";
  $xml .= "<postalCode>".$txn->address['shipping']->zip."</postalCode>\n";

	$xml .= "</ratesAndServicesRequest>\n";
	$xml .= "</eparcel>\n";

  return $xml;
}

/**
 * Internal helper function, not yet complete.  CANADAPOST returns an array
 * possible shipping methods.  We need to only return the ones that are 
 * configured for use with the current item.
 *
 */
function _australiapost_valid_service_code($code, $txn, $testing = FALSE) {
  
  switch ($code) {
    case "Priority Courier":
    	$method = array('PC' => t('Priority Courier'));
      break;
    case "XPresspost":
      $method = array('XP' => t('XPresspost'));
      break;
    case "Regular":
      $method = array('RP' => t('Regular'));
      break;
    case "Expedited":
      $method = array('EX' => t('Expedited'));
      break;
    case "US Purolator Courier":
      $method = array('USPC' => t('US Purolator Courier'));
      break;
      case "Xpresspost USA":
      $method = array('USXP' => t('Xpresspost USA'));
      break;
      case "Expedited US Commercial":
      $method = array('USEC' => t('Expedited US Commercial'));
      break;
      case "Expedited US Business":
      $method = array('USEB' => t('Expedited US Business'));
      break;
      case "Small Packets Air":
      $method = array('SPA' => t('Small Packets Air'));
      break;
      case "Small Packets Surface":
      $method = array('SPS' => t('Small Packets Surface'));
      break;
  }

  if ($testing) {
    return $method;
  }

  // TODO:  This is an ugly hack to see if the current method is one of the
  //  supported methods found in $item->shipping_mehtods for any item in the
  //  order.  This should be updated to use the shipping module's 
  //  shipping_method_filter() if possible.  This becomes more important when
  //  the shipping module gains per-item shipping capabilities.
  
  if (is_array($txn->items)) {
    foreach ($txn->items as $item) {
      if (is_array($item->shipping_methods)) {
        foreach ($item->shipping_methods as $supported) {
          if (in_array(key($method), $supported)) {
            return $method;
          }
        }
      }
    }
  }
  return NULL;
}

function _australiapost_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3) {
  $result = new StdClass();

  // Parse the URL, and make sure we can handle the schema.
  $uri = parse_url($url);
  switch ($uri['scheme']) {
    case 'http':
      $port = isset($uri['port']) ? $uri['port'] : 80;
      $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
      break;
    case 'https':
      // Note: Only works for PHP 4.3 compiled with OpenSSL.
      $port = isset($uri['port']) ? $uri['port'] : 443;
      $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
      $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20);
      break;
    default:
      $result->error = 'invalid schema '. $uri['scheme'];
      return $result;
  }

  // Make sure the socket opened properly.
  if (!$fp) {
    $result->error = trim($errno .' '. $errstr);
    return $result;
  }

  // Construct the path to act on.
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
    $path .= '?'. $uri['query'];
  }

  // Create HTTP request.
  $defaults = array(
    // RFC 2616: "non-standard ports MUST, default ports MAY be included".
    // We don't add the port to prevent from breaking rewrite rules checking
    // the host that do not take into account the port number.
    'Host' => "Host: $host",
    'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
    'Content-Length' => 'Content-Length: '. strlen($data)
  );
  
  foreach ($headers as $header => $value) {
    $defaults[$header] = $header .': '. $value;
  }

  $request = $method .' '. $path ." HTTP/1.0\r\n";
  $request .= implode("\r\n", $defaults);
  $request .= "\r\n\r\n";
  if ($data) {
    $request .= $data ."\r\n";
  }
  $result->request = $request;

  fwrite($fp, $request);

  // Fetch response.
  $response = '';
  while (!feof($fp) && $chunk = fread($fp, 1024)) {
    $response .= $chunk;
  }
  fclose($fp);
  
  // Parse response.
  list($split, $result->data) = explode("<?xml", $response, 2);
  $split = preg_split("/\r\n|\n|\r/", $split);
  $result->data = "<?xml".$result->data;

  list($protocol, $code, $text) = explode(' ', trim(array_shift($split)), 3);
  $result->headers = array();

  // Parse headers.
  while ($line = trim(array_shift($split))) {
    list($header, $value) = explode(':', $line, 2);
    if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
      // RFC 2109: the Set-Cookie response header comprises the token Set-
      // Cookie:, followed by a comma-separated list of one or more cookies.
      $result->headers[$header] .= ','. trim($value);
    }
    else {
      $result->headers[$header] = trim($value);
    }
  }

  $responses = array(
    100 => 'Continue', 101 => 'Switching Protocols',
    200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
    300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
    400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed',
    500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
  );
  // RFC 2616 states that all unknown HTTP codes must be treated the same as
  // the base code in their class.
  if (!isset($responses[$code])) {
    $code = floor($code / 100) * 100;
  }

  switch ($code) {
    case 200: // OK
    case 304: // Not modified
      break;
    case 301: // Moved permanently
    case 302: // Moved temporarily
    case 307: // Moved temporarily
      $location = $result->headers['Location'];

      if ($retry) {
        $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry);
        $result->redirect_code = $result->code;
      }
      $result->redirect_url = $location;

      break;
    default:
      $result->error = $text;
  }

  $result->code = $code;
  return $result;
}

I don't understand all the XML stuff in there - do USPS and CanadaPost require you to submit queries and receive responses in XML format?

I would really appreciate some help on this, as I'm at the stage where abandoning Drupal in favour of OSCommerce or some other CMS that "just works" is going to be just as time consuming as implementing what I need in ECommerce.

sime’s picture

I'll help you get this up to scratch. (Prefer you either attach files here or somewhere else accessible, rather than post large code snippets in the comments.)

mrmachine’s picture

StatusFileSize
new7.47 KB

hey, thanx sime

sorry about the large snippet ...

attached here is a streamlined australiapost.inc that i've cut everything out of except for the stuff i definitely know should be there ... which doesn't leave much, lol.

some things the AusPost partner should do:

1) figure out the customers Country, and if it is Australia, only show the Domestic Methods and Rates - if any other country, only show International Methods and Rates. The if/else code for doing this was in the original canadapost.inc, i think, but i cut it out from the attached australiapost.inc so i could work with a clear head.

2) i think can pretty much use the same function/technique for the testing service as in canadapost.inc, just replacing the variables.

3) is it possible to not combine items, so that each product is shipped independently?

2) and 3) don't really matter, though ... just basic interaction and integration with the AusPost service.

if you want the Australia Post API documentation, then PM me and I'll send it to you.

oh - and here's what i've come up with on the _get_rates function so far:

function australiapost_get_rates($txn, $testing = FALSE) {
$myfile=file('http://drc.edeliver.com.au/ratecalc.asp?Pickup_Postcode='variable_get('shipcalc_australiapost_postalcode', '').'&Destination_Postcode='.$txn->address['shipping']->zip.'&Country='.$txn->address['shipping']->country.'&Weight='.$weight.'&Service_Type='.$method.'&Length='$length.'&Width='.$width.'&Height='.$height.'&Quantity='.$var_quantity);

  return $rates;
}

does that look right?

thankyou so much if you are able to help.

mrmachine’s picture

StatusFileSize
new7.35 KB

ok, i've done a crash course in PHP, and I'm sorta getting the feel for what's going on in the partners file ...

everything is working fine up until the _get_rates function ... i sorta understand how some of the partner files are doing it (specifically, the ones that just get the rates by performing operations/functions on a charge that is set in the configuration) - but i don't really get how this function is used when the data is fetched from an external source (like canadapost, ups, usps etc).

i've constructed and if/else statement in the _get_rates function so that a different Aus Post submission URL will be used, depending on whether the order is for Domestic or International delivery ... my main problem is how to load my url with the necessary variables from each item that is being ordered (postage calculation via Aus Posts calculator is done by building up a URL that contains all necessary info such as weight, destination and originating postcode, etc). Then there's also this problem: whether an order is for Domestic or International delivery, I would like to present the customer, at checkout time, with the various delivery options as defined in the first part of the *.inc file - so obviously the _get_rates function must query the cost and delivery time for all these delivery options, spit out a form that allows the customer to choose which one, and then adds the value of that selection to the orders total ....

so, can someone please look at my attached australiapost.inc file, and tell me where i'm going right, and where i'm going wrong? i've also added comments to show what i'm thinking, and what i don't know, at various stages of code.

i'm doing the best i can, but i do need help.

mrmachine’s picture

oh - and is there tool that i can use that will show me what values are being assigned by my script when i run them? that would help a great deal, especially if i could see what values - if any - are being assigned to the URL i need to build to get the rates from Australia Post.

gordon’s picture

If you have the devel module installed you can use dprint_r() or if you have the xdebug php extension installed use var_dump().

If you need any help jump into #drupal-ecommerce and ask. Most of the EC maintainers are there during the day (AEST)

If this is going ok, I will include this into the next stable release of both 4.7 and 5 of EC.

gordon’s picture

Status: Postponed » Needs review
mrmachine’s picture

StatusFileSize
new9.19 KB

here's latest one.

what it can do:

* detect if a transaction is for domestic or foreign shipping, and get the rates for a) domestic - standard, express, registered b) international - air, sea.

* detect the orders destination postcode, country.

* configure registered post cost, store postcode.

what it can't do:

* detect product weight/dimension ... these are hard-coded into the $weight, $width, $length, $height variables at the moment until i figure out how to load the product_attributes.

* don't think it can process more than 1 item per order yet.

* doesn't really take notice of configured postage options ... just returns "STANDARD", "REGISTERED" and "EXPRESS" for domestic and "AIR" and "SEA" for international, regardless of if you have disabled any of those services in the content-types settings.

* doesn't give delivery time estimation in checkout yet.

* probly other stuff.

sime’s picture

StatusFileSize
new13 KB

Here's a new version. I decided to calculate shipping on every item, because there is risk otherwise of going over the limits of the auspost api. But the structure is there to take this one step further for EC5.

Please apply this patch too:
http://drupal.org/node/91948

mrmachine’s picture

unfortunately i didn't get much of a chance to look at this over the weekend. i applied the patch to shipcalc (you meant the second one?), but i get errors at checkout, and no joy (this is on drupal 4.7):

Warning: Unknown column 'Object' in 'where clause' query: product_is_shippable SELECT ptype FROM ec_product WHERE vid = Object in /home/machine/public_html/classic-chic-interiors-com/includes/database.mysql.inc on line 121

Warning: array_merge() [function.array-merge]: Argument #2 is not an array in /home/machine/public_html/classic-chic-interiors-com/modules/ecommerce/ecommerce/contrib/shipcalc/shipcalc.module on line 133

i'll be looking into this today ....

also, it is a condition of use of Australia Posts API, that the customer is shown these terms and conditions at the time of submitting the delivery-rate calculator request (maybe on the checkout page listing delivery costs would be ok ... unsure if it needs to be before that).

sime’s picture

I think change:
if (!product_is_shippable($item)) {
to
if (!product_is_shippable($item->vid)) {

Not sure why I didn't have that problem...

As for the notice, yeah we should look at that we the code is ready to commit.

mrmachine’s picture

yes, that did the trick !

lookin quite good ;)

gordon’s picture

StatusFileSize
new12.82 KB

I have taken a look and it looks really great.

I have however cut out some of the cruft, to make it easier to maintain, and fixed a couple of errors.

sime’s picture

Status: Needs review » Fixed

I have committed this to HEAD. gordon, you might like to tag this first version as 4.7

sime’s picture

Anonymous’s picture

Status: Fixed » Closed (fixed)