"; exit(); } if ($value) { if (!CRM_Utils_Type::validate($value, $type)) { CRM_Core_Error::debug_log_message("Could not find a valid entry for $name"); echo "Failure: Invalid Parameter

"; exit(); } } return $value; } /** * Constructor. * * @param string $mode * The mode of operation: live or test. * * @param $paymentProcessor * * @return \CRM_Core_Payment_PaymentExpressIPN */ public function __construct($mode, &$paymentProcessor) { parent::__construct(); $this->_mode = $mode; $this->_paymentProcessor = $paymentProcessor; } /** * The function gets called when a new order takes place. * * @param $success * @param array $privateData * Contains the name value pair of . * * @param $component * @param $amount * @param $transactionReference * * @return bool */ public function newOrderNotify($success, $privateData, $component, $amount, $transactionReference) { $ids = $input = $params = []; $input['component'] = strtolower($component); $ids['contact'] = self::retrieve('contactID', 'Integer', $privateData, TRUE); $ids['contribution'] = self::retrieve('contributionID', 'Integer', $privateData, TRUE); if ($input['component'] == "event") { $ids['event'] = self::retrieve('eventID', 'Integer', $privateData, TRUE); $ids['participant'] = self::retrieve('participantID', 'Integer', $privateData, TRUE); $ids['membership'] = NULL; } else { $ids['membership'] = self::retrieve('membershipID', 'Integer', $privateData, FALSE); } $ids['contributionRecur'] = $ids['contributionPage'] = NULL; $paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType', 'PayPal_Express', 'id', 'name' ); if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) { return FALSE; } // make sure the invoice is valid and matches what we have in the contribution record $input['invoice'] = $privateData['invoiceID']; $input['newInvoice'] = $transactionReference; $contribution = &$objects['contribution']; $input['trxn_id'] = $transactionReference; if ($contribution->invoice_id != $input['invoice']) { CRM_Core_Error::debug_log_message("Invoice values dont match between database and IPN request"); echo "Failure: Invoice values dont match between database and IPN request

"; return FALSE; } // lets replace invoice-id with Payment Processor -number because thats what is common and unique // in subsequent calls or notifications sent by google. $contribution->invoice_id = $input['newInvoice']; $input['amount'] = $amount; if ($contribution->total_amount != $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. " . $contribution->total_amount . "/" . $input['amount'] . "

"; return FALSE; } $transaction = new CRM_Core_Transaction(); // check if contribution is already completed, if so we ignore this ipn if ($contribution->contribution_status_id == 1) { CRM_Core_Error::debug_log_message("returning since contribution has already been handled"); echo "Success: Contribution has already been handled

"; return TRUE; } else { /* Since trxn_id hasn't got any use here, * lets make use of it by passing the eventID/membershipTypeID to next level. * And change trxn_id to the payment processor reference before finishing db update */ if ($ids['event']) { $contribution->trxn_id = $ids['event'] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['participant']; } else { $contribution->trxn_id = $ids['membership']; } } $this->completeTransaction($input, $ids, $objects, $transaction); return TRUE; } /** * * /** * The function returns the component(Event/Contribute..)and whether it is Test or not * * @param array $privateData * Contains the name-value pairs of transaction related data. * @param int $orderNo * send by google. * * @return array * context of this call (test, component, payment processor id) */ public static function getContext($privateData, $orderNo) { $component = NULL; $isTest = NULL; $contributionID = $privateData['contributionID']; $contribution = new CRM_Contribute_DAO_Contribution(); $contribution->id = $contributionID; if (!$contribution->find(TRUE)) { CRM_Core_Error::debug_log_message("Could not find contribution record: $contributionID"); echo "Failure: Could not find contribution record for $contributionID

"; exit(); } if (stristr($contribution->source, 'Online Contribution')) { $component = 'contribute'; } elseif (stristr($contribution->source, 'Online Event Registration')) { $component = 'event'; } $isTest = $contribution->is_test; $duplicateTransaction = 0; if ($contribution->contribution_status_id == 1) { //contribution already handled. (some processors do two notifications so this could be valid) $duplicateTransaction = 1; } if ($component == 'contribute') { if (!$contribution->contribution_page_id) { CRM_Core_Error::debug_log_message("Could not find contribution page for contribution record: $contributionID"); echo "Failure: Could not find contribution page for contribution record: $contributionID

"; exit(); } } else { $eventID = $privateData['eventID']; if (!$eventID) { CRM_Core_Error::debug_log_message("Could not find event ID"); echo "Failure: Could not find eventID

"; exit(); } // we are in event mode // make sure event exists and is valid $event = new CRM_Event_DAO_Event(); $event->id = $eventID; if (!$event->find(TRUE)) { CRM_Core_Error::debug_log_message("Could not find event: $eventID"); echo "Failure: Could not find event: $eventID

