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