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