Nuance cancel options for processors.
authoreileen <emcnaughton@wikimedia.org>
Wed, 22 Apr 2020 23:08:05 +0000 (11:08 +1200)
committerMatthew Wire <mjw@mjwconsult.co.uk>
Mon, 1 Jun 2020 10:03:36 +0000 (11:03 +0100)
This PR addresses an issue where we have insufficient nuance on processor capabilities for cancel.

There are 2 behaviours that we are trying to support with one capability
1) can this processor cope with us cancelling a recurring donation within CiviCRM
2) should we present the option of whether to or not to a user. This option is appropriate
when the processor manages the schedule (e.g Paypal) as the user may just be updating Civi to
match reality. However, for token driven processors it generally doesnt' apply.

This PR adds a new overridable capability - 'supportsOptionalCancelRecurring' which is the same as
supportsCancelRecurring by default. For the manual processor it is FALSE.

Call cancelRecurring for all cancels.

In almost all cases this will be no change as processors would have to implement the new-ish
doCancelRecurring themselves to have any change

CRM/Contribute/Form/CancelSubscription.php
CRM/Core/Payment.php
CRM/Core/Payment/Manual.php
Civi/Payment/PropertyBag.php
templates/CRM/Contribute/Form/CancelSubscription.tpl

index 831b5e9b551960e24e1ada70ca21bbc3e1574781..367336d14d095f9db518b976e57245a9d9e5c746 100644 (file)
@@ -133,8 +133,7 @@ class CRM_Contribute_Form_CancelSubscription extends CRM_Contribute_Form_Contrib
   public function buildQuickForm() {
     $this->buildQuickEntityForm();
     // Determine if we can cancel recurring contribution via API with this processor
-    $cancelSupported = $this->_paymentProcessorObj->supports('CancelRecurring');
-    if ($cancelSupported) {
+    if ($this->_paymentProcessorObj->supports('CancelRecurringNotifyOptional')) {
       $searchRange = [];
       $searchRange[] = $this->createElement('radio', NULL, NULL, ts('Yes'), '1');
       $searchRange[] = $this->createElement('radio', NULL, NULL, ts('No'), '0');
@@ -149,7 +148,6 @@ class CRM_Contribute_Form_CancelSubscription extends CRM_Contribute_Form_Contrib
     else {
       $this->assign('cancelRecurNotSupportedText', $this->_paymentProcessorObj->getText('cancelRecurNotSupportedText', []));
     }
-    $this->assign('cancelSupported', $cancelSupported);
 
     if (!empty($this->_donorEmail)) {
       $this->add('checkbox', 'is_notify', ts('Notify Contributor?') . " ({$this->_donorEmail})");
@@ -195,6 +193,8 @@ class CRM_Contribute_Form_CancelSubscription extends CRM_Contribute_Form_Contrib
 
   /**
    * Process the form submission.
+   *
+   * @throws \CRM_Core_Exception
    */
   public function postProcess() {
     $message = NULL;
@@ -212,16 +212,15 @@ class CRM_Contribute_Form_CancelSubscription extends CRM_Contribute_Form_Contrib
       }
     }
 
-    if (CRM_Utils_Array::value('send_cancel_request', $params) == 1) {
-      try {
-        $propertyBag = new PropertyBag();
-        $propertyBag->setContributionRecurID($this->getSubscriptionDetails()->recur_id);
-        $propertyBag->setRecurProcessorID($this->getSubscriptionDetails()->subscription_id);
-        $message = $this->_paymentProcessorObj->doCancelRecurring($propertyBag)['message'];
-      }
-      catch (\Civi\Payment\Exception\PaymentProcessorException $e) {
-        CRM_Core_Error::statusBounce($e->getMessage());
-      }
+    try {
+      $propertyBag = new PropertyBag();
+      $propertyBag->setIsNotifyProcessorOnCancelRecur(!empty($params['send_cancel_request']));
+      $propertyBag->setContributionRecurID($this->getSubscriptionDetails()->recur_id);
+      $propertyBag->setRecurProcessorID($this->getSubscriptionDetails()->subscription_id);
+      $message = $this->_paymentProcessorObj->doCancelRecurring($propertyBag)['message'];
+    }
+    catch (\Civi\Payment\Exception\PaymentProcessorException $e) {
+      CRM_Core_Error::statusBounce($e->getMessage());
     }
 
     if ($cancelSubscription) {
index 288359d4e5e5e710820932c2c676b8bbe3eae040..5b6a477cb19b6b4b1f65bb4af7d4b2cb85fb7f36 100644 (file)
@@ -399,6 +399,19 @@ abstract class CRM_Core_Payment {
     return method_exists(CRM_Utils_System::getClassName($this), 'cancelSubscription');
   }
 
+  /**
+   * Does the processor support the user having a choice as to whether to cancel the recurring with the processor?
+   *
+   * If this returns TRUE then there will be an option to send a cancellation request in the cancellation form.
+   *
+   * This would normally be false for processors where CiviCRM maintains the schedule.
+   *
+   * @return bool
+   */
+  protected function supportsCancelRecurringNotifyOptional() {
+    return $this->supportsCancelRecurring();
+  }
+
   /**
    * Does this processor support pre-approval.
    *
@@ -615,7 +628,10 @@ abstract class CRM_Core_Payment {
         }
 
       case 'cancelRecurNotSupportedText':
-        return ts('Automatic cancellation is not supported for this payment processor. You or the contributor will need to manually cancel this recurring contribution using the payment processor website.');
+        if (!$this->supportsCancelRecurring()) {
+          return ts('Automatic cancellation is not supported for this payment processor. You or the contributor will need to manually cancel this recurring contribution using the payment processor website.');
+        }
+        return '';
 
     }
     CRM_Core_Error::deprecatedFunctionWarning('Calls to getText must use a supported method');
@@ -1382,7 +1398,8 @@ abstract class CRM_Core_Payment {
    * @throws \Civi\Payment\Exception\PaymentProcessorException
    */
   public function doCancelRecurring(PropertyBag $propertyBag) {
-    if (method_exists($this, 'cancelSubscription')) {
+    if (method_exists($this, 'cancelSubscription')
+    && $propertyBag->getIsNotifyProcessorOnCancelRecur()) {
       $message = NULL;
       if ($this->cancelSubscription($message, $propertyBag)) {
         return ['message' => $message];
index 302756419984b8573571d58a2806f67496ccba16..0a311b48da916fbfd04420dec9c98312f15e220f 100644 (file)
@@ -196,6 +196,19 @@ class CRM_Core_Payment_Manual extends CRM_Core_Payment {
     return TRUE;
   }
 
+  /**
+   * Does the processor support the user having a choice as to whether to cancel the recurring with the processor?
+   *
+   * If this returns TRUE then there will be an option to send a cancellation request in the cancellation form.
+   *
+   * This would normally be false for processors where CiviCRM maintains the schedule.
+   *
+   * @return bool
+   */
+  protected function supportsCancelRecurringNotifyOptional() {
+    return FALSE;
+  }
+
   /**
    * Are back office payments supported.
    *
index 4423fce5eca8d4d89e6b3c81b70859652a604207..3fc564183fdd2aa6b83dd3f1cede4d634a44e9a4 100644 (file)
@@ -72,6 +72,7 @@ class PropertyBag implements \ArrayAccess {
     'transactionID'               => TRUE,
     'transaction_id'              => 'transactionID',
     'trxnResultCode'              => TRUE,
+    'isNotifyProcessorOnCancelRecur' => TRUE,
   ];
 
   /**
@@ -480,6 +481,8 @@ class PropertyBag implements \ArrayAccess {
    *
    * @param string $input
    * @param string $label e.g. 'default'
+   *
+   * @return \Civi\Payment\PropertyBag
    */
   public function setBillingCity($input, $label = 'default') {
     return $this->set('billingCity', $label, (string) $input);
@@ -794,6 +797,35 @@ class PropertyBag implements \ArrayAccess {
     return $this->set('isRecur', $label, (bool) $isRecur);
   }
 
+  /**
+   * Set whether the user has selected to notify the processor of a cancellation request.
+   *
+   * When cancelling the user may be presented with an option to notify the processor. The payment
+   * processor can take their response, if present, into account.
+   *
+   * @param bool $value
+   * @param string $label e.g. 'default'
+   *
+   * @return \Civi\Payment\PropertyBag
+   */
+  public function setIsNotifyProcessorOnCancelRecur($value, $label = 'default') {
+    return $this->set('isNotifyProcessorOnCancelRecur', $label, (bool) $value);
+  }
+
+  /**
+   * Get whether the user has selected to notify the processor of a cancellation request.
+   *
+   * When cancelling the user may be presented with an option to notify the processor. The payment
+   * processor can take their response, if present, into account.
+   *
+   * @param string $label e.g. 'default'
+   *
+   * @return \Civi\Payment\PropertyBag
+   */
+  public function getIsNotifyProcessorOnCancelRecur($label = 'default') {
+    return $this->get('isNotifyProcessorOnCancelRecur', $label);
+  }
+
   /**
    * Last name
    *
index 74e5e2f186621f32b9a11c0bd64c901ccec90dea..c193513b621c196485a25791a05b835b89032e9b 100644 (file)
@@ -12,7 +12,7 @@
 <div class="help">
   <div class="icon inform-icon"></div>&nbsp;
   {$cancelRecurDetailText}
-  {if !$cancelSupported}
+  {if $cancelRecurNotSupportedText}
     <div class="status-warning">{$cancelRecurNotSupportedText}</div>
   {/if}
 </div>