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