PluginProbe
Gravity Forms Eway / 1.5.12
Gravity Forms Eway v1.5.12
2.7.0 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.8.0 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 All 56 releases
gravityforms-eway / class.GFEwayPlugin.php

class.GFEwayPlugin.php in Gravity Forms Eway 1.5.12, at class.GFEwayPlugin.php

627 lines 20.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * custom exception types
5 */
6 class GFEwayException extends Exception {}
7 class GFEwayCurlException extends Exception {}
8
9 /**
10 * class for managing the plugin
11 */
12 class GFEwayPlugin {
13 public $urlBase; // string: base URL path to files in plugin
14 public $options; // array of plugin options
15
16 protected $acceptedCards; // hash map of accepted credit cards
17 protected $txResult = null; // results from credit card payment transaction
18 protected $formHasCcField = false; // true if current form has credit card field
19
20 /**
21 * static method for getting the instance of this singleton object
22 *
23 * @return GFEwayPlugin
24 */
25 public static function getInstance() {
26 static $instance = NULL;
27
28 if (is_null($instance)) {
29 $instance = new self();
30 }
31
32 return $instance;
33 }
34
35 /**
36 * initialise plugin
37 */
38 private function __construct() {
39 // grab options, setting new defaults for any that are missing
40 $this->initOptions();
41
42 // record plugin URL base
43 $this->urlBase = plugin_dir_url(__FILE__);
44
45 // filter the cards array to just Visa, MasterCard and Amex
46 $this->acceptedCards = array('amex' => 1, 'mastercard' => 1, 'visa' => 1);
47
48 add_action('init', array($this, 'init'));
49 }
50
51 /**
52 * initialise plug-in options, handling undefined options by setting defaults
53 */
54 protected function initOptions() {
55 $defaults = array (
56 'customerID' => '87654321',
57 'useStored' => false,
58 'useTest' => true,
59 'useBeagle' => false,
60 'roundTestAmounts' => true,
61 'forceTestAccount' => true,
62 'sslVerifyPeer' => true,
63 );
64
65 $this->options = (array) get_option(GFEWAY_PLUGIN_OPTIONS);
66
67 if (count(array_diff_assoc($defaults, $this->options)) > 0) {
68 $this->options = array_merge($defaults, $this->options);
69 update_option(GFEWAY_PLUGIN_OPTIONS, $this->options);
70 }
71 }
72
73 /**
74 * handle the plugin's init action
75 */
76 public function init() {
77 // do nothing if Gravity Forms isn't enabled
78 if (class_exists('GFCommon')) {
79 // hook into Gravity Forms to enable credit cards and trap form submissions
80 add_filter('gform_pre_render', array($this, 'gformPreRenderSniff'));
81 add_filter('gform_admin_pre_render', array($this, 'gformPreRenderSniff'));
82 add_action('gform_enable_credit_card_field', '__return_true'); // just return true to enable CC fields
83 add_filter('gform_creditcard_types', array($this, 'gformCCTypes'));
84 add_filter('gform_currency', array($this, 'gformCurrency'));
85 add_filter('gform_validation', array($this, 'gformValidation'));
86 add_action('gform_after_submission', array($this, 'gformAfterSubmission'), 10, 2);
87 add_filter('gform_custom_merge_tags', array($this, 'gformCustomMergeTags'), 10, 4);
88 add_filter('gform_replace_merge_tags', array($this, 'gformReplaceMergeTags'), 10, 7);
89
90 // hook into Gravity Forms to handle Recurring Payments custom field
91 new GFEwayRecurringField($this);
92 }
93
94 if (is_admin()) {
95 // kick off the admin handling
96 new GFEwayAdmin($this);
97 }
98 }
99
100 /**
101 * check current form for information
102 * @param array $form
103 * @return array
104 */
105 public function gformPreRenderSniff($form) {
106 // test whether form has a credit card field
107 $this->formHasCcField = self::hasFieldType($form['fields'], 'creditcard');
108
109 return $form;
110 }
111
112 /**
113 * process a form validation filter hook; if last page and has credit card field and total, attempt to bill it
114 * @param array $data an array with elements is_valid (boolean) and form (array of form elements)
115 * @return array
116 */
117 public function gformValidation($data) {
118
119 // make sure all other validations passed
120 if ($data['is_valid']) {
121 $formData = new GFEwayFormData($data['form']);
122
123 // make sure form hasn't already been submitted / processed
124 if ($this->hasFormBeenProcessed($data['form'])) {
125 $data['is_valid'] = false;
126 $formData->ccField['failed_validation'] = true;
127 $formData->ccField['validation_message'] = $this->getErrMsg(GFEWAY_ERROR_ALREADY_SUBMITTED);
128 }
129
130 // make that this is the last page of the form and that we have a credit card field and something to bill
131 // and that credit card field is not hidden (which indicates that payment is being made another way)
132 else if (!$formData->isCcHidden() && $formData->isLastPage() && is_array($formData->ccField)) {
133 if (!$formData->hasPurchaseFields()) {
134 $data['is_valid'] = false;
135 $formData->ccField['failed_validation'] = true;
136 $formData->ccField['validation_message'] = $this->getErrMsg(GFEWAY_ERROR_NO_AMOUNT);
137 }
138 else {
139 // only check credit card details if we've got something to bill
140 if ($formData->total > 0 || $formData->hasRecurringPayments()) {
141 // check for required fields
142 $required = array(
143 'ccName' => $this->getErrMsg(GFEWAY_ERROR_REQ_CARD_HOLDER),
144 'ccNumber' => $this->getErrMsg(GFEWAY_ERROR_REQ_CARD_NAME),
145 );
146 foreach ($required as $name => $message) {
147 if (empty($formData->$name)) {
148 $data['is_valid'] = false;
149 $formData->ccField['failed_validation'] = true;
150 if (!empty($formData->ccField['validation_message']))
151 $formData->ccField['validation_message'] .= '<br />';
152 $formData->ccField['validation_message'] .= $message;
153 }
154 }
155
156 // if no errors, try to bill it
157 if ($data['is_valid']) {
158 if ($formData->hasRecurringPayments()) {
159 $data = $this->processRecurringPayment($data, $formData);
160 }
161 else {
162 $data = $this->processSinglePayment($data, $formData);
163 }
164 }
165 }
166 }
167 }
168
169 // if errors, send back to credit card page
170 if (!$data['is_valid']) {
171 GFFormDisplay::set_current_page($data['form']['id'], $formData->ccField['pageNumber']);
172 }
173 }
174
175 return $data;
176 }
177
178 /**
179 * check whether this form entry's unique ID has already been used; if so, we've already done a payment attempt.
180 * @param array $form
181 * @return boolean
182 */
183 protected function hasFormBeenProcessed($form) {
184 global $wpdb;
185
186 $unique_id = RGFormsModel::get_form_unique_id($form['id']);
187
188 $sql = "select lead_id from {$wpdb->prefix}rg_lead_meta where meta_key='gfeway_unique_id' and meta_value = %s";
189 $lead_id = $wpdb->get_var($wpdb->prepare($sql, $unique_id));
190
191 return !empty($lead_id);
192 }
193
194 /**
195 * get customer ID to use with payment gateway
196 * @return string
197 */
198 protected function getCustomerID() {
199 if ($this->options['useTest'] && $this->options['forceTestAccount']) {
200 return '87654321';
201 }
202
203 return $this->options['customerID'];
204 }
205
206 /**
207 * process regular one-off payment
208 * @param array $data an array with elements is_valid (boolean) and form (array of form elements)
209 * @param GFEwayFormData $formData pre-parsed data from $data
210 * @return array
211 */
212 protected function processSinglePayment($data, $formData) {
213 try {
214 if ($this->options['useStored'])
215 $eway = new GFEwayStoredPayment($this->getCustomerID(), !$this->options['useTest']);
216 else
217 $eway = new GFEwayPayment($this->getCustomerID(), !$this->options['useTest']);
218
219 $eway->sslVerifyPeer = $this->options['sslVerifyPeer'];
220 $eway->invoiceDescription = get_bloginfo('name') . " -- {$data['form']['title']}";
221 $eway->invoiceReference = $data['form']['id'];
222 if (empty($formData->firstName) && empty($formData->lastName)) {
223 $eway->lastName = $formData->ccName; // pick up card holder's name for last name
224 }
225 else {
226 $eway->firstName = $formData->firstName;
227 $eway->lastName = $formData->lastName;
228 }
229 $eway->cardHoldersName = $formData->ccName;
230 $eway->cardNumber = $formData->ccNumber;
231 $eway->cardExpiryMonth = $formData->ccExpMonth;
232 $eway->cardExpiryYear = $formData->ccExpYear;
233 $eway->emailAddress = $formData->email;
234 $eway->address = $formData->address;
235 $eway->postcode = $formData->postcode;
236 $eway->cardVerificationNumber = $formData->ccCVN;
237
238 // if Beagle is enabled, get the country code
239 if ($this->options['useBeagle']) {
240 $eway->customerCountryCode = GFCommon::get_country_code($formData->address_country);
241 }
242
243 // allow plugins/themes to modify invoice description and reference, and set option fields
244 $eway->invoiceDescription = apply_filters('gfeway_invoice_desc', $eway->invoiceDescription, $data['form']);
245 $eway->invoiceReference = apply_filters('gfeway_invoice_ref', $eway->invoiceReference, $data['form']);
246 $eway->transactionNumber = apply_filters('gfeway_invoice_trans_number', $eway->transactionNumber, $data['form']);
247 $eway->option1 = apply_filters('gfeway_invoice_option1', '', $data['form']);
248 $eway->option2 = apply_filters('gfeway_invoice_option2', '', $data['form']);
249 $eway->option3 = apply_filters('gfeway_invoice_option3', '', $data['form']);
250
251 // if live, pass through amount exactly, but if using test site, round up to whole dollars or eWAY will fail
252 if ($this->options['useTest'] && $this->options['roundTestAmounts'])
253 $eway->amount = ceil($formData->total);
254 else
255 $eway->amount = $formData->total;
256
257 //~ error_log(__METHOD__ . "\n" . print_r($eway,1));
258 //~ error_log(__METHOD__ . "\n" . $eway->getPaymentXML());
259
260 $response = $eway->processPayment();
261 if ($response->status) {
262 // transaction was successful, so record transaction number and continue
263 $this->txResult = array (
264 'transaction_id' => $response->transactionNumber,
265 'payment_status' => ($this->options['useStored'] ? 'Pending' : 'Approved'),
266 'payment_date' => date('Y-m-d H:i:s'),
267 'payment_amount' => $response->amount,
268 'transaction_type' => 1,
269 'authcode' => $response->authCode,
270 'beagle_score' => $response->beagleScore,
271 );
272 }
273 else {
274 $data['is_valid'] = false;
275 $formData->ccField['failed_validation'] = true;
276 $formData->ccField['validation_message'] = nl2br($this->getErrMsg(GFEWAY_ERROR_EWAY_FAIL) . ":\n{$response->error}");
277 $this->txResult = array (
278 'payment_status' => 'Failed',
279 );
280 }
281 }
282 catch (GFEwayException $e) {
283 $data['is_valid'] = false;
284 $this->txResult = array (
285 'payment_status' => 'Failed',
286 );
287 $formData->ccField['failed_validation'] = true;
288 $formData->ccField['validation_message'] = nl2br($this->getErrMsg(GFEWAY_ERROR_EWAY_FAIL) . ":\n{$e->getMessage()}");
289 }
290
291 return $data;
292 }
293
294 /**
295 * process recurring payments
296 * @param array $data an array with elements is_valid (boolean) and form (array of form elements)
297 * @param GFEwayFormData $formData pre-parsed data from $data
298 * @return array
299 */
300 protected function processRecurringPayment($data, $formData) {
301 try {
302 $eway = new GFEwayRecurringPayment($this->getCustomerID(), !$this->options['useTest']);
303 $eway->sslVerifyPeer = $this->options['sslVerifyPeer'];
304 if (empty($formData->firstName) && empty($formData->lastName)) {
305 $eway->firstName = '-'; // no first name,
306 $eway->lastName = $formData->ccName; // pick up card holder's name for last name
307 }
308 else {
309 $eway->title = $formData->namePrefix;
310 $eway->firstName = $formData->firstName;
311 $eway->lastName = $formData->lastName;
312 }
313 $eway->emailAddress = $formData->email;
314 $eway->address = $formData->address_street;
315 $eway->suburb = $formData->address_suburb;
316 $eway->state = $formData->address_state;
317 $eway->postcode = $formData->postcode;
318 $eway->country = $formData->address_country;
319 $eway->phone = $formData->phone;
320 $eway->customerReference = $data['form']['id'];
321 $eway->invoiceReference = $data['form']['id'];
322 $eway->invoiceDescription = get_bloginfo('name') . " -- {$data['form']['title']}";
323 $eway->cardHoldersName = $formData->ccName;
324 $eway->cardNumber = $formData->ccNumber;
325 $eway->cardExpiryMonth = $formData->ccExpMonth;
326 $eway->cardExpiryYear = $formData->ccExpYear;
327 $eway->amountInit = $formData->recurring['amountInit'];
328 $eway->dateInit = $formData->recurring['dateInit'];
329 $eway->amountRecur = $formData->recurring['amountRecur'];
330 $eway->dateStart = $formData->recurring['dateStart'];
331 $eway->dateEnd = $formData->recurring['dateEnd'];
332 $eway->intervalSize = $formData->recurring['intervalSize'];
333 $eway->intervalType = $formData->recurring['intervalType'];
334
335 // allow plugins/themes to modify invoice description and reference, and set option fields
336 $eway->invoiceDescription = apply_filters('gfeway_invoice_desc', $eway->invoiceDescription, $data['form']);
337 $eway->customerReference = apply_filters('gfeway_invoice_ref', $eway->customerReference, $data['form']);
338 $eway->invoiceReference = apply_filters('gfeway_invoice_trans_number', $eway->invoiceReference, $data['form']);
339 $eway->customerComments = apply_filters('gfeway_invoice_cust_comments', '', $data['form']);
340
341 //~ error_log(__METHOD__ . "\n" . print_r($eway,1));
342 //~ error_log(__METHOD__ . "\n" . $eway->getPaymentXML());
343
344 $response = $eway->processPayment();
345 if ($response->status) {
346 // transaction was successful, so record transaction number and continue
347 $this->txResult = array (
348 'payment_status' => 'Approved',
349 'payment_date' => date('Y-m-d H:i:s'),
350 'transaction_type' => 1,
351 );
352 }
353 else {
354 $data['is_valid'] = false;
355 $formData->ccField['failed_validation'] = true;
356 $formData->ccField['validation_message'] = nl2br($this->getErrMsg(GFEWAY_ERROR_EWAY_FAIL) . ":\n{$response->error}");
357 $this->txResult = array (
358 'payment_status' => 'Failed',
359 );
360 }
361 }
362 catch (GFEwayException $e) {
363 $data['is_valid'] = false;
364 $this->txResult = array (
365 'payment_status' => 'Failed',
366 );
367 $formData->ccField['failed_validation'] = true;
368 $formData->ccField['validation_message'] = nl2br($this->getErrMsg(GFEWAY_ERROR_EWAY_FAIL) . ":\n{$e->getMessage()}");
369 }
370
371 return $data;
372 }
373
374 /**
375 * save the transaction details to the entry after it has been created
376 * @param array $data an array with elements is_valid (boolean) and form (array of form elements)
377 * @return array
378 */
379 public function gformAfterSubmission($entry, $form) {
380 $formData = new GFEwayFormData($form);
381
382 if (!empty($this->txResult)) {
383 foreach ($this->txResult as $key => $value) {
384 switch ($key) {
385 case 'authcode':
386 case 'beagle_score':
387 // record bank authorisation code, Beagle score
388 gform_update_meta($entry['id'], $key, $value);
389 break;
390
391 default:
392 $entry[$key] = $value;
393 break;
394 }
395 }
396 RGFormsModel::update_lead($entry);
397
398 // record entry's unique ID in database
399 $unique_id = RGFormsModel::get_form_unique_id($form['id']);
400
401 gform_update_meta($entry['id'], 'gfeway_unique_id', $unique_id);
402
403 // record payment gateway
404 gform_update_meta($entry['id'], 'payment_gateway', 'gfeway');
405 }
406 }
407
408 /**
409 * add custom merge tags
410 * @param array $merge_tags
411 * @param int $form_id
412 * @param array $fields
413 * @param int $element_id
414 * @return array
415 */
416 public function gformCustomMergeTags($merge_tags, $form_id, $fields, $element_id) {
417 if ($fields && $this->hasFieldType($fields, 'creditcard')) {
418 $merge_tags[] = array('label' => 'Transaction ID', 'tag' => '{transaction_id}');
419 $merge_tags[] = array('label' => 'Auth Code', 'tag' => '{authcode}');
420 $merge_tags[] = array('label' => 'Payment Amount', 'tag' => '{payment_amount}');
421 $merge_tags[] = array('label' => 'Payment Status', 'tag' => '{payment_status}');
422 $merge_tags[] = array('label' => 'Beagle Score', 'tag' => '{beagle_score}');
423 }
424
425 return $merge_tags;
426 }
427
428 /**
429 * replace custom merge tags
430 * @param string $text
431 * @param array $form
432 * @param array $lead
433 * @param bool $url_encode
434 * @param bool $esc_html
435 * @param bool $nl2br
436 * @param string $format
437 * @return string
438 */
439 public function gformReplaceMergeTags($text, $form, $lead, $url_encode, $esc_html, $nl2br, $format) {
440 if ($this->hasFieldType($form['fields'], 'creditcard')) {
441 if (is_null($this->txResult)) {
442 // lead loaded from database, get values from lead meta
443 $transaction_id = isset($lead['transaction_id']) ? $lead['transaction_id'] : '';
444 $payment_amount = isset($lead['payment_amount']) ? $lead['payment_amount'] : '';
445 $payment_status = isset($lead['payment_status']) ? $lead['payment_status'] : '';
446 $authcode = (string) gform_get_meta($lead['id'], 'authcode');
447 $beagle_score = (string) gform_get_meta($lead['id'], 'beagle_score');
448 }
449 else {
450 // lead not yet saved, get values from transaction results
451 $transaction_id = isset($this->txResult['transaction_id']) ? $this->txResult['transaction_id'] : '';
452 $payment_amount = isset($this->txResult['payment_amount']) ? $this->txResult['payment_amount'] : '';
453 $payment_status = isset($this->txResult['payment_status']) ? $this->txResult['payment_status'] : '';
454 $authcode = isset($this->txResult['authcode']) ? $this->txResult['authcode'] : '';
455 $beagle_score = isset($this->txResult['beagle_score']) ? $this->txResult['beagle_score'] : '';
456 }
457
458 $tags = array (
459 '{transaction_id}',
460 '{payment_amount}',
461 '{payment_status}',
462 '{authcode}',
463 '{beagle_score}',
464 );
465 $values = array (
466 $transaction_id,
467 $payment_amount,
468 $payment_status,
469 $authcode,
470 $beagle_score,
471 );
472
473 $text = str_replace($tags, $values, $text);
474 }
475
476 return $text;
477 }
478
479 /**
480 * tell Gravity Forms what credit cards we can process
481 * @param array $cards
482 * @return array
483 */
484 public function gformCCTypes($cards) {
485 $new_cards = array();
486 foreach ($cards as $i => $card) {
487 if (isset($this->acceptedCards[$card['slug']])) {
488 $new_cards[] = $card;
489 }
490 }
491 return $new_cards;
492 }
493
494 /**
495 * tell Gravity Forms what currencies we can process
496 * @param string $currency
497 * @return string
498 */
499 public function gformCurrency($currency) {
500 // only force currency to AUD if current form has a CC field
501 if ($this->formHasCcField) {
502 $currency = 'AUD';
503 }
504
505 return $currency;
506 }
507
508 /**
509 * check form to see if it has a field of specified type
510 * @param array $fields array of fields
511 * @param string $type name of field type
512 * @return boolean
513 */
514 public static function hasFieldType($fields, $type) {
515 if (is_array($fields)) {
516 foreach ($fields as $field) {
517 if (RGFormsModel::get_input_type($field) == $type)
518 return true;
519 }
520 }
521 return false;
522 }
523
524 /**
525 * get nominated error message, checking for custom error message in WP options
526 * @param string $errName the fixed name for the error message (a constant)
527 * @param boolean $useDefault whether to return the default, or check for a custom message
528 * @return string
529 */
530 public function getErrMsg($errName, $useDefault = false) {
531 static $messages = array (
532 GFEWAY_ERROR_ALREADY_SUBMITTED => 'Payment already submitted and processed - please close your browser window',
533 GFEWAY_ERROR_NO_AMOUNT => 'This form has credit card fields, but no products or totals',
534 GFEWAY_ERROR_REQ_CARD_HOLDER => 'Card holder name is required for credit card processing',
535 GFEWAY_ERROR_REQ_CARD_NAME => 'Card number is required for credit card processing',
536 GFEWAY_ERROR_EWAY_FAIL => 'Error processing card transaction',
537 );
538
539 // default
540 $msg = isset($messages[$errName]) ? $messages[$errName] : 'Unknown error';
541
542 // check for custom message
543 if (!$useDefault) {
544 $msg = get_option($errName, $msg);
545 }
546
547 return $msg;
548 }
549
550 /**
551 * send data via cURL and return result
552 * @param string $url
553 * @param string $data
554 * @param bool $sslVerifyPeer whether to validate the SSL certificate
555 * @return string $response
556 * @throws GFEwayCurlException
557 */
558 public static function curlSendRequest($url, $data, $sslVerifyPeer = true) {
559 // send data via HTTPS and receive response
560 $response = wp_remote_post($url, array(
561 'user-agent' => 'Gravity Forms eWAY',
562 'sslverify' => $sslVerifyPeer,
563 'timeout' => 60,
564 'headers' => array('Content-Type' => 'text/xml; charset=utf-8'),
565 'body' => $data,
566 ));
567
568 //~ error_log(__METHOD__ . "\n" . print_r($response,1));
569
570 if (is_wp_error($response)) {
571 throw new GFEwayCurlException($response->get_error_message());
572 }
573
574 return $response['body'];
575 }
576
577 /**
578 * get the customer's IP address dynamically from server variables
579 * @return string
580 */
581 public static function getCustomerIP() {
582 // if test mode and running on localhost, then kludge to an Aussie IP address
583 $plugin = self::getInstance();
584 if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] == '127.0.0.1' && $plugin->options['useTest']) {
585 return '210.1.199.10';
586 }
587
588 // check for remote address, ignore all other headers as they can be spoofed easily
589 if (isset($_SERVER['REMOTE_ADDR']) && self::isIpAddress($_SERVER['REMOTE_ADDR'])) {
590 return $_SERVER['REMOTE_ADDR'];
591 }
592
593 return '';
594 }
595
596 /**
597 * check whether a given string is an IP address
598 * @param string $maybeIP
599 * @return bool
600 */
601 protected static function isIpAddress($maybeIP) {
602 if (function_exists('inet_pton')) {
603 // check for IPv4 and IPv6 addresses
604 return !!inet_pton($maybeIP);
605 }
606
607 // just check for IPv4 addresses
608 return !!ip2long($maybeIP);
609 }
610
611 /**
612 * display a message (already HTML-conformant)
613 * @param string $msg HTML-encoded message to display inside a paragraph
614 */
615 public static function showMessage($msg) {
616 echo "<div class='updated fade'><p><strong>$msg</strong></p></div>\n";
617 }
618
619 /**
620 * display an error message (already HTML-conformant)
621 * @param string $msg HTML-encoded message to display inside a paragraph
622 */
623 public static function showError($msg) {
624 echo "<div class='error'><p><strong>$msg</strong></p></div>\n";
625 }
626 }
627