Merge pull request #5580 from eileenmcnaughton/CRM-16247
[civicrm-core.git] / CRM / Core / Payment.php
index 187f1c67eaf28d8d026647935b93c35f77f16a3d..2b665249af241e9c7c5d3ecfbc5b87835724720b 100644 (file)
@@ -3,7 +3,7 @@
  +--------------------------------------------------------------------+
  | CiviCRM version 4.6                                                |
  +--------------------------------------------------------------------+
- | Copyright CiviCRM LLC (c) 2004-2014                                |
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
  +--------------------------------------------------------------------+
  | This file is a part of CiviCRM.                                    |
  |                                                                    |
 use Civi\Payment\System;
 
 /**
+ * Class CRM_Core_Payment.
  *
- * @package CRM
- * @copyright CiviCRM LLC (c) 2004-2014
- * $Id$
+ * This class is the main class for the payment processor subsystem.
  *
+ * It is the parent class for payment processors. It also holds some IPN related functions
+ * that need to be moved. In particular handlePaymentMethod should be moved to a factory class.
  */
 abstract class CRM_Core_Payment {
 
@@ -70,7 +71,8 @@ abstract class CRM_Core_Payment {
   protected $_paymentProcessor;
 
   /**
-   * Singleton function used to manage this object
+   * Singleton function used to manage this object.
+   *
    * We will migrate to calling Civi\Payment\System::singleton()->getByProcessor($paymentProcessor)
    * & Civi\Payment\System::singleton()->getById($paymentProcessor) directly as the main access methods & work
    * to remove this function all together
@@ -100,8 +102,12 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * @param array $params
+   * Log payment notification message to forensic system log.
+   *
    * @todo move to factory class \Civi\Payment\System (or similar)
+   *
+   * @param array $params
+   *
    * @return mixed
    */
   public static function logPaymentNotification($params) {
@@ -118,7 +124,12 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Check if capability is supported
+   * Check if capability is supported.
+   *
+   * Capabilities have a one to one relationship with capability-related functions on this class.
+   *
+   * Payment processor classes should over-ride the capability-specific function rather than this one.
+   *
    * @param string $capability
    *   E.g BackOffice, LiveMode, FutureRecurStartDate.
    *
@@ -133,8 +144,13 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Are back office payments supported - e.g paypal standard won't permit you to enter a credit card associated with someone else's login
-   * The intention is to support off-site (other than paypal) & direct debit but that is not all working yet so to reach a 'stable' point we disable
+   * Are back office payments supported.
+   *
+   * e.g paypal standard won't permit you to enter a credit card associated
+   * with someone else's login.
+   * The intention is to support off-site (other than paypal) & direct debit but that is not all working yet so to
+   * reach a 'stable' point we disable.
+   *
    * @return bool
    */
   protected function supportsBackOffice() {
@@ -147,7 +163,8 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Are live payments supported - e.g dummy doesn't support this
+   * Are live payments supported - e.g dummy doesn't support this.
+   *
    * @return bool
    */
   protected function supportsLiveMode() {
@@ -155,7 +172,8 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Are test payments supported
+   * Are test payments supported.
+   *
    * @return bool
    */
   protected function supportsTestMode() {
@@ -163,8 +181,10 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Should the first payment date be configurable when setting up back office recurring payments
+   * Should the first payment date be configurable when setting up back office recurring payments.
+   *
    * We set this to false for historical consistency but in fact most new processors use tokens for recurring and can support this
+   *
    * @return bool
    */
   protected function supportsFutureRecurStartDate() {
@@ -172,8 +192,46 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Setter for the payment form that wants to use the processor
+   * Default payment instrument validation.
+   *
+   * Implement the usual Luhn algorithm via a static function in the CRM_Core_Payment_Form if it's a credit card
+   * Not a static function, because I need to check for payment_type.
+   *
+   * @param array $values
+   * @param array $errors
+   */
+  public function validatePaymentInstrument($values, &$errors) {
+    if ($this->_paymentProcessor['payment_type'] == 1) {
+      CRM_Core_Payment_Form::validateCreditCard($values, $errors);
+    }
+  }
+
+  /**
+   * Getter for the payment processor.
+   *
+   * The payment processor array is based on the civicrm_payment_processor table entry.
+   *
+   * @return array
+   *   Payment processor array.
+   */
+  public function getPaymentProcessor() {
+    return $this->_paymentProcessor;
+  }
+
+  /**
+   * Setter for the payment processor.
+   *
+   * @param array $processor
+   */
+  public function setPaymentProcessor($processor) {
+    $this->_paymentProcessor = $processor;
+  }
+
+  /**
+   * Setter for the payment form that wants to use the processor.
+   *
    * @deprecated
+   *
    * @param CRM_Core_Form $paymentForm
    */
   public function setForm(&$paymentForm) {
@@ -181,7 +239,7 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Getter for payment form that is using the processor
+   * Getter for payment form that is using the processor.
    * @deprecated
    * @return CRM_Core_Form
    *   A form object
@@ -191,8 +249,10 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Getter for accessing member vars
+   * Getter for accessing member vars.
+   *
    * @todo believe this is unused
+   *
    * @param string $name
    *
    * @return null
@@ -202,7 +262,7 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Get name for the payment information type
+   * Get name for the payment information type.
    * @todo - use option group + name field (like Omnipay does)
    * @return string
    */
@@ -211,7 +271,7 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Get label for the payment information type
+   * Get label for the payment information type.
    * @todo - use option group + labels (like Omnipay does)
    * @return string
    */
@@ -220,7 +280,7 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Get array of fields that should be displayed on the payment form
+   * Get array of fields that should be displayed on the payment form.
    * @todo make payment type an option value & use it in the function name - currently on debit & credit card work
    * @return array
    * @throws CiviCRM_API3_Exception
@@ -233,7 +293,7 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Get array of fields that should be displayed on the payment form for credit cards
+   * Get array of fields that should be displayed on the payment form for credit cards.
    *
    * @return array
    */
@@ -247,7 +307,7 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Get array of fields that should be displayed on the payment form for direct debits
+   * Get array of fields that should be displayed on the payment form for direct debits.
    *
    * @return array
    */
@@ -261,7 +321,8 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * Return an array of all the details about the fields potentially required for payment fields
+   * Return an array of all the details about the fields potentially required for payment fields.
+   *
    * Only those determined by getPaymentFormFields will actually be assigned to the form
    *
    * @return array
@@ -280,6 +341,7 @@ abstract class CRM_Core_Payment {
           'size' => 20,
           'maxlength' => 20,
           'autocomplete' => 'off',
+          'class' => 'creditcard',
         ),
         'is_required' => TRUE,
       ),
@@ -398,8 +460,11 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * This function collects all the information from a web/api form and invokes
-   * the relevant payment processor specific functions to perform the transaction
+   * Calling this from outside the payment subsystem is deprecated - use doPayment.
+   *
+   * Does a server to server payment transaction.
+   *
+   * Note that doPayment will throw an exception so the code may need to be modified
    *
    * @param array $params
    *   Assoc array of input parameters for this transaction.
@@ -411,8 +476,10 @@ abstract class CRM_Core_Payment {
   abstract protected function doDirectPayment(&$params);
 
   /**
-   * Process payment - this function wraps around both doTransferPayment and doDirectPayment
-   * it ensures an exception is thrown & moves some of this logic out of the form layer and makes the forms more agnostic
+   * Process payment - this function wraps around both doTransferPayment and doDirectPayment.
+   *
+   * The function ensures an exception is thrown & moves some of this logic out of the form layer and makes the forms
+   * more agnostic.
    *
    * @param array $params
    *
@@ -422,7 +489,7 @@ abstract class CRM_Core_Payment {
    *   (modified)
    * @throws CRM_Core_Exception
    */
-  public function doPayment(&$params, $component) {
+  public function doPayment(&$params, $component = 'contribute') {
     if ($this->_paymentProcessor['billing_mode'] == 4) {
       $result = $this->doTransferCheckout($params, $component);
     }
@@ -437,7 +504,7 @@ abstract class CRM_Core_Payment {
   }
 
   /**
-   * This function checks to see if we have the right config values
+   * This function checks to see if we have the right config values.
    *
    * @return string
    *   the error message if any
@@ -445,8 +512,12 @@ abstract class CRM_Core_Payment {
   abstract protected function checkConfig();
 
   /**
+   * Redirect for paypal.
+   *
+   * @todo move to paypal class or remove
+   *
    * @param $paymentProcessor
-   * @todo move to paypal class or remover
+   *
    * @return bool
    */
   public static function paypalRedirect(&$paymentProcessor) {
@@ -466,6 +537,10 @@ abstract class CRM_Core_Payment {
   }
 
   /**
+   * Handle incoming payment notification.
+   *
+   * IPNs, also called silent posts are notifications of payment outcomes or activity on an external site.
+   *
    * @todo move to0 \Civi\Payment\System factory method
    * Page callback for civicrm/payment/ipn
    */
@@ -478,16 +553,21 @@ abstract class CRM_Core_Payment {
         'mode' => @$_GET['mode'],
       )
     );
+    CRM_Utils_System::civiExit();
   }
 
   /**
-   * Payment callback handler. The processor_name or processor_id is passed in.
+   * Payment callback handler.
+   *
+   * The processor_name or processor_id is passed in.
    * Note that processor_id is more reliable as one site may have more than one instance of a
    * processor & ideally the processor will be validating the results
    * Load requested payment processor and call that processor's handle<$method> method
-   * @todo move to0 \Civi\Payment\System factory method
    *
-   * @param $method
+   * @todo move to \Civi\Payment\System factory method
+   *
+   * @param string $method
+   *   'PaymentNotification' or 'PaymentCron'
    * @param array $params
    */
   public static function handlePaymentMethod($method, $params = array()) {
@@ -496,33 +576,33 @@ abstract class CRM_Core_Payment {
     }
     self::logPaymentNotification($params);
 
-    // Query db for processor ..
-    $mode = @$params['mode'];
-
     $sql = "SELECT ppt.class_name, ppt.name as processor_name, pp.id AS processor_id
               FROM civicrm_payment_processor_type ppt
         INNER JOIN civicrm_payment_processor pp
                 ON pp.payment_processor_type_id = ppt.id
-               AND pp.is_active
-               AND pp.is_test = %1";
-    $args[1] = array($mode == 'test' ? 1 : 0, 'Integer');
+               AND pp.is_active";
 
     if (isset($params['processor_id'])) {
       $sql .= " WHERE pp.id = %2";
       $args[2] = array($params['processor_id'], 'Integer');
-      $notfound = "No active instances of payment processor ID#'{$params['processor_id']}'  were found.";
+      $notFound = "No active instances of payment processor ID#'{$params['processor_id']}'  were found.";
     }
     else {
-      $sql .= " WHERE ppt.name = %2";
+      // This is called when processor_name is passed - passing processor_id instead is recommended.
+      $sql .= " WHERE ppt.name = %2 AND pp.is_test = %1";
+      $args[1] = array(
+        (CRM_Utils_Array::value('mode', $params) == 'test') ? 1 : 0,
+        'Integer',
+      );
       $args[2] = array($params['processor_name'], 'String');
-      $notfound = "No active instances of the '{$params['processor_name']}' payment processor were found.";
+      $notFound = "No active instances of the '{$params['processor_name']}' payment processor were found.";
     }
 
     $dao = CRM_Core_DAO::executeQuery($sql, $args);
 
-    // Check whether we found anything at all ..
+    // Check whether we found anything at all.
     if (!$dao->N) {
-      CRM_Core_Error::fatal($notfound);
+      CRM_Core_Error::fatal($notFound);
     }
 
     $method = 'handle' . $method;
@@ -546,16 +626,13 @@ abstract class CRM_Core_Payment {
         }
       }
 
-      $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($dao->processor_id, $mode);
+      $processorInstance = Civi\Payment\System::singleton()->getById($dao->processor_id);
 
       // Should never be empty - we already established this processor_id exists and is active.
-      if (empty($paymentProcessor)) {
+      if (empty($processorInstance)) {
         continue;
       }
 
-      // Instantiate PP
-      $processorInstance = new $paymentClass($mode, $paymentProcessor);
-
       // Does PP implement this method, and can we call it?
       if (!method_exists($processorInstance, $method) ||
         !is_callable(array($processorInstance, $method))
@@ -576,11 +653,6 @@ abstract class CRM_Core_Payment {
         "$method method is unsupported in legacy payment processors."
       );
     }
-
-    // Exit here on web requests, allowing just the plain text response to be echoed
-    if ($method == 'handlePaymentNotification') {
-      CRM_Utils_System::civiExit();
-    }
   }
 
   /**
@@ -596,6 +668,8 @@ abstract class CRM_Core_Payment {
   }
 
   /**
+   * Get url for users to manage this recurring contribution for this processor.
+   *
    * @param int $entityID
    * @param null $entity
    * @param string $action