commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / CRM / Core / Payment / AuthorizeNet.php
1 <?php
2 /*
3 * Copyright (C) 2007
4 * Licensed to CiviCRM under the Academic Free License version 3.0.
5 *
6 * Written and contributed by Ideal Solution, LLC (http://www.idealso.com)
7 *
8 */
9
10 /**
11 *
12 * @package CRM
13 * @author Marshal Newrock <marshal@idealso.com>
14 */
15
16 /**
17 * NOTE:
18 * When looking up response codes in the Authorize.Net API, they
19 * begin at one, so always delete one from the "Position in Response"
20 */
21 class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment {
22 const CHARSET = 'iso-8859-1';
23 const AUTH_APPROVED = 1;
24 const AUTH_DECLINED = 2;
25 const AUTH_ERROR = 3;
26 const TIMEZONE = 'America/Denver';
27
28 protected $_mode = NULL;
29
30 protected $_params = array();
31
32 /**
33 * We only need one instance of this object. So we use the singleton
34 * pattern and cache the instance in this variable
35 *
36 * @var object
37 */
38 static private $_singleton = NULL;
39
40 /**
41 * Constructor.
42 *
43 * @param string $mode
44 * The mode of operation: live or test.
45 *
46 * @param $paymentProcessor
47 *
48 * @return \CRM_Core_Payment_AuthorizeNet
49 */
50 public function __construct($mode, &$paymentProcessor) {
51 $this->_mode = $mode;
52 $this->_paymentProcessor = $paymentProcessor;
53 $this->_processorName = ts('Authorize.net');
54
55 $config = CRM_Core_Config::singleton();
56 $this->_setParam('apiLogin', $paymentProcessor['user_name']);
57 $this->_setParam('paymentKey', $paymentProcessor['password']);
58 $this->_setParam('paymentType', 'AIM');
59 $this->_setParam('md5Hash', $paymentProcessor['signature']);
60
61 $this->_setParam('emailCustomer', 'TRUE');
62 $this->_setParam('timestamp', time());
63 srand(time());
64 $this->_setParam('sequence', rand(1, 1000));
65 }
66
67 /**
68 * Should the first payment date be configurable when setting up back office recurring payments.
69 * In the case of Authorize.net this is an option
70 * @return bool
71 */
72 protected function supportsFutureRecurStartDate() {
73 return TRUE;
74 }
75
76 /**
77 * Can recurring contributions be set against pledges.
78 *
79 * In practice all processors that use the baseIPN function to finish transactions or
80 * call the completetransaction api support this by looking up previous contributions in the
81 * series and, if there is a prior contribution against a pledge, and the pledge is not complete,
82 * adding the new payment to the pledge.
83 *
84 * However, only enabling for processors it has been tested against.
85 *
86 * @return bool
87 */
88 protected function supportsRecurContributionsForPledges() {
89 return TRUE;
90 }
91
92 /**
93 * Submit a payment using Advanced Integration Method.
94 *
95 * @param array $params
96 * Assoc array of input parameters for this transaction.
97 *
98 * @return array
99 * the result in a nice formatted array (or an error object)
100 */
101 public function doDirectPayment(&$params) {
102 if (!defined('CURLOPT_SSLCERT')) {
103 return self::error(9001, 'Authorize.Net requires curl with SSL support');
104 }
105
106 /*
107 * recurpayment function does not compile an array & then proces it -
108 * - the tpl does the transformation so adding call to hook here
109 * & giving it a change to act on the params array
110 */
111 $newParams = $params;
112 if (!empty($params['is_recur']) && !empty($params['contributionRecurID'])) {
113 CRM_Utils_Hook::alterPaymentProcessorParams($this,
114 $params,
115 $newParams
116 );
117 }
118 foreach ($newParams as $field => $value) {
119 $this->_setParam($field, $value);
120 }
121
122 if (!empty($params['is_recur']) && !empty($params['contributionRecurID'])) {
123 $result = $this->doRecurPayment();
124 if (is_a($result, 'CRM_Core_Error')) {
125 return $result;
126 }
127 return $params;
128 }
129
130 $postFields = array();
131 $authorizeNetFields = $this->_getAuthorizeNetFields();
132
133 // Set up our call for hook_civicrm_paymentProcessor,
134 // since we now have our parameters as assigned for the AIM back end.
135 CRM_Utils_Hook::alterPaymentProcessorParams($this,
136 $params,
137 $authorizeNetFields
138 );
139
140 foreach ($authorizeNetFields as $field => $value) {
141 // CRM-7419, since double quote is used as enclosure while doing csv parsing
142 $value = ($field == 'x_description') ? str_replace('"', "'", $value) : $value;
143 $postFields[] = $field . '=' . urlencode($value);
144 }
145
146 // Authorize.Net will not refuse duplicates, so we should check if the user already submitted this transaction
147 if ($this->_checkDupe($authorizeNetFields['x_invoice_num'])) {
148 return self::error(9004, 'It appears that this transaction is a duplicate. Have you already submitted the form once? If so there may have been a connection problem. Check your email for a receipt from Authorize.net. If you do not receive a receipt within 2 hours you can try your transaction again. If you continue to have problems please contact the site administrator.');
149 }
150
151 $submit = curl_init($this->_paymentProcessor['url_site']);
152
153 if (!$submit) {
154 return self::error(9002, 'Could not initiate connection to payment gateway');
155 }
156
157 curl_setopt($submit, CURLOPT_POST, TRUE);
158 curl_setopt($submit, CURLOPT_RETURNTRANSFER, TRUE);
159 curl_setopt($submit, CURLOPT_POSTFIELDS, implode('&', $postFields));
160 curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL'));
161
162 $response = curl_exec($submit);
163
164 if (!$response) {
165 return self::error(curl_errno($submit), curl_error($submit));
166 }
167
168 curl_close($submit);
169
170 $response_fields = $this->explode_csv($response);
171
172 // check gateway MD5 response
173 if (!$this->checkMD5($response_fields[37], $response_fields[6], $response_fields[9])) {
174 return self::error(9003, 'MD5 Verification failed');
175 }
176
177 // check for application errors
178 // TODO:
179 // AVS, CVV2, CAVV, and other verification results
180 if ($response_fields[0] != self::AUTH_APPROVED) {
181 $errormsg = $response_fields[2] . ' ' . $response_fields[3];
182 return self::error($response_fields[1], $errormsg);
183 }
184
185 // Success
186
187 // test mode always returns trxn_id = 0
188 // also live mode in CiviCRM with test mode set in
189 // Authorize.Net return $response_fields[6] = 0
190 // hence treat that also as test mode transaction
191 // fix for CRM-2566
192 if (($this->_mode == 'test') || $response_fields[6] == 0) {
193 $query = "SELECT MAX(trxn_id) FROM civicrm_contribution WHERE trxn_id RLIKE 'test[0-9]+'";
194 $p = array();
195 $trxn_id = strval(CRM_Core_DAO::singleValueQuery($query, $p));
196 $trxn_id = str_replace('test', '', $trxn_id);
197 $trxn_id = intval($trxn_id) + 1;
198 $params['trxn_id'] = sprintf('test%08d', $trxn_id);
199 }
200 else {
201 $params['trxn_id'] = $response_fields[6];
202 }
203 $params['gross_amount'] = $response_fields[9];
204 // TODO: include authorization code?
205
206 return $params;
207 }
208
209 /**
210 * Submit an Automated Recurring Billing subscription.
211 */
212 public function doRecurPayment() {
213 $template = CRM_Core_Smarty::singleton();
214
215 $intervalLength = $this->_getParam('frequency_interval');
216 $intervalUnit = $this->_getParam('frequency_unit');
217 if ($intervalUnit == 'week') {
218 $intervalLength *= 7;
219 $intervalUnit = 'days';
220 }
221 elseif ($intervalUnit == 'year') {
222 $intervalLength *= 12;
223 $intervalUnit = 'months';
224 }
225 elseif ($intervalUnit == 'day') {
226 $intervalUnit = 'days';
227 }
228 elseif ($intervalUnit == 'month') {
229 $intervalUnit = 'months';
230 }
231
232 // interval cannot be less than 7 days or more than 1 year
233 if ($intervalUnit == 'days') {
234 if ($intervalLength < 7) {
235 return self::error(9001, 'Payment interval must be at least one week');
236 }
237 elseif ($intervalLength > 365) {
238 return self::error(9001, 'Payment interval may not be longer than one year');
239 }
240 }
241 elseif ($intervalUnit == 'months') {
242 if ($intervalLength < 1) {
243 return self::error(9001, 'Payment interval must be at least one week');
244 }
245 elseif ($intervalLength > 12) {
246 return self::error(9001, 'Payment interval may not be longer than one year');
247 }
248 }
249
250 $template->assign('intervalLength', $intervalLength);
251 $template->assign('intervalUnit', $intervalUnit);
252
253 $template->assign('apiLogin', $this->_getParam('apiLogin'));
254 $template->assign('paymentKey', $this->_getParam('paymentKey'));
255 $template->assign('refId', substr($this->_getParam('invoiceID'), 0, 20));
256
257 //for recurring, carry first contribution id
258 $template->assign('invoiceNumber', $this->_getParam('contributionID'));
259 $firstPaymentDate = $this->_getParam('receive_date');
260 if (!empty($firstPaymentDate)) {
261 //allow for post dated payment if set in form
262 $startDate = date_create($firstPaymentDate);
263 }
264 else {
265 $startDate = date_create();
266 }
267 /* Format start date in Mountain Time to avoid Authorize.net error E00017
268 * we do this only if the day we are setting our start time to is LESS than the current
269 * day in mountaintime (ie. the server time of the A-net server). A.net won't accept a date
270 * earlier than the current date on it's server so if we are in PST we might need to use mountain
271 * time to bring our date forward. But if we are submitting something future dated we want
272 * the date we entered to be respected
273 */
274 $minDate = date_create('now', new DateTimeZone(self::TIMEZONE));
275 if (strtotime($startDate->format('Y-m-d')) < strtotime($minDate->format('Y-m-d'))) {
276 $startDate->setTimezone(new DateTimeZone(self::TIMEZONE));
277 }
278
279 $template->assign('startDate', $startDate->format('Y-m-d'));
280
281 $installments = $this->_getParam('installments');
282
283 // for open ended subscription totalOccurrences has to be 9999
284 $installments = empty($installments) ? 9999 : $installments;
285 $template->assign('totalOccurrences', $installments);
286
287 $template->assign('amount', $this->_getParam('amount'));
288
289 $template->assign('cardNumber', $this->_getParam('credit_card_number'));
290 $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT);
291 $exp_year = $this->_getParam('year');
292 $template->assign('expirationDate', $exp_year . '-' . $exp_month);
293
294 // name rather than description is used in the tpl - see http://www.authorize.net/support/ARB_guide.pdf
295 $template->assign('name', $this->_getParam('description', TRUE));
296
297 $template->assign('email', $this->_getParam('email'));
298 $template->assign('contactID', $this->_getParam('contactID'));
299 $template->assign('billingFirstName', $this->_getParam('billing_first_name'));
300 $template->assign('billingLastName', $this->_getParam('billing_last_name'));
301 $template->assign('billingAddress', $this->_getParam('street_address', TRUE));
302 $template->assign('billingCity', $this->_getParam('city', TRUE));
303 $template->assign('billingState', $this->_getParam('state_province'));
304 $template->assign('billingZip', $this->_getParam('postal_code', TRUE));
305 $template->assign('billingCountry', $this->_getParam('country'));
306
307 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
308 // submit to authorize.net
309
310 $submit = curl_init($this->_paymentProcessor['url_recur']);
311 if (!$submit) {
312 return self::error(9002, 'Could not initiate connection to payment gateway');
313 }
314 curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1);
315 curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
316 curl_setopt($submit, CURLOPT_HEADER, 1);
317 curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML);
318 curl_setopt($submit, CURLOPT_POST, 1);
319 curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL'));
320
321 $response = curl_exec($submit);
322
323 if (!$response) {
324 return self::error(curl_errno($submit), curl_error($submit));
325 }
326
327 curl_close($submit);
328 $responseFields = $this->_ParseArbReturn($response);
329
330 if ($responseFields['resultCode'] == 'Error') {
331 return self::error($responseFields['code'], $responseFields['text']);
332 }
333
334 // update recur processor_id with subscriptionId
335 CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_ContributionRecur', $this->_getParam('contributionRecurID'),
336 'processor_id', $responseFields['subscriptionId']
337 );
338 //only impact of assigning this here is is can be used to cancel the subscription in an automated test
339 // if it isn't cancelled a duplicate transaction error occurs
340 if (!empty($responseFields['subscriptionId'])) {
341 $this->_setParam('subscriptionId', $responseFields['subscriptionId']);
342 }
343 }
344
345 /**
346 * @return array
347 */
348 public function _getAuthorizeNetFields() {
349 $amount = $this->_getParam('total_amount');//Total amount is from the form contribution field
350 if (empty($amount)) {//CRM-9894 would this ever be the case??
351 $amount = $this->_getParam('amount');
352 }
353 $fields = array();
354 $fields['x_login'] = $this->_getParam('apiLogin');
355 $fields['x_tran_key'] = $this->_getParam('paymentKey');
356 $fields['x_email_customer'] = $this->_getParam('emailCustomer');
357 $fields['x_first_name'] = $this->_getParam('billing_first_name');
358 $fields['x_last_name'] = $this->_getParam('billing_last_name');
359 $fields['x_address'] = $this->_getParam('street_address');
360 $fields['x_city'] = $this->_getParam('city');
361 $fields['x_state'] = $this->_getParam('state_province');
362 $fields['x_zip'] = $this->_getParam('postal_code');
363 $fields['x_country'] = $this->_getParam('country');
364 $fields['x_customer_ip'] = $this->_getParam('ip_address');
365 $fields['x_email'] = $this->_getParam('email');
366 $fields['x_invoice_num'] = substr($this->_getParam('invoiceID'), 0, 20);
367 $fields['x_amount'] = $amount;
368 $fields['x_currency_code'] = $this->_getParam('currencyID');
369 $fields['x_description'] = $this->_getParam('description');
370 $fields['x_cust_id'] = $this->_getParam('contactID');
371 if ($this->_getParam('paymentType') == 'AIM') {
372 $fields['x_relay_response'] = 'FALSE';
373 // request response in CSV format
374 $fields['x_delim_data'] = 'TRUE';
375 $fields['x_delim_char'] = ',';
376 $fields['x_encap_char'] = '"';
377 // cc info
378 $fields['x_card_num'] = $this->_getParam('credit_card_number');
379 $fields['x_card_code'] = $this->_getParam('cvv2');
380 $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT);
381 $exp_year = $this->_getParam('year');
382 $fields['x_exp_date'] = "$exp_month/$exp_year";
383 }
384
385 if ($this->_mode != 'live') {
386 $fields['x_test_request'] = 'TRUE';
387 }
388
389 return $fields;
390 }
391
392 /**
393 * Checks to see if invoice_id already exists in db.
394 *
395 * @param int $invoiceId
396 * The ID to check.
397 *
398 * @return bool
399 * True if ID exists, else false
400 */
401 public function _checkDupe($invoiceId) {
402 $contribution = new CRM_Contribute_DAO_Contribution();
403 $contribution->invoice_id = $invoiceId;
404 return $contribution->find();
405 }
406
407 /**
408 * Generate HMAC_MD5
409 *
410 * @param string $key
411 * @param string $data
412 *
413 * @return string
414 * the HMAC_MD5 encoding string
415 */
416 public function hmac($key, $data) {
417 if (function_exists('mhash')) {
418 // Use PHP mhash extension
419 return (bin2hex(mhash(MHASH_MD5, $data, $key)));
420 }
421 else {
422 // RFC 2104 HMAC implementation for php.
423 // Creates an md5 HMAC.
424 // Eliminates the need to install mhash to compute a HMAC
425 // Hacked by Lance Rushing
426 // byte length for md5
427 $b = 64;
428 if (strlen($key) > $b) {
429 $key = pack("H*", md5($key));
430 }
431 $key = str_pad($key, $b, chr(0x00));
432 $ipad = str_pad('', $b, chr(0x36));
433 $opad = str_pad('', $b, chr(0x5c));
434 $k_ipad = $key ^ $ipad;
435 $k_opad = $key ^ $opad;
436 return md5($k_opad . pack("H*", md5($k_ipad . $data)));
437 }
438 }
439
440 /**
441 * Check the gateway MD5 response to make sure that this is a proper
442 * gateway response
443 *
444 * @param string $responseMD5
445 * MD5 hash generated by the gateway.
446 * @param string $transaction_id
447 * Transaction id generated by the gateway.
448 * @param string $amount
449 * Purchase amount.
450 *
451 * @param bool $ipn
452 *
453 * @return bool
454 */
455 public function checkMD5($responseMD5, $transaction_id, $amount, $ipn = FALSE) {
456 // cannot check if no MD5 hash
457 $md5Hash = $this->_getParam('md5Hash');
458 if (empty($md5Hash)) {
459 return TRUE;
460 }
461 $loginid = $this->_getParam('apiLogin');
462 $hashString = $ipn ? ($md5Hash . $transaction_id . $amount) : ($md5Hash . $loginid . $transaction_id . $amount);
463 $result = strtoupper(md5($hashString));
464
465 if ($result == $responseMD5) {
466 return TRUE;
467 }
468 else {
469 return FALSE;
470 }
471 }
472
473 /**
474 * Calculate and return the transaction fingerprint.
475 *
476 * @return string
477 * fingerprint
478 */
479 public function CalculateFP() {
480 $x_tran_key = $this->_getParam('paymentKey');
481 $loginid = $this->_getParam('apiLogin');
482 $sequence = $this->_getParam('sequence');
483 $timestamp = $this->_getParam('timestamp');
484 $amount = $this->_getParam('amount');
485 $currency = $this->_getParam('currencyID');
486 $transaction = "$loginid^$sequence^$timestamp^$amount^$currency";
487 return $this->hmac($x_tran_key, $transaction);
488 }
489
490 /**
491 * Split a CSV file. Requires , as delimiter and " as enclosure.
492 * Based off notes from http://php.net/fgetcsv
493 *
494 * @param string $data
495 * A single CSV line.
496 *
497 * @return array
498 * CSV fields
499 */
500 public function explode_csv($data) {
501 $data = trim($data);
502 //make it easier to parse fields with quotes in them
503 $data = str_replace('""', "''", $data);
504 $fields = array();
505
506 while ($data != '') {
507 $matches = array();
508 if ($data[0] == '"') {
509 // handle quoted fields
510 preg_match('/^"(([^"]|\\")*?)",?(.*)$/', $data, $matches);
511
512 $fields[] = str_replace("''", '"', $matches[1]);
513 $data = $matches[3];
514 }
515 else {
516 preg_match('/^([^,]*),?(.*)$/', $data, $matches);
517
518 $fields[] = $matches[1];
519 $data = $matches[2];
520 }
521 }
522 return $fields;
523 }
524
525 /**
526 * Extract variables from returned XML.
527 *
528 * Function is from Authorize.Net sample code, and used
529 * to prevent the requirement of XML functions.
530 *
531 * @param string $content
532 * XML reply from Authorize.Net.
533 *
534 * @return array
535 * refId, resultCode, code, text, subscriptionId
536 */
537 public function _parseArbReturn($content) {
538 $refId = $this->_substring_between($content, '<refId>', '</refId>');
539 $resultCode = $this->_substring_between($content, '<resultCode>', '</resultCode>');
540 $code = $this->_substring_between($content, '<code>', '</code>');
541 $text = $this->_substring_between($content, '<text>', '</text>');
542 $subscriptionId = $this->_substring_between($content, '<subscriptionId>', '</subscriptionId>');
543 return array(
544 'refId' => $refId,
545 'resultCode' => $resultCode,
546 'code' => $code,
547 'text' => $text,
548 'subscriptionId' => $subscriptionId,
549 );
550 }
551
552 /**
553 * Helper function for _parseArbReturn.
554 *
555 * Function is from Authorize.Net sample code, and used to avoid using
556 * PHP5 XML functions
557 */
558 public function _substring_between(&$haystack, $start, $end) {
559 if (strpos($haystack, $start) === FALSE || strpos($haystack, $end) === FALSE) {
560 return FALSE;
561 }
562 else {
563 $start_position = strpos($haystack, $start) + strlen($start);
564 $end_position = strpos($haystack, $end);
565 return substr($haystack, $start_position, $end_position - $start_position);
566 }
567 }
568
569 /**
570 * Get the value of a field if set.
571 *
572 * @param string $field
573 * The field.
574 *
575 * @param bool $xmlSafe
576 * @return mixed
577 * value of the field, or empty string if the field is
578 * not set
579 */
580 public function _getParam($field, $xmlSafe = FALSE) {
581 $value = CRM_Utils_Array::value($field, $this->_params, '');
582 if ($xmlSafe) {
583 $value = str_replace(array('&', '"', "'", '<', '>'), '', $value);
584 }
585 return $value;
586 }
587
588 /**
589 * @param null $errorCode
590 * @param null $errorMessage
591 *
592 * @return object
593 */
594 public function &error($errorCode = NULL, $errorMessage = NULL) {
595 $e = CRM_Core_Error::singleton();
596 if ($errorCode) {
597 $e->push($errorCode, 0, array(), $errorMessage);
598 }
599 else {
600 $e->push(9001, 0, array(), 'Unknown System Error.');
601 }
602 return $e;
603 }
604
605 /**
606 * Set a field to the specified value. Value must be a scalar (int,
607 * float, string, or boolean)
608 *
609 * @param string $field
610 * @param mixed $value
611 *
612 * @return bool
613 * false if value is not a scalar, true if successful
614 */
615 public function _setParam($field, $value) {
616 if (!is_scalar($value)) {
617 return FALSE;
618 }
619 else {
620 $this->_params[$field] = $value;
621 }
622 }
623
624 /**
625 * This function checks to see if we have the right config values.
626 *
627 * @return string
628 * the error message if any
629 */
630 public function checkConfig() {
631 $error = array();
632 if (empty($this->_paymentProcessor['user_name'])) {
633 $error[] = ts('APILogin is not set for this payment processor');
634 }
635
636 if (empty($this->_paymentProcessor['password'])) {
637 $error[] = ts('Key is not set for this payment processor');
638 }
639
640 if (!empty($error)) {
641 return implode('<p>', $error);
642 }
643 else {
644 return NULL;
645 }
646 }
647
648 /**
649 * @return string
650 */
651 public function accountLoginURL() {
652 return ($this->_mode == 'test') ? 'https://test.authorize.net' : 'https://authorize.net';
653 }
654
655 /**
656 * @param string $message
657 * @param array $params
658 *
659 * @return bool|object
660 */
661 public function cancelSubscription(&$message = '', $params = array()) {
662 $template = CRM_Core_Smarty::singleton();
663
664 $template->assign('subscriptionType', 'cancel');
665
666 $template->assign('apiLogin', $this->_getParam('apiLogin'));
667 $template->assign('paymentKey', $this->_getParam('paymentKey'));
668 $template->assign('subscriptionId', CRM_Utils_Array::value('subscriptionId', $params));
669
670 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
671
672 // submit to authorize.net
673 $submit = curl_init($this->_paymentProcessor['url_recur']);
674 if (!$submit) {
675 return self::error(9002, 'Could not initiate connection to payment gateway');
676 }
677
678 curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1);
679 curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
680 curl_setopt($submit, CURLOPT_HEADER, 1);
681 curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML);
682 curl_setopt($submit, CURLOPT_POST, 1);
683 curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL'));
684
685 $response = curl_exec($submit);
686
687 if (!$response) {
688 return self::error(curl_errno($submit), curl_error($submit));
689 }
690
691 curl_close($submit);
692
693 $responseFields = $this->_ParseArbReturn($response);
694 $message = "{$responseFields['code']}: {$responseFields['text']}";
695
696 if ($responseFields['resultCode'] == 'Error') {
697 return self::error($responseFields['code'], $responseFields['text']);
698 }
699 return TRUE;
700 }
701
702 /**
703 * @param string $message
704 * @param array $params
705 *
706 * @return bool|object
707 */
708 public function updateSubscriptionBillingInfo(&$message = '', $params = array()) {
709 $template = CRM_Core_Smarty::singleton();
710 $template->assign('subscriptionType', 'updateBilling');
711
712 $template->assign('apiLogin', $this->_getParam('apiLogin'));
713 $template->assign('paymentKey', $this->_getParam('paymentKey'));
714 $template->assign('subscriptionId', $params['subscriptionId']);
715
716 $template->assign('cardNumber', $params['credit_card_number']);
717 $exp_month = str_pad($params['month'], 2, '0', STR_PAD_LEFT);
718 $exp_year = $params['year'];
719 $template->assign('expirationDate', $exp_year . '-' . $exp_month);
720
721 $template->assign('billingFirstName', $params['first_name']);
722 $template->assign('billingLastName', $params['last_name']);
723 $template->assign('billingAddress', $params['street_address']);
724 $template->assign('billingCity', $params['city']);
725 $template->assign('billingState', $params['state_province']);
726 $template->assign('billingZip', $params['postal_code']);
727 $template->assign('billingCountry', $params['country']);
728
729 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
730
731 // submit to authorize.net
732 $submit = curl_init($this->_paymentProcessor['url_recur']);
733 if (!$submit) {
734 return self::error(9002, 'Could not initiate connection to payment gateway');
735 }
736
737 curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1);
738 curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
739 curl_setopt($submit, CURLOPT_HEADER, 1);
740 curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML);
741 curl_setopt($submit, CURLOPT_POST, 1);
742 curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL'));
743
744 $response = curl_exec($submit);
745
746 if (!$response) {
747 return self::error(curl_errno($submit), curl_error($submit));
748 }
749
750 curl_close($submit);
751
752 $responseFields = $this->_ParseArbReturn($response);
753 $message = "{$responseFields['code']}: {$responseFields['text']}";
754
755 if ($responseFields['resultCode'] == 'Error') {
756 return self::error($responseFields['code'], $responseFields['text']);
757 }
758 return TRUE;
759 }
760
761 /**
762 * Process incoming notification.
763 */
764 static public function handlePaymentNotification() {
765 $ipnClass = new CRM_Core_Payment_AuthorizeNetIPN(array_merge($_GET, $_REQUEST));
766 $ipnClass->main();
767 }
768
769 /**
770 * @param string $message
771 * @param array $params
772 *
773 * @return bool|object
774 */
775 public function changeSubscriptionAmount(&$message = '', $params = array()) {
776 $template = CRM_Core_Smarty::singleton();
777
778 $template->assign('subscriptionType', 'update');
779
780 $template->assign('apiLogin', $this->_getParam('apiLogin'));
781 $template->assign('paymentKey', $this->_getParam('paymentKey'));
782
783 $template->assign('subscriptionId', $params['subscriptionId']);
784
785 // for open ended subscription totalOccurrences has to be 9999
786 $installments = empty($params['installments']) ? 9999 : $params['installments'];
787 $template->assign('totalOccurrences', $installments);
788
789 $template->assign('amount', $params['amount']);
790
791 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
792
793 // submit to authorize.net
794 $submit = curl_init($this->_paymentProcessor['url_recur']);
795 if (!$submit) {
796 return self::error(9002, 'Could not initiate connection to payment gateway');
797 }
798
799 curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1);
800 curl_setopt($submit, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));
801 curl_setopt($submit, CURLOPT_HEADER, 1);
802 curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML);
803 curl_setopt($submit, CURLOPT_POST, 1);
804 curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'verifySSL'));
805
806 $response = curl_exec($submit);
807
808 if (!$response) {
809 return self::error(curl_errno($submit), curl_error($submit));
810 }
811
812 curl_close($submit);
813
814 $responseFields = $this->_ParseArbReturn($response);
815 $message = "{$responseFields['code']}: {$responseFields['text']}";
816
817 if ($responseFields['resultCode'] == 'Error') {
818 return self::error($responseFields['code'], $responseFields['text']);
819 }
820 return TRUE;
821 }
822
823 }