Commit 2e0f316e authored by sluc23's avatar sluc23
Browse files

Merge remote-tracking branch 'github/master'

# Conflicts:
#	CRM/Core/Payment/RedsysIPN.php
#	info.xml
parents 1bd7061a 6b2727a4
<?php
/*
+--------------------------------------------------------------------+
| CiviCRM version 4.4 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2013 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
/**
*
* @package CRM
* @copyright CiviCRM LLC (c) 2004-2013
* $Id$
*
*/
abstract class CRM_Core_Payment {
/**
* how are we getting billing information?
*
* FORM - we collect it on the same page
* BUTTON - the processor collects it and sends it back to us via some protocol
*/
CONST
BILLING_MODE_FORM = 1,
BILLING_MODE_BUTTON = 2,
BILLING_MODE_NOTIFY = 4;
/**
* which payment type(s) are we using?
*
* credit card
* direct debit
* or both
*
*/
CONST
PAYMENT_TYPE_CREDIT_CARD = 1,
PAYMENT_TYPE_DIRECT_DEBIT = 2;
/**
* Subscription / Recurring payment Status
* START, END
*
*/
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;
protected $_paymentForm = NULL;
/**
* singleton function used to manage this object
*
* @param string $mode the mode of operation: live or test
* @param object $paymentProcessor the details of the payment processor being invoked
* @param object $paymentForm reference to the form object if available
* @param boolean $force should we force a reload of this payment object
*
* @return object
* @static
*
*/
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'];
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];
}
/**
* Setter for the payment form that wants to use the processor
*
* @param obj $paymentForm
*
*/
function setForm(&$paymentForm) {
$this->_paymentForm = $paymentForm;
}
/**
* Getter for payment form that is using the processor
*
* @return obj A form object
*/
function getForm() {
return $this->_paymentForm;
}
/**
* Getter for accessing member vars
*
*/
function getVar($name) {
return isset($this->$name) ? $this->$name : NULL;
}
/**
* This function collects all the information from a web/api form and invokes
* the relevant payment processor specific functions to perform the transaction
*
* @param array $params assoc array of input parameters for this transaction
*
* @return array the result in an nice formatted array (or an error object)
* @abstract
*/
abstract function doDirectPayment(&$params);
/**
* This function checks to see if we have the right config values
*
* @param string $mode the mode we are operating in (live or test)
*
* @return string the error message if any
* @public
*/
abstract function checkConfig();
static function paypalRedirect(&$paymentProcessor) {
if (!$paymentProcessor) {
return FALSE;
}
if (isset($_GET['payment_date']) &&
isset($_GET['merchant_return_link']) &&
CRM_Utils_Array::value('payment_status', $_GET) == 'Completed' &&
$paymentProcessor['payment_processor_type'] == "PayPal_Standard"
) {
return TRUE;
}
return FALSE;
}
/**
* Page callback for civicrm/payment/ipn
* @public
*/
static function handleIPN() {
self::handlePaymentMethod(
'PaymentNotification',
array(
'processor_name' => @$_GET['processor_name'],
'processor_id' => @$_GET['processor_id'],
'mode' => @$_GET['mode'],
)
);
}
/**
* 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
*/
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");
}
// 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');
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.";
}
else {
$sql .= " WHERE ppt.name = %2";
$args[2] = array($params['processor_name'], 'String');
$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 ..
if (!$dao->N) {
CRM_Core_Error::fatal($notfound);
}
$method = 'handle' . $method;
$extension_instance_found = FALSE;
// In all likelihood, we'll just end up with the one instance returned here. But it's
// possible we may get more. Hence, iterate through all instances ..
while ($dao->fetch()) {
// Check pp is extension
$ext = CRM_Extension_System::singleton()->getMapper();
if ($ext->isExtensionKey($dao->class_name)) {
$paymentClass = $ext->keyToClass($dao->class_name, 'payment');
require_once $ext->classToPath($paymentClass);
}
else {
// Legacy or extension as module instance
$paymentClass = 'CRM_Core_' . $dao->class_name;
}
$paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($dao->processor_id, $mode);
// Should never be empty - we already established this processor_id exists and is active.
if (empty($paymentProcessor)) {
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))
) {
// on the off chance there is a double implementation of this processor we should keep looking for another
// note that passing processor_id is more reliable & we should work to deprecate processor_name
continue;
}
// Everything, it seems, is ok - execute pp callback handler
$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();
}
}
/**
* Function to check whether a method is present ( & supported ) by the payment processor object.
*
* @param string $method method to check for.
*
* @return boolean
* @public
*/
function isSupported($method = 'cancelSubscription') {
return method_exists(CRM_Utils_System::getClassName($this), $method);
}
function subscriptionURL($entityID = NULL, $entity = NULL, $action = 'cancel') {
if ($action == 'cancel') {
$url = 'civicrm/contribute/unsubscribe';
}
elseif ($action == 'billing') {
//in notify mode don't return the update billing url
if ($this->_paymentProcessor['billing_mode'] == self::BILLING_MODE_NOTIFY) {
return NULL;
}
$url = 'civicrm/contribute/updatebilling';
}
elseif ($action == 'update') {
$url = 'civicrm/contribute/updaterecur';
}
$session = CRM_Core_Session::singleton();
$userId = $session->get('userID');
$checksumValue = "";
if ($entityID && $entity == 'membership') {
if (!$userId) {
$contactID = CRM_Core_DAO::getFieldValue("CRM_Member_DAO_Membership", $entityID, "contact_id");
$checksumValue = CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID, NULL, 'inf');
$checksumValue = "&cs={$checksumValue}";
}
return CRM_Utils_System::url($url, "reset=1&mid={$entityID}{$checksumValue}", TRUE, NULL, FALSE, TRUE);
}
if ($entityID && $entity == 'contribution') {
if (!$userId) {
$contactID = CRM_Core_DAO::getFieldValue("CRM_Contribute_DAO_Contribution", $entityID, "contact_id");
$checksumValue = CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID, NULL, 'inf');
$checksumValue = "&cs={$checksumValue}";
}
return CRM_Utils_System::url($url, "reset=1&coid={$entityID}{$checksumValue}", TRUE, NULL, FALSE, TRUE);
}
if ($entityID && $entity == 'recur') {
if (!$userId) {
$sql = "
SELECT con.contact_id
FROM civicrm_contribution_recur rec
INNER JOIN civicrm_contribution con ON ( con.contribution_recur_id = rec.id )
WHERE rec.id = %1
GROUP BY rec.id";
$contactID = CRM_Core_DAO::singleValueQuery($sql, array(1 => array($entityID, 'Integer')));
$checksumValue = CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID, NULL, 'inf');
$checksumValue = "&cs={$checksumValue}";
}
return CRM_Utils_System::url($url, "reset=1&crid={$entityID}{$checksumValue}", TRUE, NULL, FALSE, TRUE);
}
if ($this->isSupported('accountLoginURL')) {
return $this->accountLoginURL();
}
return $this->_paymentProcessor['url_recur'];
}
/**
* Check for presence of type 1 or type 3 enabled processors (means we can do back-office submit credit/debit card trxns)
* @public
*/
static function allowBackofficeCreditCard($template = NULL, $variableName = 'newCredit') {
$newCredit = FALSE;
// restrict to type=1 (credit card) payment processor payment_types and only include billing mode types 1 and 3
$processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE,
"billing_mode IN ( 1, 3 ) AND payment_type = 1"
);
if (count($processors) > 0) {
$newCredit = TRUE;
}
if ($template) {
$template->assign($variableName, $newCredit);
}
return $newCredit;
}
}
\ No newline at end of file
......@@ -6,16 +6,18 @@
* payment processor for their payment processors.
*/
use CRM_Redsys_ExtensionUtil as E;
require_once 'CRM/Core/Payment.php';
require_once 'includes/apiRedsys.php';
class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
CONST REDSYS_CURRENCY_EURO = 978;
CONST REDSYS_LANGUAGE_SPANISH = 1;
CONST REDSYS_LANGUAGE_BASQUE = 13;
CONST REDSYS_LANGUAGE_CATALAN = 3;
CONST REDSYS_LANGUAGE_GALICIAN = 12;
CONST REDSYS_TRANSACTION_TYPE_OPERATION_STANDARD = 0;
const REDSYS_CURRENCY_EURO = 978;
const REDSYS_LANGUAGE_SPANISH = 1;
const REDSYS_LANGUAGE_BASQUE = 13;
const REDSYS_LANGUAGE_CATALAN = 3;
const REDSYS_LANGUAGE_GALICIAN = 12;
const REDSYS_TRANSACTION_TYPE_OPERATION_STANDARD = 0;
/**
* We only need one instance of this object. So we use the singleton
......@@ -24,21 +26,25 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
* @var object
* @static
*/
static private $_singleton = null;
static private $_singleton = NULL;
/**
* mode of operation: live or test
*
* @var object
*/
protected $_mode = null;
protected $_mode = NULL;
/**
* Payment Type Processor Name
* Processor type label.
*
* (Deprecated parameter but used in some messages).
*
* @deprecated
*
* @var string
*/
protected $_processorName = null;
public $_processorName = NULL;
/**
* Constructor
......@@ -48,9 +54,9 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
* @return void
*/
function __construct($mode, &$paymentProcessor) {
$this->_mode = $mode;
$this->_paymentProcessor = $paymentProcessor;
$this->_processorName = 'Redsys';
$this->_mode = $mode;
$this->_paymentProcessor = $paymentProcessor;
$this->_processorName = 'Redsys';
}
/**
......@@ -64,7 +70,7 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
*/
static function &singleton($mode, &$paymentProcessor) {
$processorName = $paymentProcessor["name"];
if (self::$_singleton[$processorName] === NULL ) {
if (self::$_singleton[$processorName] === NULL) {
self::$_singleton[$processorName] = new self($mode, $paymentProcessor);
}
return self::$_singleton[$processorName];
......@@ -81,15 +87,16 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
$error = array();
if (empty($this->_paymentProcessor["user_name"])) {
$error[] = ts( "Merchant Code is not set in the Redsys Payment Processor settings." );
$error[] = E::ts("Merchant Code is not set in the Redsys Payment Processor settings.");
}
if (empty($this->_paymentProcessor["password"])) {
$error[] = ts( "Merchant Password is not set in the Redsys Payment Processor settings." );
$error[] = E::ts("Merchant Password is not set in the Redsys Payment Processor settings.");
}
if (!empty($error)) {
return implode("<p>", $error);
} else {
}
else {
return NULL;
}
}
......@@ -100,8 +107,8 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
*
* @param type $params
*/
function doDirectPayment( &$params ) {
CRM_Core_Error::fatal( ts( "This function is not implemented" ) );
function doDirectPayment(&$params) {
CRM_Core_Error::fatal(E::ts("This function is not implemented"));
}
/**
......@@ -113,13 +120,14 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
$config = CRM_Core_Config::singleton();
if ($component != 'contribute' && $component != 'event') {
CRM_Core_Error::fatal(ts('Component is invalid'));
CRM_Core_Error::fatal(E::ts('Component is invalid'));
}
if( array_key_exists( 'webform_redirect_success', $params ) ) {
if (array_key_exists('webform_redirect_success', $params)) {
$returnURL = $params['webform_redirect_success'];
$cancelURL = $params['webform_redirect_cancel'];
} else {
}
else {
$url = ($component == 'event') ? 'civicrm/event/register' : 'civicrm/contribute/transact';
$cancel = ($component == 'event') ? '_qf_Register_display' : '_qf_Main_display';
$returnURL = CRM_Utils_System::url($url,
......@@ -127,7 +135,6 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
TRUE, NULL, FALSE
);
$cancelUrlString = "$cancel=1&cancel=1&qfKey={$params['qfKey']}";
if (CRM_Utils_Array::value('is_recur', $params)) {
$cancelUrlString .= "&isRecur=1&recurId={$params['contributionRecurID']}&contribId={$params['contributionID']}";
......@@ -169,32 +176,33 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
TRUE, NULL, FALSE, TRUE
);
// Force http if set
// Force http if set.
$redsys_settings = CRM_Core_BAO_Setting::getItem("Redsys Settings", 'redsys_settings');
if($redsys_settings['ipn_http'] == '1')
if ($redsys_settings['ipn_http'] == '1')
$merchantUrl = preg_replace('/^https:/i', 'http:', $merchantUrl);
// The payment processor id can be named payment_processor (contribution pages)
if( array_key_exists( 'payment_processor', $params ) ) {
// The payment processor id can be named payment_processor (contribution pages).
if (array_key_exists('payment_processor', $params)) {
$paymentProcessorId = $params['payment_processor'];
} elseif( array_key_exists( 'payment_processor_id', $params ) ) {
}
elseif (array_key_exists('payment_processor_id', $params)) {
$paymentProcessorId = $params['payment_processor_id'];
}
// Get the terminal for this payment processor
if( array_key_exists('merchant_terminal_' . $paymentProcessorId, $redsys_settings) ) {
if( $redsys_settings['merchant_terminal_' . $paymentProcessorId] ) {
// Get the terminal for this payment processor.
if(array_key_exists('merchant_terminal_' . $paymentProcessorId, $redsys_settings)) {
if ($redsys_settings['merchant_terminal_' . $paymentProcessorId]) {
$merchantTerminal = $redsys_settings['merchant_terminal_' . $paymentProcessorId];
}
}
// Use the default terminal if the processor doesn't have an assigned one
if( ! $merchantTerminal ) {
if(!$merchantTerminal) {
$merchantTerminal = empty($redsys_settings['merchant_terminal']) ? 1 :
$redsys_settings['merchant_terminal'];
}
$miObj = new RedsysAPI;
$miObj = new RedsysAPI();
$miObj->setParameter("Ds_Merchant_Amount", $params["amount"] * 100);
$miObj->setParameter("Ds_Merchant_Order", strval(self::formatAmount($params["contributionID"], 12)));
$miObj->setParameter("Ds_Merchant_MerchantCode", $this->_paymentProcessor["user_name"]);
......@@ -205,14 +213,14 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
$miObj->setParameter("Ds_Merchant_UrlOK", $returnURL);
$miObj->setParameter("Ds_Merchant_UrlKO", $cancelURL);
$miObj->setParameter("Ds_Merchant_ProductDescription", $params["contributionType_name"]);
$miObj->setParameter("Ds_Merchant_Titular", $params["first_name"] . " " . $params["last_name"] );
$miObj->setParameter("Ds_Merchant_Titular", $params["first_name"] . " " . $params["last_name"]);
$miObj->setParameter("Ds_Merchant_ConsumerLanguage", self::REDSYS_LANGUAGE_SPANISH);
$version = "HMAC_SHA256_V1";
$signature = $miObj->createMerchantSignature($this->_paymentProcessor["password"]);
// Print the tpl to redirect and send POST variables to RedSys Getaway
// Print the tpl to redirect and send POST variables to RedSys Getaway.
$template = CRM_Core_Smarty::singleton();
$tpl = 'CRM/Core/Payment/Redsys.tpl';
......@@ -231,9 +239,9 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
$input = $ids = $objects = array();
$ipn = new CRM_Core_Payment_RedsysIPN();
// load vars in $input, &ids
// Load vars in $input, &ids.
$ipn->getInput($input, $ids);
CRM_Core_Error::debug_log_message("Redsys IPN Response: Parameteres received \n input: " . print_r($input, TRUE) . "\n ids: " . print_r($ids, TRUE) );
CRM_Core_Error::debug_log_message("Redsys IPN Response: Parameteres received \n input: " . print_r($input, TRUE) . "\n ids: " . print_r($ids, TRUE));
$paymentProcessorID = $this->_paymentProcessor['id'];
if (!$ipn->validateData($this->_paymentProcessor, $input, $ids, $objects, TRUE, $paymentProcessorID)) {
......@@ -253,4 +261,5 @@ class CRM_Core_Payment_Redsys extends CRM_Core_Payment {
static function trimAmount($amount, $pad = '0'){
return ltrim(trim($amount), $pad);
}
}
......@@ -54,7 +54,7 @@ class CRM_Core_Payment_RedsysIPN extends CRM_Core_Payment_BaseIPN {
$contribution = &$objects['contribution'];
if (!$recur) {
if (str_replace(",", "", $contribution->total_amount) != str_replace(",", "", $input['amount']) ) {
if (str_replace(",", "", $contribution->total_amount) != str_replace(",", "", $input['amount'])) {
CRM_Core_Error::debug_log_message("Amount values dont match between database and IPN request");
echo "Failure: Amount values dont match between database and IPN request<p>";
return FALSE;
......@@ -64,43 +64,54 @@ class CRM_Core_Payment_RedsysIPN extends CRM_Core_Payment_BaseIPN {
$transaction = new CRM_Core_Transaction();
if ($input['Ds_Response'] != self::REDSYS_RESPONSE_CODE_ACCEPTED) {
$error = self::trimAmount($input['Ds_Response']);
if(array_key_exists($error, $this->_errors)) {
if (array_key_exists($error, $this->_errors)) {
$input['reasonCode'] = $this->_errors[$error];
}
CRM_Core_Error::debug_log_message("Redsys IPN Response: About to cancel contr \n input: " . print_r($input, TRUE) . "\n ids: " . print_r($ids, TRUE) . "\n objects: " . print_r($objects, TRUE) );
return $this->cancelled($objects, $transaction, $input);