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