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