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