"; exit(); } } return [$isTest, $component, $duplicateTransaction]; } /** * Main notification processing method. * * hex string from paymentexpress is passed to this function as hex string. Code based on googleIPN * mac_key is only passed if the processor is pxaccess as it is used for decryption * $dps_method is either pxaccess or pxpay * * @param string $dps_method * @param array $rawPostData * @param string $dps_url * @param string $dps_user * @param string $dps_key * @param string $mac_key * * @throws \Exception */ public static function main($dps_method, $rawPostData, $dps_url, $dps_user, $dps_key, $mac_key) { $config = CRM_Core_Config::singleton(); define('RESPONSE_HANDLER_LOG_FILE', $config->uploadDir . 'CiviCRM.PaymentExpress.log'); //Setup the log file if (!$message_log = fopen(RESPONSE_HANDLER_LOG_FILE, "a")) { error_func("Cannot open " . RESPONSE_HANDLER_LOG_FILE . " file.\n", 0); exit(1); } if ($dps_method == "pxpay") { $processResponse = CRM_Core_Payment_PaymentExpressUtils::_valueXml([ 'PxPayUserId' => $dps_user, 'PxPayKey' => $dps_key, 'Response' => $_GET['result'], ]); $processResponse = CRM_Core_Payment_PaymentExpressUtils::_valueXml('ProcessResponse', $processResponse); fwrite($message_log, sprintf("\n\r%s:- %s\n", date("D M j G:i:s T Y"), $processResponse )); // Send the XML-formatted validation request to DPS so that we can receive a decrypted XML response which contains the transaction results $curl = CRM_Core_Payment_PaymentExpressUtils::_initCURL($processResponse, $dps_url); fwrite($message_log, sprintf("\n\r%s:- %s\n", date("D M j G:i:s T Y"), $curl )); $success = FALSE; if ($response = curl_exec($curl)) { $info = curl_getinfo($curl); if ($info['http_code'] < 200 || $info['http_code'] > 299) { $log_message = "DPS error: HTTP %1 retrieving %2."; CRM_Core_Error::fatal(ts($log_message, [1 => $info['http_code'], 2 => $info['url']])); } else { fwrite($message_log, sprintf("\n\r%s:- %s\n", date("D M j G:i:s T Y"), $response)); curl_close($curl); // Assign the returned XML values to variables $valid = CRM_Core_Payment_PaymentExpressUtils::_xmlAttribute($response, 'valid'); // CRM_Core_Payment_PaymentExpressUtils::_xmlAttribute() returns NULL if preg fails. if (is_null($valid)) { CRM_Core_Error::fatal(ts("DPS error: Unable to parse XML response from DPS.", [1 => $valid])); } $success = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'Success'); $txnId = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'TxnId'); $responseText = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'ResponseText'); $authCode = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'AuthCode'); $DPStxnRef = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'DpsTxnRef'); $qfKey = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "TxnData1"); $privateData = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "TxnData2"); list($component, $paymentProcessorID,) = explode(',', CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "TxnData3")); $amount = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "AmountSettlement"); $merchantReference = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "MerchantReference"); } } else { // calling DPS failed CRM_Core_Error::fatal(ts('Unable to establish connection to the payment gateway to verify transaction response.')); exit; } } elseif ($dps_method == "pxaccess") { require_once 'PaymentExpress/pxaccess.inc.php'; global $pxaccess; $pxaccess = new PxAccess($dps_url, $dps_user, $dps_key, $mac_key); #getResponse method in PxAccess object returns PxPayResponse object #which encapsulates all the response data $rsp = $pxaccess->getResponse($rawPostData); $qfKey = $rsp->getTxnData1(); $privateData = $rsp->getTxnData2(); list($component, $paymentProcessorID) = explode(',', $rsp->getTxnData3()); $success = $rsp->getSuccess(); $authCode = $rsp->getAuthCode(); $DPStxnRef = $rsp->getDpsTxnRef(); $amount = $rsp->getAmountSettlement(); $MerchantReference = $rsp->getMerchantReference(); } $privateData = $privateData ? self::stringToArray($privateData) : ''; // Record the current count in array, before we start adding things (for later checks) $countPrivateData = count($privateData); // Private Data consists of : a=contactID, b=contributionID,c=contributionTypeID,d=invoiceID,e=membershipID,f=participantID,g=eventID $privateData['contactID'] = $privateData['a']; $privateData['contributionID'] = $privateData['b']; $privateData['contributionTypeID'] = $privateData['c']; $privateData['invoiceID'] = $privateData['d']; if ($component == "event") { $privateData['participantID'] = $privateData['f']; $privateData['eventID'] = $privateData['g']; } elseif ($component == "contribute") { if ($countPrivateData == 5) { $privateData["membershipID"] = $privateData['e']; } } $transactionReference = $authCode . "-" . $DPStxnRef; list($mode, $component, $duplicateTransaction) = self::getContext($privateData, $transactionReference); $mode = $mode ? 'test' : 'live'; $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID, $mode ); $ipn = self::singleton($mode, $component, $paymentProcessor); //Check status and take appropriate action if ($success == 1) { if ($duplicateTransaction == 0) { $ipn->newOrderNotify($success, $privateData, $component, $amount, $transactionReference); } if ($component == "event") { $finalURL = CRM_Utils_System::url('civicrm/event/register', "_qf_ThankYou_display=1&qfKey=$qfKey", FALSE, NULL, FALSE ); } elseif ($component == "contribute") { $finalURL = CRM_Utils_System::url('civicrm/contribute/transact', "_qf_ThankYou_display=1&qfKey=$qfKey", FALSE, NULL, FALSE ); } CRM_Utils_System::redirect($finalURL); } else { if ($component == "event") { $finalURL = CRM_Utils_System::url('civicrm/event/confirm', "reset=1&cc=fail&participantId=$privateData[participantID]", FALSE, NULL, FALSE ); } elseif ($component == "contribute") { $finalURL = CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=1&cancel=1&qfKey=$qfKey", FALSE, NULL, FALSE ); } CRM_Utils_System::redirect($finalURL); } } /** * Converts the comma separated name-value pairs in to an array of values. * * @param string $str * * @return array */ public static function stringToArray($str) { $vars = $labels = []; $labels = explode(',', $str); foreach ($labels as $label) { $terms = explode('=', $label); $vars[$terms[0]] = $terms[1]; } return $vars; } }