thephpleague / omnipay

A framework agnostic, multi-gateway payment processing library for PHP 5.6+
http://omnipay.thephpleague.com/
MIT License
5.94k stars 928 forks source link

Use notify url to check payment response #344

Closed sanderha closed 8 years ago

sanderha commented 8 years ago

Hello

I am trying to write a driver for the Quickpay gateway (https://quickpay.net//). I'd use an existing one, but I dont think there is one. I've set it up correctly so far, so that I can make an authorize at the gateway, so reserve some money on the customers card.

I am missing how to get information back about the payment. Quickpay does not include these things in their response, but to a callbackURL instead. I can understand there is something called a notifyUrl in Omnipay but I can't figure out how to use this to check the response on the callback url.

So my question is basicly; How do I check the response of the off-site payment, when it is not send to getReturnUrl() but to getNotifyUrl() instead?

The not-finished code for my driver can be seen here: https://github.com/NobrainerWeb/omnipay-quickpay

All help and guidelines are very much appreciated!

sanderha commented 8 years ago

@judgej Did you mean to comment on another issue?

judgej commented 8 years ago

Haha! Sorry. No idea how that happened. I replied by email to an email from a colleague about something else altogether. And somehow it ended up here!? I must have slipped when hitting reply. I'll get me coat...

Edit: deleted now - it was about memcached not working with WooCommerce due to the ttl being greater than 30 days, and no-one noticing this until now.

judgej commented 8 years ago

Hopefully I can add something to help here.

The notifyUrl is the callback URL that the remote gateway uses to notify your application of the transaction result. You will have a route at that URL to handle that response. Part of handling it will be to store the result of the transaction in the database, but only after checking the notification is from the trusted source (a hash is often used with a shared secret that itself is never sent between the application and gateway).

Some gateways allow you to provide a unique callback URL when the transaction is being submitted. Others require it to be hard-coded in the gateway when you set up the service. Some need shared secrets to be set up in advance, some generate a one-use token right at the start that you need to store in your database. I'm not sure where your gateway fits into that.

A very simple example of a notify handler for Sage Pay can be found here:

https://github.com/academe/OmniPay-SagePay-Demo/blob/master/sagepay-confirm.php

It uses the completePurchase() method to capture and validate the details sent from the gateway, and the ServerCompleteAuthorizeResponse message to return the result to let Sage Pay know whether we accept what was provided or not.

So, because this notification from the gateway is not in the user's session, it has no knowledge which transaction is being processed. So you need to store that in the database against the transactionId as a key. You give the transactionId to the gateway so that when it does the callback, it also passes back the transactionId so that you can use that to get the transaction out of the database so that it can be updated with the details of the authorisation.

When the user is returned to your site, the transactionId will be in their session still, and you can use that to look up the transaction in the database again to get the result then take appropriate action.

Some gateways do not provide enough details even in the Notification (e.g. Helcim, where you don't actually know how much was authorised). But it should give you a transactionReference at least so that you can then do another call to the gateway to get the full details, address, delivery notes etc. that will have been posted by the end user to the gateway site.

Does that provide enough context to how gateways that use callbacks work?

sanderha commented 8 years ago

Hehe no problem about your first comment being posted on the wrong issue, shit happens!

Thanks for your response, it explained quite a lot, and will most likely help me in getting further with my driver.

Would you mind taking a quick look at the process I am running for my driver, to see if it makes any sense? I am still wrapping my mind around the entire Omnipay process, so it could be great with a little feedback.

Currently it is set up like this:

First, I call authorize() (which is basicly the same as purchase() on my gateway. This then checks the data to send.

It then uses the send() method, to put this data into a PurchaseResponse , which then creates the RedirectURL, to send the user to an off-site payment form.

When the user then comes back to my website from the off-site payment CompletePurchaseRequest is used. Here I use $this->httpRequest->query->all() to send as data into CompletePurchaseResponse

Currently the isSuccessful method on CompletePurchaseReponse just returns true, because I dont get a response I can validate the response with. This is basicly where the issue with callback comes in.

The way this is set up, does it seem like the right way to do it?

Thanks a lot.

delatbabel commented 8 years ago

Hi,

What you have described above is a good example of a handler for returnUrl. As a summary, your control flow works like this:

All of this is as you have described above. Have a look in the class documentation for omnipay-paypal RestPurchaseRequest for another example along similar lines.

Now your problem is that subsequent to [note 1] the redirect, you will get a notify sent to your notifyUrl. Not all gateways do this but clearly Quickpay does as does SagePay. Some others that do include MultiCards and PaymentWall.

This message sent to your notifyUrl is a POST message containing a chunk of data. The content of that chunk of data varies from gateway to gateway but usually includes at least a status message (to update the status of the purchase) and some other data.

Your application, not your QuickPay driver, needs to be able to handle this POST request. Clearly Omnipay can't handle it because Omnipay is not an application, does not have a router and can't handle requests, it's just a data transmission layer. It would be helpful if the documentation in your QuickPay driver contained the structure of this POST request so that anyone implementing QuickPay via Omnipay has that documented.

I will say in reference to @judgej 's example for SagePay, it's very unusual for a gateway to require you to call it back during the notifyUrl -- that usually happens during the returnUrl. Normally the notifyUrl just needs to return a 200/OK response to indicate that the notification was successful.

I will take a look at your QuickPay driver and add any other comments there that I can think of.

delatbabel commented 8 years ago

[note 1]

This is not always subsequent to the redirect. The purchase() call can take some time and it's entirely possible for the POST to the notifyUrl to occur before the purchase() call returns. PaymentWall is an example of a gateway with this issue -- the purchase() call frequently takes as long as 45 seconds to complete, and the timeline of events frequently looks like this:

In this case the purchase must be assumed to be a success because the notifyUrl status takes precedence over the return response from purchase().

Some further examples of the notifyUrl in use:

sanderha commented 8 years ago

Thank you so much for all this information, it helped me understand omniapy alot better.

Also thanks for the PR you submitted with suggestions, I'll have a look ASAP