Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Notify - VPSSignature mismatch #147

Closed
sigma-technology opened this issue Nov 21, 2019 · 8 comments
Closed

Server Notify - VPSSignature mismatch #147

sigma-technology opened this issue Nov 21, 2019 · 8 comments
Labels

Comments

@sigma-technology
Copy link

I think I've found a strange bug with SagePay\Server - it is consistently failing to process the transaction for me even though all of the address/CVV checks are passing without issue.

I have followed the documentation to the letter but am getting different values returned from the getSignature() and buildSignature() methods even though I am setting the SecurityKey properly, so the isValid() call is always failing.

I've consulted the Sage Pay documentation and examined the code, and spent a long time checking through the ordering of the $signatureData array in the buildSignature() method in ServerNotifyTrait. I am completely at a loss as to why these signatures aren't matching, and I've tried disabling CSRF protection as well but it's made no difference.

Example request data:

{"VPSProtocol":"3.00","TxType":"PAYMENT","VendorTxCode":"98","VPSTxId":"{1D6F2F07-5467-66CD-2DD0-B4832F0D5C1B}","Status":"OK","StatusDetail":"0000 : The Authorisation was Successful.","TxAuthNo":"2878376","AVSCV2":"ALL MATCH","AddressResult":"MATCHED","PostCodeResult":"MATCHED","CV2Result":"MATCHED","GiftAid":"0","3DSecureStatus":"ERROR","CardType":"AMEX","Last4Digits":"0004","VPSSignature":"CABA1A286D831D13A909CE548F7EDA90","DeclineCode":"00","ExpiryDate":"1222","BankAuthCode":"99972"}

Calls to functions and their results:

getSignature(): caba1a286d831d13a909ce548f7eda90
buildSignature(): 6d1b3f53a2d708b6ac512b290e2f6eff

I would really appreciate any advice anyone can provide, as I am losing the will to live with this now.

@judgej
Copy link
Member

judgej commented Nov 21, 2019

Just a note about VendorTxCode/transactionId - they must be unique for all time. If you are using a simple incrementing integer, there is a danger that you will hit the same IDs again, especially if you are rebuilding databases when testing. I would recommend using a UUID.

@judgej
Copy link
Member

judgej commented Nov 21, 2019

The notification handler is the hardest part of Sagepay Server, so we feel your frustration. The problem is, you can't see what Sagepay sees. One thing it is worth doing, is trying Postman or something similar to send a POST to your endpoint. Often that can thrown up some extra character or errors that you could not see any other way.

On the plus side, it looks like Sagepay is sending you the result, and the authorisation is being accepted, so that is a good start. I suspect it is something simple in the notification handler. Here are the steps:

Set up your notification handler:

$gateway = OmniPay::create('SagePay\Server')->initialize([
    'vendor' => 'foobar',
    'testMode' => true,
]);

$serverRequest = $gateway->acceptNotification();

Get the transaction ID that the notification was sent on behalf of:

$transactionId = $serverRequest->getTransactionId();

Now you need to look up the transactionReference that you previously saved in the database (must be stored on the server, since this server request has no access to your front-end session). It will be a JSON string and look something like this:

{"SecurityKey":"YHCGXTXF6U","VPSTxId":"{5E293E3D-E9C6-6906-0841-822B1266256A}","VendorTxCode":"phpne-demo-39952230"}

Omnipay needs ALL those elements to work out the signature (well, the VendorTxCode is send with the server request anyway, but if you want to make a further payment against this authorisation, then it is handy being in this string as it will be needed there).

This along with everything that is POSTed to you is enough to check the signature is valid. Set that up:

$serverRequest->setTransactionReference($transactionReference);

This will then tell you whether the signature is valid:

$serverRequest->isValid();

Are you getting that far? If you are, then Sagepay needs to be told you accept it:

$serverRequest->confirm($nextUrlToSendTheUserTo);

That will output the necessary response message, then exit your application. You probably want to be in more control of the way the application exits, and there are details in the documentation on how to do that, but this crude method will get things working quickly for you while developing.

That's it in a nutshell. The steps are simple, but really, really hard to debug.

@judgej
Copy link
Member

judgej commented Nov 21, 2019

On that transactionReference, you happened to save all the parts of it individually, and want to pass them all in one-by-one, you can do that.

$serverRequest->setSecurityKey("YHCGXTXF6U");
$serverRequest->setVPSTxId("{5E293E3D-E9C6-6906-0841-822B1266256A}"); // Possibly without curly barackets.

That's an option that may help you debug.

@judgej judgej added the awaiting response Awaiting response label Nov 21, 2019
@sigma-technology
Copy link
Author

Thank you for the quick responses @judgej - I have been doing more testing today but still haven't managed to resolve it. I'm using version 3.2.3 by the way.

I've improved the VendorTxCode to ensure it's always unique now, so thank you for the heads up on that potential future issue!

So this is now what's being sent (sensitive info replaced):

Array (
    [VPSProtocol] => 3.00
    [TxType] => PAYMENT
    [Vendor] => vendor1
    [AccountType] => E
    [Description] => Order 9582
    [Amount] => 65.45
    [Currency] => GBP
    [VendorData] =>
    [VendorTxCode] => 9582-108-191122012240
    [ClientIPAddress] =>
    [ApplyAVSCV2] => 0
    [Apply3DSecure] => 0
    [BillingFirstnames] => John
    [BillingSurname] => Doe
    [BillingAddress1] => 88
    [BillingAddress2] =>
    [BillingCity] => Conwy
    [BillingPostCode] => 412
    [BillingState] =>
    [BillingCountry] => GB
    [BillingPhone] => 01234 567890
    [DeliveryFirstnames] => John
    [DeliverySurname] => Doe
    [DeliveryAddress1] => 88
    [DeliveryAddress2] =>
    [DeliveryCity] => Conwy
    [DeliveryPostCode] => 412
    [DeliveryState] =>
    [DeliveryCountry] => GB
    [DeliveryPhone] => 01234 567890
    [CustomerEMail] => [email protected]
    [AllowGiftAid] => 0
    [NotificationURL] => https://website.com/basket/payment/notify
)

