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