remove a couple of unrequired lines from test
[civicrm-core.git] / CRM / Core / Payment / PaymentExpressIPN.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
232624b1 4 | CiviCRM version 4.4 |
6a488035
TO
5 +--------------------------------------------------------------------+
6 | This file is a part of CiviCRM. |
7 | |
8 | CiviCRM is free software; you can copy, modify, and distribute it |
9 | under the terms of the GNU Affero General Public License |
10 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
11 | |
12 | CiviCRM is distributed in the hope that it will be useful, but |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
15 | See the GNU Affero General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU Affero General Public |
18 | License and the CiviCRM Licensing Exception along |
19 | with this program; if not, contact CiviCRM LLC |
20 | at info[AT]civicrm[DOT]org. If you have questions about the |
21 | GNU Affero General Public License or the licensing of CiviCRM, |
22 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
23 +--------------------------------------------------------------------+
24*/
25
26
27/*
28 * PxPay Functionality Copyright (C) 2008 Lucas Baker, Logistic Information Systems Limited (Logis)
29 * PxAccess Functionality Copyright (C) 2008 Eileen McNaughton
30 * Licensed to CiviCRM under the Academic Free License version 3.0.
31 *
32 * Grateful acknowledgements go to Donald Lobo for invaluable assistance
33 * in creating this payment processor module
34 */
35class CRM_Core_Payment_PaymentExpressIPN extends CRM_Core_Payment_BaseIPN {
36
37 /**
38 * We only need one instance of this object. So we use the singleton
39 * pattern and cache the instance in this variable
40 *
41 * @var object
42 * @static
43 */
44 static private $_singleton = NULL;
45
46 /**
47 * mode of operation: live or test
48 *
49 * @var object
50 */
51 protected $_mode = NULL;
52
53 static function retrieve($name, $type, $object, $abort = TRUE) {
54 $value = CRM_Utils_Array::value($name, $object);
55 if ($abort && $value === NULL) {
56 CRM_Core_Error::debug_log_message("Could not find an entry for $name");
57 echo "Failure: Missing Parameter - " . $name . "<p>";
58 exit();
59 }
60
61 if ($value) {
62 if (!CRM_Utils_Type::validate($value, $type)) {
63 CRM_Core_Error::debug_log_message("Could not find a valid entry for $name");
64 echo "Failure: Invalid Parameter<p>";
65 exit();
66 }
67 }
68
69 return $value;
70 }
71
72 /**
73 * Constructor
74 *
75 * @param string $mode the mode of operation: live or test
76 *
77 * @return void
78 */
79 function __construct($mode, &$paymentProcessor) {
80 parent::__construct();
81
82 $this->_mode = $mode;
83 $this->_paymentProcessor = $paymentProcessor;
84 }
85
86 /**
87 * singleton function used to manage this object
88 *
89 * @param string $mode the mode of operation: live or test
90 *
91 * @return object
92 * @static
93 */
94 static function &singleton($mode, $component, &$paymentProcessor) {
95 if (self::$_singleton === NULL) {
96 self::$_singleton = new CRM_Core_Payment_PaymentExpressIPN($mode, $paymentProcessor);
97 }
98 return self::$_singleton;
99 }
100
101 /**
102 * The function gets called when a new order takes place.
103 *
104 * @param xml $dataRoot response send by google in xml format
105 * @param array $privateData contains the name value pair of <merchant-private-data>
106 *
107 * @return void
108 *
109 */
110 function newOrderNotify($success, $privateData, $component, $amount, $transactionReference) {
111 $ids = $input = $params = array();
112
113 $input['component'] = strtolower($component);
114
115 $ids['contact'] = self::retrieve('contactID', 'Integer', $privateData, TRUE);
116 $ids['contribution'] = self::retrieve('contributionID', 'Integer', $privateData, TRUE);
117
118 if ($input['component'] == "event") {
119 $ids['event'] = self::retrieve('eventID', 'Integer', $privateData, TRUE);
120 $ids['participant'] = self::retrieve('participantID', 'Integer', $privateData, TRUE);
121 $ids['membership'] = NULL;
122 }
123 else {
124 $ids['membership'] = self::retrieve('membershipID', 'Integer', $privateData, FALSE);
125 }
126 $ids['contributionRecur'] = $ids['contributionPage'] = NULL;
127
128 $paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType',
129 'PayPal_Express', 'id', 'name'
130 );
131
132 if (!$this->validateData($input, $ids, $objects, TRUE, $paymentProcessorID)) {
133 return FALSE;
134 }
135
136 // make sure the invoice is valid and matches what we have in the contribution record
137 $input['invoice'] = $privateData['invoiceID'];
138 $input['newInvoice'] = $transactionReference;
139 $contribution = &$objects['contribution'];
140 $input['trxn_id'] = $transactionReference;
141
142 if ($contribution->invoice_id != $input['invoice']) {
143 CRM_Core_Error::debug_log_message("Invoice values dont match between database and IPN request");
144 echo "Failure: Invoice values dont match between database and IPN request<p>";
145 return;
146 }
147
148 // lets replace invoice-id with Payment Processor -number because thats what is common and unique
149 // in subsequent calls or notifications sent by google.
150 $contribution->invoice_id = $input['newInvoice'];
151
152 $input['amount'] = $amount;
153
154 if ($contribution->total_amount != $input['amount']) {
155 CRM_Core_Error::debug_log_message("Amount values dont match between database and IPN request");
156 echo "Failure: Amount values dont match between database and IPN request. " . $contribution->total_amount . "/" . $input['amount'] . "<p>";
157 return;
158 }
159
160 $transaction = new CRM_Core_Transaction();
161
6a488035
TO
162 // check if contribution is already completed, if so we ignore this ipn
163
164 if ($contribution->contribution_status_id == 1) {
165 CRM_Core_Error::debug_log_message("returning since contribution has already been handled");
166 echo "Success: Contribution has already been handled<p>";
167 return TRUE;
168 }
169 else {
170 /* Since trxn_id hasn't got any use here,
171 * lets make use of it by passing the eventID/membershipTypeID to next level.
172 * And change trxn_id to the payment processor reference before finishing db update */
173
174 if ($ids['event']) {
175 $contribution->trxn_id = $ids['event'] . CRM_Core_DAO::VALUE_SEPARATOR . $ids['participant'];
176 }
177 else {
178 $contribution->trxn_id = $ids['membership'];
179 }
180 }
181 $this->completeTransaction($input, $ids, $objects, $transaction);
182 return TRUE;
183 }
184
185 /**
186
187 /**
188 * The function returns the component(Event/Contribute..)and whether it is Test or not
189 *
190 * @param array $privateData contains the name-value pairs of transaction related data
191 * @param int $orderNo <order-total> send by google
192 *
193 * @return array context of this call (test, component, payment processor id)
194 * @static
195 */
196 static function getContext($privateData, $orderNo) {
197
198 $component = NULL;
199 $isTest = NULL;
200
201 $contributionID = $privateData['contributionID'];
202 $contribution = new CRM_Contribute_DAO_Contribution();
203 $contribution->id = $contributionID;
204
205 if (!$contribution->find(TRUE)) {
206 CRM_Core_Error::debug_log_message("Could not find contribution record: $contributionID");
207 echo "Failure: Could not find contribution record for $contributionID<p>";
208 exit();
209 }
210
211 if (stristr($contribution->source, 'Online Contribution')) {
212 $component = 'contribute';
213 }
214 elseif (stristr($contribution->source, 'Online Event Registration')) {
215 $component = 'event';
216 }
217 $isTest = $contribution->is_test;
218
219 $duplicateTransaction = 0;
220 if ($contribution->contribution_status_id == 1) {
221 //contribution already handled. (some processors do two notifications so this could be valid)
222 $duplicateTransaction = 1;
223 }
224
225 if ($component == 'contribute') {
226 if (!$contribution->contribution_page_id) {
227 CRM_Core_Error::debug_log_message("Could not find contribution page for contribution record: $contributionID");
228 echo "Failure: Could not find contribution page for contribution record: $contributionID<p>";
229 exit();
230 }
231 }
232 else {
233
234 $eventID = $privateData['eventID'];
235
236 if (!$eventID) {
237 CRM_Core_Error::debug_log_message("Could not find event ID");
238 echo "Failure: Could not find eventID<p>";
239 exit();
240 }
241
242 // we are in event mode
243 // make sure event exists and is valid
244 $event = new CRM_Event_DAO_Event();
245 $event->id = $eventID;
246 if (!$event->find(TRUE)) {
247 CRM_Core_Error::debug_log_message("Could not find event: $eventID");
248 echo "Failure: Could not find event: $eventID<p>";
249 exit();
250 }
251 }
252
253 return array($isTest, $component, $duplicateTransaction);
254 }
255
256 /**
257 * This method is handles the response that will be invoked by the
258 * notification or request sent by the payment processor.
259 *hex string from paymentexpress is passed to this function as hex string. Code based on googleIPN
260 * mac_key is only passed if the processor is pxaccess as it is used for decryption
261 * $dps_method is either pxaccess or pxpay
262 */
263 static function main($dps_method, $rawPostData, $dps_url, $dps_user, $dps_key, $mac_key) {
264
265 $config = CRM_Core_Config::singleton();
266 define('RESPONSE_HANDLER_LOG_FILE', $config->uploadDir . 'CiviCRM.PaymentExpress.log');
267
268 //Setup the log file
269 if (!$message_log = fopen(RESPONSE_HANDLER_LOG_FILE, "a")) {
270 error_func("Cannot open " . RESPONSE_HANDLER_LOG_FILE . " file.\n", 0);
271 exit(1);
272 }
273
274 if ($dps_method == "pxpay") {
275 $processResponse = CRM_Core_Payment_PaymentExpressUtils::_valueXml(array(
276 'PxPayUserId' => $dps_user,
277 'PxPayKey' => $dps_key,
278 'Response' => $_GET['result'],
279 ));
280 $processResponse = CRM_Core_Payment_PaymentExpressUtils::_valueXml('ProcessResponse', $processResponse);
281
282 fwrite($message_log, sprintf("\n\r%s:- %s\n", date("D M j G:i:s T Y"),
283 $processResponse
284 ));
285
286 // Send the XML-formatted validation request to DPS so that we can receive a decrypted XML response which contains the transaction results
287 $curl = CRM_Core_Payment_PaymentExpressUtils::_initCURL($processResponse, $dps_url);
288
289 fwrite($message_log, sprintf("\n\r%s:- %s\n", date("D M j G:i:s T Y"),
290 $curl
291 ));
292 $success = FALSE;
293 if ($response = curl_exec($curl)) {
294 fwrite($message_log, sprintf("\n\r%s:- %s\n", date("D M j G:i:s T Y"),
295 $response
296 ));
297 curl_close($curl);
298
299 // Assign the returned XML values to variables
300 $valid = CRM_Core_Payment_PaymentExpressUtils::_xmlAttribute($response, 'valid');
301 $success = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'Success');
302 $txnId = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'TxnId');
303 $responseText = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'ResponseText');
304 $authCode = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'AuthCode');
305 $DPStxnRef = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, 'DpsTxnRef');
306 $qfKey = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "TxnData1");
307 $privateData = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "TxnData2");
308 list($component,$paymentProcessorID,) =explode(',', CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "TxnData3"));
309 $amount = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "AmountSettlement");
310 $merchantReference = CRM_Core_Payment_PaymentExpressUtils::_xmlElement($response, "MerchantReference");
311 }
312 else {
313 // calling DPS failed
314 CRM_Core_Error::fatal(ts('Unable to establish connection to the payment gateway to verify transaction response.'));
315 exit;
316 }
317 }
318 elseif ($dps_method == "pxaccess") {
319
320 require_once ('PaymentExpress/pxaccess.inc.php');
321 global $pxaccess;
322 $pxaccess = new PxAccess($dps_url, $dps_user, $dps_key, $mac_key);
323 #getResponse method in PxAccess object returns PxPayResponse object
324 #which encapsulates all the response data
325 $rsp = $pxaccess->getResponse($rawPostData);
326
327 $qfKey = $rsp->getTxnData1();
328 $privateData = $rsp->getTxnData2();
329 list($component,$paymentProcessorID) = explode(',',$rsp->getTxnData3());
330 $success = $rsp->getSuccess();
331 $authCode = $rsp->getAuthCode();
332 $DPStxnRef = $rsp->getDpsTxnRef();
333 $amount = $rsp->getAmountSettlement();
334 $MerchantReference = $rsp->getMerchantReference();
335 }
336
337 $privateData = $privateData ? self::stringToArray($privateData) : '';
338
339 // Record the current count in array, before we start adding things (for later checks)
340 $countPrivateData = count($privateData);
341
342 // Private Data consists of : a=contactID, b=contributionID,c=contributionTypeID,d=invoiceID,e=membershipID,f=participantID,g=eventID
343 $privateData['contactID'] = $privateData['a'];
344 $privateData['contributionID'] = $privateData['b'];
345 $privateData['contributionTypeID'] = $privateData['c'];
346 $privateData['invoiceID'] = $privateData['d'];
347
348 if ($component == "event") {
349 $privateData['participantID'] = $privateData['f'];
350 $privateData['eventID'] = $privateData['g'];
351 }
352 elseif ($component == "contribute") {
353
354 if ($countPrivateData == 5) {
355 $privateData["membershipID"] = $privateData['e'];
356 }
357 }
358
359 $transactionReference = $authCode . "-" . $DPStxnRef;
360
361 list($mode, $component, $duplicateTransaction) = self::getContext($privateData, $transactionReference);
362 $mode = $mode ? 'test' : 'live';
363
364
365 $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID,
366 $mode
367 );
368
369 $ipn = self::singleton($mode, $component, $paymentProcessor);
370
371
372 //Check status and take appropriate action
373
374 if ($success == 1) {
375 if ($duplicateTransaction == 0) {
376 $ipn->newOrderNotify($success, $privateData, $component, $amount, $transactionReference);
377 }
378
379 if ($component == "event") {
380 $finalURL = CRM_Utils_System::url('civicrm/event/register',
381 "_qf_ThankYou_display=1&qfKey=$qfKey",
382 FALSE, NULL, FALSE
383 );
384 }
385 elseif ($component == "contribute") {
386 $finalURL = CRM_Utils_System::url('civicrm/contribute/transact',
387 "_qf_ThankYou_display=1&qfKey=$qfKey",
388 FALSE, NULL, FALSE
389 );
390 }
391
392 CRM_Utils_System::redirect($finalURL);
393 }
394 else {
395
396 if ($component == "event") {
397 $finalURL = CRM_Utils_System::url('civicrm/event/confirm',
398 "reset=1&cc=fail&participantId=$privateData[participantID]",
399 FALSE, NULL, FALSE
400 );
401 }
402 elseif ($component == "contribute") {
403 $finalURL = CRM_Utils_System::url('civicrm/contribute/transact',
404 "_qf_Main_display=1&cancel=1&qfKey=$qfKey",
405 FALSE, NULL, FALSE
406 );
407 }
408
409 CRM_Utils_System::redirect($finalURL);
410 }
411 }
412
413 /**
414 * Converts the comma separated name-value pairs in <TxnData2>
415 * to an array of values.
416 */
417 static function stringToArray($str) {
418 $vars = $labels = array();
419 $labels = explode(',', $str);
420 foreach ($labels as $label) {
421 $terms = explode('=', $label);
422 $vars[$terms[0]] = $terms[1];
423 }
424 return $vars;
425 }
426}
427