-
-
Notifications
You must be signed in to change notification settings - Fork 825
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
We should give user feedback in case of a Payment Processor Exception #15676
We should give user feedback in case of a Payment Processor Exception #15676
Conversation
(Standard links)
|
@artfulrobot +1 for concept - however, can't we do away with the |
@artfulrobot I like where you've put the try-catch block. I think we are trying to get away from the CRM_Core_Error & can just call
in the next line and the remove the CRM_Core_Error handling that follows |
oh snap - @mattwire said more or less the same thing. In general statusBounce is setMessage + return to the previous page |
@mattwire @eileenmcnaughton yes I agree - I had to look up Much cleaner to do away with it, but I don't know which other code uses it so didn't want to break anything there. |
How does that ↑ look? |
@artfulrobot I think we should get rid of the |
e91985f
to
7ee8eb3
Compare
@artfulrobot Having asked you to remove |
I have thoughts about this but have been wanting to get the @artfulrobot getters & setters resolved before getting into this one |
ok that's back in for now. |
@artfulrobot - needs rebase, then I'm happy for it to be merged. @eileenmcnaughton comments make sense but do not hold up this PR. |
2b38ecf
to
368bd76
Compare
@mattwire I've rebased. |
368bd76
to
4ae44cc
Compare
I've been dithering on this - an external function could be calling cancelSubscription & handling an error. However, looking into this there are already scenarios where exceptions are thrown - notable Paypal - so it makes sense to be consistent. In addition I would note that this is much less heavily used that the doPayment function & possibly not called at all in extensions However, I'd at least like to get thoughts on this alternate approach which starts to deprecate cancelSubscription & handles the in between @mattwire @artfulrobot |
@eileenmcnaughton So have I got this right? The point of this PR is:
It's a bit tricky because processors might need to do this in reverse - i.e. they write a nice new It's a nice thing to do though - I'm in favour of cleaner signatures, clearer results. With that in mind:
Basically I'm not sure whether all processors can and all processors have and all processors do store a unique value in that field. I recently updated GoCardless to use that field instead of Also, I think we need to document the result. I like functions which are We don't need to return a bool, because we should throw an exception if it fails. I 'm unclear which processors return FALSE (and not an error) but I'd suggest looking for that and throwing an exception. e.g.
|
@artfulrobot I'm completely in favour of adding the recur ID into the params array - it's really stupid that it doesn't get passed through - I just wanted it in it's own PR, and it should include a unit test to make sure we don't lose it again. |
@artfulrobot right - there is always migration involved if you add new methods & that isn't instant. On the other hand I'm just a little worried about changing the behaviour of an existing function AND that function is problematic because of the function_exists nastiness around it so it would be nice to migrate off it. I agree with having something sensible passed in. Unless we are serious about renaming the processor_id field (hint I think that could be painful) I think we might as well pass in ['processor_id' => x, 'contribution_recur_id' => y]; & the default doCancelRecurring can ensure those are set |
I don't think the OK, why don't we use a propertyBag for this?
That way we give yet more examples of use, and we get a mild bit of validation added for good measure. Strictly speaking, having the two is redundancy though. I'd prefer to stick with just one (recur ID seems more sensible to me as we know it is guaranteed to eb unique). |
@artfulrobot the problem is that the isSupportsCancelRecurring is tied to the existence of a function that isn't needed. ie. you don't need to override doCancelRecurring for a good chunk of processors because they support it simply by recognising that the the contribution_recur record has been updated - so ideally they would only need to implement isSupportsCancelRecurring & return TRUE & nothing else. Yes, I think a property bag makes sense here. Just recur ID makes sense but if it was a batch process then not discarding the value we have in order to refetch it would have a performance impact. |
@eileenmcnaughton I don't understand the cancellation (or the change process) support feature. As I understand there are two features 1.UI can offer a Cancel link when viewing a contact's recurring contribs.
These both require a payment processor service to offer an API that allows recurring subscriptions to be cancelled, and for the payment class to implements that. I'm unclear what logic determines which of these are offered. I understood that the UI checks the payment class's I understand that the default implementation of that (on Payment classes can override Sorry if I'm being thick. As a side note: I think it's also possible that whether a particular payment can be cancelled/changed depends on other factors - currently impossible since no ContribRecurs are passed into these supports functions. |
@artfulrobot so from the calling code's point of view both Eway & Paypal support cancelRecurring - that's all the calling code needs to know. However, Eway code doesn't need to do anything to support recurring because it just has a cron that charges all valid recurring profiles so it should be able to declare that if you are using Eway you can cancel recurrings but it doesn't need a 'cancelRecurring' function as it doesn't take any actual action. But we can't add a default empty cancelRecurring action to CRM_Core_Payment to handle that because then 'supportsCancelRecurring' would always exist. We CAN have a default empty doCancelRecurring though - which would get us to the point where they just need to implement the one thing - return TRUE when supportsCancelRecurring is called |
@eileenmcnaughton thanks for the explanation. So at this point, eway has to do this to enable cancellations through the UI
because then the default supportsCancelRecurring works, but the doing function does nothing. The problem is that we can't include a stub signature of cancelSubscription in Soon we will require payment classes that support cancellations to override So for a processor like eway, it will have to:
And for a processor like GoCardless
In the far future, once nobody's running pre 5.2[?] then we can drop the cancelSubscription from processors. So...
|
No Eway would need to have
It shouldn't need to implement doCancelRecurring - we should alter the proposal above so cancelRecurring is only called if exists (& add a deprecation if it is called via doCancelRecurring to encourage people to stop calling doCancelRecurring). In core we would switch to calling doCancelRecurring in 5.22 so Eway would only need to implement that from it's 5.22 release onwards - I rely on the extension system to have people run compatible versions in general, but perhaps I'd add some transitional support in a deprecated function for a couple of months |
Opps - comment above on wrong PR - now deleted - leaving this in case of any confusion |
Regardless of the discussion above I have just looked again into the Paypal code & am feeling better about switching the expectation to be that cancelRecurring throws exceptions. Currently the paypal code will already throw an exception under some circumstances in cancelRecurring - so switching to always throwing an exception is not changing the expectation but standardising it. I'd like to keep the conversation going but I think I can merge this as 'better in than out' based on the above . |
Currently invokeAPI will return an error for a curl function and throw a processorException for an outcome other than success. In just about every instance where it is called the Core_Error is converted into an exception anyway - this can all be cleaned up as a follow up. The exceptions are doQuery - called by doPayment doDirectPayment - this is called by doPayment which converts it into an exception. Calling doDirectPayment directly was deprecated in 4.6 createRecurringPayments - called by doExpressCheckout which in already throws exceptions in other scenarios, updateSubscriptionBillingInfo - we can update the one place that calls this in core as a follow up like civicrm#15676 However, note that an exception was already thrown in all these places in the more common error scenario of the action not succeeding as opposed to the rare curl error scenario Follow ons to consider 1) introduce more nuance into PaymentProcessorExceptions - extend the exception class with PaymentProcessorConfigException, PaymentProcessingException or similar to differentiate 2) remove all the handling for invokeApi to have returned an error object. 3) fix the code that calls updateSubscriptionBillingInfo 4) ensure docs reflect that doPayment should throw an exception. AFAIK we have not yet documented other 'do' functions & I'd prefer to stdise them first
Currently invokeAPI will return an error for a curl function and throw a processorException for an outcome other than success. In just about every instance where it is called the Core_Error is converted into an exception anyway - this can all be cleaned up as a follow up. The exceptions are doQuery - called by doPayment doDirectPayment - this is called by doPayment which converts it into an exception. Calling doDirectPayment directly was deprecated in 4.6 createRecurringPayments - called by doExpressCheckout which in already throws exceptions in other scenarios, updateSubscriptionBillingInfo - we can update the one place that calls this in core as a follow up like civicrm#15676 However, note that an exception was already thrown in all these places in the more common error scenario of the action not succeeding as opposed to the rare curl error scenario Follow ons to consider 1) introduce more nuance into PaymentProcessorExceptions - extend the exception class with PaymentProcessorConfigException, PaymentProcessingException or similar to differentiate 2) remove all the handling for invokeApi to have returned an error object. 3) fix the code that calls updateSubscriptionBillingInfo 4) ensure docs reflect that doPayment should throw an exception. AFAIK we have not yet documented other 'do' functions & I'd prefer to stdise them first
Currently invokeAPI will return an error for a curl function and throw a processorException for an outcome other than success. In just about every instance where it is called the Core_Error is converted into an exception anyway - this can all be cleaned up as a follow up. The exceptions are doQuery - called by doPayment doDirectPayment - this is called by doPayment which converts it into an exception. Calling doDirectPayment directly was deprecated in 4.6 createRecurringPayments - called by doExpressCheckout which in already throws exceptions in other scenarios, updateSubscriptionBillingInfo - we can update the one place that calls this in core as a follow up like civicrm#15676 However, note that an exception was already thrown in all these places in the more common error scenario of the action not succeeding as opposed to the rare curl error scenario Follow ons to consider 1) introduce more nuance into PaymentProcessorExceptions - extend the exception class with PaymentProcessorConfigException, PaymentProcessingException or similar to differentiate 2) remove all the handling for invokeApi to have returned an error object. 3) fix the code that calls updateSubscriptionBillingInfo 4) ensure docs reflect that doPayment should throw an exception. AFAIK we have not yet documented other 'do' functions & I'd prefer to stdise them first
Currently invokeAPI will return an error for a curl function and throw a processorException for an outcome other than success. In just about every instance where it is called the Core_Error is converted into an exception anyway - this can all be cleaned up as a follow up. The exceptions are doQuery - called by doPayment doDirectPayment - this is called by doPayment which converts it into an exception. Calling doDirectPayment directly was deprecated in 4.6 createRecurringPayments - called by doExpressCheckout which in already throws exceptions in other scenarios, updateSubscriptionBillingInfo - we can update the one place that calls this in core as a follow up like civicrm#15676 However, note that an exception was already thrown in all these places in the more common error scenario of the action not succeeding as opposed to the rare curl error scenario Follow ons to consider 1) introduce more nuance into PaymentProcessorExceptions - extend the exception class with PaymentProcessorConfigException, PaymentProcessingException or similar to differentiate 2) remove all the handling for invokeApi to have returned an error object. 3) fix the code that calls updateSubscriptionBillingInfo 4) ensure docs reflect that doPayment should throw an exception. AFAIK we have not yet documented other 'do' functions & I'd prefer to stdise them first
Payment processors can throw exceptions when trying to do things like update or cancel a recurring payment. But the system expects them to return a CRM_Core_Error.
Before
no errors, modal edit forms just hang if error.
After
user should get error
Technical Details
I added a catch block for this particular exception and wrapped it up in an CRM_Core_Error.
I've not been able to test this though, hence the WIP.