Using 7.x-1.0-beta2 version of commerce_sagepay_form
... with this amendment to take account of US addresses: http://drupal.org/node/1613234

Using 7.x-1.x-dev version of addressfield module.

Ireland

A customer in Ireland reported this error from Sagepay.

Form Transaction Error

This transaction attempt has failed. Please use the Proceed button go back to the web store from which you were purchasing. The details of the failure are given below.

Status: MALFORMED

Status Detail: 3090 : The BillingPostCode is required.

The quick fix was to put a dummy postcode for Ireland:

This code is around line 500 at the end of function _commerce_sagepay_form_encrypted_order()
I've included a few lines after the fix so you can find the right spot in 7.x-1.0-beta2 version of commerce_sagepay_form.

  // Fix for Sagepay error with addresses in Ireland.
  // Status: 	MALFORMED
  // Status Detail: 	3090 : The BillingPostCode is required.
  // Irish addresses don't have a postcode, but Sagepay needs a (dummy) 
  // postcode.
  if ($address['country'] == 'IE') {
    $query['BillingPostcode'] = '0000';
    $query['DeliveryPostcode'] = '0000';
  }
  // End Fix

  $keys = array_keys($query);
  $query_string = '';
  foreach($keys as $key){
    $query_string .= $key . '=' . $query[$key] . '&';
  }
  $query_string = substr($query_string, 0, strlen($query_string) -1);

  // Encrypt the order details using base64 and the secret key from the settings.
  return base64_encode(_commerce_sagepay_form_simple_xor($query_string, $settings['enc_key']));  
}

function _commerce_sagepay_form_base64Decode($scrambled) {
   $output = "";

This was tested and works fine in the live environment.
My choice of '0000' for dummy postcode value is quite arbitrary, based on what I saw some other sites doing when I did a search for a solution to this Sagepay error.

Other countries without postcodes

Looking at the addressfield module (7.x-1.x-dev) we find the following code in:
sites/all/modules/addressfield/plugins/format/address.inc

  // Those countries do not seem to have a relevant postal code.
  if (in_array($address['country'], array('AF', 'AG', 'AL', 'AO', 'BB', 'BI', 'BJ', 'BO', 'BS', 'BW', 'BZ', 'CF', 'CG', 'CM', 'CO', 'DJ', 'DM', 'EG', 'ER', 'FJ', 'GD', 'GH', 'GM', 'GQ', 'GY', 'IE', 'KI', 'KM', 'KP', 'KY', 'LC', 'LY', 'ML', 'MR', 'NA', 'NR', 'RW', 'SB', 'SC', 'SL', 'SR', 'ST', 'TD', 'TG', 'TL', 'TO', 'TT', 'TV', 'TZ', 'UG', 'VC', 'VU', 'WS', 'ZW'))) {
    unset($format['locality_block']['postal_code']);

    // Remove the prefix from the first widget of the block.
    $element_children = element_children($format['locality_block']);
    $first_child = reset($element_children);
    unset($format['locality_block'][$first_child]['#prefix']);
  }

I tested with addresses from some of these countries (Afghanistan, Antigua and Barbuda, Colombia, Zimbabwe) and I got the 3090 error each time from Sagepay.

According to the Sagepay Form Protocol and Integration Guideline document, appendix A1, Sagepay needs a post code every time. (To get this document, sign up here: http://www.sagepay.com/developers/sign_up)

All fields must contain a value including the Post Code field
even if the customer does not have a post code. Providing a blank
field will cause an error.

... but no guidance is given on what dummy value to use, so I figure that '0000' is as good as any.

So I've changed the code to read as follows:

  // Fix for Sagepay error:
  // Status: 	MALFORMED
  // Status Detail: 	3090 : The BillingPostCode is required.
  //
  // For countries without a postcode Sagepay needs a (dummy) postcode.
  if (!isset($address['postal_code'])) {
    $query['BillingPostcode'] = '0000';
    $query['DeliveryPostcode'] = '0000';
  }
  // End Fix

  $keys = array_keys($query);
  $query_string = '';
  foreach($keys as $key){
    $query_string .= $key . '=' . $query[$key] . '&';
  }
  $query_string = substr($query_string, 0, strlen($query_string) -1);

  // Encrypt the order details using base64 and the secret key from the settings.
  return base64_encode(_commerce_sagepay_form_simple_xor($query_string, $settings['enc_key']));  
}

function _commerce_sagepay_form_base64Decode($scrambled) {
   $output = "";

I've tested this new code locally. I am about to put it live and I'll report back to this issue if any of our customers have any issues with it.

Comments

FrancescoUK’s picture

Thank you for the fix suggestion, I applied and tested with a fake IE address to verify it and seems fine.

FrancescoUK’s picture

BTW, about your generic fix that I applied, I'm slightly concern that it will not report an error for whomever forgets to insert a post code.
I hadn't the time to test it, but I assume that on SagePay side the transaction will be market as wrong.

FrancescoUK’s picture

I'm afraid the the following fix:

  if (!isset($address['postal_code'])) {
    $query['BillingPostcode'] = '0000';
    $query['DeliveryPostcode'] = '0000';
  }

doesn't work as expected, at least on my installation.
I had a purchase from the US where the customer entered the postcode correctly (it's in the log in drupal commerce), but once on SagePay the postcode was 0000.

So I've commented out and going to apply the "IE" version.

ikos’s picture

I think it's ok to do this as if someone forgot to enter a postcode it would be picked up earlier in the process.

I will create a patch we can test before committing.

ikos’s picture

Status: Active » Fixed

I have put a simple fix in to add a default postcode when there is not one present. We can assume the AddressField module has already validated the address and will have ensured a postcode is present if one is required.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.