2

I'm getting the following error when trying to use SagePay Server (from printing out the response):

["data":protected]=>array(3) {
["VPSProtocol"]=>
string(4) "2.23"
["Status"]=>
string(7) "INVALID"
["StatusDetail"]=>
string(107) "The data in the BillingCountry field you supplied is an invalid length. Must be an ISO 3166-1 country code."

}

but I am passing the value "GB" as the billingCountry which is correct (SagePay expects billingCountry to be alphabetical and a max of 2 characters)

 ["card"]=>
    object(Omnipay\Common\CreditCard)#46 (1) {
      ["parameters":protected]=>
      object(Symfony\Component\HttpFoundation\ParameterBag)#48 (1) {
        ["parameters":protected]=>
        array(11) {
          ["email"]=>
          string(17) "test@test.com"
          ["billingFirstName"]=>
          string(3) "Joe"
          ["shippingFirstName"]=>
          string(3) "Joe"
          ["billingLastName"]=>
          string(6) "Bloggs"
          ["shippingLastName"]=>
          string(6) "Bloggs"
          ["billingAddress1"]=>
          string(9) "Address 1"
          ["billingAddress2"]=>
          string(9) "Address 2"
          ["billingCity"]=>
          string(4) "City"
          ["billingPostcode"]=>
          string(7) "AB1 1BA"
          ["billingCountry"]=>
          string(2) "GB"
          ["billingPhone"]=>
          string(13) "01234 567 890"
        }
      }

And this is my code:

$response = $this->gateway->Purchase(array(
        'description'=> 'Online order',
        'currency'=> 'GBP',
        'transactionId'=> mt_rand(99, 9999),
        'transactionReference' => 'test order',
        'amount'=> '1.00',
        'returnUrl' => 'http://www.test.com/returnURL/',
        'cancelUrl' => 'http://www.test.com/cancelURL/',
        'card' => array(
            'email' =>  'test@test.com',
            'clientIp' => '123.123.123.123',
            'firstName' => 'Joe',
            'LastName' => 'Bloggs',
            'billingAddress1' => 'Address 1',
            'billingAddress2' => 'Address 2',
            'billingCity' => 'City',
            'billingPostcode' => 'AB1 1BA',
            'billingCountry' => 'GB',
            'billingPhone' => '01234 567 890'
        )))->send();

I can't work it out as it all looks correct. I think I'm going slightly mad! Am I missing the totally obvious?

Updated 28.07.2014: I have been trying various things including sending the data in different formats:

   $formInputData = array(
            'firstName' => 'Joe',
            'lastName' => 'Bloggs',
            'billingAddress1' => '88',
            'billingAddress2' => 'Address 2',
            'billingCity' => 'City',
            'billingPostcode' => '412',
            'billingCountry' => 'GB',
            'billingPhone' => '01234 567 890',
            'email' =>  'test@test.com',
            'clientIp' => '123.123.123.123'
        );


     $card = new CreditCard($formInputData);

     $transactionID = mt_rand(99, 9999);

     $response = $this->gateway->purchase(['amount' => '1.00', 'returnUrl' => 'http://www.example.com/return/', 'transactionId'=> $transactionID, 'description'=> 'Online order', 'transactionReference' => 'test order', 'currency'=> 'GBP', 'card' => $card])->send();

and it doesn't make any difference.

I have also tried sending the same data (but with the addition of test credit card details in the card parameter) to both SagePay Direct and CardSave.

With SagePay Direct I get "The BillingCountry field is missing from the POST".

With CardSave the transaction goes through successfully but I notice that the country field has "N/A" when I look at the transaction history in my CardSave.

JoJo
  • 161
  • 1
  • 12
  • The firstName and lastName fields - should they not be BillingFirstNames (yes, plural) and BillingSurname? – Jason Aug 08 '14 at 12:41

5 Answers5

1

The current release of omnipay/sagepay is incorrectly setting the US state for non-US billing addresses.

If the address is outside the US it passes NULL for the state which SagePay rejects. Unfortunately SagePay's error messages often refer to a neighbouring field rather than the actual error. So it is complaining about the BillingCountry not being set when it actually means the state's not been set.

There is a fix in the Git repository for this bug, but it isn't in the main releases so Composer misses it.

In the meantime you can just manually set the state to be an empty string $data['card']['state'] = ''.

drmonkeyninja
  • 8,490
  • 4
  • 31
  • 59
  • Thanks drmonkeyninja, I had just come by here to add that info as an update as just came across it myself! This does indeed fix the problem. Unfortunately the error from SagePay was rather misleading! – JoJo Aug 14 '14 at 12:57
  • SagePay's error messages can often be confusing. I've found that if it is reporting that a field is not set it usually means an adjacent field contains an error that needs checking. – drmonkeyninja Aug 14 '14 at 13:50
  • That's handy to know for the future! – JoJo Aug 14 '14 at 14:10
  • For anyone reading this, the mentioned fix has been in releases from January 2016. – Jason Aug 09 '17 at 10:12
0

Could this be a case-sensitivity issue? Does changing it to "BillingCountry" make any difference?

user1112560
  • 185
  • 1
  • 8
  • Alas I wish it was something so simple! No "BillingCountry" doesn't work either. Echoing the response shows me the billingCountry is being set correctly (see second block of code in my original post). billingCountry is the correct field to use as per the Omnipay documentation. – JoJo Jul 31 '14 at 07:23
  • I would stick to the letter case in the documentation throughout, e.g. BillingCountry. SagePay can only return one error at a time - not a bunch of errors if there are many - so the error you are seeing may not be the initial reason it is failing. – Jason Aug 08 '14 at 12:38
