INFRA-132 - Drupal.WhiteSpace.ScopeIndent.Incorrect
[civicrm-core.git] / CRM / Core / Payment / IATS.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the Affero General Public License Version 1, |
12 | March 2002. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @author Alan Dixon
32 * @copyright CiviCRM LLC (c) 2004-2014
33 * $Id$
34 *
35 */
36 class CRM_Core_Payment_IATS extends CRM_Core_Payment {
37 # (not used, implicit in the API, might need to convert?)
38 const CHARSET = 'UFT-8';
39 /* check IATS website for additional supported currencies */
40 const CURRENCIES = 'CAD,USD,AUD,GBP,EUR,NZD';
41
42 /**
43 * We only need one instance of this object. So we use the singleton
44 * pattern and cache the instance in this variable
45 *
46 * @var object
47 */
48 static private $_singleton = NULL;
49
50 /**
51 * Constructor
52 *
53 * @param string $mode
54 * The mode of operation: live or test.
55 *
56 * @param $paymentProcessor
57 *
58 * @return \CRM_Core_Payment_IATS
59 */
60 public function __construct($mode, &$paymentProcessor) {
61 $this->_paymentProcessor = $paymentProcessor;
62 $this->_processorName = ts('IATS');
63
64 // get merchant data from config
65 $config = CRM_Core_Config::singleton();
66 // live or test
67 $this->_profile['mode'] = $mode;
68 $this->_profile['webserver'] = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
69 $currencyID = $config->defaultCurrency;
70
71 if (!in_array($currencyID, explode(',', self::CURRENCIES))) {
72 // Configuration error: default currency must be in CURRENCIES const
73 return self::error('Invalid configuration:' . $currencyID . ', you must use one of ' . self::CURRENCIES . ' with IATS');
74 }
75 }
76
77 /**
78 * This function collects all the information from a web/api form and invokes
79 * the relevant payment processor specific functions to perform the transaction
80 *
81 * @param array $params
82 * Assoc array of input parameters for this transaction.
83 *
84 * @return array
85 * the result in an nice formatted array (or an error object)
86 * @abstract
87 */
88 public function doDirectPayment(&$params) {
89 // $result = '';
90 // foreach($params as $key => $value) {
91 // $result .= "<strong>$key</strong>: $value<br />";
92 // }
93 // return self::error($result);
94 // make sure i've been called correctly ...
95
96 if (!$this->_profile) {
97 return self::error('Unexpected error, missing profile');
98 }
99 if (!in_array($params['currencyID'], explode(',', self::CURRENCIES))) {
100 return self::error('Invalid currency selection, must be one of ' . self::CURRENCIES);
101 }
102 $isRecur = CRM_Utils_Array::value('is_recur', $params, FALSE);
103 // AgentCode = $this->_paymentProcessor['signature'];
104 // Password = $this->_paymentProcessor['password' ];
105 // beginning of modified sample code from IATS php api include IATS supplied api library
106
107 if ($isRecur) {
108 include_once 'Services/IATS/iats_reoccur.php';
109 $iatslink1 = new iatslinkReoccur();
110 }
111 else {
112 include_once 'Services/IATS/iatslink.php';
113 $iatslink1 = new iatslink();
114 }
115
116 $iatslink1->setTestMode($this->_profile['mode'] != 'live');
117 $iatslink1->setWebServer($this->_profile['webserver']);
118
119 // return self::error($this->_profile['webserver']);
120
121 // Put your invoice here
122 $iatslink1->setInvoiceNumber($params['invoiceID']);
123
124 // $iatslink1->setCardType("VISA");
125 // If CardType is not set, iatslink will find the cardType
126 // CardType not set because IATS uses different names!
127 // $iatslink1->setCardType($params['credit_card_type']);
128
129 $iatslink1->setCardNumber($params['credit_card_number']);
130 $expiry_string = sprintf('%02d/%02d', $params['month'], ($params['year'] % 100));
131 $iatslink1->setCardExpiry($expiry_string);
132 $amount = sprintf('%01.2f', $params['amount']);
133 // sell
134 $iatslink1->setDollarAmount($amount);
135 // refund
136 //$iatslink1->setDollarAmount(-1.15);
137
138 $AgentCode = $this->_paymentProcessor['signature'];
139 $Password = $this->_paymentProcessor['password'];
140 $iatslink1->setAgentCode($AgentCode);
141 $iatslink1->setPassword($Password);
142 // send IATS my invoiceID to match things up later
143 $iatslink1->setInvoiceNumber($params['invoiceID']);
144
145 // Set billing fields
146 $iatslink1->setFirstName($params['billing_first_name']);
147 $iatslink1->setLastName($params['billing_last_name']);
148 $iatslink1->setStreetAddress($params['street_address']);
149 $iatslink1->setCity($params['city']);
150 $iatslink1->setState($params['state_province']);
151 $iatslink1->setZipCode($params['postal_code']);
152 // and now go! ... uses curl to post and retrieve values
153 // after various data integrity tests
154 // simple version
155 if (!$isRecur) {
156 // cvv2 only seems to get set for this!
157 $iatslink1->setCVV2($params['cvv2']);
158
159 // Allow further manipulation of the arguments via custom hooks,
160 // before initiating processCreditCard()
161 CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $iatslink1);
162
163 $iatslink1->processCreditCard();
164 // extra fields for recurring donations
165 }
166 else {
167 // implicit - test?: 1 == $params['frequency_interval'];
168 $scheduleType = NULL;
169 if ($params['installments']) {
170 $paymentsRecur = $params['installments'] - 1;
171 }
172 // handle unspecified installments by setting to 10 years, IATS doesn't allow indefinitely recurring contributions
173 else {
174 switch ($params['frequency_unit']) {
175 case 'week':
176 $paymentsRecur = 520;
177 case 'month':
178 $paymentsRecur = 120;
179 }
180 }
181 // IATS requires end date, calculated here
182
183 // to be converted to date format later
184 $startTime = time();
185 $date = getdate($startTime);
186
187 switch ($params['frequency_unit']) {
188 case 'week':
189 $scheduleType = 'WEEKLY';
190 $scheduleDate = $date['wday'] + 1;
191 $endTime = $startTime + ($paymentsRecur * 7 * 24 * 60 * 60);
192 break;
193
194 case 'month':
195 $scheduleType = 'MONTHLY';
196 $scheduleDate = $date['mday'];
197 $date['mon'] += $paymentsRecur;
198 while ($date['mon'] > 12) {
199 $date['mon'] -= 12;
200 $date['year'] += 1;
201 }
202 $endTime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
203 break;
204
205 default:
206 die('Invalid frequency unit!');
207 break;
208 }
209 $endDate = date('Y-m-d', $endTime);
210 $startDate = date('Y-m-d', $startTime);
211 $iatslink1->setReoccuringStatus("ON");
212 $iatslink1->setBeginDate($startDate);
213 $iatslink1->setEndDate($endDate);
214 $iatslink1->setScheduleType($scheduleType);
215 $iatslink1->setScheduleDate($scheduleDate);
216
217 // Allow further manipulation of the arguments via custom hooks,
218 // before initiating the curl process
219 CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $iatslink1);
220
221 // this next line is the reoccc equiv of processCreditCard
222 $iatslink1->createReoccCustomer();
223 }
224
225 if ($iatslink1->getStatus() == 1) {
226 // this just means we got some kind of answer, not necessarily approved
227 $result = $iatslink1->getAuthorizationResult();
228 //return self::error($result);
229 $result = explode(':', $result, 2);
230 $trxn_result = trim($result[0]);
231 $trxn_id = trim($result[1]);
232 if ($trxn_result == 'OK') {
233 $params['trxn_id'] = $trxn_id . ':' . time();
234 $params['gross_amount'] = $amount;
235 return $params;
236 }
237 // createReoccCustomer() may return other, valid result codes...
238 elseif (preg_match('/A\d+/', $trxn_result)) {
239 $params['trxn_id'] = $trxn_result;
240 $params['gross_amount'] = $amount;
241 return $params;
242 }
243 else {
244 return self::error($trxn_id);
245 }
246 }
247 else {
248 return self::error($iatslink1->getError());
249 }
250 }
251
252 /**
253 * @param null $error
254 *
255 * @return object
256 */
257 public function &error($error = NULL) {
258 $e = CRM_Core_Error::singleton();
259 if (is_object($error)) {
260 $e->push($error->getResponseCode(),
261 0, NULL,
262 $error->getMessage()
263 );
264 }
265 elseif ($error && is_numeric($error)) {
266 $e->push($error,
267 0, NULL,
268 $this->errorString($error)
269 );
270 }
271 elseif (is_string($error)) {
272 $e->push(9002,
273 0, NULL,
274 $error
275 );
276 }
277 else {
278 $e->push(9001, 0, NULL, "Unknown System Error.");
279 }
280 return $e;
281 }
282
283 /**
284 * @param int $error_id
285 *
286 * @return string
287 */
288 public function errorString($error_id) {
289 $errors = array(
290 1 => 'Agent Code has not been set up on the authorization system.',
291 2 => 'Unable to process transaction. Verify and re-enter credit card information.',
292 3 => 'Charge card expired.',
293 4 => 'Incorrect expiration date.',
294 5 => 'Invalid transaction. Verify and re-enter credit card information.',
295 6 => 'Transaction not supported by institution.',
296 7 => 'Lost or stolen card.',
297 8 => 'Invalid card status.',
298 9 => 'Restricted card status. Usually on corporate cards restricted to specific sales.',
299 10 => 'Error. Please verify and re-enter credit card information.',
300 11 => 'General decline code, may have different reasons for each card type. Please have your client call customer service.',
301 14 => 'This means that the credit card is over the limit.',
302 15 => 'Decline code, may have different reasons for each card type. Please have your client call customer service.',
303 16 => 'Invalid charge card number. Verify and re-enter credit card information.',
304 17 => 'Unable to authorize transaction. Verify card information with customer and re-enter. Could be invalid name or expiry date.',
305 18 => 'Card not supported by institution.',
306 19 => 'Incorrect CVV2.',
307 22 => 'Bank Timeout. Bank lines may be down or busy. Re-try transaction later.',
308 23 => 'System error. Re-try transaction later.',
309 24 => 'Charge card expired.',
310 25 => 'Capture card. Reported lost or stolen.',
311 27 => 'System error, please re-enter transaction.',
312 29 => 'Rejected by Ticketmaster.',
313 31 => 'Manual reject code ',
314 39 => 'Contact Ticketmaster 1-888-955-5455 ',
315 40 => 'Card not supported by Ticketmaster. Invalid cc number.',
316 41 => 'Invalid Expiry date ',
317 100 => 'Authorization system down. DO NOT REPROCESS.',
318 );
319 return ' <strong>' . $errors[(integer) $error_id] . '</strong>';
320 }
321
322 /**
323 * This function checks to see if we have the right config values
324 *
325 * @internal param string $mode the mode we are operating in (live or test)
326 *
327 * @return string
328 * the error message if any
329 */
330 public function checkConfig() {
331 $error = array();
332
333 if (empty($this->_paymentProcessor['signature'])) {
334 $error[] = ts('Agent Code is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
335 }
336
337 if (empty($this->_paymentProcessor['password'])) {
338 $error[] = ts('Password is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
339 }
340
341 if (!empty($error)) {
342 return implode('<p>', $error);
343 }
344 else {
345 return NULL;
346 }
347 }
348
349 }