| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
-*/
+ */
+
+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 {
/**
* FORM - we collect it on the same page
* BUTTON - the processor collects it and sends it back to us via some protocol
*/
- CONST
+ const
BILLING_MODE_FORM = 1,
BILLING_MODE_BUTTON = 2,
BILLING_MODE_NOTIFY = 4;
* credit card
* direct debit
* or both
- *
+ * @todo create option group - nb omnipay uses a 3rd type - transparent redirect cc
*/
- CONST
+ const
PAYMENT_TYPE_CREDIT_CARD = 1,
PAYMENT_TYPE_DIRECT_DEBIT = 2;
/**
* Subscription / Recurring payment Status
* START, END
- *
*/
- CONST
+ const
RECURRING_PAYMENT_START = 'START',
RECURRING_PAYMENT_END = 'END';
- /**
- * We only need one instance of this object. So we use the singleton
- * pattern and cache the instance in this variable
- *
- * @var object
- * @static
- */
- static private $_singleton = NULL;
-
protected $_paymentProcessor;
/**
- * @var CRM_Core_Form
- */
- protected $_paymentForm = NULL;
-
- /**
- * Singleton function used to manage this object
+ * Singleton function used to manage this object.
*
- * @param string $mode the mode of operation: live or test
- * @param array $paymentProcessor the details of the payment processor being invoked
- * @param object $paymentForm deprecated - avoid referring to this if possible. If you have to use it document why as this is scary interaction
- * @param boolean $force should we force a reload of this payment 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
*
- * @return CRM_Core_Payment
- * @static
+ * @param string $mode
+ * The mode of operation: live or test.
+ * @param array $paymentProcessor
+ * The details of the payment processor being invoked.
+ * @param object $paymentForm
+ * Deprecated - avoid referring to this if possible. If you have to use it document why as this is scary interaction.
+ * @param bool $force
+ * Should we force a reload of this payment object.
*
+ * @return CRM_Core_Payment
+ * @throws \CRM_Core_Exception
*/
- static function &singleton($mode = 'test', &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
+ public static function &singleton($mode = 'test', &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
// make sure paymentProcessor is not empty
// CRM-7424
if (empty($paymentProcessor)) {
return CRM_Core_DAO::$_nullObject;
}
-
- $cacheKey = "{$mode}_{$paymentProcessor['id']}_" . (int)isset($paymentForm);
-
- if (!isset(self::$_singleton[$cacheKey]) || $force) {
- $config = CRM_Core_Config::singleton();
- $ext = CRM_Extension_System::singleton()->getMapper();
- if ($ext->isExtensionKey($paymentProcessor['class_name'])) {
- $paymentClass = $ext->keyToClass($paymentProcessor['class_name'], 'payment');
- require_once ($ext->classToPath($paymentClass));
- }
- else {
- $paymentClass = 'CRM_Core_' . $paymentProcessor['class_name'];
- if (empty($paymentClass)) {
- throw new CRM_Core_Exception('no class provided');
- }
- require_once (str_replace('_', DIRECTORY_SEPARATOR, $paymentClass) . '.php');
- }
-
- //load the object.
- self::$_singleton[$cacheKey] = $paymentClass::singleton($mode, $paymentProcessor);
- }
-
- //load the payment form for required processor.
- //if ($paymentForm !== NULL) {
- //self::$_singleton[$cacheKey]->setForm($paymentForm);
- //}
-
- return self::$_singleton[$cacheKey];
+ //we use two lines because we can't remove the '&singleton' without risking breakage
+ //of extension classes that extend this one
+ $object = Civi\Payment\System::singleton()->getByProcessor($paymentProcessor);
+ return $object;
}
/**
+ * Log payment notification message to forensic system log.
+ *
+ * @todo move to factory class \Civi\Payment\System (or similar)
+ *
* @param array $params
*
* @return mixed
}
/**
- * Check if capability is supported
- * @param string $capability e.g BackOffice, LiveMode, FutureRecurStartDate
+ * 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.
*
* @return bool
*/
}
/**
- * 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() {
}
/**
- * Are back office payments supported - e.g paypal standard won't permit you to enter a credit card associated with someone else's login
+ * Are live payments supported - e.g dummy doesn't support this.
+ *
* @return bool
*/
protected function supportsLiveMode() {
}
/**
- * Should the first payment date be configurable when setting up back office recurring payments
+ * Are test payments supported.
+ *
+ * @return bool
+ */
+ protected function supportsTestMode() {
+ return TRUE;
+ }
+
+ /**
+ * 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() {
}
/**
- * Setter for the payment form that wants to use the processor
+ * Default payment instrument validation.
*
- * @param CRM_Core_Form $paymentForm
+ * 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
*/
- function setForm(&$paymentForm) {
- $this->_paymentForm = $paymentForm;
+ public function validatePaymentInstrument($values, &$errors) {
+ if ($this->_paymentProcessor['payment_type'] == 1) {
+ CRM_Core_Payment_Form::validateCreditCard($values, $errors);
+ }
}
/**
- * Getter for payment form that is using the processor
+ * Setter for the payment form that wants to use the processor.
+ *
+ * @deprecated
*
- * @return CRM_Core_Form A form object
+ * @param CRM_Core_Form $paymentForm
*/
- function getForm() {
+ public function setForm(&$paymentForm) {
+ $this->_paymentForm = $paymentForm;
+ }
+
+ /**
+ * Getter for payment form that is using the processor.
+ * @deprecated
+ * @return CRM_Core_Form
+ * A form object
+ */
+ public function getForm() {
return $this->_paymentForm;
}
/**
- * Getter for accessing member vars
- *
+ * Getter for accessing member vars.
+ * @todo believe this is unused
* @param string $name
*
* @return null
*/
- function getVar($name) {
+ public function getVar($name) {
return isset($this->$name) ? $this->$name : NULL;
}
/**
- * 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
*/
public function getPaymentTypeName() {
}
/**
- * Get label for the payment information type
- *
+ * Get label for the payment information type.
+ * @todo - use option group + labels (like Omnipay does)
* @return string
*/
public function getPaymentTypeLabel() {
}
/**
- * 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
}
/**
- * 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
*/
}
/**
- * 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
*/
}
/**
- * 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 field metadata
+ * @return array
+ * field metadata
*/
public function getPaymentFormFieldsMetadata() {
//@todo convert credit card type into an option value
'attributes' => array(
'size' => 20,
'maxlength' => 20,
- 'autocomplete' => 'off'
+ 'autocomplete' => 'off',
),
'is_required' => TRUE,
),
'attributes' => array(
'size' => 5,
'maxlength' => 10,
- 'autocomplete' => 'off'
+ 'autocomplete' => 'off',
),
'is_required' => CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME,
'cvv_backoffice_required',
'rule_message' => ts('Please enter a valid value for your card security code. This is usually the last 3-4 digits on the card\'s signature panel.'),
'rule_name' => 'integer',
'rule_parameters' => NULL,
- )),
+ ),
+ ),
),
'credit_card_exp_date' => array(
'htmlType' => 'date',
'rule_message' => ts('Card expiration date cannot be a past date.'),
'rule_name' => 'currentDate',
'rule_parameters' => TRUE,
- )),
+ ),
+ ),
),
'credit_card_type' => array(
'htmlType' => 'select',
'attributes' => array(
'size' => 20,
'maxlength' => 34,
- 'autocomplete' => 'on'
+ 'autocomplete' => 'on',
),
'is_required' => TRUE,
),
'attributes' => array(
'size' => 20,
'maxlength' => 34,
- 'autocomplete' => 'off'
+ 'autocomplete' => 'off',
),
'rules' => array(
array(
'rule_message' => ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'),
'rule_name' => 'nopunctuation',
'rule_parameters' => NULL,
- )),
+ ),
+ ),
'is_required' => TRUE,
),
//e.g. SWIFT-BIC can have maxlength of 11 digits
'attributes' => array(
'size' => 20,
'maxlength' => 11,
- 'autocomplete' => 'off'
+ 'autocomplete' => 'off',
),
'is_required' => TRUE,
'rules' => array(
'rule_message' => ts('Please enter a valid Bank Identification Number (value must not contain punctuation characters).'),
'rule_name' => 'nopunctuation',
'rule_parameters' => NULL,
- )),
+ ),
+ ),
),
'bank_name' => array(
'htmlType' => 'text',
'attributes' => array(
'size' => 20,
'maxlength' => 64,
- 'autocomplete' => 'off'
+ 'autocomplete' => 'off',
),
'is_required' => TRUE,
- )
+ ),
);
}
/**
- * 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.
*
- * @param array $params assoc array of input parameters for this 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.
*
- * @return array the result in an nice formatted array (or an error object)
+ * @return array
+ * the result in an nice formatted array (or an error object)
* @abstract
*/
- abstract function doDirectPayment(&$params);
+ abstract protected function doDirectPayment(&$params);
/**
* Process payment - this function wraps around both doTransferPayment and doDirectPayment
*
* @param $component
*
+ * @return array
+ * (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);
}
if (is_a($result, 'CRM_Core_Error')) {
throw new CRM_Core_Exception(CRM_Core_Error::getMessages($result));
}
+ //CRM-15767 - Submit Credit Card Contribution not being saved
+ return $result;
}
/**
- * 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
- * @public
+ * @return string
+ * the error message if any
*/
- abstract function checkConfig();
+ abstract protected function checkConfig();
/**
* @param $paymentProcessor
- *
+ * @todo move to paypal class or remover
* @return bool
*/
- static function paypalRedirect(&$paymentProcessor) {
+ public static function paypalRedirect(&$paymentProcessor) {
if (!$paymentProcessor) {
return FALSE;
}
}
/**
+ * @todo move to0 \Civi\Payment\System factory method
* Page callback for civicrm/payment/ipn
- * @public
*/
- static function handleIPN() {
+ public static function handleIPN() {
self::handlePaymentMethod(
'PaymentNotification',
array(
'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
*
- * @public
- * @param $method
+ * @todo move to \Civi\Payment\System factory method
+ *
+ * @param string $method
+ * 'PaymentNotification' or 'PaymentCron'
* @param array $params
*/
- static function handlePaymentMethod($method, $params = array()) {
+ public static function handlePaymentMethod($method, $params = array()) {
if (!isset($params['processor_id']) && !isset($params['processor_name'])) {
CRM_Core_Error::fatal("Either 'processor_id' or 'processor_name' param is required for payment callback");
}
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;
}
}
- $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($dao->processor_id, $mode);
+ $processorInstance = $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 = $paymentClass::singleton($mode, $paymentProcessor);
-
// Does PP implement this method, and can we call it?
if (!method_exists($processorInstance, $method) ||
!is_callable(array($processorInstance, $method))
$extension_instance_found = TRUE;
}
- if (!$extension_instance_found) CRM_Core_Error::fatal(
- "No extension instances of the '{$params['processor_name']}' payment processor were found.<br />" .
- "$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();
+ if (!$extension_instance_found) {
+ CRM_Core_Error::fatal(
+ "No extension instances of the '{$params['processor_name']}' payment processor were found.<br />" .
+ "$method method is unsupported in legacy payment processors."
+ );
}
}
/**
* Check whether a method is present ( & supported ) by the payment processor object.
*
- * @param string $method method to check for.
+ * @param string $method
+ * Method to check for.
*
- * @return boolean
- * @public
+ * @return bool
*/
- function isSupported($method = 'cancelSubscription') {
+ public function isSupported($method = 'cancelSubscription') {
return method_exists(CRM_Utils_System::getClassName($this), $method);
}
/**
+ * Get url for users to manage this recurring contribution for this processor.
+ *
* @param int $entityID
* @param null $entity
* @param string $action
*
* @return string
*/
- function subscriptionURL($entityID = NULL, $entity = NULL, $action = 'cancel') {
+ public function subscriptionURL($entityID = NULL, $entity = NULL, $action = 'cancel') {
// Set URL
switch ($action) {
- case 'cancel' :
+ case 'cancel':
$url = 'civicrm/contribute/unsubscribe';
break;
- case 'billing' :
+
+ case 'billing':
//in notify mode don't return the update billing url
if (!$this->isSupported('updateSubscriptionBillingInfo')) {
return NULL;
}
$url = 'civicrm/contribute/updatebilling';
break;
- case 'update' :
+
+ case 'update':
$url = 'civicrm/contribute/updaterecur';
break;
}
- $session = CRM_Core_Session::singleton();
- $userId = $session->get('userID');
- $contactID = 0;
+ $session = CRM_Core_Session::singleton();
+ $userId = $session->get('userID');
+ $contactID = 0;
$checksumValue = '';
- $entityArg = '';
+ $entityArg = '';
// Find related Contact
if ($entityID) {
switch ($entity) {
- case 'membership' :
+ case 'membership':
$contactID = CRM_Core_DAO::getFieldValue("CRM_Member_DAO_Membership", $entityID, "contact_id");
$entityArg = 'mid';
break;
- case 'contribution' :
+ case 'contribution':
$contactID = CRM_Core_DAO::getFieldValue("CRM_Contribute_DAO_Contribution", $entityID, "contact_id");
$entityArg = 'coid';
break;
- case 'recur' :
+ case 'recur':
$sql = "
SELECT con.contact_id
FROM civicrm_contribution_recur rec
// Else default
return $this->_paymentProcessor['url_recur'];
}
+
}