Commit | Line | Data |
---|---|---|
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 | ||
4ae44cc6 RLAR |
16 | use Civi\Payment\Exception\PaymentProcessorException; |
17 | ||
c866eb5f TO |
18 | /** |
19 | * NOTE: | |
6a488035 TO |
20 | * When looking up response codes in the Authorize.Net API, they |
21 | * begin at one, so always delete one from the "Position in Response" | |
22 | */ | |
23 | class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment { | |
7da04cde TO |
24 | const CHARSET = 'iso-8859-1'; |
25 | const AUTH_APPROVED = 1; | |
26 | const AUTH_DECLINED = 2; | |
27 | const AUTH_ERROR = 3; | |
16106615 | 28 | const AUTH_REVIEW = 4; |
7da04cde | 29 | const TIMEZONE = 'America/Denver'; |
6a488035 TO |
30 | |
31 | protected $_mode = NULL; | |
32 | ||
be2fb01f | 33 | protected $_params = []; |
6a488035 | 34 | |
9fa25593 | 35 | /** |
36 | * @var GuzzleHttp\Client | |
37 | */ | |
38 | protected $guzzleClient; | |
39 | ||
40 | /** | |
41 | * @return \GuzzleHttp\Client | |
42 | */ | |
43 | public function getGuzzleClient(): \GuzzleHttp\Client { | |
44 | return $this->guzzleClient ?? new \GuzzleHttp\Client(); | |
45 | } | |
46 | ||
47 | /** | |
48 | * @param \GuzzleHttp\Client $guzzleClient | |
49 | */ | |
50 | public function setGuzzleClient(\GuzzleHttp\Client $guzzleClient) { | |
51 | $this->guzzleClient = $guzzleClient; | |
52 | } | |
53 | ||
6a488035 | 54 | /** |
fe482240 | 55 | * Constructor. |
6a488035 | 56 | * |
6a0b768e TO |
57 | * @param string $mode |
58 | * The mode of operation: live or test. | |
6a488035 | 59 | * |
77b97be7 EM |
60 | * @param $paymentProcessor |
61 | * | |
62 | * @return \CRM_Core_Payment_AuthorizeNet | |
6a488035 | 63 | */ |
00be9182 | 64 | public function __construct($mode, &$paymentProcessor) { |
6a488035 TO |
65 | $this->_mode = $mode; |
66 | $this->_paymentProcessor = $paymentProcessor; | |
6a488035 | 67 | |
6a488035 TO |
68 | $this->_setParam('apiLogin', $paymentProcessor['user_name']); |
69 | $this->_setParam('paymentKey', $paymentProcessor['password']); | |
70 | $this->_setParam('paymentType', 'AIM'); | |
6a488035 TO |
71 | } |
72 | ||
fbcb6fba | 73 | /** |
d09edf64 | 74 | * Should the first payment date be configurable when setting up back office recurring payments. |
fbcb6fba EM |
75 | * In the case of Authorize.net this is an option |
76 | * @return bool | |
77 | */ | |
d8ce0d68 | 78 | protected function supportsFutureRecurStartDate() { |
fbcb6fba EM |
79 | return TRUE; |
80 | } | |
81 | ||
677fe56c EM |
82 | /** |
83 | * Can recurring contributions be set against pledges. | |
84 | * | |
85 | * In practice all processors that use the baseIPN function to finish transactions or | |
86 | * call the completetransaction api support this by looking up previous contributions in the | |
87 | * series and, if there is a prior contribution against a pledge, and the pledge is not complete, | |
88 | * adding the new payment to the pledge. | |
89 | * | |
90 | * However, only enabling for processors it has been tested against. | |
91 | * | |
92 | * @return bool | |
93 | */ | |
94 | protected function supportsRecurContributionsForPledges() { | |
95 | return TRUE; | |
96 | } | |
97 | ||
6a488035 | 98 | /** |
fe482240 | 99 | * Submit a payment using Advanced Integration Method. |
6a488035 | 100 | * |
5d8d2f38 MW |
101 | * @param array|\Civi\Payment\PropertyBag $params |
102 | * | |
103 | * @param string $component | |
6a488035 | 104 | * |
a6c01b45 | 105 | * @return array |
5d8d2f38 | 106 | * Result array (containing at least the key payment_status_id) |
9fa25593 | 107 | * |
108 | * @throws \Civi\Payment\Exception\PaymentProcessorException | |
6a488035 | 109 | */ |
5d8d2f38 MW |
110 | public function doPayment(&$params, $component = 'contribute') { |
111 | $propertyBag = \Civi\Payment\PropertyBag::cast($params); | |
112 | $this->_component = $component; | |
113 | $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate'); | |
114 | ||
115 | // If we have a $0 amount, skip call to processor and set payment_status to Completed. | |
116 | // Conceivably a processor might override this - perhaps for setting up a token - but we don't | |
117 | // have an example of that at the moment. | |
118 | if ($propertyBag->getAmount() == 0) { | |
119 | $result['payment_status_id'] = array_search('Completed', $statuses); | |
120 | $result['payment_status'] = 'Completed'; | |
121 | return $result; | |
122 | } | |
123 | ||
6a488035 | 124 | if (!defined('CURLOPT_SSLCERT')) { |
5aa04455 | 125 | // Note that guzzle doesn't necessarily require CURL, although it prefers it. But we should leave this error |
126 | // here unless someone suggests it is not required since it's likely helpful. | |
127 | throw new PaymentProcessorException('Authorize.Net requires curl with SSL support', 9001); | |
6a488035 TO |
128 | } |
129 | ||
130 | /* | |
b44e3f84 | 131 | * recurpayment function does not compile an array & then process it - |
6a488035 TO |
132 | * - the tpl does the transformation so adding call to hook here |
133 | * & giving it a change to act on the params array | |
134 | */ | |
135 | $newParams = $params; | |
cd125a40 | 136 | if (!empty($params['is_recur']) && !empty($params['contributionRecurID'])) { |
6a488035 TO |
137 | CRM_Utils_Hook::alterPaymentProcessorParams($this, |
138 | $params, | |
139 | $newParams | |
140 | ); | |
141 | } | |
142 | foreach ($newParams as $field => $value) { | |
143 | $this->_setParam($field, $value); | |
144 | } | |
145 | ||
cd125a40 | 146 | if (!empty($params['is_recur']) && !empty($params['contributionRecurID'])) { |
2edc95e6 | 147 | $this->doRecurPayment(); |
5d8d2f38 MW |
148 | $params['payment_status_id'] = array_search('Pending', $statuses); |
149 | $params['payment_status'] = 'Pending'; | |
6a488035 TO |
150 | return $params; |
151 | } | |
152 | ||
be2fb01f | 153 | $postFields = []; |
6a488035 TO |
154 | $authorizeNetFields = $this->_getAuthorizeNetFields(); |
155 | ||
156 | // Set up our call for hook_civicrm_paymentProcessor, | |
157 | // since we now have our parameters as assigned for the AIM back end. | |
158 | CRM_Utils_Hook::alterPaymentProcessorParams($this, | |
159 | $params, | |
160 | $authorizeNetFields | |
161 | ); | |
162 | ||
163 | foreach ($authorizeNetFields as $field => $value) { | |
164 | // CRM-7419, since double quote is used as enclosure while doing csv parsing | |
165 | $value = ($field == 'x_description') ? str_replace('"', "'", $value) : $value; | |
166 | $postFields[] = $field . '=' . urlencode($value); | |
167 | } | |
168 | ||
169 | // Authorize.Net will not refuse duplicates, so we should check if the user already submitted this transaction | |
74fd8b01 | 170 | if ($this->checkDupe($authorizeNetFields['x_invoice_num'], $params['contributionID'] ?? NULL)) { |
39fef483 | 171 | throw new PaymentProcessorException('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.', 9004); |
6a488035 TO |
172 | } |
173 | ||
5aa04455 | 174 | $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_site'], [ |
175 | 'body' => implode('&', $postFields), | |
176 | 'curl' => [ | |
177 | CURLOPT_RETURNTRANSFER => TRUE, | |
178 | CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), | |
179 | ], | |
180 | ])->getBody(); | |
6a488035 TO |
181 | |
182 | $response_fields = $this->explode_csv($response); | |
48e3da59 | 183 | |
184 | // fetch available contribution statuses | |
185 | $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); | |
186 | ||
a1278f71 | 187 | $result = []; |
6a488035 TO |
188 | // check for application errors |
189 | // TODO: | |
190 | // AVS, CVV2, CAVV, and other verification results | |
16106615 | 191 | switch ($response_fields[0]) { |
d167bea8 | 192 | case self::AUTH_REVIEW: |
a1278f71 | 193 | $result = $this->setStatusPaymentPending($result); |
16106615 | 194 | break; |
195 | ||
d167bea8 | 196 | case self::AUTH_ERROR: |
d15a29b5 | 197 | $errormsg = $response_fields[2] . ' ' . $response_fields[3]; |
39fef483 | 198 | throw new PaymentProcessorException($errormsg, $response_fields[1]); |
16106615 | 199 | |
d167bea8 | 200 | case self::AUTH_DECLINED: |
16106615 | 201 | $errormsg = $response_fields[2] . ' ' . $response_fields[3]; |
39fef483 | 202 | throw new PaymentProcessorException($errormsg, $response_fields[1]); |
16106615 | 203 | |
204 | default: | |
205 | // Success | |
a1278f71 MW |
206 | $result['trxn_id'] = !empty($response_fields[6]) ? $response_fields[6] : $this->getTestTrxnID(); |
207 | $result = $this->setStatusPaymentCompleted($result); | |
16106615 | 208 | break; |
6a488035 | 209 | } |
524cd99e | 210 | |
a1278f71 | 211 | return $result; |
6a488035 TO |
212 | } |
213 | ||
214 | /** | |
fe482240 | 215 | * Submit an Automated Recurring Billing subscription. |
6a488035 | 216 | */ |
00be9182 | 217 | public function doRecurPayment() { |
6a488035 TO |
218 | $template = CRM_Core_Smarty::singleton(); |
219 | ||
220 | $intervalLength = $this->_getParam('frequency_interval'); | |
221 | $intervalUnit = $this->_getParam('frequency_unit'); | |
2edc95e6 | 222 | if ($intervalUnit === 'week') { |
6a488035 TO |
223 | $intervalLength *= 7; |
224 | $intervalUnit = 'days'; | |
225 | } | |
2edc95e6 | 226 | elseif ($intervalUnit === 'year') { |
6a488035 TO |
227 | $intervalLength *= 12; |
228 | $intervalUnit = 'months'; | |
229 | } | |
31c873bf | 230 | elseif ($intervalUnit === 'day') { |
6a488035 | 231 | $intervalUnit = 'days'; |
2edc95e6 | 232 | // interval cannot be less than 7 days or more than 1 year |
6a488035 | 233 | if ($intervalLength < 7) { |
2edc95e6 | 234 | throw new PaymentProcessorException('Payment interval must be at least one week', 9001); |
6a488035 | 235 | } |
2edc95e6 | 236 | if ($intervalLength > 365) { |
237 | throw new PaymentProcessorException('Payment interval may not be longer than one year', 9001); | |
6a488035 TO |
238 | } |
239 | } | |
2edc95e6 | 240 | elseif ($intervalUnit === 'month') { |
241 | $intervalUnit = 'months'; | |
6a488035 | 242 | if ($intervalLength < 1) { |
2edc95e6 | 243 | throw new PaymentProcessorException('Payment interval must be at least one week', 9001); |
6a488035 | 244 | } |
2edc95e6 | 245 | if ($intervalLength > 12) { |
246 | throw new PaymentProcessorException('Payment interval may not be longer than one year', 9001); | |
6a488035 TO |
247 | } |
248 | } | |
249 | ||
250 | $template->assign('intervalLength', $intervalLength); | |
251 | $template->assign('intervalUnit', $intervalUnit); | |
252 | ||
253 | $template->assign('apiLogin', $this->_getParam('apiLogin')); | |
254 | $template->assign('paymentKey', $this->_getParam('paymentKey')); | |
255 | $template->assign('refId', substr($this->_getParam('invoiceID'), 0, 20)); | |
256 | ||
257 | //for recurring, carry first contribution id | |
258 | $template->assign('invoiceNumber', $this->_getParam('contributionID')); | |
259 | $firstPaymentDate = $this->_getParam('receive_date'); | |
260 | if (!empty($firstPaymentDate)) { | |
261 | //allow for post dated payment if set in form | |
262 | $startDate = date_create($firstPaymentDate); | |
263 | } | |
264 | else { | |
265 | $startDate = date_create(); | |
266 | } | |
267 | /* Format start date in Mountain Time to avoid Authorize.net error E00017 | |
268 | * we do this only if the day we are setting our start time to is LESS than the current | |
269 | * day in mountaintime (ie. the server time of the A-net server). A.net won't accept a date | |
270 | * earlier than the current date on it's server so if we are in PST we might need to use mountain | |
271 | * time to bring our date forward. But if we are submitting something future dated we want | |
272 | * the date we entered to be respected | |
273 | */ | |
274 | $minDate = date_create('now', new DateTimeZone(self::TIMEZONE)); | |
9b873358 | 275 | if (strtotime($startDate->format('Y-m-d')) < strtotime($minDate->format('Y-m-d'))) { |
6a488035 TO |
276 | $startDate->setTimezone(new DateTimeZone(self::TIMEZONE)); |
277 | } | |
278 | ||
481a74f4 | 279 | $template->assign('startDate', $startDate->format('Y-m-d')); |
a2dd09cc | 280 | |
6a488035 | 281 | $installments = $this->_getParam('installments'); |
a2dd09cc DL |
282 | |
283 | // for open ended subscription totalOccurrences has to be 9999 | |
284 | $installments = empty($installments) ? 9999 : $installments; | |
285 | $template->assign('totalOccurrences', $installments); | |
6a488035 TO |
286 | |
287 | $template->assign('amount', $this->_getParam('amount')); | |
288 | ||
289 | $template->assign('cardNumber', $this->_getParam('credit_card_number')); | |
290 | $exp_month = str_pad($this->_getParam('month'), 2, '0', STR_PAD_LEFT); | |
291 | $exp_year = $this->_getParam('year'); | |
292 | $template->assign('expirationDate', $exp_year . '-' . $exp_month); | |
293 | ||
294 | // name rather than description is used in the tpl - see http://www.authorize.net/support/ARB_guide.pdf | |
295 | $template->assign('name', $this->_getParam('description', TRUE)); | |
296 | ||
297 | $template->assign('email', $this->_getParam('email')); | |
298 | $template->assign('contactID', $this->_getParam('contactID')); | |
299 | $template->assign('billingFirstName', $this->_getParam('billing_first_name')); | |
300 | $template->assign('billingLastName', $this->_getParam('billing_last_name')); | |
301 | $template->assign('billingAddress', $this->_getParam('street_address', TRUE)); | |
302 | $template->assign('billingCity', $this->_getParam('city', TRUE)); | |
303 | $template->assign('billingState', $this->_getParam('state_province')); | |
304 | $template->assign('billingZip', $this->_getParam('postal_code', TRUE)); | |
305 | $template->assign('billingCountry', $this->_getParam('country')); | |
fc219888 EM |
306 | // Required to be set for s |
307 | $template->ensureVariablesAreAssigned(['subscriptionType']); | |
6a488035 | 308 | $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); |
6a488035 | 309 | |
9fa25593 | 310 | // Submit to authorize.net |
311 | $response = $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [ | |
312 | 'headers' => [ | |
313 | 'Content-Type' => 'text/xml; charset=UTF8', | |
314 | ], | |
315 | 'body' => $arbXML, | |
316 | 'curl' => [ | |
317 | CURLOPT_RETURNTRANSFER => TRUE, | |
318 | CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), | |
319 | ], | |
320 | ]); | |
321 | $responseFields = $this->_ParseArbReturn((string) $response->getBody()); | |
322 | ||
323 | if ($responseFields['resultCode'] === 'Error') { | |
884a213d | 324 | throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); |
6a488035 TO |
325 | } |
326 | ||
327 | // update recur processor_id with subscriptionId | |
328 | CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_ContributionRecur', $this->_getParam('contributionRecurID'), | |
329 | 'processor_id', $responseFields['subscriptionId'] | |
330 | ); | |
331 | //only impact of assigning this here is is can be used to cancel the subscription in an automated test | |
332 | // if it isn't cancelled a duplicate transaction error occurs | |
a7488080 | 333 | if (!empty($responseFields['subscriptionId'])) { |
6a488035 TO |
334 | $this->_setParam('subscriptionId', $responseFields['subscriptionId']); |
335 | } | |
336 | } | |
337 | ||
6c786a9b EM |
338 | /** |
339 | * @return array | |
340 | */ | |
00be9182 | 341 | public function _getAuthorizeNetFields() { |
518fa0ee SL |
342 | //Total amount is from the form contribution field |
343 | $amount = $this->_getParam('total_amount'); | |
344 | //CRM-9894 would this ever be the case?? | |
345 | if (empty($amount)) { | |
6a488035 TO |
346 | $amount = $this->_getParam('amount'); |
347 | } | |
be2fb01f | 348 | $fields = []; |
6a488035 TO |
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'); | |
5bd23b41 | 361 | $fields['x_invoice_num'] = $this->_getParam('invoiceID'); |
353ffa53 | 362 | $fields['x_amount'] = $amount; |
6a488035 TO |
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 | ||
6a488035 TO |
387 | /** |
388 | * Split a CSV file. Requires , as delimiter and " as enclosure. | |
389 | * Based off notes from http://php.net/fgetcsv | |
390 | * | |
6a0b768e TO |
391 | * @param string $data |
392 | * A single CSV line. | |
6a488035 | 393 | * |
a6c01b45 CW |
394 | * @return array |
395 | * CSV fields | |
6a488035 | 396 | */ |
00be9182 | 397 | public function explode_csv($data) { |
6a488035 TO |
398 | $data = trim($data); |
399 | //make it easier to parse fields with quotes in them | |
400 | $data = str_replace('""', "''", $data); | |
be2fb01f | 401 | $fields = []; |
6a488035 TO |
402 | |
403 | while ($data != '') { | |
be2fb01f | 404 | $matches = []; |
6a488035 TO |
405 | if ($data[0] == '"') { |
406 | // handle quoted fields | |
407 | preg_match('/^"(([^"]|\\")*?)",?(.*)$/', $data, $matches); | |
408 | ||
409 | $fields[] = str_replace("''", '"', $matches[1]); | |
410 | $data = $matches[3]; | |
411 | } | |
412 | else { | |
413 | preg_match('/^([^,]*),?(.*)$/', $data, $matches); | |
414 | ||
415 | $fields[] = $matches[1]; | |
416 | $data = $matches[2]; | |
417 | } | |
418 | } | |
419 | return $fields; | |
420 | } | |
421 | ||
422 | /** | |
fe482240 | 423 | * Extract variables from returned XML. |
6a488035 TO |
424 | * |
425 | * Function is from Authorize.Net sample code, and used | |
426 | * to prevent the requirement of XML functions. | |
427 | * | |
6a0b768e TO |
428 | * @param string $content |
429 | * XML reply from Authorize.Net. | |
6a488035 | 430 | * |
a6c01b45 CW |
431 | * @return array |
432 | * refId, resultCode, code, text, subscriptionId | |
6a488035 | 433 | */ |
00be9182 | 434 | public function _parseArbReturn($content) { |
353ffa53 TO |
435 | $refId = $this->_substring_between($content, '<refId>', '</refId>'); |
436 | $resultCode = $this->_substring_between($content, '<resultCode>', '</resultCode>'); | |
437 | $code = $this->_substring_between($content, '<code>', '</code>'); | |
438 | $text = $this->_substring_between($content, '<text>', '</text>'); | |
6a488035 | 439 | $subscriptionId = $this->_substring_between($content, '<subscriptionId>', '</subscriptionId>'); |
be2fb01f | 440 | return [ |
6a488035 TO |
441 | 'refId' => $refId, |
442 | 'resultCode' => $resultCode, | |
443 | 'code' => $code, | |
444 | 'text' => $text, | |
445 | 'subscriptionId' => $subscriptionId, | |
be2fb01f | 446 | ]; |
6a488035 TO |
447 | } |
448 | ||
449 | /** | |
fe482240 | 450 | * Helper function for _parseArbReturn. |
6a488035 TO |
451 | * |
452 | * Function is from Authorize.Net sample code, and used to avoid using | |
453 | * PHP5 XML functions | |
54957108 | 454 | * |
455 | * @param string $haystack | |
456 | * @param string $start | |
457 | * @param string $end | |
458 | * | |
459 | * @return bool|string | |
6a488035 | 460 | */ |
00be9182 | 461 | public function _substring_between(&$haystack, $start, $end) { |
6a488035 TO |
462 | if (strpos($haystack, $start) === FALSE || strpos($haystack, $end) === FALSE) { |
463 | return FALSE; | |
464 | } | |
465 | else { | |
466 | $start_position = strpos($haystack, $start) + strlen($start); | |
467 | $end_position = strpos($haystack, $end); | |
468 | return substr($haystack, $start_position, $end_position - $start_position); | |
469 | } | |
470 | } | |
471 | ||
472 | /** | |
fe482240 | 473 | * Get the value of a field if set. |
6a488035 | 474 | * |
6a0b768e TO |
475 | * @param string $field |
476 | * The field. | |
6a488035 | 477 | * |
2a6da8d7 | 478 | * @param bool $xmlSafe |
72b3a70c CW |
479 | * @return mixed |
480 | * value of the field, or empty string if the field is | |
16b10e64 | 481 | * not set |
6a488035 | 482 | */ |
00be9182 | 483 | public function _getParam($field, $xmlSafe = FALSE) { |
6a488035 | 484 | $value = CRM_Utils_Array::value($field, $this->_params, ''); |
a2dd09cc | 485 | if ($xmlSafe) { |
be2fb01f | 486 | $value = str_replace(['&', '"', "'", '<', '>'], '', $value); |
a2dd09cc | 487 | } |
6a488035 TO |
488 | return $value; |
489 | } | |
490 | ||
6a488035 TO |
491 | /** |
492 | * Set a field to the specified value. Value must be a scalar (int, | |
493 | * float, string, or boolean) | |
494 | * | |
495 | * @param string $field | |
496 | * @param mixed $value | |
497 | * | |
a6c01b45 CW |
498 | * @return bool |
499 | * false if value is not a scalar, true if successful | |
6a488035 | 500 | */ |
00be9182 | 501 | public function _setParam($field, $value) { |
6a488035 TO |
502 | if (!is_scalar($value)) { |
503 | return FALSE; | |
504 | } | |
505 | else { | |
506 | $this->_params[$field] = $value; | |
507 | } | |
508 | } | |
509 | ||
510 | /** | |
fe482240 | 511 | * This function checks to see if we have the right config values. |
6a488035 | 512 | * |
a6c01b45 CW |
513 | * @return string |
514 | * the error message if any | |
6a488035 | 515 | */ |
00be9182 | 516 | public function checkConfig() { |
be2fb01f | 517 | $error = []; |
6a488035 TO |
518 | if (empty($this->_paymentProcessor['user_name'])) { |
519 | $error[] = ts('APILogin is not set for this payment processor'); | |
520 | } | |
521 | ||
522 | if (empty($this->_paymentProcessor['password'])) { | |
523 | $error[] = ts('Key is not set for this payment processor'); | |
524 | } | |
525 | ||
526 | if (!empty($error)) { | |
527 | return implode('<p>', $error); | |
528 | } | |
529 | else { | |
530 | return NULL; | |
531 | } | |
532 | } | |
533 | ||
6c786a9b EM |
534 | /** |
535 | * @return string | |
536 | */ | |
00be9182 | 537 | public function accountLoginURL() { |
6a488035 TO |
538 | return ($this->_mode == 'test') ? 'https://test.authorize.net' : 'https://authorize.net'; |
539 | } | |
540 | ||
6c786a9b EM |
541 | /** |
542 | * @param string $message | |
9e03a6d0 | 543 | * @param \Civi\Payment\PropertyBag $params |
6c786a9b EM |
544 | * |
545 | * @return bool|object | |
884a213d | 546 | * @throws \Civi\Payment\Exception\PaymentProcessorException |
6c786a9b | 547 | */ |
ff708ff0 | 548 | public function cancelSubscription(&$message = '', $params = []) { |
6a488035 TO |
549 | $template = CRM_Core_Smarty::singleton(); |
550 | ||
551 | $template->assign('subscriptionType', 'cancel'); | |
552 | ||
553 | $template->assign('apiLogin', $this->_getParam('apiLogin')); | |
554 | $template->assign('paymentKey', $this->_getParam('paymentKey')); | |
9e03a6d0 | 555 | $template->assign('subscriptionId', $params->getRecurProcessorID()); |
6a488035 TO |
556 | |
557 | $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); | |
558 | ||
884a213d | 559 | $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [ |
560 | 'headers' => [ | |
561 | 'Content-Type' => 'text/xml; charset=UTF8', | |
562 | ], | |
563 | 'body' => $arbXML, | |
564 | 'curl' => [ | |
565 | CURLOPT_RETURNTRANSFER => TRUE, | |
566 | CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), | |
567 | ], | |
568 | ])->getBody(); | |
6a488035 TO |
569 | |
570 | $responseFields = $this->_ParseArbReturn($response); | |
571 | $message = "{$responseFields['code']}: {$responseFields['text']}"; | |
572 | ||
884a213d | 573 | if ($responseFields['resultCode'] === 'Error') { |
574 | throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); | |
6a488035 TO |
575 | } |
576 | return TRUE; | |
577 | } | |
578 | ||
6c786a9b | 579 | /** |
884a213d | 580 | * Update payment details at Authorize.net. |
581 | * | |
6c786a9b EM |
582 | * @param string $message |
583 | * @param array $params | |
584 | * | |
585 | * @return bool|object | |
884a213d | 586 | * |
587 | * @throws \Civi\Payment\Exception\PaymentProcessorException | |
6c786a9b | 588 | */ |
be2fb01f | 589 | public function updateSubscriptionBillingInfo(&$message = '', $params = []) { |
6a488035 TO |
590 | $template = CRM_Core_Smarty::singleton(); |
591 | $template->assign('subscriptionType', 'updateBilling'); | |
592 | ||
593 | $template->assign('apiLogin', $this->_getParam('apiLogin')); | |
594 | $template->assign('paymentKey', $this->_getParam('paymentKey')); | |
595 | $template->assign('subscriptionId', $params['subscriptionId']); | |
596 | ||
597 | $template->assign('cardNumber', $params['credit_card_number']); | |
598 | $exp_month = str_pad($params['month'], 2, '0', STR_PAD_LEFT); | |
599 | $exp_year = $params['year']; | |
600 | $template->assign('expirationDate', $exp_year . '-' . $exp_month); | |
601 | ||
602 | $template->assign('billingFirstName', $params['first_name']); | |
603 | $template->assign('billingLastName', $params['last_name']); | |
604 | $template->assign('billingAddress', $params['street_address']); | |
605 | $template->assign('billingCity', $params['city']); | |
606 | $template->assign('billingState', $params['state_province']); | |
607 | $template->assign('billingZip', $params['postal_code']); | |
608 | $template->assign('billingCountry', $params['country']); | |
609 | ||
610 | $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); | |
611 | ||
884a213d | 612 | $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [ |
613 | 'headers' => [ | |
614 | 'Content-Type' => 'text/xml; charset=UTF8', | |
615 | ], | |
616 | 'body' => $arbXML, | |
617 | 'curl' => [ | |
618 | CURLOPT_RETURNTRANSFER => TRUE, | |
619 | CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), | |
620 | ], | |
621 | ])->getBody(); | |
6a488035 | 622 | |
884a213d | 623 | // submit to authorize.net |
6a488035 TO |
624 | $responseFields = $this->_ParseArbReturn($response); |
625 | $message = "{$responseFields['code']}: {$responseFields['text']}"; | |
626 | ||
884a213d | 627 | if ($responseFields['resultCode'] === 'Error') { |
628 | throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); | |
6a488035 TO |
629 | } |
630 | return TRUE; | |
631 | } | |
632 | ||
23de1ac0 EM |
633 | /** |
634 | * Process incoming notification. | |
635 | */ | |
9645e182 | 636 | public function handlePaymentNotification() { |
23de1ac0 EM |
637 | $ipnClass = new CRM_Core_Payment_AuthorizeNetIPN(array_merge($_GET, $_REQUEST)); |
638 | $ipnClass->main(); | |
639 | } | |
640 | ||
6c786a9b | 641 | /** |
884a213d | 642 | * Change the amount of the recurring payment. |
643 | * | |
6c786a9b EM |
644 | * @param string $message |
645 | * @param array $params | |
646 | * | |
647 | * @return bool|object | |
884a213d | 648 | * |
649 | * @throws \Civi\Payment\Exception\PaymentProcessorException | |
6c786a9b | 650 | */ |
be2fb01f | 651 | public function changeSubscriptionAmount(&$message = '', $params = []) { |
6a488035 TO |
652 | $template = CRM_Core_Smarty::singleton(); |
653 | ||
654 | $template->assign('subscriptionType', 'update'); | |
655 | ||
656 | $template->assign('apiLogin', $this->_getParam('apiLogin')); | |
657 | $template->assign('paymentKey', $this->_getParam('paymentKey')); | |
658 | ||
659 | $template->assign('subscriptionId', $params['subscriptionId']); | |
702c1203 JM |
660 | |
661 | // for open ended subscription totalOccurrences has to be 9999 | |
662 | $installments = empty($params['installments']) ? 9999 : $params['installments']; | |
663 | $template->assign('totalOccurrences', $installments); | |
664 | ||
6a488035 TO |
665 | $template->assign('amount', $params['amount']); |
666 | ||
667 | $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); | |
668 | ||
884a213d | 669 | $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [ |
670 | 'headers' => [ | |
671 | 'Content-Type' => 'text/xml; charset=UTF8', | |
672 | ], | |
673 | 'body' => $arbXML, | |
674 | 'curl' => [ | |
675 | CURLOPT_RETURNTRANSFER => TRUE, | |
676 | CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), | |
677 | ], | |
678 | ])->getBody(); | |
6a488035 | 679 | |
6ea16bbf | 680 | $responseFields = $this->_parseArbReturn($response); |
6a488035 TO |
681 | $message = "{$responseFields['code']}: {$responseFields['text']}"; |
682 | ||
884a213d | 683 | if ($responseFields['resultCode'] === 'Error') { |
4ae44cc6 | 684 | throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); |
6a488035 TO |
685 | } |
686 | return TRUE; | |
687 | } | |
96025800 | 688 | |
524cd99e | 689 | /** |
690 | * Get an appropriate test trannsaction id. | |
691 | * | |
692 | * @return string | |
693 | */ | |
694 | protected function getTestTrxnID() { | |
695 | // test mode always returns trxn_id = 0 | |
696 | // also live mode in CiviCRM with test mode set in | |
697 | // Authorize.Net return $response_fields[6] = 0 | |
698 | // hence treat that also as test mode transaction | |
699 | // fix for CRM-2566 | |
700 | $query = "SELECT MAX(trxn_id) FROM civicrm_contribution WHERE trxn_id RLIKE 'test[0-9]+'"; | |
701 | $trxn_id = (string) (CRM_Core_DAO::singleValueQuery($query)); | |
702 | $trxn_id = str_replace('test', '', $trxn_id); | |
703 | $trxn_id = (int) ($trxn_id) + 1; | |
704 | return sprintf('test%08d', $trxn_id); | |
705 | } | |
706 | ||
6a488035 | 707 | } |