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