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