Merge pull request #15881 from civicrm/5.20
[civicrm-core.git] / CRM / Core / Payment / PayflowPro.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +----------------------------------------------------------------------------+
f8c91418 4 | Payflow Pro Core Payment Module for CiviCRM version 5 |
6a488035
TO
5 +----------------------------------------------------------------------------+
6 | Licensed to CiviCRM under the Academic Free License version 3.0 |
7 | |
8 | Written & Contributed by Eileen McNaughton - 2009 |
9 +---------------------------------------------------------------------------+
10 */
28518c90
EM
11
12/**
0b014509 13 * Class CRM_Core_Payment_PayflowPro.
28518c90 14 */
6a488035
TO
15class CRM_Core_Payment_PayflowPro extends CRM_Core_Payment {
16 // (not used, implicit in the API, might need to convert?)
7da04cde 17 const
353ffa53 18 CHARSET = 'UFT-8';
6a488035
TO
19
20 /**
21 * We only need one instance of this object. So we use the singleton
22 * pattern and cache the instance in this variable
23 *
24 * @var object
6a488035
TO
25 */
26 static private $_singleton = NULL;
27
3ecad08a 28 /**
6a488035
TO
29 * Constructor
30 *
6a0b768e
TO
31 * @param string $mode
32 * The mode of operation: live or test.
6c786a9b
EM
33 * @param $paymentProcessor
34 */
00be9182 35 public function __construct($mode, &$paymentProcessor) {
6a488035
TO
36 // live or test
37 $this->_mode = $mode;
38 $this->_paymentProcessor = $paymentProcessor;
39 $this->_processorName = ts('Payflow Pro');
40 }
ceb10dc7 41
6a488035
TO
42 /*
43 * This function sends request and receives response from
44 * the processor. It is the main function for processing on-server
45 * credit card transactions
46 */
518fa0ee 47
b5c2afd0
EM
48 /**
49 * This function collects all the information from a web/api form and invokes
50 * the relevant payment processor specific functions to perform the transaction
51 *
6a0b768e
TO
52 * @param array $params
53 * Assoc array of input parameters for this transaction.
b5c2afd0 54 *
a6c01b45
CW
55 * @return array
56 * the result in an nice formatted array (or an error object)
b5c2afd0
EM
57 * @abstract
58 */
00be9182 59 public function doDirectPayment(&$params) {
6a488035 60 if (!defined('CURLOPT_SSLCERT')) {
27e1cd31 61 CRM_Core_Error::fatal(ts('Payflow Pro requires curl with SSL support'));
6a488035
TO
62 }
63
64 /*
65 * define variables for connecting with the gateway
66 */
ceb10dc7 67
6a488035
TO
68 // Are you using the Payflow Fraud Protection Service?
69 // Default is YES, change to NO or blank if not.
70 //This has not been investigated as part of writing this payment processor
71 $fraud = 'NO';
72 //if you have not set up a separate user account the vendor name is used as the username
73 if (!$this->_paymentProcessor['subject']) {
74 $user = $this->_paymentProcessor['user_name'];
75 }
76 else {
77 $user = $this->_paymentProcessor['subject'];
78 }
79
80 // ideally this id would be passed through into this class as
81 // part of the paymentProcessor
82 //object with the other variables. It seems inefficient to re-query to get it.
83 //$params['processor_id'] = CRM_Core_DAO::getFieldValue(
84 // 'CRM_Contribute_DAO_ContributionP
85 //age',$params['contributionPageID'], 'payment_processor_id' );
86
87 /*
88 *Create the array of variables to be sent to the processor from the $params array
89 * passed into this function
90 *
91 */
92
be2fb01f 93 $payflow_query_array = [
6a488035
TO
94 'USER' => $user,
95 'VENDOR' => $this->_paymentProcessor['user_name'],
96 'PARTNER' => $this->_paymentProcessor['signature'],
97 'PWD' => $this->_paymentProcessor['password'],
98 // C - Direct Payment using credit card
99 'TENDER' => 'C',
100 // A - Authorization, S - Sale
101 'TRXTYPE' => 'S',
102 'ACCT' => urlencode($params['credit_card_number']),
103 'CVV2' => $params['cvv2'],
104 'EXPDATE' => urlencode(sprintf('%02d', (int) $params['month']) . substr($params['year'], 2, 2)),
105 'ACCTTYPE' => urlencode($params['credit_card_type']),
768e3308 106 'AMT' => urlencode($this->getAmount($params)),
6a488035
TO
107 'CURRENCY' => urlencode($params['currency']),
108 'FIRSTNAME' => $params['billing_first_name'],
109 //credit card name
110 'LASTNAME' => $params['billing_last_name'],
111 //credit card name
112 'STREET' => $params['street_address'],
113 'CITY' => urlencode($params['city']),
114 'STATE' => urlencode($params['state_province']),
115 'ZIP' => urlencode($params['postal_code']),
116 'COUNTRY' => urlencode($params['country']),
117 'EMAIL' => $params['email'],
118 'CUSTIP' => urlencode($params['ip_address']),
119 'COMMENT1' => urlencode($params['contributionType_accounting_code']),
120 'COMMENT2' => $mode,
121 'INVNUM' => urlencode($params['invoiceID']),
122 'ORDERDESC' => urlencode($params['description']),
123 'VERBOSITY' => 'MEDIUM',
124 'BILLTOCOUNTRY' => urlencode($params['country']),
be2fb01f 125 ];
6a488035
TO
126
127 if ($params['installments'] == 1) {
0f76c95a 128 $params['is_recur'] = FALSE;
6a488035
TO
129 }
130
131 if ($params['is_recur'] == TRUE) {
132
133 $payflow_query_array['TRXTYPE'] = 'R';
134 $payflow_query_array['OPTIONALTRX'] = 'S';
768e3308 135 $payflow_query_array['OPTIONALTRXAMT'] = $this->getAmount($params);
6a488035
TO
136 //Amount of the initial Transaction. Required
137 $payflow_query_array['ACTION'] = 'A';
138 //A for add recurring (M-modify,C-cancel,R-reactivate,I-inquiry,P-payment
139 $payflow_query_array['PROFILENAME'] = urlencode('RegularContribution');
140 //A for add recurring (M-modify,C-cancel,R-reactivate,I-inquiry,P-payment
141 if ($params['installments'] > 0) {
142 $payflow_query_array['TERM'] = $params['installments'] - 1;
143 //ie. in addition to the one happening with this transaction
144 }
145 // $payflow_query_array['COMPANYNAME']
146 // $payflow_query_array['DESC'] = not set yet Optional
147 // description of the goods or
148 //services being purchased.
149 //This parameter applies only for ACH_CCD accounts.
150 // The
151 // $payflow_query_array['MAXFAILPAYMENTS'] = 0;
152 // number of payment periods (as s
153 //pecified by PAYPERIOD) for which the transaction is allowed
154 //to fail before PayPal cancels a profile. the default
155 // value of 0 (zero) specifies no
156 //limit. Retry
157 //attempts occur until the term is complete.
158 // $payflow_query_array['RETRYNUMDAYS'] = (not set as can't assume business rule
159
160 switch ($params['frequency_unit']) {
161 case '1 week':
797b807e 162 $params['next_sched_contribution_date'] = mktime(0, 0, 0, date("m"), date("d") + 7,
6a488035
TO
163 date("Y")
164 );
165 $params['end_date'] = mktime(0, 0, 0, date("m"), date("d") + (7 * $payflow_query_array['TERM']),
166 date("Y")
167 );
797b807e 168 $payflow_query_array['START'] = date('mdY', $params['next_sched_contribution_date']);
6a488035
TO
169 $payflow_query_array['PAYPERIOD'] = "WEEK";
170 $params['frequency_unit'] = "week";
171 $params['frequency_interval'] = 1;
172 break;
173
174 case '2 weeks':
797b807e 175 $params['next_sched_contribution_date'] = mktime(0, 0, 0, date("m"), date("d") + 14, date("Y"));
2aa397bc 176 $params['end_date'] = mktime(0, 0, 0, date("m"), date("d") + (14 * $payflow_query_array['TERM']), date("Y ")
6a488035 177 );
797b807e 178 $payflow_query_array['START'] = date('mdY', $params['next_sched_contribution_date']);
6a488035
TO
179 $payflow_query_array['PAYPERIOD'] = "BIWK";
180 $params['frequency_unit'] = "week";
181 $params['frequency_interval'] = 2;
182 break;
183
184 case '4 weeks':
797b807e 185 $params['next_sched_contribution_date'] = mktime(0, 0, 0, date("m"), date("d") + 28, date("Y")
6a488035 186 );
2aa397bc 187 $params['end_date'] = mktime(0, 0, 0, date("m"), date("d") + (28 * $payflow_query_array['TERM']), date("Y")
6a488035 188 );
797b807e 189 $payflow_query_array['START'] = date('mdY', $params['next_sched_contribution_date']);
6a488035
TO
190 $payflow_query_array['PAYPERIOD'] = "FRWK";
191 $params['frequency_unit'] = "week";
192 $params['frequency_interval'] = 4;
193 break;
194
195 case '1 month':
797b807e 196 $params['next_sched_contribution_date'] = mktime(0, 0, 0, date("m") + 1,
6a488035
TO
197 date("d"), date("Y")
198 );
199 $params['end_date'] = mktime(0, 0, 0, date("m") +
200 (1 * $payflow_query_array['TERM']),
201 date("d"), date("Y")
202 );
797b807e 203 $payflow_query_array['START'] = date('mdY', $params['next_sched_contribution_date']);
6a488035
TO
204 $payflow_query_array['PAYPERIOD'] = "MONT";
205 $params['frequency_unit'] = "month";
206 $params['frequency_interval'] = 1;
207 break;
208
209 case '3 months':
2aa397bc 210 $params['next_sched_contribution_date'] = mktime(0, 0, 0, date("m") + 3, date("d"), date("Y")
6a488035
TO
211 );
212 $params['end_date'] = mktime(0, 0, 0, date("m") +
213 (3 * $payflow_query_array['TERM']),
214 date("d"), date("Y")
215 );
797b807e 216 $payflow_query_array['START'] = date('mdY', $params['next_sched_contribution_date']);
6a488035
TO
217 $payflow_query_array['PAYPERIOD'] = "QTER";
218 $params['frequency_unit'] = "month";
219 $params['frequency_interval'] = 3;
220 break;
221
222 case '6 months':
797b807e 223 $params['next_sched_contribution_date'] = mktime(0, 0, 0, date("m") + 6, date("d"),
6a488035
TO
224 date("Y")
225 );
226 $params['end_date'] = mktime(0, 0, 0, date("m") +
227 (6 * $payflow_query_array['TERM']),
228 date("d"), date("Y")
229 );
2aa397bc 230 $payflow_query_array['START'] = date('mdY', $params['next_sched_contribution_date']
6a488035
TO
231 );
232 $payflow_query_array['PAYPERIOD'] = "SMYR";
233 $params['frequency_unit'] = "month";
234 $params['frequency_interval'] = 6;
235 break;
236
237 case '1 year':
797b807e 238 $params['next_sched_contribution_date'] = mktime(0, 0, 0, date("m"), date("d"),
6a488035
TO
239 date("Y") + 1
240 );
241 $params['end_date'] = mktime(0, 0, 0, date("m"), date("d"),
242 date("Y") +
243 (1 * $payflow_query_array['TEM'])
244 );
797b807e 245 $payflow_query_array['START'] = date('mdY', $params['next_sched_contribution_date']);
6a488035
TO
246 $payflow_query_array['PAYPERIOD'] = "YEAR";
247 $params['frequency_unit'] = "year";
248 $params['frequency_interval'] = 1;
249 break;
250 }
251 }
252
253 CRM_Utils_Hook::alterPaymentProcessorParams($this, $params, $payflow_query_array);
254 $payflow_query = $this->convert_to_nvp($payflow_query_array);
255
256 /*
257 * Check to see if we have a duplicate before we send
258 */
d253aeb8 259 if ($this->checkDupe($params['invoiceID'], CRM_Utils_Array::value('contributionID', $params))) {
6a488035
TO
260 return self::errorExit(9003, '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. 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.');
261 }
262
263 // ie. url at payment processor to submit to.
264 $submiturl = $this->_paymentProcessor['url_site'];
265
266 $responseData = self::submit_transaction($submiturl, $payflow_query);
267
268 /*
ceb10dc7 269 * Payment successfully sent to gateway - process the response now
6a488035
TO
270 */
271 $result = strstr($responseData, "RESULT");
3ae790f3
JM
272 if (empty($result)) {
273 return self::errorExit(9016, "No RESULT code from PayPal.");
274 }
275
be2fb01f 276 $nvpArray = [];
6a488035
TO
277 while (strlen($result)) {
278 // name
279 $keypos = strpos($result, '=');
280 $keyval = substr($result, 0, $keypos);
281 // value
282 $valuepos = strpos($result, '&') ? strpos($result, '&') : strlen($result);
283 $valval = substr($result, $keypos + 1, $valuepos - $keypos - 1);
284 // decoding the respose
285 $nvpArray[$keyval] = $valval;
286 $result = substr($result, $valuepos + 1, strlen($result));
287 }
288 // get the result code to validate.
289 $result_code = $nvpArray['RESULT'];
290 /*debug
e70a7fc0
TO
291 echo "<p>Params array</p><br>";
292 print_r($params);
293 echo "<p></p><br>";
294 echo "<p>Values to Payment Processor</p><br>";
295 print_r($payflow_query_array);
296 echo "<p></p><br>";
297 echo "<p>Results from Payment Processor</p><br>";
298 print_r($nvpArray);
299 echo "<p></p><br>";
300 */
6a488035
TO
301
302 switch ($result_code) {
303 case 0:
304
305 /*******************************************************
306 * Success !
f8c91418 307 * This is a successful transaction. Payflow Pro does return further information
6a488035
TO
308 * about transactions to help you identify fraud including whether they pass
309 * the cvv check, the avs check. This is stored in
310 * CiviCRM as part of the transact
311 * but not further processing is done. Business rules would need to be defined
6a488035
TO
312 *******************************************************/
313 $params['trxn_id'] = $nvpArray['PNREF'] . $nvpArray['TRXPNREF'];
314 //'trxn_id' is varchar(255) field. returned value is length 12
315 $params['trxn_result_code'] = $nvpArray['AUTHCODE'] . "-Cvv2:" . $nvpArray['CVV2MATCH'] . "-avs:" . $nvpArray['AVSADDR'];
316
317 if ($params['is_recur'] == TRUE) {
318 $params['recur_trxn_id'] = $nvpArray['PROFILEID'];
319 //'trxn_id' is varchar(255) field. returned value is length 12
320 }
321 return $params;
322
323 case 1:
324 return self::errorExit(9008, "There is a payment processor configuration problem. This is usually due to invalid account information or ip restrictions on the account. You can verify ip restriction by logging // into Manager. See Service Settings >> Allowed IP Addresses. ");
325
326 case 12:
327 // Hard decline from bank.
328 return self::errorExit(9009, "Your transaction was declined ");
329
330 case 13:
331 // Voice authorization required.
332 return self::errorExit(9010, "Your Transaction is pending. Contact Customer Service to complete your order.");
333
334 case 23:
335 // Issue with credit card number or expiration date.
336 return self::errorExit(9011, "Invalid credit card information. Please re-enter.");
337
338 case 26:
339 return self::errorExit(9012, "You have not configured your payment processor with the correct credentials. Make sure you have provided both the <vendor> and the <user> variables ");
340
341 default:
342 return self::errorExit(9013, "Error - from payment processor: [" . $result_code . " " . $nvpArray['RESPMSG'] . "] ");
343 }
344
345 return self::errorExit(9014, "Check the code - all transactions should have been headed off before they got here. Something slipped through the net");
346 }
347
6c786a9b 348 /**
f8c91418
CB
349 * Produces error message and returns from class
350 *
6c786a9b
EM
351 * @param null $errorCode
352 * @param null $errorMessage
353 *
354 * @return object
355 */
00be9182 356 public function &errorExit($errorCode = NULL, $errorMessage = NULL) {
6a488035
TO
357 $e = CRM_Core_Error::singleton();
358 if ($errorCode) {
359 $e->push($errorCode, 0, NULL, $errorMessage);
360 }
361 else {
362 $e->push(9000, 0, NULL, 'Unknown System Error.');
363 }
364 return $e;
365 }
366
6c786a9b 367 /**
f8c91418
CB
368 * NOTE: 'doTransferCheckout' not implemented
369 *
c490a46a 370 * @param array $params
6c786a9b
EM
371 * @param $component
372 *
373 * @throws Exception
374 */
00be9182 375 public function doTransferCheckout(&$params, $component) {
6a488035
TO
376 CRM_Core_Error::fatal(ts('This function is not implemented'));
377 }
378
f8c91418 379 /**
6a488035
TO
380 * This public function checks to see if we have the right processor config values set
381 *
382 * NOTE: Called by Events and Contribute to check config params are set prior to trying
383 * register any credit card details
384 *
f8c91418
CB
385 * @return string|null
386 * the error message if any, null if OK
b5c2afd0 387 */
00be9182 388 public function checkConfig() {
be2fb01f 389 $errorMsg = [];
6a488035
TO
390 if (empty($this->_paymentProcessor['user_name'])) {
391 $errorMsg[] = ' ' . ts('ssl_merchant_id is not set for this payment processor');
392 }
393
394 if (empty($this->_paymentProcessor['url_site'])) {
be2fb01f 395 $errorMsg[] = ' ' . ts('URL is not set for %1', [1 => $this->_paymentProcessor['name']]);
6a488035
TO
396 }
397
398 if (!empty($errorMsg)) {
399 return implode('<p>', $errorMsg);
400 }
401 else {
402 return NULL;
403 }
404 }
6a488035 405
6c786a9b 406 /**
f8c91418
CB
407 * convert to a name/value pair (nvp) string
408 *
6c786a9b
EM
409 * @param $payflow_query_array
410 *
411 * @return array|string
412 */
00be9182 413 public function convert_to_nvp($payflow_query_array) {
6a488035
TO
414 foreach ($payflow_query_array as $key => $value) {
415 $payflow_query[] = $key . '[' . strlen($value) . ']=' . $value;
416 }
417 $payflow_query = implode('&', $payflow_query);
418
419 return $payflow_query;
420 }
421
6c786a9b 422 /**
f8c91418
CB
423 * Submit transaction using cURL
424 *
425 * @param string $submiturl Url to direct HTTPS GET to
426 * @param $payflow_query value string to be posted
6c786a9b
EM
427 *
428 * @return mixed|object
429 */
00be9182 430 public function submit_transaction($submiturl, $payflow_query) {
6a488035
TO
431 // get data ready for API
432 $user_agent = $_SERVER['HTTP_USER_AGENT'];
433 // Here's your custom headers; adjust appropriately for your setup:
434 $headers[] = "Content-Type: text/namevalue";
435 //or text/xml if using XMLPay.
436 $headers[] = "Content-Length : " . strlen($data);
437 // Length of data to be passed
438 // Here the server timeout value is set to 45, but notice
439 // below in the cURL section, the timeout
440 // for cURL is 90 seconds. You want to make sure the server
441 // timeout is less, then the connection.
442 $headers[] = "X-VPS-Timeout: 45";
443 //random unique number - the transaction is retried using this transaction ID
444 // in this function but if that doesn't work and it is re- submitted
f8c91418 445 // it is treated as a new attempt. Payflow Pro doesn't allow
6a488035
TO
446 // you to change details (e.g. card no) when you re-submit
447 // you can only try the same details
448 $headers[] = "X-VPS-Request-ID: " . rand(1, 1000000000);
449 // optional header field
450 $headers[] = "X-VPS-VIT-Integration-Product: CiviCRM";
451 // other Optional Headers. If used adjust as necessary.
452 // Name of your OS
453 //$headers[] = "X-VPS-VIT-OS-Name: Linux";
454 // OS Version
455 //$headers[] = "X-VPS-VIT-OS-Version: RHEL 4";
456 // What you are using
457 //$headers[] = "X-VPS-VIT-Client-Type: PHP/cURL";
458 // For your info
459 //$headers[] = "X-VPS-VIT-Client-Version: 0.01";
460 // For your info
461 //$headers[] = "X-VPS-VIT-Client-Architecture: x86";
462 // Application version
463 //$headers[] = "X-VPS-VIT-Integration-Version: 0.01";
464 $ch = curl_init();
465 curl_setopt($ch, CURLOPT_URL, $submiturl);
466 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
467 curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
468 curl_setopt($ch, CURLOPT_HEADER, 1);
469 // tells curl to include headers in response
470 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
471 // return into a variable
472 curl_setopt($ch, CURLOPT_TIMEOUT, 90);
473 // times out after 90 secs
439b9688
LS
474 if (ini_get('open_basedir') == '' && ini_get('safe_mode') == 'Off') {
475 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
476 }
aaffa79f 477 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, Civi::settings()->get('verifySSL'));
6a488035
TO
478 // this line makes it work under https
479 curl_setopt($ch, CURLOPT_POSTFIELDS, $payflow_query);
480 //adding POST data
aaffa79f 481 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, Civi::settings()->get('verifySSL') ? 2 : 0);
6a488035
TO
482 //verifies ssl certificate
483 curl_setopt($ch, CURLOPT_FORBID_REUSE, TRUE);
484 //forces closure of connection when done
485 curl_setopt($ch, CURLOPT_POST, 1);
486 //data sent as POST
487
488 // Try to submit the transaction up to 3 times with 5 second delay. This can be used
489 // in case of network issues. The idea here is since you are posting via HTTPS there
490 // could be general network issues, so try a few times before you tell customer there
491 // is an issue.
492
493 $i = 1;
494 while ($i++ <= 3) {
495 $responseData = curl_exec($ch);
496 $responseHeaders = curl_getinfo($ch);
497 if ($responseHeaders['http_code'] != 200) {
498 // Let's wait 5 seconds to see if its a temporary network issue.
499 sleep(5);
500 }
501 elseif ($responseHeaders['http_code'] == 200) {
502 // we got a good response, drop out of loop.
503 break;
504 }
505 }
3ae790f3 506 if ($responseHeaders['http_code'] != 200) {
27e1cd31 507 return self::errorExit(9015, "Error connecting to the Payflow Pro API server.");
3ae790f3 508 }
6a488035
TO
509
510 /*
511 * Transaction submitted -
512 * See if we had a curl error - if so tell 'em and bail out
513 *
514 * NOTE: curl_error does not return a logical value (see its documentation), but
515 * a string, which is empty when there was no error.
516 */
517 if ((curl_errno($ch) > 0) || (strlen(curl_error($ch)) > 0)) {
518 curl_close($ch);
519 $errorNum = curl_errno($ch);
520 $errorDesc = curl_error($ch);
521
522 //Paranoia - in the unlikley event that 'curl' errno fails
2aa397bc
TO
523 if ($errorNum == 0) {
524 $errorNum = 9005;
525 }
6a488035
TO
526
527 // Paranoia - in the unlikley event that 'curl' error fails
2aa397bc
TO
528 if (strlen($errorDesc) == 0) {
529 $errorDesc = "Connection to payment gateway failed";
530 }
6a488035
TO
531 if ($errorNum = 60) {
532 return self::errorExit($errorNum, "Curl error - " . $errorDesc .
533 " Try this link for more information http://curl.haxx.se/d
534 ocs/sslcerts.html"
535 );
536 }
537
538 return self::errorExit($errorNum, "Curl error - " . $errorDesc .
539 " processor response = " . $processorResponse
540 );
541 }
542
543 /*
544 * If null data returned - tell 'em and bail out
545 *
546 * NOTE: You will not necessarily get a string back, if the request failed for
547 * any reason, the return value will be the boolean false.
548 */
549 if (($responseData === FALSE) || (strlen($responseData) == 0)) {
550 curl_close($ch);
551 return self::errorExit(9006, "Error: Connection to payment gateway failed - no data
552 returned. Gateway url set to $submiturl");
553 }
554
555 /*
556 * If gateway returned no data - tell 'em and bail out
557 */
558 if (empty($responseData)) {
559 curl_close($ch);
560 return self::errorExit(9007, "Error: No data returned from payment gateway.");
561 }
562
563 /*
564 * Success so far - close the curl and check the data
565 */
566 curl_close($ch);
567 return $responseData;
568 }
6a488035 569
6a488035 570}