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