0

The firstName and lastName fields - they should be:

  • BillingFirstNames (yes, plural)
  • BillingSurname

The properties for all the fields are summarised in a gateway package I write last year:

https://github.com/academe/SagePay/blob/master/src/Academe/SagePay/Metadata/Transaction.php

This includes the field names, lengths, mandatory status, allowed characters, and which transaction types the fields are used in. These details are difficult to extract from the documentation, and many are not documented at all, and have been found through trial-and-error.

Edit: this is probably a silly answer if Omnipay translates the field names, so feel free to ignore it. My head was in SagePay and not Omnipay.

Jason
  • 4,411
  • 7
  • 40
  • 53
  • Ha, no it is a good point but I have posted an additional answer to show that OmniPay does indeed translate them but thank you :) – JoJo Aug 08 '14 at 12:55
  • So is this sorted now? – Jason Aug 09 '14 at 14:02
  • No it's not! I couldn't get any help on it so I switched back to CI-Merchant. I then also had problems with that (can't remember what but it was written by the same people lol), so I ended up switching to SagePay Direct with CI Merchant which I've done before and know works. I do need to find a solution to this as future clients may need it but as I needed the project live and I had wasted a lot of hours on this I had to give up! – JoJo Aug 11 '14 at 06:59
  • I'm working with the Sagepay interface today and tomorrow. I'll let you know how that goes and how I got over any issues. I'm hoping it is going to be smooth, but the other Omnipay gateways have not been so far - it is the lack of documentation that is the problem (you need to read the uncommented code to try and work out how to use it - even the example application needs a PhD in the Silex micro-language to understand). It is unfortunate, but just the way is. – Jason Aug 12 '14 at 10:50
  • Ha have fun! OmniPay is brilliant in that it makes it easy to switch between payment gateways with bare minimal code changes but the downside to that is like you say, the lack of documentation or examples. I'm probably doing something wrong with my code for SagePay Server but I can't see the wood for the trees on this one :) – JoJo Aug 12 '14 at 14:06
  • Have you tried the example application? It may give a few clues. I have it installed here for the moment: http://www.acadweb.co.uk/helcim/demo/ Give it a try, or if you aren't happy putting your vendor ID in (I'm *not* saving anything you put in), try installing it yourself. It needs to run on the root path of a domain, without some manual fixes to paths and URLs. – Jason Aug 13 '14 at 15:20
  • Hi Jason, missed this comment. It turns out the error was due to the BillingState being passed as NULL as it was outside of the US. A simple fix (see accepted answer) sorts out the issue. – JoJo Aug 19 '14 at 14:39
0

To respond to Jason's comments/answers, Omnipay handles multiple payment gateways and so converts fields passed to the correct field names as expected by the particular gateway. In the case of SagePay this is:

protected function getBaseAuthorizeData()
{
    $this->validate('amount', 'card', 'transactionId');
    $card = $this->getCard();

    $data = $this->getBaseData();
    $data['Description'] = $this->getDescription();
    $data['Amount'] = $this->getAmount();
    $data['Currency'] = $this->getCurrency();
    $data['VendorTxCode'] = $this->getTransactionId();
    $data['ClientIPAddress'] = $this->getClientIp();
    $data['ApplyAVSCV2'] = $this->getApplyAVSCV2() ?: 0;
    $data['Apply3DSecure'] = $this->getApply3DSecure() ?: 0;

    // billing details
    $data['BillingFirstnames'] = $card->getFirstName();
    $data['BillingSurname'] = $card->getLastName();
    $data['BillingAddress1'] = $card->getBillingAddress1();
    $data['BillingAddress2'] = $card->getBillingAddress2();
    $data['BillingCity'] = $card->getBillingCity();
    $data['BillingPostCode'] = $card->getBillingPostcode();
    $data['BillingState'] = $card->getBillingCountry() === 'US' ? $card->getBillingState() : null;
    $data['BillingCountry'] = $card->getBillingCountry();
    $data['BillingPhone'] = $card->getBillingPhone();

    // shipping details
    $data['DeliveryFirstnames'] = $card->getFirstName();
    $data['DeliverySurname'] = $card->getLastName();
    $data['DeliveryAddress1'] = $card->getShippingAddress1();
    $data['DeliveryAddress2'] = $card->getShippingAddress2();
    $data['DeliveryCity'] = $card->getShippingCity();
    $data['DeliveryPostCode'] = $card->getShippingPostcode();
    $data['DeliveryState'] = $card->getShippingCountry() === 'US' ? $card->getShippingState() : null;
    $data['DeliveryCountry'] = $card->getShippingCountry();
    $data['DeliveryPhone'] = $card->getShippingPhone();
    $data['CustomerEMail'] = $card->getEmail();

    return $data;
}
JoJo
  • 161
  • 1
  • 12
0

Since this bug has been fixed, I have also encountered the error. However the bug spoken about above has now been fixed.

In my case the problem stemmed from using "DeliveryXXXX" instead of "ShippingXXXX". Once I changed all instances of Delivery to Shipping I stopped receiving the error.

mickburkejnr
  • 3,652
  • 12
  • 76
  • 109