-
Notifications
You must be signed in to change notification settings - Fork 45
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
Sagepay support #42
Sagepay support #42
Conversation
thanks @mattwire sorry I didn't get around to replying to your email! |
@@ -352,6 +352,12 @@ public function sendData($data) | |||
case 'DeliveryCountry': | |||
$data[$key] = $data['BillingCountry']; | |||
break; | |||
case 'NotificationURL': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton Ok, I agree it looks like case 'NotificationURL'
is redundant but I need to pass in qfKey as an URL parameter to generate the redirect URLs correctly. Currently I'm doing that directly in CRM_Core_PaymentExtended::getNotifyURL
.
@@ -639,7 +639,7 @@ public function handlePaymentNotification() { | |||
if (empty(CRM_Core_Session::singleton()->getLoggedInContactID())) { | |||
// Don't be too efficient on processing the ipn return. | |||
// So far the best way of telling the difference is the session. | |||
sleep(45); | |||
// sleep(45); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I have 2 thoughts here - fundamental issue is ipn & browser returning at the same time, starting processing the payment at the same time & sending 2 emails.
- We could put ipn_delay_time in the metadata . Some processors are more concurrent than others making it more or less of an issue.
- we could use a mysql lock - that mostly works prior to mysql 5.7 & can be totally solid in 5.7 with perhaps a small patch - CRM_Core_Lock I believe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't fully understand the process... But from the sagepay side they expect a response within 1 second otherwise their IPN request fails. (I spent hours trying to work out why it was timing out on their side, until I found that in their docs somewhere).
Sagepay is a 4-step process as we:
- Redirect to sagepay servers to make payment.
- Sagepay notifies CiviCRM of payment status and requests a redirect URL (via IPN).
- Sagepay receives IPN response (redirectURL) from CiviCRM within 1 second of request (otherwise it retries and times out).
- Sagepay redirects to redirectURL.
I think most redirect processors are only 3-steps (ie don't have separate steps 2-3) so maybe this is somewhat unique to sagepay? I don't generally like the idea of arbitrary delays in code (ie. sleep) so if it makes sense to use a mysql lock that might be better? Though I don't really understand the code and reason for the delay :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattwire I don't suppose you are using mysql 5.7.5+? I feel like using mysql locking is an appropriate way to prevent the 2 processes both processing the ipn at the same time - however, if only works properly with mysql 5.7.5+
Some discussion & links here civicrm/civicrm-core#11720 (but I have added a version of this to extension)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I've rebased sagepay branch against master, looking much cleaner now. And I've raised PR #47 to set ipn delay for sagepay
4b646da
to
a7ae3fd
Compare
@@ -657,7 +657,12 @@ public function processPaymentNotification($params) { | |||
$originalRequest = $_REQUEST; | |||
$_REQUEST = $params; | |||
try { | |||
$response = $this->gateway->completePurchase($params)->send(); | |||
if ($this->gateway->supportsAcceptNotification()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mattwire I've just added this commit to the main repo - I'm afraid I'm cherry picking my way through your commits as time permits rather than assessing as a whole, as there are a lot of issues in there to consider
Hi @mattwire I've cherry-picked out the things that I'm comfortable with - I'll need to work with you on the remaining items before I feel sure - they probably need splitting out into issues |
@mattwire do you want to rebase this - quite a bit is merged |
aff9ef1
to
f8fe1e1
Compare
Ok so for the Sagepay fixes the way to do this is to submit a change to the composer.json to point towards your git repository to retrieve the Sagepay ones - if you do that I'll run composer afterwards & push. I have some reservations - mostly with the buildSignature change but I'm prepared to leave that with you to try to negotiate upstream |
644af07
to
630e9c0
Compare
@eileenmcnaughton I've added a forked version of the omnipay-sagepay library via this commit: 688f917 |
CRM/Core/Payment/PaymentExtended.php
Outdated
@@ -201,7 +201,7 @@ protected function getGoBackUrl($qfKey) { | |||
protected function getNotifyUrl($allowLocalHost = FALSE) { | |||
$url = CRM_Utils_System::url( | |||
'civicrm/payment/ipn/' . $this->formatted_transaction_id . '/' . $this->_paymentProcessor['id'], | |||
NULL, | |||
array('qfKey' => CRM_Utils_Request::retrieve('qfKey', 'String')), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton So the session key is required for sagepay because the IPN handler returns the redirect URL to sagepay when requested, rather than using the URL via the initial transfer offsite. Any thoughts on how this could be handled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattwire I'm trying to remove those functions that have been migrated to core from this extension - that goBackUrl is one of them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton We're talking about getNotifyUrl
and not getGoBackUrl
. However, I see the same applies. Basically the session key (qfKey) is essential for sagepay because (uniquely) Sagepay does the final user redirect back from their site ONLY after the IPN has completed. It needs the session key to be returned so that it can redirect the user to the CiviCRM payment confirmation page. Any idea how we could gracefully override this in core?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattwire normally the QFKey is stored in the user session before leaving the site & retrieved from there on return?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so discussion on mattermost - we should see if we can modify "next url" so that it is set to civicrm/payment/ipn/5
or equivalent and then we would not need the session key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep - although looking at the above - there is an Omnipay-specific thing already in that function
- 'civicrm/payment/ipn/' . $this->formatted_transaction_id . '/'
Hmm - we shouldn't be sending next url to Sagepay but returnUrl - that will figure out nextUrl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton I don't think there is any way around putting the qfKey in getNotifyUrl:
Done a bit of debugging as I couldn't remember exactly what was going on.
No session key supplied via getNotifyUrl (standard):
- CiviCRM payment submitted.
- Goes offsite to Sagepay.
- Sagepay returns via ipn (eg. civicrm/payment/ipn/146/5).
- CiviCRM server responds to IPN via
processPaymentNotification
and thenredirectOrExit
. Both of these functions are OUTSIDE of the session sogetStoredUrl
inredirectOrExit
fails. - Sagepay receives redirect URL via
$response->confirm($redirectUrl, $userMsg)
and redirects the users browser to that URL, but the URL is invalid because we didn't know the session key.
Session key supplied via getNotifyUrl (modified):
- CiviCRM payment submitted.
- Goes offsite to Sagepay.
- Sagepay returns via ipn (eg. civicrm/payment/ipn/146/5?qfKey=abc1234).
- CiviCRM server responds to IPN via
processPaymentNotification
and thenredirectOrExit
. Both of these functions are OUTSIDE of the session sogetStoredUrl
inredirectOrExit
fails. But we then try again, retrieving the session key from the request URL and now we have access to the session so it works. - Sagepay receives redirect URL via
$response->confirm($redirectUrl, $userMsg)
and redirects the users browser to that URL which is the correct URL because it came from the stored session.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mattwire so
$output = $response->confirm($redirectUrl, $userMsg);
only got added for SagePay and it seems like it would work if getReturnUrl were returned instead.
if (empty($redirectUrl)) { | ||
$redirectUrl = $this->getReturnSuccessUrl(CRM_Utils_Request::retrieve('qfKey', 'String')); | ||
} | ||
if (method_exists($response, 'confirm')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton For sagepay we don't seem to have a stored URL (because we are in the IPN handler?) so we need to generate one here with the session key that is passed back to us via the IPN request URL.
@@ -839,7 +839,7 @@ protected function redirectOrExit($outcome, $response) { | |||
if (empty($redirectUrl)) { | |||
$redirectUrl = $this->getReturnSuccessUrl(CRM_Utils_Request::retrieve('qfKey', 'String')); | |||
} | |||
if (method_exists($response, 'confirm')) { | |||
if ($redirectUrl && method_exists($response, 'confirm')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton The !$redirectUrl was added by you recently, but I'm not sure I understand why. In sagepay case at least it needs to have a redirectUrl in order to call those functions as that redirect URL is passed back to sagepay via the IPN handler. Uniquely(?) for sagepay the confirm/error/invalid methods do not return as it is up to the sagepay website to initiate the redirect.
if (empty($value)) { | ||
unset($cardFields[$name]); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton Is this something that's acceptable for all payment processors?
@mattwire did composer run cleanly for you? I got Loading composer repositories with package infoReading composer.json of omnipay/tests (master) Skipped branch master, Could not parse version constraint 4^: Invalid version string "4^" Updating dependencies (including require-dev) Problem 1 |
fec31b0
to
fe221bc
Compare
3c66ef3
to
cdd9733
Compare
8072646
to
8bb8101
Compare
@mattwire - ooh wish I'd seen this before - this may solve a few sagepay related issues we are having. How are you getting on with this - anything we could do to help get it over the line? Do you know if it covers all CiviCRM use cases including backend transactions / recurring etc? |
@jamienovick It's in use on quite a few sites. It supports everything the omnipay extension supports, as it's a "redirect offsite" type processor you can't do backend stuff. If you'd like to help see this in the main extension (most of it already is) you could get some resource to look at the outstanding changes and the history of comments behind it (most is to do with the redirect back to site after payment). |
Closing in favour of #147 |
This is now in use on a production site and working. However, there are a number of changes to core extension files as well as the vendor library so probably can't be merged as is!
@eileenmcnaughton If you get a chance to review any of my changes that would be appreciated!