CRM-16808 standardisation of paypal express
authorEileen McNaughton <eileen@fuzion.co.nz>
Sun, 12 Jul 2015 05:26:24 +0000 (17:26 +1200)
committerEileen McNaughton <eileen@fuzion.co.nz>
Sun, 12 Jul 2015 05:26:24 +0000 (17:26 +1200)
CRM/Contribute/BAO/Contribution/Utils.php
CRM/Contribute/Form/Contribution/Confirm.php
CRM/Contribute/Form/Contribution/Main.php
CRM/Core/Payment.php
CRM/Core/Payment/PayPalImpl.php

index 503480b80861a9eb860dae5b2271881ee6cf67e1..a92cda79435c14592497d46caf1da9e4dd2f695a 100644 (file)
@@ -184,29 +184,17 @@ class CRM_Contribute_BAO_Contribution_Utils {
         }
       }
     }
-    elseif ($form->_contributeMode == 'express') {
-      if ($form->_values['is_monetary'] && $form->_amount > 0.0) {
-        // determine if express + recurring and direct accordingly
-        if (!empty($paymentParams['is_recur']) && $paymentParams['is_recur'] == 1) {
-          if (is_object($payment)) {
-            $result = $payment->createRecurringPayments($paymentParams);
-          }
-          else {
-            CRM_Core_Error::fatal($paymentObjError);
-          }
+
+    elseif ($isPaymentTransaction && $form->_contributeMode) {
+      if ($form->_contributeMode == 'express' && !empty($paymentParams['is_recur']) && $paymentParams['is_recur'] == 1) {
+        if (is_object($payment)) {
+          $result = $payment->createRecurringPayments($paymentParams);
         }
         else {
-          if (is_object($payment)) {
-            $result = $payment->doExpressCheckout($paymentParams);
-          }
-          else {
-            CRM_Core_Error::fatal($paymentObjError);
-          }
+          CRM_Core_Error::fatal($paymentObjError);
         }
       }
-    }
-    elseif ($isPaymentTransaction) {
-      if ($form->_contributeMode == 'direct') {
+      else {
         $paymentParams['contactID'] = $contactID;
 
         // Fix for CRM-14354. If the membership is recurring, don't create a
@@ -296,7 +284,7 @@ class CRM_Contribute_BAO_Contribution_Utils {
     }
     //Do not send an email if Recurring contribution is done via Direct Mode
     //We will send email once the IPN is received.
-    if ($form->_contributeMode == 'direct') {
+    if (!$isPayLater) {
       return $result;
     }
 
index dcd9cce58f6fa462359c1b5479f6f34172747e76..6506549f7a6fa3e457a56e92dd0145f34b7689ed 100644 (file)
@@ -916,7 +916,6 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
   ) {
     $transaction = new CRM_Core_Transaction();
     $contribSoftContactId = $addressID = NULL;
-    $contributeMode = $form->_contributeMode;
     $isMonetary = !empty($form->_values['is_monetary']);
     $isEmailReceipt = !empty($form->_values['is_email_receipt']);
     // How do these vary from params? These are currently passed to
index f1ca8f54d2af5b128770cf0d3c3fe819085a5d04..ac7490095fa24e1aec85c4747f38e1f6d8acec13 100644 (file)
@@ -1293,82 +1293,100 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
     // generate and set an invoiceID for this transaction
     $invoiceID = md5(uniqid(rand(), TRUE));
     $this->set('invoiceID', $invoiceID);
+    $params['invoiceID'] = $invoiceID;
+    $params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']);
 
     // required only if is_monetary and valid positive amount
     if ($this->_values['is_monetary'] &&
       is_array($this->_paymentProcessor) &&
       ((float ) $params['amount'] > 0.0 || $memFee > 0.0)
     ) {
-
-      // default mode is direct
-      $this->set('contributeMode', 'direct');
+      $this->setContributeMode();
 
       if ($this->_paymentProcessor &&
-        $this->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON
+        $this->_paymentProcessor['object']->supportsPreApproval()
       ) {
-        //get the button name
-        $buttonName = $this->controller->getButtonName();
-        if (in_array($buttonName,
-            array($this->_expressButtonName, $this->_expressButtonName . '_x', $this->_expressButtonName . '_y')
-          ) && empty($params['is_pay_later'])
-        ) {
-          $this->handlePaypalExpress($invoiceID, $params);
+         $this->handlePreApproval($params);
         }
       }
-      elseif ($this->_paymentProcessor &&
-        $this->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_NOTIFY
-      ) {
-        $this->set('contributeMode', 'notify');
-      }
-    }
 
-    // should we skip the confirm page?
     if (empty($this->_values['is_confirm_enabled'])) {
-      // call the post process hook for the main page before we switch to confirm
-      $this->postProcessHook();
+      $this->skipToThankYouPage();
+    }
 
-      // build the confirm page
-      $confirmForm = &$this->controller->_pages['Confirm'];
-      $confirmForm->preProcess();
-      $confirmForm->buildQuickForm();
+  }
 
-      // the confirmation page is valid
-      $data = &$this->controller->container();
-      $data['valid']['Confirm'] = 1;
+  /**
+   * Assign the billing mode to the template.
+   *
+   * This is required for legacy support for contributeMode in templates.
+   *
+   * The goal is to remove this parameter & use more relevant parameters.
+   */
+  protected function setContributeMode() {
+    switch ($this->_paymentProcessor['billing_mode']) {
+      case CRM_Core_Payment::BILLING_MODE_FORM:
+        $this->set('contributeMode', 'direct');
+        break;
 
-      // confirm the contribution
-      // mainProcess calls the hook also
-      $confirmForm->mainProcess();
-      $qfKey = $this->controller->_key;
+      case CRM_Core_Payment::BILLING_MODE_BUTTON:
+        $this->set('contributeMode', 'express');
+        break;
 
-      // redirect to thank you page
-      CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_ThankYou_display=1&qfKey=$qfKey", TRUE, NULL, FALSE));
+      case CRM_Core_Payment::BILLING_MODE_NOTIFY:
+        $this->set('contributeMode', 'notify');
+        break;
     }
 
   }
 
   /**
-   * @param $invoiceID
-   * @param $params
+   * Handle pre approval for processors.
+   *
+   * This fits with the flow where a pre-approval is done and then confirmed in the next stage when confirm is hit.
    *
-   * @return mixed
+   * This applies to processors that
+   * @param array $params
    */
-  protected function handlePaypalExpress($invoiceID, $params) {
-    $this->set('contributeMode', 'express');
-
-    $params['invoiceID'] = $invoiceID;
-    $params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']);
-
-    $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
-    $token = $payment->setExpressCheckout($params);
-    if (is_a($token, 'CRM_Core_Error')) {
-      CRM_Core_Error::displaySessionError($token);
+  protected function handlePreApproval(&$params) {
+    try {
+      $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
+      $result = $payment->doPreApproval($params);
+    }
+    catch (\Civi\Payment\Exception\PaymentProcessorException $e) {
+      CRM_Core_Error::displaySessionError($e->getMessage());
       CRM_Utils_System::redirect($params['cancelURL']);
     }
 
-    $this->set('token', $token);
-    $paymentURL = $this->_paymentProcessor['url_site'] . "/cgi-bin/webscr?cmd=_express-checkout&token=$token";
-    CRM_Utils_System::redirect($paymentURL);
+    $this->set('pre_approval_parameters', $result['pre_approval_parameters']);
+    if (!empty($result['redirect_url'])) {
+      CRM_Utils_System::redirect($result['redirect_url']);
+    }
+  }
+
+  /**
+   * Process confirm function and pass browser to the thank you page.
+   */
+  protected function skipToThankYouPage() {
+    // call the post process hook for the main page before we switch to confirm
+    $this->postProcessHook();
+
+    // build the confirm page
+    $confirmForm = &$this->controller->_pages['Confirm'];
+    $confirmForm->preProcess();
+    $confirmForm->buildQuickForm();
+
+    // the confirmation page is valid
+    $data = &$this->controller->container();
+    $data['valid']['Confirm'] = 1;
+
+    // confirm the contribution
+    // mainProcess calls the hook also
+    $confirmForm->mainProcess();
+    $qfKey = $this->controller->_key;
+
+    // redirect to thank you page
+    CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_ThankYou_display=1&qfKey=$qfKey", TRUE, NULL, FALSE));
   }
 
 }
index 12cb1e904889980f04b03043c0d422e835e2bd39..9410f7f16857362ed360e6f990ed11d28f20577a 100644 (file)
@@ -222,6 +222,35 @@ abstract class CRM_Core_Payment {
     return FALSE;
   }
 
+  /**
+   * Does this processor support pre-approval.
+   *
+   * This would generally look like a redirect to enter credentials which can then be used in a later payment call.
+   *
+   * Currently Paypal express supports this, with a redirect to paypal after the 'Main' form is submitted in the
+   * contribution page. This token can then be processed at the confirm phase. Although this flow 'looks' like the
+   * 'notify' flow a key difference is that in the notify flow they don't have to return but in this flow they do.
+   *
+   * @return bool
+   */
+  protected function supportsPreApproval() {
+    return FALSE;
+  }
+
+  /**
+   * Function to action pre-approval if supported
+   *
+   * @param array $params
+   *   Parameters from the form
+   * @param string component
+   *  contribution or event.
+   *
+   * This function returns an array which should contain
+   *   - pre_approval_parameters (this will be stored on the calling form & available later)
+   *   - redirect_url (if set the browser will be redirected to this.
+   */
+  protected function doPreApproval($params) {}
+
   /**
    * Default payment instrument validation.
    *
@@ -631,13 +660,16 @@ abstract class CRM_Core_Payment {
    * The function ensures an exception is thrown & moves some of this logic out of the form layer and makes the forms
    * more agnostic.
    *
-   * Payment processors should set contribution_status_id. This function adds some historical defaults ie. the
+   * Payment processors should set payment_status_id. This function adds some historical defaults ie. the
    * assumption that if a 'doDirectPayment' processors comes back it completed the transaction & in fact
    * doTransferCheckout would not traditionally come back.
    *
    * doDirectPayment does not do an immediate payment for Authorize.net or Paypal so the default is assumed
    * to be Pending.
    *
+   * Once this function is fully rolled out then it will be preferred for processors to throw exceptions than to
+   * return Error objects
+   *
    * @param array $params
    *
    * @param string $component
@@ -656,7 +688,12 @@ abstract class CRM_Core_Payment {
       }
     }
     else {
-      $result = $this->doDirectPayment($params, $component);
+      if ($this->_paymentProcessor['billing_mode'] ==1) {
+        $result = $this->doDirectPayment($params, $component);
+      }
+      else {
+        $result = $this->doExpressCheckout($params);
+      }
       if (is_array($result) && !isset($result['payment_status_id'])) {
         if (!empty($params['is_recur'])) {
           // See comment block.
index 87e04c1de0c44e3a4507c4a35fd7ace5f80be4b5..7cc8755026538f3caf43a50fd68ef0380e551e38 100644 (file)
@@ -82,6 +82,24 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment {
     return FALSE;
   }
 
+  /**
+   * Does this processor support pre-approval.
+   *
+   * This would generally look like a redirect to enter credentials which can then be used in a later payment call.
+   *
+   * Currently Paypal express supports this, with a redirect to paypal after the 'Main' form is submitted in the
+   * contribution page. This token can then be processed at the confirm phase. Although this flow 'looks' like the
+   * 'notify' flow a key difference is that in the notify flow they don't have to return but in this flow they do.
+   *
+   * @return bool
+   */
+  protected function supportsPreApproval() {
+    if ($this->_processorName == ts('PayPal_Express')) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
   /**
    * Opportunity for the payment processor to override the entire form build.
    *
@@ -598,6 +616,24 @@ class CRM_Core_Payment_PayPalImpl extends CRM_Core_Payment {
     return FALSE;
   }
 
+ /**
+ * Function to action pre-approval if supported
+ *
+ * @param array $params
+ *   Parameters from the form
+ *
+ * @return array
+ *   - pre_approval_parameters (this will be stored on the calling form & available later)
+ *   - redirect_url (if set the browser will be redirected to this.
+ */
+  public function doPreApproval(&$params) {
+    $token = $this->setExpressCheckOut($params);
+    return array(
+      'pre_approval_parameters' => array('token' => $token),
+      'redirect_url' => $this->_paymentProcessor['url_site'] . "/cgi-bin/webscr?cmd=_express-checkout&token=$token",
+    );
+  }
+
   /**
    * @param array $params
    * @param string $component