All looks pretty good to me, apart from maybe the ClientIPAddress being missing. I'm specifying that as part of the Card object but it doesn't seem to transfer into the request here.

What's coming back:

{"VPSProtocol":"3.00","TxType":"PAYMENT","VendorTxCode":"9582-108-191122012240",
"VPSTxId":"{24B7369E-F6AD-CD00-0B2F-8ED465CA5E85}","Status":"OK",
"StatusDetail":"0000 : The Authorisation was Successful.","TxAuthNo":"2891347",
"AVSCV2":"ALL MATCH","AddressResult":"MATCHED","PostCodeResult":"MATCHED",
"CV2Result":"MATCHED","GiftAid":"0","3DSecureStatus":"OK",
"CAVV":"bXFTYm9tbXF4bm42Z2hKbm9aRWo=","CardType":"VISA","Last4Digits":"0006",
"VPSSignature":"6574F9F97BEED87460CC54F57365A785","DeclineCode":"00",
"ExpiryDate":"1222","BankAuthCode":"999777"}

I'm storing the json TransactionReference in the database before redirecting to Sage Pay:

{"SecurityKey":"PXKZZHIPEZ","VPSTxId":"{24B7369E-F6AD-CD00-0B2F-8ED465CA5E85}","VendorTxCode":"9582-108-191122012240"}

Then in the notifcation handler doing:

$notifyRequest = $gateway->acceptNotification();
$notifyRequest->setTransactionReference($transactionRefFromDb);

And still, when I run $notifyRequest->isValid() it's still returning false because apparently the signatures don't match. It is successfully returning me to the failUrl, but obviously should be taking me to success...

if (! $notifyRequest->isValid()) {
    $notifyRequest->invalid($omnipay->getConfigUrl('fail'), 'Signature not valid');
} else {
    $notifyRequest->confirm($omnipay->getConfigUrl('success'));
}

Sorry for the huge essay, but I am completely at a loss as to what is going on here. Is the Token part required (from the bottom of the readme)? I don't need to store the card details for more than one transaction so didn't think I'd need this.

@sigma-technology
Copy link
Author

I've finally figured it out thanks to Postman, a tool which I'd heard of but never used before. Firstly, I apologise for any wasted time, and secondly thank you very much for taking the time to help me out. You've helped me to improve the integration and also given me a new tool in my debugging arsenal.

For anyone else experiencing the same problem, my issue was that within the notification handler the vendor field hadn't been set properly within the gateway's parameters before calling isValid(). So the SecurityKey was correct, but the vendor field was null, so the signature wasn't matching. I just did a var_dump() on the notification and almost immediately saw that the vendor was missing so figured it was probably that.

Thank you @judgej for building and maintaining this repo. I have to say this integration has been one of the more bizarre I've come across as far as payment gateways go, so it can't have been/be easy. I hope you have a great weekend.

@judgej
Copy link
Member

judgej commented Nov 23, 2019

Don't apologise - nobody's time has been wasted :-)

Thank you for following up and providing details of the source of the problem. If there is anything that missing - even it just a snippet or two - from the documentation, then anything you have to add would be much appreciated. For me, I tend not to understand what is going on until I have a clear picture of the architecture in my head. I think sometimes it takes that key phrase to crystallise the image. But if not, don't worry, we have documentation in this issue.

@judgej judgej removed the awaiting response Awaiting response label Nov 23, 2019
@sigma-technology
Copy link
Author

To be honest the documentation already covers what my issue was, and had I followed it properly it would have worked from the start. My problem was I'd created a helper class to build and configure the gateways, then not called the initialize() function within that helper class to set up the proper parameters. I was so covinced that it was an issue with the SecurityKey that I wasn't looking elsewhere. I'd like to say lesson learned, but I'm sure something very similar will happen again next week 😆 Maybe I'm just in the wrong profession!

@heyyassar
Copy link

Hi everyone,
I have same problem where the signature returned from sagepay is not matching with md5 hash im generating in c#.
this is the format of my hash string my secrete key which i have stored is okay, status is OK, vendorTxCode is unique, vendor name in lower case. im stuck in this issue for last 5 days . your help will be highly appreciated.
String format:
$@"{paymentResponse.VPSTxId}{paymentResponse.VendorTxCode}{paymentResponse.Status}{paymentResponse.TxAuthNo}{VendorName}{paymentResponse.AVSCV2}{SecurityKey}{paymentResponse.AddressResult}{paymentResponse.PostCodeResult}{paymentResponse.CV2Result}{paymentResponse.GiftAid}{paymentResponse.ThreeDSecureStatus}{paymentResponse.CAVV}{paymentResponse.AddressStatus}{paymentResponse.PayerStatus}{paymentResponse.CardType}{paymentResponse.Last4Digits}{paymentResponse.DeclineCode}{paymentResponse.ExpiryDate}{paymentResponse.FraudResponse}{paymentResponse.BankAuthCode}";
String Result:
{79334006-A2A8-9B15-CA1D-A5B59262661B}4FA92744-DD26-47B6-B759-7D111A94DD99OK11416492huddersfieldtaALL MATCHH7RVJYGRFIMATCHEDMATCHEDMATCHED0VISA0006001225999777

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants