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