dev/financial#131 Remove last places where Core Processors return error object
[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 115 if (!defined('CURLOPT_SSLCERT')) {
5aa04455 116 // Note that guzzle doesn't necessarily require CURL, although it prefers it. But we should leave this error
117 // here unless someone suggests it is not required since it's likely helpful.
118 throw new PaymentProcessorException('Authorize.Net requires curl with SSL support', 9001);
6a488035
TO
119 }
120
121 /*
b44e3f84 122 * recurpayment function does not compile an array & then process it -
6a488035
TO
123 * - the tpl does the transformation so adding call to hook here
124 * & giving it a change to act on the params array
125 */
126 $newParams = $params;
cd125a40 127 if (!empty($params['is_recur']) && !empty($params['contributionRecurID'])) {
6a488035
TO
128 CRM_Utils_Hook::alterPaymentProcessorParams($this,
129 $params,
130 $newParams
131 );
132 }
133 foreach ($newParams as $field => $value) {
134 $this->_setParam($field, $value);
135 }
136
cd125a40 137 if (!empty($params['is_recur']) && !empty($params['contributionRecurID'])) {
2edc95e6 138 $this->doRecurPayment();
6a488035
TO
139 return $params;
140 }
141
be2fb01f 142 $postFields = [];
6a488035
TO
143 $authorizeNetFields = $this->_getAuthorizeNetFields();
144
145 // Set up our call for hook_civicrm_paymentProcessor,
146 // since we now have our parameters as assigned for the AIM back end.
147 CRM_Utils_Hook::alterPaymentProcessorParams($this,
148 $params,
149 $authorizeNetFields
150 );
151
152 foreach ($authorizeNetFields as $field => $value) {
153 // CRM-7419, since double quote is used as enclosure while doing csv parsing
154 $value = ($field == 'x_description') ? str_replace('"', "'", $value) : $value;
155 $postFields[] = $field . '=' . urlencode($value);
156 }
157
158 // Authorize.Net will not refuse duplicates, so we should check if the user already submitted this transaction
d253aeb8 159 if ($this->checkDupe($authorizeNetFields['x_invoice_num'], CRM_Utils_Array::value('contributionID', $params))) {
39fef483 160 throw new PaymentProcessorException('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.', 9004);
6a488035
TO
161 }
162
5aa04455 163 $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_site'], [
164 'body' => implode('&', $postFields),
165 'curl' => [
166 CURLOPT_RETURNTRANSFER => TRUE,
167 CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
168 ],
169 ])->getBody();
6a488035
TO
170
171 $response_fields = $this->explode_csv($response);
48e3da59 172
173 // fetch available contribution statuses
174 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
175
6a488035
TO
176 // check for application errors
177 // TODO:
178 // AVS, CVV2, CAVV, and other verification results
16106615 179 switch ($response_fields[0]) {
d167bea8 180 case self::AUTH_REVIEW:
16106615 181 $params['payment_status_id'] = array_search('Pending', $contributionStatus);
182 break;
183
d167bea8 184 case self::AUTH_ERROR:
16106615 185 $params['payment_status_id'] = array_search('Failed', $contributionStatus);
d15a29b5 186 $errormsg = $response_fields[2] . ' ' . $response_fields[3];
39fef483 187 throw new PaymentProcessorException($errormsg, $response_fields[1]);
16106615 188
d167bea8 189 case self::AUTH_DECLINED:
16106615 190 $errormsg = $response_fields[2] . ' ' . $response_fields[3];
39fef483 191 throw new PaymentProcessorException($errormsg, $response_fields[1]);
16106615 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]+'";
be2fb01f 203 $p = [];
16106615 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;
6a488035 214 }
6a488035
TO
215 // TODO: include authorization code?
216
217 return $params;
218 }
219
220 /**
fe482240 221 * Submit an Automated Recurring Billing subscription.
6a488035 222 */
00be9182 223 public function doRecurPayment() {
6a488035
TO
224 $template = CRM_Core_Smarty::singleton();
225
226 $intervalLength = $this->_getParam('frequency_interval');
227 $intervalUnit = $this->_getParam('frequency_unit');
2edc95e6 228 if ($intervalUnit === 'week') {
6a488035
TO
229 $intervalLength *= 7;
230 $intervalUnit = 'days';
231 }
2edc95e6 232 elseif ($intervalUnit === 'year') {
6a488035
TO
233 $intervalLength *= 12;
234 $intervalUnit = 'months';
235 }
31c873bf 236 elseif ($intervalUnit === 'day') {
6a488035 237 $intervalUnit = 'days';
2edc95e6 238 // interval cannot be less than 7 days or more than 1 year
6a488035 239 if ($intervalLength < 7) {
2edc95e6 240 throw new PaymentProcessorException('Payment interval must be at least one week', 9001);
6a488035 241 }
2edc95e6 242 if ($intervalLength > 365) {
243 throw new PaymentProcessorException('Payment interval may not be longer than one year', 9001);
6a488035
TO
244 }
245 }
2edc95e6 246 elseif ($intervalUnit === 'month') {
247 $intervalUnit = 'months';
6a488035 248 if ($intervalLength < 1) {
2edc95e6 249 throw new PaymentProcessorException('Payment interval must be at least one week', 9001);
6a488035 250 }
2edc95e6 251 if ($intervalLength > 12) {
252 throw new PaymentProcessorException('Payment interval may not be longer than one year', 9001);
6a488035
TO
253 }
254 }
255
256 $template->assign('intervalLength', $intervalLength);
257 $template->assign('intervalUnit', $intervalUnit);
258
259 $template->assign('apiLogin', $this->_getParam('apiLogin'));
260 $template->assign('paymentKey', $this->_getParam('paymentKey'));
261 $template->assign('refId', substr($this->_getParam('invoiceID'), 0, 20));
262
263 //for recurring, carry first contribution id
264 $template->assign('invoiceNumber', $this->_getParam('contributionID'));
265 $firstPaymentDate = $this->_getParam('receive_date');
266 if (!empty($firstPaymentDate)) {
267 //allow for post dated payment if set in form
268 $startDate = date_create($firstPaymentDate);
269 }
270 else {
271 $startDate = date_create();
272 }
273 /* Format start date in Mountain Time to avoid Authorize.net error E00017
274 * we do this only if the day we are setting our start time to is LESS than the current
275 * day in mountaintime (ie. the server time of the A-net server). A.net won't accept a date
276 * earlier than the current date on it's server so if we are in PST we might need to use mountain
277 * time to bring our date forward. But if we are submitting something future dated we want
278 * the date we entered to be respected
279 */
280 $minDate = date_create('now', new DateTimeZone(self::TIMEZONE));
9b873358 281 if (strtotime($startDate->format('Y-m-d')) < strtotime($minDate->format('Y-m-d'))) {
6a488035
TO
282 $startDate->setTimezone(new DateTimeZone(self::TIMEZONE));
283 }
284
481a74f4 285 $template->assign('startDate', $startDate->format('Y-m-d'));
a2dd09cc 286
6a488035 287 $installments = $this->_getParam('installments');
a2dd09cc
DL
288
289 // for open ended subscription totalOccurrences has to be 9999
290 $installments = empty($installments) ? 9999 : $installments;
291 $template->assign('totalOccurrences', $installments);
6a488035
TO
292
293 $template->assign('amount', $this->_getParam('amount'));
294
295 $template->assign('cardNumber', $this->_getParam('credit_card_number'));
296 $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT);
297 $exp_year = $this->_getParam('year');
298 $template->assign('expirationDate', $exp_year . '-' . $exp_month);
299
300 // name rather than description is used in the tpl - see http://www.authorize.net/support/ARB_guide.pdf
301 $template->assign('name', $this->_getParam('description', TRUE));
302
303 $template->assign('email', $this->_getParam('email'));
304 $template->assign('contactID', $this->_getParam('contactID'));
305 $template->assign('billingFirstName', $this->_getParam('billing_first_name'));
306 $template->assign('billingLastName', $this->_getParam('billing_last_name'));
307 $template->assign('billingAddress', $this->_getParam('street_address', TRUE));
308 $template->assign('billingCity', $this->_getParam('city', TRUE));
309 $template->assign('billingState', $this->_getParam('state_province'));
310 $template->assign('billingZip', $this->_getParam('postal_code', TRUE));
311 $template->assign('billingCountry', $this->_getParam('country'));
312
313 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
6a488035 314
9fa25593 315 // Submit to authorize.net
316 $response = $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [
317 'headers' => [
318 'Content-Type' => 'text/xml; charset=UTF8',
319 ],
320 'body' => $arbXML,
321 'curl' => [
322 CURLOPT_RETURNTRANSFER => TRUE,
323 CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
324 ],
325 ]);
326 $responseFields = $this->_ParseArbReturn((string) $response->getBody());
327
328 if ($responseFields['resultCode'] === 'Error') {
884a213d 329 throw new PaymentProcessorException($responseFields['text'], $responseFields['code']);
6a488035
TO
330 }
331
332 // update recur processor_id with subscriptionId
333 CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_ContributionRecur', $this->_getParam('contributionRecurID'),
334 'processor_id', $responseFields['subscriptionId']
335 );
336 //only impact of assigning this here is is can be used to cancel the subscription in an automated test
337 // if it isn't cancelled a duplicate transaction error occurs
a7488080 338 if (!empty($responseFields['subscriptionId'])) {
6a488035
TO
339 $this->_setParam('subscriptionId', $responseFields['subscriptionId']);
340 }
341 }
342
6c786a9b
EM
343 /**
344 * @return array
345 */
00be9182 346 public function _getAuthorizeNetFields() {
518fa0ee
SL
347 //Total amount is from the form contribution field
348 $amount = $this->_getParam('total_amount');
349 //CRM-9894 would this ever be the case??
350 if (empty($amount)) {
6a488035
TO
351 $amount = $this->_getParam('amount');
352 }
be2fb01f 353 $fields = [];
6a488035
TO
354 $fields['x_login'] = $this->_getParam('apiLogin');
355 $fields['x_tran_key'] = $this->_getParam('paymentKey');
356 $fields['x_email_customer'] = $this->_getParam('emailCustomer');
357 $fields['x_first_name'] = $this->_getParam('billing_first_name');
358 $fields['x_last_name'] = $this->_getParam('billing_last_name');
359 $fields['x_address'] = $this->_getParam('street_address');
360 $fields['x_city'] = $this->_getParam('city');
361 $fields['x_state'] = $this->_getParam('state_province');
362 $fields['x_zip'] = $this->_getParam('postal_code');
363 $fields['x_country'] = $this->_getParam('country');
364 $fields['x_customer_ip'] = $this->_getParam('ip_address');
365 $fields['x_email'] = $this->_getParam('email');
5bd23b41 366 $fields['x_invoice_num'] = $this->_getParam('invoiceID');
353ffa53 367 $fields['x_amount'] = $amount;
6a488035
TO
368 $fields['x_currency_code'] = $this->_getParam('currencyID');
369 $fields['x_description'] = $this->_getParam('description');
370 $fields['x_cust_id'] = $this->_getParam('contactID');
371 if ($this->_getParam('paymentType') == 'AIM') {
372 $fields['x_relay_response'] = 'FALSE';
373 // request response in CSV format
374 $fields['x_delim_data'] = 'TRUE';
375 $fields['x_delim_char'] = ',';
376 $fields['x_encap_char'] = '"';
377 // cc info
378 $fields['x_card_num'] = $this->_getParam('credit_card_number');
379 $fields['x_card_code'] = $this->_getParam('cvv2');
380 $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT);
381 $exp_year = $this->_getParam('year');
382 $fields['x_exp_date'] = "$exp_month/$exp_year";
383 }
384
385 if ($this->_mode != 'live') {
386 $fields['x_test_request'] = 'TRUE';
387 }
388
389 return $fields;
390 }
391
6a488035
TO
392 /**
393 * Generate HMAC_MD5
394 *
395 * @param string $key
396 * @param string $data
397 *
a6c01b45
CW
398 * @return string
399 * the HMAC_MD5 encoding string
7c550ca0 400 */
00be9182 401 public function hmac($key, $data) {
6a488035
TO
402 if (function_exists('mhash')) {
403 // Use PHP mhash extension
404 return (bin2hex(mhash(MHASH_MD5, $data, $key)));
405 }
406 else {
407 // RFC 2104 HMAC implementation for php.
408 // Creates an md5 HMAC.
409 // Eliminates the need to install mhash to compute a HMAC
410 // Hacked by Lance Rushing
411 // byte length for md5
412 $b = 64;
413 if (strlen($key) > $b) {
414 $key = pack("H*", md5($key));
415 }
353ffa53
TO
416 $key = str_pad($key, $b, chr(0x00));
417 $ipad = str_pad('', $b, chr(0x36));
418 $opad = str_pad('', $b, chr(0x5c));
6a488035
TO
419 $k_ipad = $key ^ $ipad;
420 $k_opad = $key ^ $opad;
421 return md5($k_opad . pack("H*", md5($k_ipad . $data)));
422 }
423 }
424
6a488035 425 /**
fe482240 426 * Calculate and return the transaction fingerprint.
6a488035 427 *
a6c01b45
CW
428 * @return string
429 * fingerprint
7c550ca0 430 */
00be9182 431 public function CalculateFP() {
353ffa53
TO
432 $x_tran_key = $this->_getParam('paymentKey');
433 $loginid = $this->_getParam('apiLogin');
434 $sequence = $this->_getParam('sequence');
435 $timestamp = $this->_getParam('timestamp');
436 $amount = $this->_getParam('amount');
437 $currency = $this->_getParam('currencyID');
6a488035
TO
438 $transaction = "$loginid^$sequence^$timestamp^$amount^$currency";
439 return $this->hmac($x_tran_key, $transaction);
440 }
441
442 /**
443 * Split a CSV file. Requires , as delimiter and " as enclosure.
444 * Based off notes from http://php.net/fgetcsv
445 *
6a0b768e
TO
446 * @param string $data
447 * A single CSV line.
6a488035 448 *
a6c01b45
CW
449 * @return array
450 * CSV fields
6a488035 451 */
00be9182 452 public function explode_csv($data) {
6a488035
TO
453 $data = trim($data);
454 //make it easier to parse fields with quotes in them
455 $data = str_replace('""', "''", $data);
be2fb01f 456 $fields = [];
6a488035
TO
457
458 while ($data != '') {
be2fb01f 459 $matches = [];
6a488035
TO
460 if ($data[0] == '"') {
461 // handle quoted fields
462 preg_match('/^"(([^"]|\\")*?)",?(.*)$/', $data, $matches);
463
464 $fields[] = str_replace("''", '"', $matches[1]);
465 $data = $matches[3];
466 }
467 else {
468 preg_match('/^([^,]*),?(.*)$/', $data, $matches);
469
470 $fields[] = $matches[1];
471 $data = $matches[2];
472 }
473 }
474 return $fields;
475 }
476
477 /**
fe482240 478 * Extract variables from returned XML.
6a488035
TO
479 *
480 * Function is from Authorize.Net sample code, and used
481 * to prevent the requirement of XML functions.
482 *
6a0b768e
TO
483 * @param string $content
484 * XML reply from Authorize.Net.
6a488035 485 *
a6c01b45
CW
486 * @return array
487 * refId, resultCode, code, text, subscriptionId
6a488035 488 */
00be9182 489 public function _parseArbReturn($content) {
353ffa53
TO
490 $refId = $this->_substring_between($content, '<refId>', '</refId>');
491 $resultCode = $this->_substring_between($content, '<resultCode>', '</resultCode>');
492 $code = $this->_substring_between($content, '<code>', '</code>');
493 $text = $this->_substring_between($content, '<text>', '</text>');
6a488035 494 $subscriptionId = $this->_substring_between($content, '<subscriptionId>', '</subscriptionId>');
be2fb01f 495 return [
6a488035
TO
496 'refId' => $refId,
497 'resultCode' => $resultCode,
498 'code' => $code,
499 'text' => $text,
500 'subscriptionId' => $subscriptionId,
be2fb01f 501 ];
6a488035
TO
502 }
503
504 /**
fe482240 505 * Helper function for _parseArbReturn.
6a488035
TO
506 *
507 * Function is from Authorize.Net sample code, and used to avoid using
508 * PHP5 XML functions
54957108 509 *
510 * @param string $haystack
511 * @param string $start
512 * @param string $end
513 *
514 * @return bool|string
6a488035 515 */
00be9182 516 public function _substring_between(&$haystack, $start, $end) {
6a488035
TO
517 if (strpos($haystack, $start) === FALSE || strpos($haystack, $end) === FALSE) {
518 return FALSE;
519 }
520 else {
521 $start_position = strpos($haystack, $start) + strlen($start);
522 $end_position = strpos($haystack, $end);
523 return substr($haystack, $start_position, $end_position - $start_position);
524 }
525 }
526
527 /**
fe482240 528 * Get the value of a field if set.
6a488035 529 *
6a0b768e
TO
530 * @param string $field
531 * The field.
6a488035 532 *
2a6da8d7 533 * @param bool $xmlSafe
72b3a70c
CW
534 * @return mixed
535 * value of the field, or empty string if the field is
16b10e64 536 * not set
6a488035 537 */
00be9182 538 public function _getParam($field, $xmlSafe = FALSE) {
6a488035 539 $value = CRM_Utils_Array::value($field, $this->_params, '');
a2dd09cc 540 if ($xmlSafe) {
be2fb01f 541 $value = str_replace(['&', '"', "'", '<', '>'], '', $value);
a2dd09cc 542 }
6a488035
TO
543 return $value;
544 }
545
6a488035
TO
546 /**
547 * Set a field to the specified value. Value must be a scalar (int,
548 * float, string, or boolean)
549 *
550 * @param string $field
551 * @param mixed $value
552 *
a6c01b45
CW
553 * @return bool
554 * false if value is not a scalar, true if successful
6a488035 555 */
00be9182 556 public function _setParam($field, $value) {
6a488035
TO
557 if (!is_scalar($value)) {
558 return FALSE;
559 }
560 else {
561 $this->_params[$field] = $value;
562 }
563 }
564
565 /**
fe482240 566 * This function checks to see if we have the right config values.
6a488035 567 *
a6c01b45
CW
568 * @return string
569 * the error message if any
6a488035 570 */
00be9182 571 public function checkConfig() {
be2fb01f 572 $error = [];
6a488035
TO
573 if (empty($this->_paymentProcessor['user_name'])) {
574 $error[] = ts('APILogin is not set for this payment processor');
575 }
576
577 if (empty($this->_paymentProcessor['password'])) {
578 $error[] = ts('Key is not set for this payment processor');
579 }
580
581 if (!empty($error)) {
582 return implode('<p>', $error);
583 }
584 else {
585 return NULL;
586 }
587 }
588
6c786a9b
EM
589 /**
590 * @return string
591 */
00be9182 592 public function accountLoginURL() {
6a488035
TO
593 return ($this->_mode == 'test') ? 'https://test.authorize.net' : 'https://authorize.net';
594 }
595
6c786a9b
EM
596 /**
597 * @param string $message
598 * @param array $params
599 *
600 * @return bool|object
884a213d 601 * @throws \Civi\Payment\Exception\PaymentProcessorException
6c786a9b 602 */
be2fb01f 603 public function cancelSubscription(&$message = '', $params = []) {
6a488035
TO
604 $template = CRM_Core_Smarty::singleton();
605
606 $template->assign('subscriptionType', 'cancel');
607
608 $template->assign('apiLogin', $this->_getParam('apiLogin'));
609 $template->assign('paymentKey', $this->_getParam('paymentKey'));
884a213d 610 $template->assign('subscriptionId', $params['subscriptionId']);
6a488035
TO
611
612 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
613
884a213d 614 $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [
615 'headers' => [
616 'Content-Type' => 'text/xml; charset=UTF8',
617 ],
618 'body' => $arbXML,
619 'curl' => [
620 CURLOPT_RETURNTRANSFER => TRUE,
621 CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
622 ],
623 ])->getBody();
6a488035
TO
624
625 $responseFields = $this->_ParseArbReturn($response);
626 $message = "{$responseFields['code']}: {$responseFields['text']}";
627
884a213d 628 if ($responseFields['resultCode'] === 'Error') {
629 throw new PaymentProcessorException($responseFields['text'], $responseFields['code']);
6a488035
TO
630 }
631 return TRUE;
632 }
633
6c786a9b 634 /**
884a213d 635 * Update payment details at Authorize.net.
636 *
6c786a9b
EM
637 * @param string $message
638 * @param array $params
639 *
640 * @return bool|object
884a213d 641 *
642 * @throws \Civi\Payment\Exception\PaymentProcessorException
6c786a9b 643 */
be2fb01f 644 public function updateSubscriptionBillingInfo(&$message = '', $params = []) {
6a488035
TO
645 $template = CRM_Core_Smarty::singleton();
646 $template->assign('subscriptionType', 'updateBilling');
647
648 $template->assign('apiLogin', $this->_getParam('apiLogin'));
649 $template->assign('paymentKey', $this->_getParam('paymentKey'));
650 $template->assign('subscriptionId', $params['subscriptionId']);
651
652 $template->assign('cardNumber', $params['credit_card_number']);
653 $exp_month = str_pad($params['month'], 2, '0', STR_PAD_LEFT);
654 $exp_year = $params['year'];
655 $template->assign('expirationDate', $exp_year . '-' . $exp_month);
656
657 $template->assign('billingFirstName', $params['first_name']);
658 $template->assign('billingLastName', $params['last_name']);
659 $template->assign('billingAddress', $params['street_address']);
660 $template->assign('billingCity', $params['city']);
661 $template->assign('billingState', $params['state_province']);
662 $template->assign('billingZip', $params['postal_code']);
663 $template->assign('billingCountry', $params['country']);
664
665 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
666
884a213d 667 $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [
668 'headers' => [
669 'Content-Type' => 'text/xml; charset=UTF8',
670 ],
671 'body' => $arbXML,
672 'curl' => [
673 CURLOPT_RETURNTRANSFER => TRUE,
674 CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
675 ],
676 ])->getBody();
6a488035 677
884a213d 678 // submit to authorize.net
6a488035
TO
679 $responseFields = $this->_ParseArbReturn($response);
680 $message = "{$responseFields['code']}: {$responseFields['text']}";
681
884a213d 682 if ($responseFields['resultCode'] === 'Error') {
683 throw new PaymentProcessorException($responseFields['text'], $responseFields['code']);
6a488035
TO
684 }
685 return TRUE;
686 }
687
23de1ac0
EM
688 /**
689 * Process incoming notification.
690 */
518fa0ee 691 public static function handlePaymentNotification() {
23de1ac0
EM
692 $ipnClass = new CRM_Core_Payment_AuthorizeNetIPN(array_merge($_GET, $_REQUEST));
693 $ipnClass->main();
694 }
695
6c786a9b 696 /**
884a213d 697 * Change the amount of the recurring payment.
698 *
6c786a9b
EM
699 * @param string $message
700 * @param array $params
701 *
702 * @return bool|object
884a213d 703 *
704 * @throws \Civi\Payment\Exception\PaymentProcessorException
6c786a9b 705 */
be2fb01f 706 public function changeSubscriptionAmount(&$message = '', $params = []) {
6a488035
TO
707 $template = CRM_Core_Smarty::singleton();
708
709 $template->assign('subscriptionType', 'update');
710
711 $template->assign('apiLogin', $this->_getParam('apiLogin'));
712 $template->assign('paymentKey', $this->_getParam('paymentKey'));
713
714 $template->assign('subscriptionId', $params['subscriptionId']);
702c1203
JM
715
716 // for open ended subscription totalOccurrences has to be 9999
717 $installments = empty($params['installments']) ? 9999 : $params['installments'];
718 $template->assign('totalOccurrences', $installments);
719
6a488035
TO
720 $template->assign('amount', $params['amount']);
721
722 $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl');
723
884a213d 724 $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [
725 'headers' => [
726 'Content-Type' => 'text/xml; charset=UTF8',
727 ],
728 'body' => $arbXML,
729 'curl' => [
730 CURLOPT_RETURNTRANSFER => TRUE,
731 CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
732 ],
733 ])->getBody();
6a488035 734
6ea16bbf 735 $responseFields = $this->_parseArbReturn($response);
6a488035
TO
736 $message = "{$responseFields['code']}: {$responseFields['text']}";
737
884a213d 738 if ($responseFields['resultCode'] === 'Error') {
4ae44cc6 739 throw new PaymentProcessorException($responseFields['text'], $responseFields['code']);
6a488035
TO
740 }
741 return TRUE;
742 }
96025800 743
6a488035 744}