-
-
Notifications
You must be signed in to change notification settings - Fork 245
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
getTransactionId needed for NotificationInterface? #148
Comments
Hmm, I feel like the reference should always be used, of at all possible. |
The assumption is that the gateways all support a merchant site transaction ID because they all know the merchant site will have its own way to index and reference the transactions internally. Plus the fact that some gateways do not allocate a reference it their own until the notification comes in, and so ALL you have is the merchant transaction ID. I'll gather some evidence to see what is generally available. |
A gateway can and should still add it when available, but not sure it should be in the interface. But I'm also not sure about this interface, I've never used it. Do you have a good example? Can't is just be the complete purchase response, but returning a 'reply' response? |
It depends on how the process for accepting remote notifications should work. If it's
then |
After discussing that a bit more in detail, we think that the usage of acceptNotification() should be like this:
The reason is the PayPal driver which needs to contact the payment server first before the transaction ID and status will be available and the NotificationInterface does not extend from ResponseInterface. As no isSuccessful() method is available then, acceptNotification() must throw an exception (InvalidRequestException?) in case the request is tampered. In Omnipay 3.x we also need an optional PSR-7 request as parameter for acceptNotification() and a PSR-7 response that contains the data that must be returned to the payment gateway so it knowns the payment status update was OK:
The reason is that in environments with separated front-ends and back-ends which communication e.g. via JSON, acceptNotification() can't create a request from the environment itself. Also, being able to inject a PSR-7 request makes testing easier. |
But still, what is the difference with completePurchase? You can already always inject the Symfony request object. And we are currently still using Symfony for server request and response, to make upgrades easier. We could perhaps add methods to convert those. |
A problem with throwing an exception at that stage is that you do not get a notification object to either analyse or to respond with. Logging the details is very useful for analysis. The response is always needed - there MUST be a http response, whether the data has been tampered with or not, and whether the transaction is successful or not. So you don't want an exception, since the processing is not exceptional so far as omnipay is concerned; a http response can be returned as normal. The application still needs to know if the data is valid and untampered, and needs to know whether the transaction is successful so it can decide what to do with that data before sending the response it is given. |
@barryvdh The main difference is an object with another interface which doesn't need the transactionId in advance I think. @judgej Our suggestion to throw an exception is based on the current NotificationInterface. If you would like to always return a ResponseInterface, the NotificationInterface must then inherit from the ResponseInterface. Throwing an exception makes analysis different (you have to log the request yourself instead of using getData() or something like that) but would be OK in my opinion. You don't need to do anything besides logging and return a HTTP code 400 if the request is invalid at all. At the end it all boils down to how you would like the process of acceptNotification() to work. |
Here are the differences I can see:
I am beginning to see that the notification object MUST return a HTTP response. That response may be decorated with data from the application (this will be gateway-specific). The general flow of the notification will be:
Step 2 will involve logging details if the server request has been tampered with, looking up the transaction if not, updating the transaction as necessary (remembering that a notification can be sent multiple times, so be prepared for repeats). Step 3 will include providing any final result data to the notification object (e.g. message, error state if the application could not process the data), then ask for the HTTP response object. So I think the Q: Will the driver ever need to do a callback to the gateway (in an ever-depending hole) in this process, or is ALL the data needed in the notification request the gateway has sent and the transaction details in local storage? Is Paypal an example of this callback to a callback? |
The http session is not required for complete, and it usually doesn't redirect I think. Imho this difference is application logic. Most gateways I encounter can use complete* in the same way. redirectUrl: Both can get the payment details from the database (no session required), check the payment status (usually by sending the original parameters + the current HttpRequest). Only redirect shows 'THank you, your status is: ...' page and notify will return something back to the gateway (most cases just an HTTP status 200 is ok, sometimes the reference or whatever). So in my view, you could just extend the complete* message, with a default response of '200 ok', and override it when needed. So you can call something like this: $reponse = $gateway->comletePurchase($params)->send();
if ($respose->isSuccessfull()) {
//..
}
$response->sendReply(); // or: return $response->getReplyResponse(); |
Some gateways require a callback indeed. Eg they just notify that the status change, but a HTTP request is required to verify the actual result. |
@barryvdh Both redirectUrl (back to the application) and notifyUrl have to contain the order ID (transaction ID) besides the transaction reference in some way. The main difference is that in the redirectUrl you can add the order ID like you want, in the notifyUrl the order ID parameter name is often named by the payment gateway (and therefore different between gateways) or you only get a token and have to request the details including the order ID yourself (PayPal). |
According to our thoughts it should be like this now:
This also means that the |
So these are different issues I think:
For 3, I think we all agree. As for 1, I can sort of see this might be the case sometimes, but for the implementations I have seen, this usually isn't a problem. Sometimes you can call completePurchase only on the notifyUrl, sometimes also on the redirectUrl. It is perfectly fine to make another request to the gateway, using the provided token. But this shouldn't matter to the user. Or do you mean that you need the transactionId from the HttpRequest, to load the correct parameters BEFORE calling completePurchase? |
1.) The suggestion would be to use 2.) A transactionReference is NOT always available, in fact most of the time its not. Think about gateways where you need to redirect to the gateway and have no transactionReference in advance for storing it in the database for later lookup of the order. Jason opened this issue because a the moment, there are concrete problems with gateways requiring a redirect. Edit: returnUrl instead of redirectUrl in 1.) |
@aimeos It's interesting that Just a small note on the redirect and notify URLs: not all gateways allow you to customise this URL at runtime. For some gateways the URLs are fixed in the gateway configuration, and only data POSTed can be accessed. For some gateways the redirect URL returns the user to the site using a GET request with a fixed URL, so all you have to work with is what you put in the user session before you sent them off to the remote site. There seems to be an exception to every rule. |
@judgej Seemed a possible way as there's no other API method that could do the job. Additionally, you've said |
At some point you need a response object, since a response must be sent, and since the notification responses for gateways are very specific and particular about what is returned, the gateway driver- with the knowledge of what the gateway is expecting - is the thing that should generate that response. Now where that is done, I'm not entirely sure, since there are a number of things that can happen in the notification handler flow:
So Edit: too many words. I think I need to sketch this out. |
But for example Mollie, which has an offline gateway. You can either pass the transactionReference directly (eg. from database) or it will try to get it from the current http request (eg. notify); https://github.com/thephpleague/omnipay-mollie/blob/master/src/Message/CompletePurchaseRequest.php I think you should, even in the case of the notification, always call the ->send() method to verify the request. In some gateways this will not be an actual request but just checking the hash, but still. The notification interface could be used to get some data without actually checking, but then that data will still need to be passed to completePurchase to verify the request, so you could just as well do it there. Only case I can think of is that you need to load the parameters from the database, based on the notification HttpRequest, so it would be something like: $notification = $gateway->acceptNofication();
$params = TransactionRepository::findByReference($notification->transactionReference())->params;
$response = $gateway->completePurchase($params)->send();
if ($response->isSuccessful()) { ... } But do you have an example of where you can't request the final status based on the notification alone? |
To be able to add order data for generating the final response, the code should look like this now:
|
Ok, but remember that we are still using Symfony HttpFoundation for both incoming requests and responses. And the HttpResponse is already available inside the gateway. if( $gateway->supportsAcceptNotification() )
{
// Get the parameters from the incoming HttpRequest
$notification = $gateway->acceptNotification();
$params['transactionId'] = $notification->getTransationId();
$params['transactionReference'] = 'xxx'; // Tx reference from database
$params['amount'] = 'xxx'; // Total amount from order
$params['currency'] = 'xxx'; // Currency from order
// send request to gateway if necessary (PayPal)
// return final response of evaluation
$response = $gateway->completePurchase( $params )->send();
// verify that the notify request hasn't been tampered
if( $response->isSuccessful() ) {
// save $response->getTransactionStatus();
}
// return HttpResponse to finish the update for the gateway (like a transaction)
return $response->getReplyResponse();
} So imho, it would be okay to have something like this to extract those parameters. But the handling of the status should be left to the complete* requests/responses. |
Remember, there are no session parameters, because there is no session. There is also no browser to send a POST redirect to. I don't know of any gateways that expect a HTTP redirect as a response to the notify. They all just need text, JSON, XML, or just a HTTP code. One gateway requests you to send a HTML page back, that it then passes on to the user via the gateway, but that's a weird one. |
I know, this is as for the notifyUrl when no session parameters are available, so INSTEAD OF the session parameters and not always possible to set the url. And the response would indeed be just a 200 'ok' in 99% of the cases, but some gateways require something like 'success' or the transaction id back. |
What about drivers supporting The parameters sent by the gateway may differ for returnUrl and notifyUrl so this might end up in methods doing two completely different things, so I would suggest to avoid using |
Can you provide a real example? |
@aimeos I agree that just extending the |
But this is only an issue when BOTH of the following is true:
So let's use an actual real example where this is a problem. |
Yes, the routes for handling notifications and for handling returnUrls with
The method @barryvdh I remember a payment gateway from Deutsche Bank where this was the case. |
More on the Deutsche Bank example: They only allowed URLs without GET parameters and sent an encrypted token that must be used to fetch the details. |
You don't need GET parameters; |
Not all applications are based an Laravel and Symfony where you can define your own routes. There are a lot of applications where you will have problems and Aimeos for example can't make sure there are no URLs without GET parameters as we are not responsible for the host application. |
And do you need all the parameters? Or just the token? Can't find the omnipay deutsche bank repo. |
As far as I remember, they only send the token as parameter. |
https://github.com/thephpleague/omnipay-common/blob/master/src/Common/Message/NotificationInterface.php
The
NotificationInterface
has agetTransactionReference()
method to fetch the remote transaction reference. For some gateways the reference is already supplied to the merchant site when the transaction was initiated. For other gateways this is the first point at which the reference is provided, so it can be logged, but cannot be used to look up the transaction in storage.The
transactionId
is the only identifier that the merchant site can use to guarantee being able to look up the transaction, and it is supplied in every notification callback that I am aware of (except Authorize.Net DPM, but our driver defines a custom field to put that in, so we know it will be there).My proposal is to add
getTransactionId()
to theNotificationInterface
so that all drivers know this needs to be implemented.The text was updated successfully, but these errors were encountered: