Overview

Classes

  • GF_Personal_Data
  • GFAddOn
  • GFAddOnFeedsTable
  • GFAPI
  • GFFeedAddOn
  • GFPaymentAddOn
  • GFPaymentStatsTable
  • Overview
  • Class
   1: <?php
   2: /**
   3:  * Specialist Add-On class designed for use by Add-Ons that collect payment
   4:  *
   5:  * @package GFPaymentAddOn
   6:  */
   7: 
   8: // If Gravity Forms doesn't exist, bail.
   9: if ( ! class_exists( 'GFForms' ) ) {
  10:     die();
  11: }
  12: 
  13: // Require GFFeedAddOn.
  14: require_once( 'class-gf-feed-addon.php' );
  15: 
  16: /**
  17:  * Class GFPaymentAddOn
  18:  *
  19:  * Used to extend Gravity Forms. Specifically, payment add-ons.
  20:  *
  21:  * @since Unknown
  22:  *
  23:  * @uses GFFeedAddOn
  24:  */
  25: abstract class GFPaymentAddOn extends GFFeedAddOn {
  26: 
  27:     /**
  28:      * Defines the version of GFPaymentAddOn.
  29:      *
  30:      * @since  Unknown
  31:      * @access private
  32:      *
  33:      * @used-by GFPaymentAddOn::setup()
  34:      *
  35:      * @var string The version string.
  36:      */
  37:     private $_payment_version = '1.3';
  38: 
  39:     /**
  40:      * Defines if the credit card field is required by the payment add-on.
  41:      *
  42:      * If set to true, user will not be able to create feeds for a form until a credit card field has been added.
  43:      *
  44:      * @since  Unknown
  45:      * @access protected
  46:      *
  47:      * @used-by GFPaymentAddOn::before_delete_field()
  48:      * @used-by GFPaymentAddOn::feed_list_message()
  49:      * @used-by GFPaymentAddOn::init_admin()
  50:      *
  51:      * @var bool True if the payment add-on requires a credit card field. Otherwise, false.
  52:      */
  53:     protected $_requires_credit_card = false;
  54: 
  55:     /**
  56:      * Defines if the payment add-on supports callbacks.
  57:      *
  58:      * If set to true, callbacks/webhooks/IPN will be enabled and the appropriate database table will be created.
  59:      *
  60:      * @since  Unknown
  61:      * @access protected
  62:      *
  63:      * @used-by GFPaymentAddOn::upgrade_payment()
  64:      *
  65:      * @var bool True if the add-on supports callbacks. Otherwise, false.
  66:      */
  67:     protected $_supports_callbacks = false;
  68: 
  69:     /**
  70:      * Stores authorization results returned from the payment gateway.
  71:      *
  72:      * @since  Unknown
  73:      * @access protected
  74:      *
  75:      * @used-by GFPaymentAddOn::entry_post_save()
  76:      * @used-by GFPaymentAddOn::validation()
  77:      *
  78:      * @var array
  79:      */
  80:     protected $authorization = array();
  81: 
  82:     /**
  83:      * Stores the redirect URL that the user should be sent to for payment.
  84:      *
  85:      * @since  Unknown
  86:      * @access protected
  87:      *
  88:      * @used-by GFPaymentAddOn::confirmation()
  89:      * @used-by GFPaymentAddOn::entry_post_save()
  90:      *
  91:      * @var string The URL to redirect to. Defaults to empty string.
  92:      */
  93:     protected $redirect_url = '';
  94: 
  95:     /**
  96:      * Stores the current feed being processed.
  97:      *
  98:      * @since  Unknown
  99:      * @access protected
 100:      *
 101:      * @used-by GFPaymentAddOn::entry_post_save()
 102:      * @used-by GFPaymentAddOn::validation()
 103:      *
 104:      * @var array|array The current Feed Object. Defaults to false.
 105:      */
 106:     protected $current_feed = false;
 107: 
 108:     /**
 109:      * Stores the current submission data being processed.
 110:      *
 111:      * @since  Unknown
 112:      * @access protected
 113:      *
 114:      * @used-by GFPaymentAddOn::entry_post_save()
 115:      * @used-by GFPaymentAddOn::validation()
 116:      *
 117:      * @var array|bool The form submission data. Defaults to false.
 118:      */
 119:     protected $current_submission_data = false;
 120: 
 121:     /**
 122:      * Defines if the payment add-on is a payment gateway add-on.
 123:      *
 124:      * @since  Unknown
 125:      * @access protected
 126:      *
 127:      * @used-by GFPaymentAddOn::entry_post_save()
 128:      * @used-by GFPaymentAddOn::is_payment_gateway()
 129:      * @used-by GFPaymentAddOn::is_payment_gateway()
 130:      *
 131:      * @var bool Set to true if it is a payment gateway add-on. Defaults to false.
 132:      */
 133:     protected $is_payment_gateway = false;
 134: 
 135:     /**
 136:      * Defines if only a single feed should be processed.
 137:      *
 138:      * @since  Unknown
 139:      * @access protected
 140:      *
 141:      * @used-by GFFeedAddOn::maybe_process_feed()
 142:      *
 143:      * @var bool True if only a single feed should be processed. Otherwise, false.
 144:      */
 145:     protected $_single_feed_submission = true;
 146: 
 147:     /**
 148:      * Indicates if the payment gateway requires monetary amounts to be formatted as the smallest unit for the currency being used.
 149:      *
 150:      * For example, $100.00 will be formatted as 10000.
 151:      *
 152:      * @since  Unknown
 153:      * @access protected
 154:      *
 155:      * @used-by GFPaymentAddOn::get_amount_export()
 156:      * @used-by GFPaymentAddOn::get_amount_import()
 157:      *
 158:      * @var bool True if the smallest unit should be used. Otherwise, will include the decimal places.
 159:      */
 160:     protected $_requires_smallest_unit = false;
 161: 
 162:     //--------- Initialization ----------
 163:     /**
 164:      * Runs before the payment add-on is initialized.
 165:      *
 166:      * @since  Unknown
 167:      * @access public
 168:      *
 169:      * @used-by GFAddOn::__construct()
 170:      * @uses    GFAddOn::pre_init()
 171:      * @uses    GFPaymentAddOn::payment_method_is_overridden()
 172:      * @uses    GFPaymentAddOn::setup_cron()
 173:      * @uses    GFPaymentAddOn::maybe_process_callback()
 174:      *
 175:      * @return void
 176:      */
 177:     public function pre_init() {
 178:         parent::pre_init();
 179: 
 180:         // Intercepting callback requests.
 181:         add_action( 'parse_request', array( $this, 'maybe_process_callback' ) );
 182: 
 183:         if ( $this->payment_method_is_overridden( 'check_status' ) ) {
 184:             $this->setup_cron();
 185:         }
 186: 
 187:     }
 188: 
 189:     /**
 190:      * Runs when the payment add-on is initialized.
 191:      *
 192:      * @since  Unknown
 193:      * @access public
 194:      *
 195:      * @uses GFFeedAddOn::init()
 196:      * @uses GFPaymentAddOn::confirmation()
 197:      * @uses GFPaymentAddOn::maybe_validate()
 198:      * @uses GFPaymentAddOn::entry_post_save()
 199:      *
 200:      * @return void
 201:      */
 202:     public function init() {
 203: 
 204:         parent::init();
 205: 
 206:         add_filter( 'gform_confirmation', array( $this, 'confirmation' ), 20, 4 );
 207: 
 208:         add_filter( 'gform_validation', array( $this, 'maybe_validate' ), 20 );
 209:         add_filter( 'gform_entry_post_save', array( $this, 'entry_post_save' ), 10, 2 );
 210: 
 211:         if ( $this->_requires_credit_card ) {
 212:             add_filter( 'gform_register_init_scripts', array( $this, 'register_creditcard_token_script' ), 10, 3 );
 213:             add_filter( 'gform_field_content', array( $this, 'add_creditcard_token_input' ), 10, 5 );
 214:             add_filter( 'gform_form_args', array( $this, 'force_ajax_for_creditcard_tokens' ), 10, 1 );
 215:         }
 216: 
 217:     }
 218: 
 219:     /**
 220:      * Runs only when the payment add-on is initialized in the admin.
 221:      *
 222:      * @since  Unknown
 223:      * @access public
 224:      *
 225:      * @uses GFFeedAddOn::init_admin()
 226:      * @uses GFPaymentAddOn::$_requires_credit_card
 227:      * @uses GFPaymentAddOn::supported_currencies()
 228:      * @uses GFPaymentAddOn::entry_deleted()
 229:      * @uses GFPaymentAddOn::entry_info()
 230:      *
 231:      * @return void
 232:      */
 233:     public function init_admin() {
 234: 
 235:         parent::init_admin();
 236: 
 237:         if ( $this->_requires_credit_card ) {
 238:             // Enable the credit card field.
 239:             add_filter( 'gform_enable_credit_card_field', '__return_true' );
 240:         }
 241: 
 242:         add_filter( 'gform_currencies', array( $this, 'supported_currencies' ) );
 243: 
 244:         add_filter( 'gform_delete_lead', array( $this, 'entry_deleted' ) );
 245:         add_action( 'gform_before_delete_field', array( $this, 'before_delete_field' ), 10, 2 );
 246: 
 247:         if ( rgget( 'page' ) == 'gf_entries' ) {
 248:             add_action( 'gform_payment_details', array( $this, 'entry_info' ), 10, 2 );
 249:         }
 250:     }
 251: 
 252:     /**
 253:      * Runs only when AJAX actions are being performed.
 254:      *
 255:      * @since  Unknown
 256:      * @access public
 257:      *
 258:      * @uses GFFeedAddOn::init_ajax()
 259:      * @uses GFPaymentAddOn::ajax_cancel_subscription()
 260:      * @uses GFPaymentAddOn::before_delete_field()
 261:      *
 262:      * @return void
 263:      */
 264:     public function init_ajax() {
 265:         parent::init_ajax();
 266: 
 267:         add_action( 'wp_ajax_gaddon_cancel_subscription', array( $this, 'ajax_cancel_subscription' ) );
 268:     }
 269: 
 270:     /**
 271:      * Runs the setup of the payment add-on.
 272:      *
 273:      * @since  Unknown
 274:      * @access public
 275:      *
 276:      * @uses GFFeedAddOn::setup()
 277:      * @uses GFPaymentAddOn::upgrade_payment()
 278:      * @uses GFAddOn::$_slug
 279:      * @uses GFPaymentAddOn::$_payment_version
 280:      *
 281:      * @return void
 282:      */
 283:     public function setup() {
 284: 
 285:         parent::setup();
 286: 
 287:         $installed_version = get_option( 'gravityformsaddon_payment_version' );
 288: 
 289: 
 290:         $installed_addons = get_option( 'gravityformsaddon_payment_addons' );
 291:         if ( ! is_array( $installed_addons ) ) {
 292:             $installed_addons = array();
 293:         }
 294: 
 295:         if ( $installed_version != $this->_payment_version ) {
 296:             $this->upgrade_payment( $installed_version );
 297: 
 298:             $installed_addons = array( $this->_slug );
 299:             update_option( 'gravityformsaddon_payment_addons', $installed_addons );
 300:         }
 301:         elseif ( ! in_array( $this->_slug, $installed_addons ) ) {
 302: 
 303:             $this->upgrade_payment( $installed_version );
 304: 
 305:             $installed_addons[] = $this->_slug;
 306:             update_option( 'gravityformsaddon_payment_addons', $installed_addons );
 307:         }
 308: 
 309: 
 310:         update_option( 'gravityformsaddon_payment_version', $this->_payment_version );
 311: 
 312:     }
 313: 
 314:     /**
 315:      * Upgrades the payment add-on framework database tables.
 316:      *
 317:      * Not intended to be used.
 318:      *
 319:      * @since  Unknown
 320:      * @access private
 321:      *
 322:      * @uses GFFormsModel::dbDelta()
 323:      * @uses GFPaymentAddOn::$_supports_callbacks
 324:      * @uses GFForms::drop_index()
 325:      *
 326:      * @global $wpdb
 327:      * @param null $previous_versions Not used.
 328:      *
 329:      * @return void
 330:      */
 331:     private function upgrade_payment( $previous_versions ) {
 332:         global $wpdb;
 333: 
 334:         $charset_collate = GFFormsModel::get_db_charset();
 335: 
 336:         $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_transaction (
 337:                   id int(10) unsigned not null auto_increment,
 338:                   lead_id int(10) unsigned not null,
 339:                   transaction_type varchar(30) not null,
 340:                   transaction_id varchar(50),
 341:                   subscription_id varchar(50),
 342:                   is_recurring tinyint(1) not null default 0,
 343:                   amount decimal(19,2),
 344:                   date_created datetime,
 345:                   PRIMARY KEY  (id),
 346:                   KEY lead_id (lead_id),
 347:                   KEY transaction_type (transaction_type),
 348:                   KEY type_lead (lead_id,transaction_type)
 349:                 ) $charset_collate;";
 350: 
 351:         gf_upgrade()->dbDelta( $sql );
 352: 
 353: 
 354:         if ( $this->_supports_callbacks ) {
 355:             $sql = "CREATE TABLE {$wpdb->prefix}gf_addon_payment_callback (
 356:                       id int(10) unsigned not null auto_increment,
 357:                       lead_id int(10) unsigned not null,
 358:                       addon_slug varchar(250) not null,
 359:                       callback_id varchar(250),
 360:                       date_created datetime,
 361:                       PRIMARY KEY  (id),
 362:                       KEY addon_slug_callback_id (addon_slug(50),callback_id(100))
 363:                     ) $charset_collate;";
 364: 
 365:             gf_upgrade()->dbDelta( $sql );
 366: 
 367:             // Dropping legacy index.
 368:             gf_upgrade()->drop_index( "{$wpdb->prefix}gf_addon_payment_callback", 'slug_callback_id' );
 369:         }
 370: 
 371: 
 372:     }
 373: 
 374:     /**
 375:      * Gets called when Gravity Forms upgrade process is completed. This function is intended to be used internally, override the upgrade() function to execute database update scripts.
 376:      * @param $db_version - Current Gravity Forms database version
 377:      * @param $previous_db_version - Previous Gravity Forms database version
 378:      * @param $force_upgrade - True if this is a request to force an upgrade. False if this is a standard upgrade (due to version change)
 379:      */
 380:     public function post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade ){
 381: 
 382:         // Forcing Upgrade
 383:         if( $force_upgrade ){
 384: 
 385:             $installed_version = get_option( 'gravityformsaddon_payment_version' );
 386: 
 387:             $this->upgrade_payment( $installed_version );
 388: 
 389:             update_option( 'gravityformsaddon_payment_version', $this->_payment_version );
 390: 
 391:         }
 392: 
 393:         parent::post_gravityforms_upgrade( $db_version, $previous_db_version, $force_upgrade );
 394:     }
 395: 
 396:     //--------- Submission Process ------
 397: 
 398:     /**
 399:      * Handles post-submission confirmations.
 400:      *
 401:      * @since  Unknown
 402:      * @access public
 403:      *
 404:      * @uses GFPaymentAddOn::$redirect_url
 405:      *
 406:      * @param array $confirmation The confirmation details.
 407:      * @param array $form         The Form Object that the confirmation is being run for.
 408:      * @param array $entry        The Entry Object associated with the submission.
 409:      * @param bool  $ajax         If the submission was done using AJAX.
 410:      *
 411:      * @return array The confirmation details.
 412:      */
 413:     public function confirmation( $confirmation, $form, $entry, $ajax ) {
 414: 
 415:         if ( empty( $this->redirect_url ) ) {
 416:             return $confirmation;
 417:         }
 418: 
 419:         $confirmation = array( 'redirect' => $this->redirect_url );
 420: 
 421:         return $confirmation;
 422:     }
 423: 
 424:     /**
 425:      * Override this function to specify a URL to the third party payment processor.
 426:      *
 427:      * Useful when developing a payment gateway that processes the payment outside of the website (i.e. PayPal Standard).
 428:      *
 429:      * @since  Unknown
 430:      * @access public
 431:      *
 432:      * @used-by GFPaymentAddOn::entry_post_save()
 433:      *
 434:      * @param array $feed            Active payment feed containing all the configuration data.
 435:      * @param array $submission_data Contains form field data submitted by the user as well as payment information (i.e. payment amount, setup fee, line items, etc...).
 436:      * @param array $form            Current form array containing all form settings.
 437:      * @param array $entry           Current entry array containing entry information (i.e data submitted by users).
 438:      *
 439:      * @return void|string Return a full URL (including http:// or https://) to the payment processor.
 440:      */
 441:     public function redirect_url( $feed, $submission_data, $form, $entry ) {
 442: 
 443:     }
 444: 
 445:     /**
 446:      * Check if the rest of the form has passed validation, is the last page, and that the honeypot field has not been completed.
 447:      *
 448:      * @since  Unknown
 449:      * @access public
 450:      *
 451:      * @used-by GFPaymentAddOn::init()
 452:      * @uses    GFFormDisplay::is_last_page()
 453:      * @uses    GFFormDisplay::get_max_field_id()
 454:      * @uses    GFPaymentAddOn::validation()
 455:      *
 456:      * @param array $validation_result Contains the validation result, the Form Object, and the failed validation page number.
 457:      *
 458:      * @return array $validation_result
 459:      */
 460:     public function maybe_validate( $validation_result ) {
 461: 
 462:         $form            = $validation_result['form'];
 463:         $is_last_page    = GFFormDisplay::is_last_page( $form );
 464:         $failed_honeypot = false;
 465: 
 466:         if ( $is_last_page && rgar( $form, 'enableHoneypot' ) ) {
 467:             $honeypot_id     = GFFormDisplay::get_max_field_id( $form ) + 1;
 468:             $failed_honeypot = ! rgempty( "input_{$honeypot_id}" );
 469:         }
 470: 
 471:         // Validation called by partial entries feature via the heartbeat API.
 472:         $is_heartbeat = rgpost('action') == 'heartbeat';
 473: 
 474:         if ( ! $validation_result['is_valid'] || ! $is_last_page || $failed_honeypot || $is_heartbeat) {
 475:             return $validation_result;
 476:         }
 477: 
 478:         return $this->validation( $validation_result );
 479:     }
 480: 
 481:     /**
 482:      * Handles the validation and processing of payments.
 483:      *
 484:      * @since  Unknown
 485:      * @access public
 486:      *
 487:      * @uses GFPaymentAddOn::get_payment_feed
 488:      * @uses GFPaymentAddOn::get_submission_data
 489:      * @uses GFPaymentAddOn::$is_payment_gateway
 490:      * @uses GFPaymentAddOn::$current_feed
 491:      * @uses GFPaymentAddOn::$current_submission_data
 492:      * @uses GFPaymentAddOn::payment_method_is_overridden
 493:      * @uses GFPaymentAddOn::authorize
 494:      * @uses GFPaymentAddOn::subscribe
 495:      * @uses GFPaymentAddOn::get_validation_result
 496:      * @uses GFPaymentAddOn::$authorization
 497:      * @uses GFFeedAddOn::$_single_submission_feed
 498:      * @uses GFFormsModel::create_lead
 499:      * @uses GFAddOn::log_debug
 500:      * @uses GFFormDisplay::set_current_page
 501:      *
 502:      * @param array $validation_result The validation details to use.
 503:      *
 504:      * @return array The validation details after completion.
 505:      */
 506:     public function validation( $validation_result ) {
 507: 
 508:         if ( ! $validation_result['is_valid'] ) {
 509:             return $validation_result;
 510:         }
 511: 
 512:         $form  = $validation_result['form'];
 513:         $entry = GFFormsModel::create_lead( $form );
 514:         $feed  = $this->get_payment_feed( $entry, $form );
 515: 
 516:         if ( ! $feed ) {
 517:             return $validation_result;
 518:         }
 519: 
 520:         $submission_data = $this->get_submission_data( $feed, $form, $entry );
 521: 
 522:         //Do not process payment if payment amount is 0
 523:         if ( floatval( $submission_data['payment_amount'] ) <= 0 ) {
 524: 
 525:             $this->log_debug( __METHOD__ . '(): Payment amount is zero or less. Not sending to payment gateway.' );
 526: 
 527:             return $validation_result;
 528:         }
 529: 
 530: 
 531:         $this->is_payment_gateway      = true;
 532:         $this->current_feed            = $this->_single_submission_feed = $feed;
 533:         $this->current_submission_data = $submission_data;
 534: 
 535:         $performed_authorization = false;
 536:         $is_subscription         = $feed['meta']['transactionType'] == 'subscription';
 537: 
 538:         if ( $this->payment_method_is_overridden( 'authorize' ) && ! $is_subscription ) {
 539: 
 540:             //Running an authorization only transaction if function is implemented and this is a single payment
 541:             $this->authorization = $this->authorize( $feed, $submission_data, $form, $entry );
 542: 
 543:             $performed_authorization = true;
 544: 
 545:         } elseif ( $this->payment_method_is_overridden( 'subscribe' ) && $is_subscription ) {
 546: 
 547:             $subscription = $this->subscribe( $feed, $submission_data, $form, $entry );
 548: 
 549:             $this->authorization['is_authorized'] = rgar($subscription,'is_success');
 550:             $this->authorization['error_message'] = rgar( $subscription, 'error_message' );
 551:             $this->authorization['subscription']  = $subscription;
 552: 
 553:             $performed_authorization = true;
 554:         }
 555: 
 556:         if ( $performed_authorization ) {
 557:             $this->log_debug( __METHOD__ . "(): Authorization result for form #{$form['id']} submission => " . print_r( $this->authorization, 1 ) );
 558:         }
 559: 
 560:         if ( $performed_authorization && ! rgar( $this->authorization, 'is_authorized' ) ) {
 561:             $validation_result = $this->get_validation_result( $validation_result, $this->authorization );
 562: 
 563:             //Setting up current page to point to the credit card page since that will be the highlighted field
 564:             GFFormDisplay::set_current_page( $validation_result['form']['id'], $validation_result['credit_card_page'] );
 565:         }
 566: 
 567:         return $validation_result;
 568:     }
 569: 
 570:     /**
 571:      * Override this method to add integration code to the payment processor in order to authorize a credit card with or
 572:      * without capturing payment.
 573:      *
 574:      * This method is executed during the form validation process and allows the form submission process to fail with a
 575:      * validation error if there is anything wrong with the payment/authorization. This method is only supported by
 576:      * single payments. For subscriptions or recurring payments, use the GFPaymentAddOn::subscribe() method.
 577:      *
 578:      * @since  Unknown
 579:      * @access public
 580:      *
 581:      * @used-by GFPaymentAddOn::validation()
 582:      *
 583:      * @param array $feed            Current configured payment feed.
 584:      * @param array $submission_data Contains form field data submitted by the user as well as payment information
 585:      *                               (i.e. payment amount, setup fee, line items, etc...).
 586:      * @param array $form            The Form Object.
 587:      * @param array $entry           The Entry Object. NOTE: the entry hasn't been saved to the database at this point,
 588:      *                               so this $entry object does not have the 'ID' property and is only a memory
 589:      *                               representation of the entry.
 590:      *
 591:      * @return array {
 592:      *     Return an $authorization array.
 593:      *
 594:      *     @type bool   $is_authorized  True if the payment is authorized. Otherwise, false.
 595:      *     @type string $error_message  The error message, if present.
 596:      *     @type string $transaction_id The transaction ID.
 597:      *     @type array  $captured_payment {
 598:      *         If payment is captured, an additional array is created.
 599:      *
 600:      *         @type bool   $is_success     If the payment capture is successful.
 601:      *         @type string $error_message  The error message, if any.
 602:      *         @type string $transaction_id The transaction ID of the captured payment.
 603:      *         @type int    $amount         The amount of the captured payment, if successful.
 604:      *     }
 605:      * }
 606:      */
 607:     public function authorize( $feed, $submission_data, $form, $entry ) {
 608: 
 609:     }
 610: 
 611:     /**
 612:      * Override this method to capture a single payment that has been authorized via the authorize() method.
 613:      *
 614:      * Use only with single payments. For subscriptions, use subscribe() instead.
 615:      *
 616:      * @since  Unknown
 617:      * @access public
 618:      *
 619:      * @used-by GFPaymentAddOn::entry_post_save()
 620:      *
 621:      * @param array $authorization   Contains the result of the authorize() function.
 622:      * @param array $feed            Current configured payment feed.
 623:      * @param array $submission_data Contains form field data submitted by the user as well as payment information.
 624:      *                               (i.e. payment amount, setup fee, line items, etc...).
 625:      * @param array $form            Current form array containing all form settings.
 626:      * @param array $entry           Current entry array containing entry information (i.e data submitted by users).
 627:      *
 628:      * @return array {
 629:      *     Return an array with the information about the captured payment in the following format:
 630:      *
 631:      *     @type bool   $is_success     If the payment capture is successful.
 632:      *     @type string $error_message  The error message, if any.
 633:      *     @type string $transaction_id The transaction ID of the captured payment.
 634:      *     @type int    $amount         The amount of the captured payment, if successful.
 635:      *     @type string $payment_method The card issuer.
 636:      * }
 637:      */
 638:     public function capture( $authorization, $feed, $submission_data, $form, $entry ) {
 639: 
 640:     }
 641: 
 642:     /**
 643:      * Override this method to add integration code to the payment processor in order to create a subscription.
 644:      *
 645:      * This method is executed during the form validation process and allows the form submission process to fail with a
 646:      * validation error if there is anything wrong when creating the subscription.
 647:      *
 648:      * @since  Unknown
 649:      * @access public
 650:      *
 651:      * @used-by GFPaymentAddOn::validation()
 652:      *
 653:      * @param array $feed            Current configured payment feed.
 654:      * @param array $submission_data Contains form field data submitted by the user as well as payment information
 655:      *                               (i.e. payment amount, setup fee, line items, etc...).
 656:      * @param array $form            Current form array containing all form settings.
 657:      * @param array $entry           Current entry array containing entry information (i.e data submitted by users).
 658:      *                               NOTE: the entry hasn't been saved to the database at this point, so this $entry
 659:      *                               object does not have the 'ID' property and is only a memory representation of the entry.
 660:      *
 661:      * @return array {
 662:      *     Return an $subscription array in the following format:
 663:      *
 664:      *     @type bool   $is_success      If the subscription is successful.
 665:      *     @type string $error_message   The error message, if applicable.
 666:      *     @type string $subscription_id The subscription ID.
 667:      *     @type int    $amount          The subscription amount.
 668:      *     @type array  $captured_payment {
 669:      *         If payment is captured, an additional array is created.
 670:      *
 671:      *         @type bool   $is_success     If the payment capture is successful.
 672:      *         @type string $error_message  The error message, if any.
 673:      *         @type string $transaction_id The transaction ID of the captured payment.
 674:      *         @type int    $amount         The amount of the captured payment, if successful.
 675:      *     }
 676:      *
 677:      * To implement an initial/setup fee for gateways that don't support setup fees as part of subscriptions, manually
 678:      * capture the funds for the setup fee as a separate transaction and send that payment information in the
 679:      * following 'captured_payment' array:
 680:      *
 681:      * 'captured_payment' => [
 682:      *     'name'           => 'Setup Fee',
 683:      *     'is_success'     => true|false,
 684:      *     'error_message'  => 'error message',
 685:      *     'transaction_id' => 'xxx',
 686:      *     'amount'         => 20
 687:      * ]
 688:      */
 689:     public function subscribe( $feed, $submission_data, $form, $entry ) {
 690: 
 691:     }
 692: 
 693:     /**
 694:      * Override this method to add integration code to the payment processor in order to cancel a subscription.
 695:      *
 696:      * This method is executed when a subscription is canceled from the Payment Gateway (i.e. Stripe or PayPal).
 697:      *
 698:      * @since  Unknown
 699:      * @access public
 700:      *
 701:      * @used-by GFPaymentAddOn::ajax_cancel_subscription()
 702:      *
 703:      * @param array $entry Current entry array containing entry information (i.e data submitted by users).
 704:      * @param array $feed  Current configured payment feed.
 705:      *
 706:      * @return bool Returns true if the subscription was cancelled successfully and false otherwise.
 707:      *
 708:      */
 709:     public function cancel( $entry, $feed ) {
 710:         return false;
 711:     }
 712: 
 713:     /**
 714:      * Gets the payment validation result.
 715:      *
 716:      * @since  Unknown
 717:      * @access public
 718:      *
 719:      * @used-by GFPaymentAddOn::validation()
 720:      *
 721:      * @param array $validation_result    Contains the form validation results.
 722:      * @param array $authorization_result Contains the form authorization results.
 723:      *
 724:      * @return array The validation result for the credit card field.
 725:      */
 726:     public function get_validation_result( $validation_result, $authorization_result ) {
 727: 
 728:         $credit_card_page = 0;
 729:         foreach ( $validation_result['form']['fields'] as &$field ) {
 730:             if ( $field->type == 'creditcard' ) {
 731:                 $field->failed_validation  = true;
 732:                 $field->validation_message = $authorization_result['error_message'];
 733:                 $credit_card_page          = $field->pageNumber;
 734:                 break;
 735:             }
 736:         }
 737: 
 738:         $validation_result['credit_card_page'] = $credit_card_page;
 739:         $validation_result['is_valid']         = false;
 740: 
 741:         return $validation_result;
 742: 
 743:     }
 744: 
 745:     /**
 746:      * Handles additional processing after an entry is saved.
 747:      *
 748:      * @since  Unknown
 749:      * @access public
 750:      *
 751:      * @used-by GFPaymentAddOn::init()
 752:      * @uses    GFPaymentAddOn::$is_payment_gateway
 753:      * @uses    GFPaymentAddOn::$current_feed
 754:      * @uses    GFPaymentAddOn::$authorization
 755:      * @uses    GFPaymentAddOn::process_subscription()
 756:      * @uses    GFPaymentAddOn::payment_method_is_overridden()
 757:      * @uses    GFPaymentAddOn::process_capture()
 758:      * @uses    GFPaymentAddOn::redirect_url()
 759:      *
 760:      * @param array $entry The Entry Object.
 761:      * @param array $form  The Form Object.
 762:      *
 763:      * @return array The Entry Object.
 764:      */
 765:     public function entry_post_save( $entry, $form ) {
 766: 
 767:         if ( ! $this->is_payment_gateway ) {
 768:             return $entry;
 769:         }
 770: 
 771:         // Saving which gateway was used to process this entry.
 772:         gform_update_meta( $entry['id'], 'payment_gateway', $this->_slug );
 773: 
 774:         $feed = $this->current_feed;
 775: 
 776:         if ( ! empty( $this->authorization ) ) {
 777:             // If an authorization was done, capture it.
 778: 
 779:             if ( $feed['meta']['transactionType'] == 'subscription' ) {
 780: 
 781:                 $entry = $this->process_subscription( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
 782: 
 783:             } else {
 784: 
 785:                 if ( $this->payment_method_is_overridden( 'capture' ) && rgempty( 'captured_payment', $this->authorization ) ) {
 786: 
 787:                     $this->authorization['captured_payment'] = $this->capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
 788: 
 789:                 }
 790: 
 791:                 $entry = $this->process_capture( $this->authorization, $feed, $this->current_submission_data, $form, $entry );
 792:             }
 793:         } elseif ( $this->payment_method_is_overridden( 'redirect_url' ) ) {
 794: 
 795:             // If the url_redirect() function is overridden, call it.
 796: 
 797:             // Getting URL to redirect to ( saved to be used by the confirmation() function ).
 798:             $this->redirect_url = $this->redirect_url( $feed, $this->current_submission_data, $form, $entry );
 799: 
 800:             // Setting transaction_type to subscription or one time payment.
 801:             $entry['transaction_type'] = rgars( $feed, 'meta/transactionType' ) == 'subscription' ? 2 : 1;
 802:             $entry['payment_status']   = 'Processing';
 803: 
 804:         }
 805: 
 806:         return $entry;
 807:     }
 808: 
 809:     /**
 810:      * Processed the capturing of payments.
 811:      *
 812:      * @since  Unknown
 813:      * @access public
 814:      *
 815:      * @used-by GFPaymentAddOn::entry_post_save()
 816:      * @uses    GFPaymentAddOn::complete_authorization()
 817:      * @uses    GFPaymentAddOn::complete_payment()
 818:      * @uses    GFPaymentAddOn::fail_payment()
 819:      *
 820:      * @param array $authorization   The payment authorization details.
 821:      * @param array $feed            The Feed Object.
 822:      * @param array $submission_data The form submission data.
 823:      * @param array $form            The Form Object.
 824:      * @param array $entry           The Entry Object.
 825:      *
 826:      * @return array The Entry Object.
 827:      */
 828:     public function process_capture( $authorization, $feed, $submission_data, $form, $entry ) {
 829: 
 830:         $payment = rgar( $authorization, 'captured_payment' );
 831:         if ( empty( $payment ) && rgar( $authorization, 'is_authorized' ) ) {
 832:             if ( ! rgar( $authorization, 'amount' ) ) {
 833:                 $authorization['amount'] = rgar( $submission_data, 'payment_amount' );
 834:             }
 835: 
 836:             $this->complete_authorization( $entry, $authorization );
 837: 
 838:             return $entry;
 839:         }
 840: 
 841:         $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $payment, 1 ) );
 842: 
 843:         if ( $payment['is_success'] ) {
 844: 
 845:             $entry['is_fulfilled']     = '1';
 846:             $payment['payment_status'] = 'Paid';
 847:             $payment['payment_date']   = gmdate( 'Y-m-d H:i:s' );
 848:             $payment['type']           = 'complete_payment';
 849:             $this->complete_payment( $entry, $payment );
 850: 
 851:         } else {
 852: 
 853:             $entry['payment_status'] = 'Failed';
 854:             $payment['type']         = 'fail_payment';
 855:             $payment['note']         = sprintf( esc_html__( 'Payment failed to be captured. Reason: %s', 'gravityforms' ), $payment['error_message'] );
 856:             $this->fail_payment( $entry, $payment );
 857: 
 858:         }
 859: 
 860:         return $entry;
 861: 
 862:     }
 863: 
 864:     /**
 865:      * Processes payment subscriptions.
 866:      *
 867:      * @since  Unknown
 868:      * @access public
 869:      *
 870:      * @used-by GFPaymentAddOn::entry_post_save()
 871:      * @uses    GFPaymentAddOn::insert_transaction()
 872:      * @uses    GFCommon::to_money()
 873:      * @uses    GFAddOn::add_note()
 874:      * @uses    GFPaymentAddOn::start_subscription()
 875:      * @uses    GFAPI::update_entry()
 876:      * @uses    GFPaymentAddOn::post_payment_action()
 877:      *
 878:      * @param array $authorization   The payment authorization details.
 879:      * @param array $feed            The Feed Object.
 880:      * @param array $submission_data The form submission data.
 881:      * @param array $form            The Form Object.
 882:      * @param array $entry           The Entry Object.
 883:      *
 884:      * @return array The Entry Object.
 885:      */
 886:     public function process_subscription( $authorization, $feed, $submission_data, $form, $entry ) {
 887: 
 888:         $subscription = rgar( $authorization, 'subscription' );
 889:         if ( empty( $subscription ) ) {
 890:             return $entry;
 891:         }
 892: 
 893:         $this->log_debug( __METHOD__ . "(): Updating entry #{$entry['id']} with result => " . print_r( $subscription, 1 ) );
 894: 
 895:         // If setup fee / trial is captured as part of a separate transaction.
 896:         $payment      = rgar( $subscription, 'captured_payment' );
 897:         $payment_name = rgempty( 'name', $payment ) ? esc_html__( 'Initial payment', 'gravityforms' ) : $payment['name'];
 898: 
 899:         if ( $payment && $payment['is_success'] ) {
 900: 
 901:             $this->insert_transaction( $entry['id'], 'payment', $payment['transaction_id'], $payment['amount'], false, rgar( $subscription, 'subscription_id' ) );
 902: 
 903:             $amount_formatted = GFCommon::to_money( $payment['amount'], $entry['currency'] );
 904:             $note             = sprintf( esc_html__( '%s has been captured successfully. Amount: %s. Transaction Id: %s', 'gravityforms' ), $payment_name, $amount_formatted, $payment['transaction_id'] );
 905:             $this->add_note( $entry['id'], $note, 'success' );
 906: 
 907:         } elseif ( $payment && ! $payment['is_success'] ) {
 908: 
 909:             $this->add_note( $entry['id'], sprintf( esc_html__( 'Failed to capture %s. Reason: %s.', 'gravityforms' ), $payment['error_message'], $payment_name ), 'error' );
 910: 
 911:         }
 912: 
 913:         // Updating subscription information.
 914:         if ( $subscription['is_success'] ) {
 915: 
 916:             $entry = $this->start_subscription( $entry, $subscription );
 917: 
 918:         } else {
 919: 
 920:             $entry['payment_status'] = 'Failed';
 921:             GFAPI::update_entry( $entry );
 922: 
 923:             $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription failed to be created. Reason: %s', 'gravityforms' ), $subscription['error_message'] ), 'error' );
 924: 
 925:             $subscription['type'] = 'fail_create_subscription';
 926:             $this->post_payment_action( $entry, $subscription );
 927: 
 928:         }
 929: 
 930:         return $entry;
 931: 
 932:     }
 933: 
 934:     /**
 935:      * Inserts a new transaction item.
 936:      *
 937:      * @since  Unknown
 938:      * @access public
 939:      *
 940:      * @used-by GFPaymentAddOn::add_subscription_payment()
 941:      * @used-by GFPaymentAddOn::complete_authorization()
 942:      * @used-by GFPaymentAddOn::process_subscription()
 943:      * @used-by GFPaymentAddOn::refund_payment()
 944:      * @uses    wpdb::get_var()
 945:      * @uses    wpdb::prepare()
 946:      * @uses    wpdb::query()
 947:      * @uses    wpdb::$insert_id
 948:      *
 949:      * @global wpdb        $wpdb             The wpdb object.
 950:      * @param  int         $entry_id         The entry ID that contains the transaction.
 951:      * @param  string      $transaction_type The transaction type.
 952:      * @param  string      $transaction_id   The ID of the transaction to be inserted.
 953:      * @param  float       $amount           The transaction amount.
 954:      * @param  int|null    $is_recurring     If the transaction is recurring. Defaults to null.
 955:      * @param  string|null $subscription_id  The subscription ID tied to the transaction, if related to a subscription.
 956:      *                                       Defaults to null.
 957:      *
 958:      * @return int|WP_Error The row ID from the database entry. WP_Error if error.
 959:      */
 960:     public function insert_transaction( $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring = null, $subscription_id = null ) {
 961:         global $wpdb;
 962: 
 963:         // @todo: make sure stats does not show setup fee as a recurring payment
 964:         $payment_count = $wpdb->get_var( $wpdb->prepare( "SELECT count(id) FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE lead_id=%d", $entry_id ) );
 965:         $is_recurring  = $payment_count > 0 && $transaction_type == 'payment' ? 1 : 0;
 966:         $subscription_id = empty( $subscription_id ) ? '' : $subscription_id;
 967: 
 968:         $sql = $wpdb->prepare(
 969:             " INSERT INTO {$wpdb->prefix}gf_addon_payment_transaction (lead_id, transaction_type, transaction_id, amount, is_recurring, date_created, subscription_id)
 970:                                 values(%d, %s, %s, %f, %d, utc_timestamp(), %s)", $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring, $subscription_id
 971:         );
 972:         $wpdb->query( $sql );
 973: 
 974:         $txn_id = $wpdb->insert_id;
 975: 
 976:         /**
 977:          * Fires after a payment transaction is created in Gravity Forms.
 978:          *
 979:          * @since Unknown
 980:          *
 981:          * @param int    $txn_id           The overall Transaction ID.
 982:          * @param int    $entry_id         The new Entry ID.
 983:          * @param string $transaction_type The Type of transaction that was made.
 984:          * @param int    $transaction_id   The transaction ID.
 985:          * @param string $amount           The amount payed in the transaction.
 986:          * @param bool   $is_recurring     True or false if this is an ongoing payment.
 987:          */
 988:         do_action( 'gform_post_payment_transaction', $txn_id, $entry_id, $transaction_type, $transaction_id, $amount, $is_recurring, $subscription_id );
 989:         if ( has_filter( 'gform_post_payment_transaction' ) ) {
 990:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_transaction.' );
 991:         }
 992: 
 993:         return $txn_id;
 994:     }
 995: 
 996:     /**
 997:      * Gets the payment submission feed.
 998:      *
 999:      * @since  Unknown
1000:      * @access public
1001:      *
1002:      * @used-by GFPaymentAddOn::ajax_cancel_subscription()
1003:      * @used-by GFPaymentAddOn::process_callback_action()
1004:      * @used-by GFPaymentAddOn::validation()
1005:      * @uses    GFFeedAddOn::get_feeds_by_entry()
1006:      * @uses    GFFeedAddOn::get_feed()
1007:      * @uses    GFFeedAddOn::get_feeds()
1008:      * @uses    GFFeedAddOn::pre_process_feeds()
1009:      * @uses    GFFeedAddOn::is_feed_condition_met()
1010:      *
1011:      * @param array      $entry The Entry Object.
1012:      * @param bool|array $form  The Form Object. Defaults to false.
1013:      *
1014:      * @return array The submission feed.
1015:      */
1016:     public function get_payment_feed( $entry, $form = false ) {
1017:         $submission_feed = false;
1018: 
1019:         // Only occurs if entry has already been processed and feed has been stored in entry meta.
1020:         if ( $entry['id'] ) {
1021:             $feeds           = $this->get_feeds_by_entry( $entry['id'] );
1022:             $submission_feed = empty( $feeds ) ? false : $this->get_feed( $feeds[0] );
1023:         } elseif ( $form ) {
1024: 
1025:             // Getting all feeds.
1026:             $feeds = $this->get_feeds( $form['id'] );
1027:             $feeds = $this->pre_process_feeds( $feeds, $entry, $form );
1028: 
1029:             foreach ( $feeds as $feed ) {
1030:                 if ( $feed['is_active'] && $this->is_feed_condition_met( $feed, $form, $entry ) ) {
1031:                     $submission_feed = $feed;
1032:                     break;
1033:                 }
1034:             }
1035:         }
1036: 
1037: 
1038:         return $submission_feed;
1039:     }
1040: 
1041:     /**
1042:      * Determines if this is a payment gateway add-on.
1043:      *
1044:      * @since  Unknown
1045:      * @access public
1046:      *
1047:      * @used-by GFPaymentAddOn::entry_info()
1048:      * @uses    GFPaymentAddOn::$is_payment_gateway()
1049:      * @uses    GFAddOn::$_slug
1050:      *
1051:      * @param int $entry_id The entry ID.
1052:      *
1053:      * @return bool True if it is a payment gateway. False otherwise.
1054:      */
1055:     public function is_payment_gateway( $entry_id ) {
1056: 
1057:         if ( $this->is_payment_gateway ) {
1058:             return true;
1059:         }
1060: 
1061:         $gateway = gform_get_meta( $entry_id, 'payment_gateway' );
1062: 
1063:         return $gateway == $this->_slug;
1064:     }
1065: 
1066:     /**
1067:      * Gets the payment submission data.
1068:      *
1069:      * @since  Unknown
1070:      * @access public
1071:      *
1072:      * @used-by GFPaymentAddOn::validation()
1073:      * @uses    GFPaymentAddOn::billing_info_fields()
1074:      * @uses    GFPaymentAddOn::get_credit_card_field()
1075:      * @uses    GFAddOn::get_field_value()
1076:      * @uses    GFPaymentAddOn::remove_spaces_from_card_number()
1077:      * @uses    GFPaymentAddOn::get_order_data()
1078:      *
1079:      * @param array $feed  The Feed Object.
1080:      * @param array $form  The Form Object.
1081:      * @param array $entry The Entry Object.
1082:      *
1083:      * @return array The payment submission data.
1084:      */
1085:     public function get_submission_data( $feed, $form, $entry ) {
1086: 
1087:         $submission_data = array();
1088: 
1089:         $submission_data['form_title'] = $form['title'];
1090: 
1091:         // Getting mapped field data.
1092:         $billing_fields = $this->billing_info_fields();
1093:         foreach ( $billing_fields as $billing_field ) {
1094:             $field_name                     = $billing_field['name'];
1095:             $input_id                       = rgar( $feed['meta'], "billingInformation_{$field_name}" );
1096:             $submission_data[ $field_name ] = $this->get_field_value( $form, $entry, $input_id );
1097:         }
1098: 
1099:         // Getting credit card field data.
1100:         $card_field = $this->get_credit_card_field( $form );
1101:         if ( $card_field ) {
1102: 
1103:             $submission_data['card_number']          = $this->remove_spaces_from_card_number( rgpost( "input_{$card_field->id}_1" ) );
1104:             $submission_data['card_expiration_date'] = rgpost( "input_{$card_field->id}_2" );
1105:             $submission_data['card_security_code']   = rgpost( "input_{$card_field->id}_3" );
1106:             $submission_data['card_name']            = rgpost( "input_{$card_field->id}_5" );
1107: 
1108:         }
1109: 
1110:         // Getting product field data.
1111:         $order_info      = $this->get_order_data( $feed, $form, $entry );
1112:         $submission_data = array_merge( $submission_data, $order_info );
1113: 
1114:         /**
1115:          * Enables the Submission Data to be modified before it is used during feed processing by the payment add-on.
1116:          *
1117:          * @since 1.9.12.8
1118:          *
1119:          * @param array $submission_data The customer and transaction data.
1120:          * @param array $feed The Feed Object.
1121:          * @param array $form The Form Object.
1122:          * @param array $entry The Entry Object.
1123:          *
1124:          * @return array $submission_data
1125:          */
1126: 
1127:         return gf_apply_filters( array( 'gform_submission_data_pre_process_payment', $form['id'] ), $submission_data, $feed, $form, $entry );
1128:     }
1129: 
1130:     /**
1131:      * Gets the credit card field object.
1132:      *
1133:      * @since  Unknown
1134:      * @access public
1135:      *
1136:      * @used-by GFPaymentAddOn::before_delete_field()
1137:      * @used-by GFPaymentAddOn::get_submission_data()
1138:      * @used-by GFPaymentAddOn::has_credit_card_field()
1139:      * @uses    GFAPI::get_fields_by_type()
1140:      *
1141:      * @param array $form The Form Object.
1142:      *
1143:      * @return bool|GF_Field_CreditCard The credit card field object, if found. Otherwise, false.
1144:      */
1145:     public function get_credit_card_field( $form ) {
1146:         $fields = GFAPI::get_fields_by_type( $form, array( 'creditcard' ) );
1147: 
1148:         return empty( $fields ) ? false : $fields[0];
1149:     }
1150: 
1151:     /**
1152:      * Checks if a form has a credit card field.
1153:      *
1154:      * @since  Unknown
1155:      * @access public
1156:      *
1157:      * @used-by GFPaymentAddOn::feed_list_message()
1158:      * @uses    GFPaymentAddOn::get_credit_card_field()
1159:      *
1160:      * @param array $form The Form Object.
1161:      *
1162:      * @return bool True if the form has a credit card field. False otherwise.
1163:      */
1164:     public function has_credit_card_field( $form ) {
1165:         return $this->get_credit_card_field( $form ) !== false;
1166:     }
1167: 
1168:     /**
1169:      * Gets payment order data.
1170:      *
1171:      * @since  Unknown
1172:      * @access public
1173:      *
1174:      * @used-by GFPaymentAddOn::get_submission_data()
1175:      * @uses    GFCommon::get_product_fields()
1176:      * @uses    GFCommon::to_number()
1177:      *
1178:      * @param array $feed  The Feed Object.
1179:      * @param array $form  The Form Object.
1180:      * @param array $entry The Entry Object.
1181:      *
1182:      * @return array {
1183:      *     The order data.
1184:      *
1185:      *     @type float $payment_amount The payment amount of the order.
1186:      *     @type float $setup_fee      The setup fee, if any.
1187:      *     @type float $trial          The trial fee, if any.
1188:      *     @type float $discounts      Discounts applied, if any.
1189:      * }
1190:      */
1191:     public function get_order_data( $feed, $form, $entry ) {
1192: 
1193:         $products = GFCommon::get_product_fields( $form, $entry );
1194: 
1195:         $payment_field   = $feed['meta']['transactionType'] == 'product' ? rgars( $feed, 'meta/paymentAmount' ) : rgars( $feed, 'meta/recurringAmount' );
1196:         $setup_fee_field = rgar( $feed['meta'], 'setupFee_enabled' ) ? $feed['meta']['setupFee_product'] : false;
1197:         $trial_field     = rgar( $feed['meta'], 'trial_enabled' ) ? rgars( $feed, 'meta/trial_product' ) : false;
1198: 
1199:         $amount       = 0;
1200:         $line_items   = array();
1201:         $discounts    = array();
1202:         $fee_amount   = 0;
1203:         $trial_amount = 0;
1204:         foreach ( $products['products'] as $field_id => $product ) {
1205: 
1206:             $quantity      = $product['quantity'] ? $product['quantity'] : 1;
1207:             $product_price = GFCommon::to_number( $product['price'], $entry['currency'] );
1208: 
1209:             $options = array();
1210:             if ( is_array( rgar( $product, 'options' ) ) ) {
1211:                 foreach ( $product['options'] as $option ) {
1212:                     $options[] = $option['option_name'];
1213:                     $product_price += $option['price'];
1214:                 }
1215:             }
1216: 
1217:             $is_trial_or_setup_fee = false;
1218: 
1219:             if ( ! empty( $trial_field ) && $trial_field == $field_id ) {
1220: 
1221:                 $trial_amount          = $product_price * $quantity;
1222:                 $is_trial_or_setup_fee = true;
1223: 
1224:             } elseif ( ! empty( $setup_fee_field ) && $setup_fee_field == $field_id ) {
1225: 
1226:                 $fee_amount            = $product_price * $quantity;
1227:                 $is_trial_or_setup_fee = true;
1228:             }
1229: 
1230:             // Do not add to line items if the payment field selected in the feed is not the current field.
1231:             if ( is_numeric( $payment_field ) && $payment_field != $field_id ) {
1232:                 continue;
1233:             }
1234: 
1235:             // Do not add to line items if the payment field is set to "Form Total" and the current field was used for trial or setup fee.
1236:             if ( $is_trial_or_setup_fee && ! is_numeric( $payment_field ) ) {
1237:                 continue;
1238:             }
1239: 
1240:             $amount += $product_price * $quantity;
1241: 
1242:             $description = '';
1243:             if ( ! empty( $options ) ) {
1244:                 $description = esc_html__( 'options: ', 'gravityforms' ) . ' ' . implode( ', ', $options );
1245:             }
1246: 
1247:             if ( $product_price >= 0 ) {
1248:                 $line_items[] = array(
1249:                     'id'          => $field_id,
1250:                     'name'        => $product['name'],
1251:                     'description' => $description,
1252:                     'quantity'    => $quantity,
1253:                     'unit_price'  => GFCommon::to_number( $product_price, $entry['currency'] ),
1254:                     'options'     => rgar( $product, 'options' )
1255:                 );
1256:             } else {
1257:                 $discounts[] = array(
1258:                     'id'          => $field_id,
1259:                     'name'        => $product['name'],
1260:                     'description' => $description,
1261:                     'quantity'    => $quantity,
1262:                     'unit_price'  => GFCommon::to_number( $product_price, $entry['currency'] ),
1263:                     'options'     => rgar( $product, 'options' )
1264:                 );
1265:             }
1266:         }
1267: 
1268:         if ( $trial_field == 'enter_amount' ) {
1269:             $trial_amount = rgar( $feed['meta'], 'trial_amount' ) ? GFCommon::to_number( rgar( $feed['meta'], 'trial_amount' ), $entry['currency'] ) : 0;
1270:         }
1271: 
1272:         if ( ! empty( $products['shipping']['name'] ) && ! is_numeric( $payment_field ) ) {
1273:             $line_items[] = array(
1274:                 'id'          => $products['shipping']['id'],
1275:                 'name'        => $products['shipping']['name'],
1276:                 'description' => '',
1277:                 'quantity'    => 1,
1278:                 'unit_price'  => GFCommon::to_number( $products['shipping']['price'], $entry['currency'] ),
1279:                 'is_shipping' => 1
1280:             );
1281:             $amount += $products['shipping']['price'];
1282:         }
1283: 
1284:         return array(
1285:             'payment_amount' => $amount,
1286:             'setup_fee'      => $fee_amount,
1287:             'trial'          => $trial_amount,
1288:             'line_items'     => $line_items,
1289:             'discounts'      => $discounts
1290:         );
1291:     }
1292: 
1293:     /**
1294:      * Checks if the callback should be processed by this payment add-on.
1295:      *
1296:      * @since  Unknown
1297:      * @access public
1298:      *
1299:      * @used-by GFPaymentAddOn::maybe_process_callback()
1300:      * @uses    GFAddOn::$_slug
1301:      *
1302:      * @return bool True if valid. False otherwise.
1303:      */
1304:     public function is_callback_valid() {
1305:         if ( rgget( 'callback' ) != $this->_slug ) {
1306:             return false;
1307:         }
1308: 
1309:         return true;
1310:     }
1311: 
1312: 
1313:     //--------- Callback (aka Webhook)----------------
1314: 
1315:     /**
1316:      * Conditionally initiates processing of the callback.
1317:      *
1318:      * Checks to see if the callback is valid, processes callback actions, then returns the appropriate response.
1319:      *
1320:      * @since  Unknown
1321:      * @access public
1322:      *
1323:      * @used-by GFPaymentAddOn::pre_init()
1324:      * @uses    GFPaymentAddOn::is_callback_valid()
1325:      * @uses    GFAddOn::$_slug
1326:      * @uses    GFPaymentAddOn::callback()
1327:      * @uses    GFPaymentAddOn::display_callback_error()
1328:      * @uses    GFPaymentAddOn::process_callback_action()
1329:      * @uses    GFPaymentAddOn::post_callback()
1330:      *
1331:      * @return void
1332:      */
1333:     public function maybe_process_callback() {
1334: 
1335:         // Ignoring requests that are not this addon's callbacks.
1336:         if ( ! $this->is_callback_valid() ) {
1337:             return;
1338:         }
1339: 
1340:         // Returns either false or an array of data about the callback request which payment add-on will then use
1341:         // to generically process the callback data
1342:         $this->log_debug( __METHOD__ . '(): Initializing callback processing for: ' . $this->_slug );
1343: 
1344:         $callback_action = $this->callback();
1345: 
1346:         $this->log_debug( __METHOD__ . '(): Result from gateway callback => ' . print_r( $callback_action, true ) );
1347: 
1348:         $result = false;
1349:         if ( is_wp_error( $callback_action ) ) {
1350:             $this->display_callback_error( $callback_action );
1351:         } elseif ( $callback_action && is_array( $callback_action ) && rgar( $callback_action, 'type' ) && ! rgar( $callback_action, 'abort_callback' ) ) {
1352: 
1353:             $result = $this->process_callback_action( $callback_action );
1354: 
1355:             $this->log_debug( __METHOD__ . '(): Result of callback action => ' . print_r( $result, true ) );
1356: 
1357:             if ( is_wp_error( $result ) ) {
1358:                 $this->display_callback_error( $result );
1359:             } elseif ( ! $result ) {
1360:                 status_header( 200 );
1361:                 echo 'Callback could not be processed.';
1362:             } else {
1363:                 status_header( 200 );
1364:                 echo 'Callback processed successfully.';
1365:             }
1366:         } else {
1367:             status_header( 200 );
1368:             echo 'Callback bypassed';
1369:         }
1370: 
1371:         $this->post_callback( $callback_action, $result );
1372: 
1373:         die();
1374:     }
1375: 
1376:     /**
1377:      * Displays a callback error, if needed.
1378:      *
1379:      * @since  Unknown
1380:      * @access public
1381:      *
1382:      * @uses WP_Error::get_error_data()
1383:      * @uses WP_Error::get_error_message()
1384:      *
1385:      * @param WP_Error $error The error.
1386:      *
1387:      * @return void
1388:      */
1389:     private function display_callback_error( $error ) {
1390: 
1391:         $data   = $error->get_error_data();
1392:         $status = ! rgempty( 'status_header', $data ) ? $data['status_header'] : 200;
1393: 
1394:         status_header( $status );
1395:         echo $error->get_error_message();
1396:     }
1397: 
1398:     /**
1399:      * Processes callback based on provided data.
1400:      *
1401:      * @since  Unknown
1402:      * @access private
1403:      *
1404:      * @uses GFPaymentAddOn::is_duplicate_callback()
1405:      * @uses GFAPI::get_entry()
1406:      * @uses GFPaymentAddOn::complete_payment()
1407:      * @uses GFPaymentAddOn::refund_payment()
1408:      * @uses GFPaymentAddOn::fail_payment()
1409:      * @uses GFPaymentAddOn::add_pending_payment()
1410:      * @uses GFPaymentAddOn::void_authorization()
1411:      * @uses GFPaymentAddOn::start_subscription()
1412:      * @uses GFPaymentAddOn::get_payment_feed()
1413:      * @uses GFPaymentAddOn::cancel_subscription()
1414:      * @uses GFPaymentAddOn::expire_subscription()
1415:      * @uses GFPaymentAddOn::add_subscription_payment()
1416:      * @uses GFPaymentAddOn::fail_subscription_payment()
1417:      * @uses GFPaymentAddOn::register_callback()
1418:      *
1419:      * @param array $action {
1420:      *     The action to perform.
1421:      *
1422:      *     @type string $type             The callback action type. Required.
1423:      *     @type string $transaction_id   The transaction ID to perform the action on. Required if the action is a payment.
1424:      *     @type string $subscription_id  The subscription ID. Required if this is related to a subscription.
1425:      *     @type string $amount           The transaction amount. Typically required.
1426:      *     @type int    $entry_id         The ID of the entry associated with the action. Typically required.
1427:      *     @type string $transaction_type The transaction type to process this action as. Optional.
1428:      *     @type string $payment_status   The payment status to set the payment to. Optional.
1429:      *     @type string $note             The note to associate with this payment action. Optional.
1430:      * }
1431:      *
1432:      * @return bool|mixed True, unless a custom transaction type defines otherwise.
1433:      */
1434:     private function process_callback_action( $action ) {
1435:         $this->log_debug( __METHOD__ . '(): Processing callback action.' );
1436:         $action = wp_parse_args(
1437:             $action, array(
1438:                 'type'             => false,
1439:                 'amount'           => false,
1440:                 'amount_formatted' => false,
1441:                 'transaction_type' => false,
1442:                 'transaction_id'   => false,
1443:                 'subscription_id'  => false,
1444:                 'entry_id'         => false,
1445:                 'payment_status'   => false,
1446:                 'note'             => false,
1447:             )
1448:         );
1449: 
1450:         $result = false;
1451: 
1452:         if ( rgar( $action, 'id' ) && $this->is_duplicate_callback( $action['id'] ) ) {
1453:             return new WP_Error( 'duplicate', sprintf( esc_html__( 'This webhook has already been processed (Event Id: %s)', 'gravityforms' ), $action['id'] ) );
1454:         }
1455: 
1456:         $entry = GFAPI::get_entry( $action['entry_id'] );
1457:         if ( ! $entry || is_wp_error( $entry ) ) {
1458:             return $result;
1459:         }
1460: 
1461:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1462: 
1463:         /**
1464:          * Performs actions before the the payment action callback is processed.
1465:          *
1466:          * @since Unknown
1467:          *
1468:          * @param array $action The action array.
1469:          * @param array $entry  The Entry Object.
1470:          */
1471:         do_action( 'gform_action_pre_payment_callback', $action, $entry );
1472:         if ( has_filter( 'gform_action_pre_payment_callback' ) ) {
1473:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_action_pre_payment_callback.' );
1474:         }
1475: 
1476:         switch ( $action['type'] ) {
1477:             case 'complete_payment':
1478:                 $result = $this->complete_payment( $entry, $action );
1479:                 break;
1480:             case 'refund_payment':
1481:                 $result = $this->refund_payment( $entry, $action );
1482:                 break;
1483:             case 'fail_payment':
1484:                 $result = $this->fail_payment( $entry, $action );
1485:                 break;
1486:             case 'add_pending_payment':
1487:                 $result = $this->add_pending_payment( $entry, $action );
1488:                 break;
1489:             case 'void_authorization':
1490:                 $result = $this->void_authorization( $entry, $action );
1491:                 break;
1492:             case 'create_subscription':
1493:                 $result = $this->start_subscription( $entry, $action );
1494:                 $result = rgar( $result, 'payment_status' ) == 'Active' && rgar( $result, 'transaction_id' ) == rgar( $action, 'subscription_id' );
1495:                 break;
1496:             case 'cancel_subscription':
1497:                 $feed   = $this->get_payment_feed( $entry );
1498:                 $result = $this->cancel_subscription( $entry, $feed, $action['note'] );
1499:                 break;
1500:             case 'expire_subscription':
1501:                 $result = $this->expire_subscription( $entry, $action );
1502:                 break;
1503:             case 'add_subscription_payment':
1504:                 $result = $this->add_subscription_payment( $entry, $action );
1505:                 break;
1506:             case 'fail_subscription_payment':
1507:                 $result = $this->fail_subscription_payment( $entry, $action );
1508:                 break;
1509:             default:
1510:                 // Handle custom events.
1511:                 if ( is_callable( array( $this, rgar( $action, 'callback' ) ) ) ) {
1512:                     $result = call_user_func_array( array( $this, $action['callback'] ), array( $entry, $action ) );
1513:                 }
1514:                 break;
1515:         }
1516: 
1517:         if ( rgar( $action, 'id' ) && $result ) {
1518:             $this->register_callback( $action['id'], $action['entry_id'] );
1519:         }
1520: 
1521:         /**
1522:          * Fires right after the payment callback.
1523:          *
1524:          * @since Unknown
1525:          *
1526:          * @param array $entry The Entry Object
1527:          * @param array $action {
1528:          *     The action performed.
1529:          *
1530:          *     @type string $type             The callback action type. Required.
1531:          *     @type string $transaction_id   The transaction ID to perform the action on. Required if the action is a payment.
1532:          *     @type string $subscription_id  The subscription ID. Required if this is related to a subscription.
1533:          *     @type string $amount           The transaction amount. Typically required.
1534:          *     @type int    $entry_id         The ID of the entry associated with the action. Typically required.
1535:          *     @type string $transaction_type The transaction type to process this action as. Optional.
1536:          *     @type string $payment_status   The payment status to set the payment to. Optional.
1537:          *     @type string $note             The note to associate with this payment action. Optional.
1538:          * }
1539:          * @param mixed $result The Result Object.
1540:          */
1541:         do_action( 'gform_post_payment_callback', $entry, $action, $result );
1542:         if ( has_filter( 'gform_post_payment_callback' ) ) {
1543:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_callback.' );
1544:         }
1545: 
1546:         return $result;
1547:     }
1548: 
1549:     /**
1550:      * Registers a callback action.
1551:      *
1552:      * @since  Unknown
1553:      * @access public
1554:      *
1555:      * @uses wpdb::insert()
1556:      * @uses GFAddOn::$_slug
1557:      *
1558:      * @global wpdb   $wpdb
1559:      * @param  string $callback_id The callback ID for the action.
1560:      * @param  int    $entry_id    The entry ID associated with the callback.
1561:      *
1562:      * @return void
1563:      */
1564:     public function register_callback( $callback_id, $entry_id ) {
1565:         global $wpdb;
1566: 
1567:         $wpdb->insert( "{$wpdb->prefix}gf_addon_payment_callback", array(
1568:             'addon_slug'   => $this->_slug,
1569:             'callback_id'  => $callback_id,
1570:             'lead_id'      => $entry_id,
1571:             'date_created' => gmdate( 'Y-m-d H:i:s' )
1572:         ) );
1573:     }
1574: 
1575:     /**
1576:      * Checks if a callback is duplicate.
1577:      *
1578:      * @since  Unknown
1579:      * @access public
1580:      *
1581:      * @uses wpdb::$prefix
1582:      * @uses wpdb::prepare()
1583:      * @uses wpdb::get_var()
1584:      *
1585:      * @global wpdb   $wpdb
1586:      * @param  string $callback_id The callback ID to chack.
1587:      *
1588:      * @return bool If the callback is a duplicate, true. Otherwise, false.
1589:      */
1590:     public function is_duplicate_callback( $callback_id ) {
1591:         global $wpdb;
1592: 
1593:         $sql = $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s AND callback_id=%s", $this->_slug, $callback_id );
1594:         if ( $wpdb->get_var( $sql ) ) {
1595:             return true;
1596:         }
1597: 
1598:         return false;
1599:     }
1600: 
1601:     public function callback() {
1602:     }
1603: 
1604:     public function post_callback( $callback_action, $result ) {
1605:     }
1606: 
1607: 
1608:     // # PAYMENT INTERACTION FUNCTIONS
1609: 
1610:     public function add_pending_payment( $entry, $action ) {
1611:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1612:         if ( empty( $action['payment_status'] ) ) {
1613:             $action['payment_status'] = 'Pending';
1614:         }
1615: 
1616:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1617: 
1618:         if ( empty( $action['note'] ) ) {
1619:             $action['note'] = sprintf( esc_html__( 'Payment is pending. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1620:         }
1621: 
1622:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1623:         $this->add_note( $entry['id'], $action['note'] );
1624:         $this->post_payment_action( $entry, $action );
1625: 
1626:         return true;
1627:     }
1628: 
1629:     public function complete_authorization( &$entry, $action ) {
1630:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1631:         if ( ! rgar( $action, 'payment_status' ) ) {
1632:             $action['payment_status'] = 'Authorized';
1633:         }
1634: 
1635:         if ( ! rgar( $action, 'transaction_type' ) ) {
1636:             $action['transaction_type'] = 'authorization';
1637:         }
1638: 
1639:         if ( ! rgar( $action, 'payment_date' ) ) {
1640:             $action['payment_date'] = gmdate( 'y-m-d H:i:s' );
1641:         }
1642: 
1643:         $entry['transaction_id']   = rgar( $action, 'transaction_id' );
1644:         $entry['transaction_type'] = '1';
1645:         $entry['payment_status']   = $action['payment_status'];
1646: 
1647:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1648: 
1649:         if ( ! rgar( $action, 'note' ) ) {
1650:             $action['note'] = sprintf( esc_html__( 'Payment has been authorized. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1651:         }
1652: 
1653:         GFAPI::update_entry( $entry );
1654:         $this->add_note( $entry['id'], $action['note'], 'success' );
1655:         $this->post_payment_action( $entry, $action );
1656: 
1657:         return true;
1658:     }
1659: 
1660:     public function complete_payment( &$entry, $action ) {
1661:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1662:         if ( ! rgar( $action, 'payment_status' ) ) {
1663:             $action['payment_status'] = 'Paid';
1664:         }
1665: 
1666:         if ( ! rgar( $action, 'transaction_type' ) ) {
1667:             $action['transaction_type'] = 'payment';
1668:         }
1669: 
1670:         if ( ! rgar( $action, 'payment_date' ) ) {
1671:             $action['payment_date'] = gmdate( 'y-m-d H:i:s' );
1672:         }
1673: 
1674:         $entry['is_fulfilled']     = '1';
1675:         $entry['transaction_id']   = rgar( $action, 'transaction_id' );
1676:         $entry['transaction_type'] = '1';
1677:         $entry['payment_status']   = $action['payment_status'];
1678:         $entry['payment_amount']   = rgar( $action, 'amount' );
1679:         $entry['payment_date']     = $action['payment_date'];
1680:         $entry['payment_method']   = rgar( $action, 'payment_method' );
1681: 
1682:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1683: 
1684:         if ( ! rgar( $action, 'note' ) ) {
1685:             $action['note'] = sprintf( esc_html__( 'Payment has been completed. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1686:         }
1687: 
1688:         GFAPI::update_entry( $entry );
1689:         $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] );
1690:         $this->add_note( $entry['id'], $action['note'], 'success' );
1691: 
1692:         /**
1693:          * Fires after a payment is completed through a form
1694:          *
1695:          * @param array $entry The Entry object
1696:          * @param array $action The Action Object
1697:          * $action = array(
1698:          *     'type' => 'cancel_subscription',     // See Below
1699:          *     'transaction_id' => '',              // What is the ID of the transaction made?
1700:          *     'subscription_id' => '',             // What is the ID of the Subscription made?
1701:          *     'amount' => '0.00',                  // Amount to charge?
1702:          *     'entry_id' => 1,                     // What entry to check?
1703:          *     'transaction_type' => '',
1704:          *     'payment_status' => '',
1705:          *     'note' => ''
1706:          * );
1707:          *
1708:          * 'type' can be:
1709:          *
1710:          * - complete_payment
1711:          * - refund_payment
1712:          * - fail_payment
1713:          * - add_pending_payment
1714:          * - void_authorization
1715:          * - create_subscription
1716:          * - cancel_subscription
1717:          * - expire_subscription
1718:          * - add_subscription_payment
1719:          * - fail_subscription_payment
1720:          */
1721:         do_action( 'gform_post_payment_completed', $entry, $action );
1722:         if ( has_filter( 'gform_post_payment_completed' ) ) {
1723:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_completed.' );
1724:         }
1725:         $this->post_payment_action( $entry, $action );
1726: 
1727:         return true;
1728:     }
1729: 
1730:     public function refund_payment( $entry, $action ) {
1731:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1732:         if ( empty( $action['payment_status'] ) ) {
1733:             $action['payment_status'] = 'Refunded';
1734:         }
1735: 
1736:         if ( empty( $action['transaction_type'] ) ) {
1737:             $action['transaction_type'] = 'refund';
1738:         }
1739: 
1740:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1741: 
1742:         if ( empty( $action['note'] ) ) {
1743:             $action['note'] = sprintf( esc_html__( 'Payment has been refunded. Amount: %s. Transaction Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['transaction_id'] );
1744:         }
1745: 
1746:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1747:         $this->insert_transaction( $entry['id'], $action['transaction_type'], $action['transaction_id'], $action['amount'] );
1748:         $this->add_note( $entry['id'], $action['note'] );
1749: 
1750:         /**
1751:          * Fires after a payment is refunded
1752:          *
1753:          * @param array $entry The Entry object
1754:          * @param array $action The Action Object
1755:          * $action = array(
1756:          *     'type' => 'cancel_subscription',     // See Below
1757:          *     'transaction_id' => '',              // What is the ID of the transaction made?
1758:          *     'subscription_id' => '',             // What is the ID of the Subscription made?
1759:          *     'amount' => '0.00',                  // Amount to charge?
1760:          *     'entry_id' => 1,                     // What entry to check?
1761:          *     'transaction_type' => '',
1762:          *     'payment_status' => '',
1763:          *     'note' => ''
1764:          * );
1765:          *
1766:          * 'type' can be:
1767:          *
1768:          * - complete_payment
1769:          * - refund_payment
1770:          * - fail_payment
1771:          * - add_pending_payment
1772:          * - void_authorization
1773:          * - create_subscription
1774:          * - cancel_subscription
1775:          * - expire_subscription
1776:          * - add_subscription_payment
1777:          * - fail_subscription_payment
1778:          */
1779:         do_action( 'gform_post_payment_refunded', $entry, $action );
1780:         if ( has_filter( 'gform_post_payment_refunded' ) ) {
1781:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_refunded.' );
1782:         }
1783:         $this->post_payment_action( $entry, $action );
1784: 
1785:         return true;
1786:     }
1787: 
1788:     public function fail_payment( $entry, $action ) {
1789:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1790:         if ( empty( $action['payment_status'] ) ) {
1791:             $action['payment_status'] = 'Failed';
1792:         }
1793: 
1794:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1795: 
1796:         if ( empty( $action['note'] ) ) {
1797:             $action['note'] = sprintf( esc_html__( 'Payment has failed. Amount: %s.', 'gravityforms' ), $action['amount_formatted'] );
1798:         }
1799: 
1800:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1801:         $this->add_note( $entry['id'], $action['note'] );
1802:         $this->post_payment_action( $entry, $action );
1803: 
1804:         return true;
1805:     }
1806: 
1807:     public function void_authorization( $entry, $action ) {
1808:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1809:         if ( empty( $action['payment_status'] ) ) {
1810:             $action['payment_status'] = 'Voided';
1811:         }
1812: 
1813:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1814: 
1815:         if ( empty( $action['note'] ) ) {
1816:             $action['note'] = sprintf( esc_html__( 'Authorization has been voided. Transaction Id: %s', 'gravityforms' ), $action['transaction_id'] );
1817:         }
1818: 
1819:         GFAPI::update_entry_property( $entry['id'], 'payment_status', $action['payment_status'] );
1820:         $this->add_note( $entry['id'], $action['note'] );
1821:         $this->post_payment_action( $entry, $action );
1822: 
1823:         return true;
1824:     }
1825: 
1826:     /**
1827:      * Used to start a new subscription. Updates the associcated entry with the payment and transaction details and adds an entry note.
1828:      *
1829:      * @param  [array]  $entry           Entry object
1830:      * @param  [string] $subscription_id ID of the subscription
1831:      * @param  [float]  $amount          Numeric amount of the initial subscription payment
1832:      *
1833:      * @return [array]  $entry           Entry Object
1834:      */
1835: 
1836:     public function start_subscription( $entry, $subscription ) {
1837:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1838:         if ( ! $this->has_subscription( $entry ) ) {
1839:             $entry['payment_status']   = 'Active';
1840:             $entry['payment_amount']   = $subscription['amount'];
1841:             $entry['payment_date']     = ! rgempty( 'subscription_start_date', $subscription ) ? $subscription['subscription_start_date'] : gmdate( 'Y-m-d H:i:s' );
1842:             $entry['transaction_id']   = $subscription['subscription_id'];
1843:             $entry['transaction_type'] = '2'; // subscription
1844:             $entry['is_fulfilled']     = '1';
1845: 
1846:             $result = GFAPI::update_entry( $entry );
1847:             $this->add_note( $entry['id'], sprintf( esc_html__( 'Subscription has been created. Subscription Id: %s.', 'gravityforms' ), $subscription['subscription_id'] ), 'success' );
1848: 
1849:             $subscription = $this->maybe_add_action_amount_formatted( $subscription, $entry['currency'] );
1850: 
1851:             if ( empty( $subscription['payment_status'] ) ) {
1852:                 $subscription['payment_status'] = 'Active';
1853:             }
1854: 
1855:             /**
1856:              * Fires when someone starts a subscription
1857:              *
1858:              * @param array $entry Entry Object
1859:              * @param array $subscription The new Subscription object
1860:              */
1861:             do_action( 'gform_post_subscription_started', $entry, $subscription );
1862:             if ( has_filter( 'gform_post_subscription_started' ) ) {
1863:                 $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_subscription_started.' );
1864:             }
1865: 
1866:             $subscription['type'] = 'create_subscription';
1867:             $this->post_payment_action( $entry, $subscription );
1868: 
1869:         }
1870: 
1871:         return $entry;
1872:     }
1873: 
1874:     /**
1875:      * A payment on an existing subscription.
1876:      *
1877:      * @param  [array] $data  Transaction data including 'amount' and 'subscriber_id'
1878:      * @param  [array] $entry Entry object
1879:      *
1880:      * @return true
1881:      */
1882:     public function add_subscription_payment( $entry, $action ) {
1883:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1884:         if ( empty( $action['transaction_type'] ) ) {
1885:             $action['transaction_type'] = 'payment';
1886:         }
1887: 
1888:         if ( empty( $action['payment_status'] ) ) {
1889:             $action['payment_status'] = 'Active';
1890:         }
1891: 
1892:         // Set payment status back to active if a previous payment attempt failed.
1893:         if ( strtolower( $entry['payment_status'] ) != 'active' ) {
1894:             $entry['payment_status'] = 'Active';
1895:             GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Active' );
1896:         }
1897: 
1898:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1899: 
1900:         if ( empty( $action['note'] ) ) {
1901:             $action['note'] = sprintf( esc_html__( 'Subscription has been paid. Amount: %s. Subscription Id: %s', 'gravityforms' ), $action['amount_formatted'], $action['subscription_id'] );
1902:         }
1903: 
1904:         $transaction_id = ! empty( $action['transaction_id'] ) ? $action['transaction_id'] : $action['subscription_id'];
1905: 
1906:         $this->insert_transaction( $entry['id'], $action['transaction_type'], $transaction_id, $action['amount'], null, rgar( $action, 'subscription_id') );
1907:         $this->add_note( $entry['id'], $action['note'], 'success' );
1908: 
1909:         /**
1910:          * Fires after a payment is made on an existing subscription.
1911:          *
1912:          * @param array $entry The Entry Object
1913:          * @param array $action The Action Object
1914:          * $action = array(
1915:          *     'type' => 'cancel_subscription',     // See Below
1916:          *     'transaction_id' => '',              // What is the ID of the transaction made?
1917:          *     'subscription_id' => '',             // What is the ID of the Subscription made?
1918:          *     'amount' => '0.00',                  // Amount to charge?
1919:          *     'entry_id' => 1,                     // What entry to check?
1920:          *     'transaction_type' => '',
1921:          *     'payment_status' => '',
1922:          *     'note' => ''
1923:          * );
1924:          *
1925:          * 'type' can be:
1926:          *
1927:          * - complete_payment
1928:          * - refund_payment
1929:          * - fail_payment
1930:          * - add_pending_payment
1931:          * - void_authorization
1932:          * - create_subscription
1933:          * - cancel_subscription
1934:          * - expire_subscription
1935:          * - add_subscription_payment
1936:          * - fail_subscription_payment
1937:          */
1938:         do_action( 'gform_post_add_subscription_payment', $entry, $action );
1939:         if ( has_filter( 'gform_post_add_subscription_payment' ) ) {
1940:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_add_subscription_payment.' );
1941:         }
1942:         $this->post_payment_action( $entry, $action );
1943: 
1944:         return true;
1945:     }
1946: 
1947:     public function fail_subscription_payment( $entry, $action ) {
1948:         $this->log_debug( __METHOD__ . '(): Processing request.' );
1949:         if ( empty( $action['payment_status'] ) ) {
1950:             $action['payment_status'] = 'Failed';
1951:         }
1952: 
1953:         $action = $this->maybe_add_action_amount_formatted( $action, $entry['currency'] );
1954: 
1955:         if ( empty( $action['note'] ) ) {
1956:             $action['note'] = sprintf( esc_html__( 'Subscription payment has failed. Amount: %s. Subscription Id: %s.', 'gravityforms' ), $action['amount_formatted'], $action['subscription_id'] );
1957:         }
1958: 
1959:         GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Failed' );
1960:         $this->add_note( $entry['id'], $action['note'], 'error' );
1961: 
1962:         // keep 'gform_subscription_payment_failed' for backward compatability
1963:         /**
1964:          * @deprecated Use gform_post_fail_subscription_payment now
1965:          */
1966:         do_action( 'gform_subscription_payment_failed', $entry, $action['subscription_id'] );
1967:         if ( has_filter( 'gform_subscription_payment_failed' ) ) {
1968:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_payment_failed.' );
1969:         }
1970:         /**
1971:          * Fires after a subscription payment has failed
1972:          *
1973:          * @param array $entry The Entry Object
1974:          * @param array $action The Action Object
1975:          * $action = array(
1976:          *     'type' => 'cancel_subscription',     // See Below
1977:          *     'transaction_id' => '',              // What is the ID of the transaction made?
1978:          *     'subscription_id' => '',             // What is the ID of the Subscription made?
1979:          *     'amount' => '0.00',                  // Amount to charge?
1980:          *     'entry_id' => 1,                     // What entry to check?
1981:          *     'transaction_type' => '',
1982:          *     'payment_status' => '',
1983:          *     'note' => ''
1984:          * );
1985:          *
1986:          * 'type' can be:
1987:          *
1988:          * - complete_payment
1989:          * - refund_payment
1990:          * - fail_payment
1991:          * - add_pending_payment
1992:          * - void_authorization
1993:          * - create_subscription
1994:          * - cancel_subscription
1995:          * - expire_subscription
1996:          * - add_subscription_payment
1997:          * - fail_subscription_payment
1998:          */
1999:         do_action( 'gform_post_fail_subscription_payment', $entry, $action );
2000:         if ( has_filter( 'gform_post_fail_subscription_payment' ) ) {
2001:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_fail_subscription_payment.' );
2002:         }
2003:         $this->post_payment_action( $entry, $action );
2004: 
2005:         return true;
2006:     }
2007: 
2008:     public function cancel_subscription( $entry, $feed, $note = null ) {
2009:         $this->log_debug( __METHOD__ . '(): Processing request.' );
2010:         if ( ! $note ) {
2011:             $note = sprintf( esc_html__( 'Subscription has been cancelled. Subscription Id: %s.', 'gravityforms' ), $entry['transaction_id'] );
2012:         }
2013: 
2014:         if ( strtolower( $entry['payment_status'] ) == 'cancelled' ) {
2015:             $this->log_debug( __METHOD__ . '(): Subscription is already canceled.' );
2016: 
2017:             return false;
2018:         }
2019: 
2020:         GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Cancelled' );
2021:         $this->add_note( $entry['id'], $note );
2022: 
2023:         // Include $subscriber_id as 3rd parameter for backwards compatibility
2024:         do_action( 'gform_subscription_canceled', $entry, $feed, $entry['transaction_id'] );
2025: 
2026:         // Include alternative spelling of "cancelled".
2027:         do_action( 'gform_subscription_cancelled', $entry, $feed, $entry['transaction_id'] );
2028: 
2029:         if ( has_filter( 'gform_subscription_canceled' ) || has_filter( 'gform_subscription_cancelled' ) ) {
2030:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_subscription_canceled.' );
2031:         }
2032: 
2033:         $action = array(
2034:             'type'            => 'cancel_subscription',
2035:             'subscription_id' => $entry['transaction_id'],
2036:             'entry_id'        => $entry['id'],
2037:             'payment_status'  => 'Cancelled',
2038:             'note'            => $note,
2039:         );
2040:         $this->post_payment_action( $entry, $action );
2041: 
2042:         return true;
2043:     }
2044: 
2045:     public function expire_subscription( $entry, $action ) {
2046:         $this->log_debug( __METHOD__ . '(): Processing request.' );
2047:         if ( empty( $action['payment_status'] ) ) {
2048:             $action['payment_status'] = 'Expired';
2049:         }
2050: 
2051:         if ( empty( $action['note'] ) ) {
2052:             $action['note'] = sprintf( esc_html__( 'Subscription has expired. Subscriber Id: %s', 'gravityforms' ), $action['subscription_id'] );
2053:         }
2054: 
2055:         GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Expired' );
2056:         $this->add_note( $entry['id'], $action['note'] );
2057:         $this->post_payment_action( $entry, $action );
2058: 
2059:         return true;
2060:     }
2061: 
2062:     public function has_subscription( $entry ) {
2063:         if ( rgar( $entry, 'transaction_type' ) == 2 && ! rgempty( 'transaction_id', $entry ) ) {
2064:             return true;
2065:         } else {
2066:             return false;
2067:         }
2068:     }
2069: 
2070:     /**
2071:      * Retrieves the ID of the entry associated with the supplied subscription or transaction ID.
2072:      *
2073:      * @since 2.3.3.9 Updated to search the _gf_addon_payment_transaction table if the ID was not found in the entry table.
2074:      * @since unknown
2075:      *
2076:      * @param string $transaction_id The subscription or transaction ID.
2077:      *
2078:      * @return bool|string
2079:      */
2080:     public function get_entry_by_transaction_id( $transaction_id ) {
2081:         if ( empty( $transaction_id ) ) {
2082:             return false;
2083:         }
2084: 
2085:         global $wpdb;
2086: 
2087:         $entry_table_name = self::get_entry_table_name();
2088: 
2089:         $sql      = $wpdb->prepare( "SELECT id FROM {$entry_table_name} WHERE transaction_id = %s", $transaction_id );
2090:         $entry_id = $wpdb->get_var( $sql );
2091: 
2092:         if ( ! $entry_id ) {
2093:             $sql      = $wpdb->prepare( "SELECT lead_id FROM {$wpdb->prefix}gf_addon_payment_transaction WHERE transaction_id = %s", $transaction_id );
2094:             $entry_id = $wpdb->get_var( $sql );
2095:         }
2096: 
2097:         return $entry_id ? $entry_id : false;
2098:     }
2099: 
2100:     /**
2101:      * Helper for making the gform_post_payment_action hook available to the various payment interaction methods. Also handles sending notifications for payment events.
2102:      *
2103:      * @since 2.3.6.6 Added the $action to the GFAPI::send_notifications() $data param.
2104:      * @since unknown
2105:      *
2106:      * @param array $entry
2107:      * @param array $action
2108:      */
2109:     public function post_payment_action( $entry, $action ) {
2110:         do_action( 'gform_post_payment_action', $entry, $action );
2111:         if ( has_filter( 'gform_post_payment_action' ) ) {
2112:             $this->log_debug( __METHOD__ . '(): Executing functions hooked to gform_post_payment_action.' );
2113:         }
2114: 
2115:         $form             = GFAPI::get_form( $entry['form_id'] );
2116:         $supported_events = $this->supported_notification_events( $form );
2117:         if ( ! empty( $supported_events ) ) {
2118:             if ( ! empty( $action['payment_status'] ) ) {
2119:                 $action['payment_status'] = GFCommon::get_entry_payment_status_text( $action['payment_status'] );
2120:             }
2121:             GFAPI::send_notifications( $form, $entry, rgar( $action, 'type' ), array( 'payment_action' => $action ) );
2122:         }
2123:     }
2124: 
2125: 
2126:     // -------- Cron --------------------
2127:     public function setup_cron() {
2128:         // Setting up cron
2129:         $cron_name = "{$this->_slug}_cron";
2130: 
2131:         add_action( $cron_name, array( $this, 'check_status' ) );
2132: 
2133:         if ( ! wp_next_scheduled( $cron_name ) ) {
2134:             wp_schedule_event( time(), 'hourly', $cron_name );
2135:         }
2136: 
2137: 
2138:     }
2139: 
2140:     public function check_status() {
2141: 
2142:     }
2143: 
2144:     //--------- List Columns ------------
2145:     public function feed_list_columns() {
2146:         return array(
2147:             'feedName'        => esc_html__( 'Name', 'gravityforms' ),
2148:             'transactionType' => esc_html__( 'Transaction Type', 'gravityforms' ),
2149:             'amount'          => esc_html__( 'Amount', 'gravityforms' )
2150:         );
2151:     }
2152: 
2153:     public function get_column_value_transactionType( $feed ) {
2154:         switch ( rgar( $feed['meta'], 'transactionType' ) ) {
2155:             case 'subscription' :
2156:                 return esc_html__( 'Subscription', 'gravityforms' );
2157:                 break;
2158:             case 'product' :
2159:                 return esc_html__( 'Products and Services', 'gravityforms' );
2160:                 break;
2161:             case 'donation' :
2162:                 return esc_html__( 'Donations', 'gravityforms' );
2163:                 break;
2164: 
2165:         }
2166: 
2167:         return esc_html__( 'Unsupported transaction type', 'gravityforms' );
2168:     }
2169: 
2170:     public function get_column_value_amount( $feed ) {
2171:         $form     = $this->get_current_form();
2172:         $field_id = $feed['meta']['transactionType'] == 'subscription' ? rgars( $feed, 'meta/recurringAmount' ) : rgars( $feed, 'meta/paymentAmount' );
2173:         if ( $field_id == 'form_total' ) {
2174:             $label = esc_html__( 'Form Total', 'gravityforms' );
2175:         } else {
2176:             $field = GFFormsModel::get_field( $form, $field_id );
2177:             $label = GFCommon::get_label( $field );
2178:         }
2179: 
2180:         return $label;
2181:     }
2182: 
2183: 
2184:     //--------- Feed Settings ----------------
2185: 
2186:     /**
2187:      * Remove the add new button from the title if the form requires a credit card field.
2188:      *
2189:      * @return string
2190:      */
2191:     public function feed_list_title() {
2192:         if ( $this->_requires_credit_card && ! $this->has_credit_card_field( $this->get_current_form() ) ) {
2193:             return $this->form_settings_title();
2194:         }
2195: 
2196:         return parent::feed_list_title();
2197:     }
2198: 
2199:     public function feed_list_message() {
2200: 
2201:         if ( $this->_requires_credit_card && ! $this->has_credit_card_field( $this->get_current_form() ) ) {
2202:             return $this->requires_credit_card_message();
2203:         }
2204: 
2205:         return parent::feed_list_message();
2206:     }
2207: 
2208:     public function requires_credit_card_message() {
2209:         $url = add_query_arg( array( 'view' => null, 'subview' => null ) );
2210: 
2211:         return sprintf( esc_html__( "You must add a Credit Card field to your form before creating a feed. Let's go %sadd one%s!", 'gravityforms' ), "<a href='" . esc_url( $url ) . "'>", '</a>' );
2212:     }
2213: 
2214:     public function feed_settings_fields() {
2215: 
2216:         return array(
2217: 
2218:             array(
2219:                 'description' => '',
2220:                 'fields'      => array(
2221:                     array(
2222:                         'name'     => 'feedName',
2223:                         'label'    => esc_html__( 'Name', 'gravityforms' ),
2224:                         'type'     => 'text',
2225:                         'class'    => 'medium',
2226:                         'required' => true,
2227:                         'tooltip'  => '<h6>' . esc_html__( 'Name', 'gravityforms' ) . '</h6>' . esc_html__( 'Enter a feed name to uniquely identify this setup.', 'gravityforms' )
2228:                     ),
2229:                     array(
2230:                         'name'     => 'transactionType',
2231:                         'label'    => esc_html__( 'Transaction Type', 'gravityforms' ),
2232:                         'type'     => 'select',
2233:                         'onchange' => "jQuery(this).parents('form').submit();",
2234:                         'choices'  => array(
2235:                             array(
2236:                                 'label' => esc_html__( 'Select a transaction type', 'gravityforms' ),
2237:                                 'value' => ''
2238:                             ),
2239:                             array(
2240:                                 'label' => esc_html__( 'Products and Services', 'gravityforms' ),
2241:                                 'value' => 'product'
2242:                             ),
2243:                             array( 'label' => esc_html__( 'Subscription', 'gravityforms' ), 'value' => 'subscription' ),
2244:                         ),
2245:                         'tooltip'  => '<h6>' . esc_html__( 'Transaction Type', 'gravityforms' ) . '</h6>' . esc_html__( 'Select a transaction type.', 'gravityforms' )
2246:                     ),
2247:                 )
2248:             ),
2249:             array(
2250:                 'title'      => esc_html__( 'Subscription Settings', 'gravityforms' ),
2251:                 'dependency' => array(
2252:                     'field'  => 'transactionType',
2253:                     'values' => array( 'subscription' )
2254:                 ),
2255:                 'fields'     => array(
2256:                     array(
2257:                         'name'     => 'recurringAmount',
2258:                         'label'    => esc_html__( 'Recurring Amount', 'gravityforms' ),
2259:                         'type'     => 'select',
2260:                         'choices'  => $this->recurring_amount_choices(),
2261:                         'required' => true,
2262:                         'tooltip'  => '<h6>' . esc_html__( 'Recurring Amount', 'gravityforms' ) . '</h6>' . esc_html__( "Select which field determines the recurring payment amount, or select 'Form Total' to use the total of all pricing fields as the recurring amount.", 'gravityforms' )
2263:                     ),
2264:                     array(
2265:                         'name'    => 'billingCycle',
2266:                         'label'   => esc_html__( 'Billing Cycle', 'gravityforms' ),
2267:                         'type'    => 'billing_cycle',
2268:                         'tooltip' => '<h6>' . esc_html__( 'Billing Cycle', 'gravityforms' ) . '</h6>' . esc_html__( 'Select your billing cycle.  This determines how often the recurring payment should occur.', 'gravityforms' )
2269:                     ),
2270:                     array(
2271:                         'name'    => 'recurringTimes',
2272:                         'label'   => esc_html__( 'Recurring Times', 'gravityforms' ),
2273:                         'type'    => 'select',
2274:                         'choices' => array(
2275:                                          array(
2276:                                              'label' => esc_html__( 'infinite', 'gravityforms' ),
2277:                                              'value' => '0'
2278:                                          )
2279:                                      ) + $this->get_numeric_choices( 1, 100 ),
2280:                         'tooltip' => '<h6>' . esc_html__( 'Recurring Times', 'gravityforms' ) . '</h6>' . esc_html__( 'Select how many times the recurring payment should be made.  The default is to bill the customer until the subscription is canceled.', 'gravityforms' )
2281:                     ),
2282:                     array(
2283:                         'name'  => 'setupFee',
2284:                         'label' => esc_html__( 'Setup Fee', 'gravityforms' ),
2285:                         'type'  => 'setup_fee',
2286:                     ),
2287:                     array(
2288:                         'name'    => 'trial',
2289:                         'label'   => esc_html__( 'Trial', 'gravityforms' ),
2290:                         'type'    => 'trial',
2291:                         'hidden'  => $this->get_setting( 'setupFee_enabled' ),
2292:                         'tooltip' => '<h6>' . esc_html__( 'Trial Period', 'gravityforms' ) . '</h6>' . esc_html__( 'Enable a trial period.  The user\'s recurring payment will not begin until after this trial period.', 'gravityforms' )
2293:                     ),
2294:                 )
2295:             ),
2296:             array(
2297:                 'title'      => esc_html__( 'Products &amp; Services Settings', 'gravityforms' ),
2298:                 'dependency' => array(
2299:                     'field'  => 'transactionType',
2300:                     'values' => array( 'product', 'donation' )
2301:                 ),
2302:                 'fields'     => array(
2303:                     array(
2304:                         'name'          => 'paymentAmount',
2305:                         'label'         => esc_html__( 'Payment Amount', 'gravityforms' ),
2306:                         'type'          => 'select',
2307:                         'choices'       => $this->product_amount_choices(),
2308:                         'required'      => true,
2309:                         'default_value' => 'form_total',
2310:                         'tooltip'       => '<h6>' . esc_html__( 'Payment Amount', 'gravityforms' ) . '</h6>' . esc_html__( "Select which field determines the payment amount, or select 'Form Total' to use the total of all pricing fields as the payment amount.", 'gravityforms' )
2311:                     ),
2312:                 )
2313:             ),
2314:             array(
2315:                 'title'      => esc_html__( 'Other Settings', 'gravityforms' ),
2316:                 'dependency' => array(
2317:                     'field'  => 'transactionType',
2318:                     'values' => array( 'subscription', 'product', 'donation' )
2319:                 ),
2320:                 'fields'     => $this->other_settings_fields()
2321:             ),
2322: 
2323:         );
2324:     }
2325: 
2326:     public function other_settings_fields() {
2327:         $other_settings = array(
2328:             array(
2329:                 'name'      => 'billingInformation',
2330:                 'label'     => esc_html__( 'Billing Information', 'gravityforms' ),
2331:                 'type'      => 'field_map',
2332:                 'field_map' => $this->billing_info_fields(),
2333:                 'tooltip'   => '<h6>' . esc_html__( 'Billing Information', 'gravityforms' ) . '</h6>' . esc_html__( 'Map your Form Fields to the available listed fields.', 'gravityforms' )
2334:             ),
2335:         );
2336: 
2337:         $option_choices = $this->option_choices();
2338:         if ( ! empty( $option_choices ) ) {
2339:             $other_settings[] = array(
2340:                 'name'    => 'options',
2341:                 'label'   => esc_html__( 'Options', 'gravityforms' ),
2342:                 'type'    => 'checkbox',
2343:                 'choices' => $option_choices,
2344:             );
2345:         }
2346: 
2347:         $other_settings[] = array(
2348:             'name'    => 'conditionalLogic',
2349:             'label'   => esc_html__( 'Conditional Logic', 'gravityforms' ),
2350:             'type'    => 'feed_condition',
2351:             'tooltip' => '<h6>' . esc_html__( 'Conditional Logic', 'gravityforms' ) . '</h6>' . esc_html__( 'When conditions are enabled, form submissions will only be sent to the payment gateway when the conditions are met. When disabled, all form submissions will be sent to the payment gateway.', 'gravityforms' )
2352:         );
2353: 
2354:         return $other_settings;
2355:     }
2356: 
2357:     public function settings_billing_cycle( $field, $echo = true ) {
2358: 
2359:         $intervals = $this->supported_billing_intervals();
2360:         //get unit so the length drop down is populated with the appropriate numbers for initial load
2361:         $unit = $this->get_setting( $field['name'] . '_unit' );
2362:         //Length drop down
2363:         $interval_keys = array_keys( $intervals );
2364:         if ( ! $unit ) {
2365:             $first_interval = $intervals[ $interval_keys[0] ];
2366:         } else {
2367:             $first_interval = $intervals[ $unit ];
2368:         }
2369:         $length_field = array(
2370:             'name'    => $field['name'] . '_length',
2371:             'type'    => 'select',
2372:             'choices' => $this->get_numeric_choices( $first_interval['min'], $first_interval['max'] )
2373:         );
2374: 
2375:         $html = $this->settings_select( $length_field, false );
2376: 
2377:         //Unit drop down
2378:         $choices = array();
2379:         foreach ( $intervals as $unit => $interval ) {
2380:             if ( ! empty( $interval ) ) {
2381:                 $choices[] = array( 'value' => $unit, 'label' => $interval['label'] );
2382:             }
2383:         }
2384: 
2385:         $unit_field = array(
2386:             'name'     => $field['name'] . '_unit',
2387:             'type'     => 'select',
2388:             'onchange' => "loadBillingLength('" . esc_attr( $field['name'] ) . "')",
2389:             'choices'  => $choices,
2390:         );
2391: 
2392:         $html .= '&nbsp' . $this->settings_select( $unit_field, false );
2393: 
2394:         $html .= "<script type='text/javascript'>var " . $field['name'] . '_intervals = ' . json_encode( $intervals ) . ';</script>';
2395: 
2396:         if ( $echo ) {
2397:             echo $html;
2398:         }
2399: 
2400:         return $html;
2401:     }
2402: 
2403:     public function settings_setup_fee( $field, $echo = true ) {
2404: 
2405:         $enabled_field = array(
2406:             'name'       => $field['name'] . '_checkbox',
2407:             'type'       => 'checkbox',
2408:             'horizontal' => true,
2409:             'choices'    => array(
2410:                 array(
2411:                     'label'    => esc_html__( 'Enabled', 'gravityforms' ),
2412:                     'name'     => $field['name'] . '_enabled',
2413:                     'value'    => '1',
2414:                     'onchange' => "if(jQuery(this).prop('checked')){jQuery('#{$field['name']}_product').show('slow'); jQuery('#gaddon-setting-row-trial').hide('slow');} else {jQuery('#{$field['name']}_product').hide('slow'); jQuery('#gaddon-setting-row-trial').show('slow');}",
2415:                 ),
2416:             )
2417:         );
2418: 
2419:         $html = $this->settings_checkbox( $enabled_field, false );
2420: 
2421:         $form = $this->get_current_form();
2422: 
2423:         $is_enabled = $this->get_setting( "{$field['name']}_enabled" );
2424: 
2425:         $product_field = array(
2426:             'name'    => $field['name'] . '_product',
2427:             'type'    => 'select',
2428:             'class'   => $is_enabled ? '' : 'hidden',
2429:             'choices' => $this->get_payment_choices( $form )
2430:         );
2431: 
2432:         $html .= '&nbsp' . $this->settings_select( $product_field, false );
2433: 
2434:         if ( $echo ) {
2435:             echo $html;
2436:         }
2437: 
2438:         return $html;
2439:     }
2440: 
2441:     public function set_trial_onchange( $field ) {
2442: 
2443:         return "if(jQuery(this).prop('checked')){jQuery('#{$field['name']}_product').show('slow');if (jQuery('#{$field['name']}_product').val() == 'enter_amount'){jQuery('#{$field['name']}_amount').show();}} else {jQuery('#{$field['name']}_product').hide('slow');jQuery('#{$field['name']}_amount').hide();}";
2444: 
2445:     }
2446: 
2447:     public function settings_trial( $field, $echo = true ) {
2448: 
2449:         //--- Enabled field ---
2450:         $enabled_field = array(
2451:             'name'       => $field['name'] . '_checkbox',
2452:             'type'       => 'checkbox',
2453:             'horizontal' => true,
2454:             'choices'    => array(
2455:                 array(
2456:                     'label'    => esc_html__( 'Enabled', 'gravityforms' ),
2457:                     'name'     => $field['name'] . '_enabled',
2458:                     'value'    => '1',
2459:                     'onchange' => $this->set_trial_onchange( $field )
2460:                 ),
2461:             )
2462:         );
2463: 
2464:         $html = $this->settings_checkbox( $enabled_field, false );
2465: 
2466:         //--- Select Product field ---
2467:         $form            = $this->get_current_form();
2468:         $payment_choices = array_merge( $this->get_payment_choices( $form ), array(
2469:             array(
2470:                 'label' => esc_html__( 'Enter an amount', 'gravityforms' ),
2471:                 'value' => 'enter_amount'
2472:             )
2473:         ) );
2474: 
2475:         $product_field = array(
2476:             'name'     => $field['name'] . '_product',
2477:             'type'     => 'select',
2478:             'class'    => $this->get_setting( "{$field['name']}_enabled" ) ? '' : 'hidden',
2479:             'onchange' => "if(jQuery(this).val() == 'enter_amount'){ jQuery('#{$field['name']}_amount').show();} else { jQuery('#{$field['name']}_amount').hide(); }",
2480:             'choices'  => $payment_choices,
2481:         );
2482: 
2483:         $html .= '&nbsp' . $this->settings_select( $product_field, false );
2484: 
2485:         //--- Trial Amount field ----
2486:         $amount_field = array(
2487:             'type'  => 'text',
2488:             'name'  => "{$field['name']}_amount",
2489:             'class' => $this->get_setting( "{$field['name']}_enabled" ) && $this->get_setting( "{$field['name']}_product" ) == 'enter_amount' ? 'gform_currency' : 'hidden gform_currency',
2490:         );
2491: 
2492:         $html .= '&nbsp;' . $this->settings_text( $amount_field, false );
2493: 
2494: 
2495:         if ( $echo ) {
2496:             echo $html;
2497:         }
2498: 
2499:         return $html;
2500:     }
2501: 
2502:     public function recurring_amount_choices() {
2503:         $form                = $this->get_current_form();
2504:         $recurring_choices   = $this->get_payment_choices( $form );
2505:         $recurring_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' );
2506: 
2507:         return $recurring_choices;
2508:     }
2509: 
2510:     public function product_amount_choices() {
2511:         $form              = $this->get_current_form();
2512:         $product_choices   = $this->get_payment_choices( $form );
2513:         $product_choices[] = array( 'label' => esc_html__( 'Form Total', 'gravityforms' ), 'value' => 'form_total' );
2514: 
2515:         return $product_choices;
2516:     }
2517: 
2518:     public function option_choices() {
2519: 
2520:         $option_choices = array(
2521:             array(
2522:                 'label' => esc_html__( 'Sample Option', 'gravityforms' ),
2523:                 'name'  => 'sample_option',
2524:                 'value' => 'sample_option'
2525:             ),
2526:         );
2527: 
2528:         return $option_choices;
2529:     }
2530: 
2531:     public function billing_info_fields() {
2532: 
2533:         $fields = array(
2534:             array( 'name' => 'email', 'label' => esc_html__( 'Email', 'gravityforms' ), 'required' => false ),
2535:             array( 'name' => 'address', 'label' => esc_html__( 'Address', 'gravityforms' ), 'required' => false ),
2536:             array( 'name' => 'address2', 'label' => esc_html__( 'Address 2', 'gravityforms' ), 'required' => false ),
2537:             array( 'name' => 'city', 'label' => esc_html__( 'City', 'gravityforms' ), 'required' => false ),
2538:             array( 'name' => 'state', 'label' => esc_html__( 'State', 'gravityforms' ), 'required' => false ),
2539:             array( 'name' => 'zip', 'label' => esc_html__( 'Zip', 'gravityforms' ), 'required' => false ),
2540:             array( 'name' => 'country', 'label' => esc_html__( 'Country', 'gravityforms' ), 'required' => false ),
2541:         );
2542: 
2543:         return $fields;
2544:     }
2545: 
2546:     public function get_numeric_choices( $min, $max ) {
2547:         $choices = array();
2548:         for ( $i = $min; $i <= $max; $i ++ ) {
2549:             $choices[] = array( 'label' => $i, 'value' => $i );
2550:         }
2551: 
2552:         return $choices;
2553:     }
2554: 
2555:     public function supported_billing_intervals() {
2556: 
2557:         $billing_cycles = array(
2558:             'day'   => array( 'label' => esc_html__( 'day(s)', 'gravityforms' ), 'min' => 1, 'max' => 365 ),
2559:             'week'  => array( 'label' => esc_html__( 'week(s)', 'gravityforms' ), 'min' => 1, 'max' => 52 ),
2560:             'month' => array( 'label' => esc_html__( 'month(s)', 'gravityforms' ), 'min' => 1, 'max' => 12 ),
2561:             'year'  => array( 'label' => esc_html__( 'year(s)', 'gravityforms' ), 'min' => 1, 'max' => 10 )
2562:         );
2563: 
2564:         return $billing_cycles;
2565:     }
2566: 
2567:     public function get_payment_choices( $form ) {
2568:         $fields  = GFAPI::get_fields_by_type( $form, array( 'product' ) );
2569:         $choices = array(
2570:             array( 'label' => esc_html__( 'Select a product field', 'gravityforms' ), 'value' => '' ),
2571:         );
2572: 
2573:         foreach ( $fields as $field ) {
2574:             $field_id    = $field->id;
2575:             $field_label = RGFormsModel::get_label( $field );
2576:             $choices[]   = array( 'value' => $field_id, 'label' => $field_label );
2577:         }
2578: 
2579:         return $choices;
2580:     }
2581: 
2582:     //--------- Stats Page -------------------
2583:     public function get_results_page_config() {
2584: 
2585:         return array(
2586:             'title'        => _x( 'Sales', 'toolbar label', 'gravityforms' ),
2587:             'search_title' => _x( 'Filter', 'metabox title', 'gravityforms' ),
2588:             'capabilities' => array( 'gravityforms_view_entries' ),
2589:             'callbacks'    => array(
2590:                 'fields'    => array( $this, 'results_fields' ),
2591:                 'data'      => array( $this, 'results_data' ),
2592:                 'markup'    => array( $this, 'results_markup' ),
2593:                 'filter_ui' => array( $this, 'results_filter_ui' )
2594:             )
2595:         );
2596:     }
2597: 
2598:     public function results_fields( $form ) {
2599: 
2600:         if ( $this->has_feed( $form['id'] ) ) {
2601:             return $form['fields'];
2602:         } else {
2603:             return false;
2604:         }
2605: 
2606:     }
2607: 
2608: 
2609:     public function results_markup( $html, $data, $form, $fields ) {
2610: 
2611:         $html = "<table width='100%' id='gaddon-results-summary'>
2612:                     <tr>
2613:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Today', 'gravityforms' ) . "</td>
2614:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Yesterday', 'gravityforms' ) . "</td>
2615:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Last 30 Days', 'gravityforms' ) . "</td>
2616:                         <td class='gaddon-results-summary-label'>" . esc_html__( 'Total', 'gravityforms' ) . "</td>
2617:                     </tr>
2618:                     <tr>
2619:                         <td class='gaddon-results-summary-data'>
2620:                             <div class='gaddon-results-summary-data-box'>
2621:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['today']['revenue']}</div>
2622:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2623:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['today']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2624:                             </div>
2625:                         </td>
2626:                         <td class='gaddon-results-summary-data'>
2627:                             <div class='gaddon-results-summary-data-box'>
2628:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['yesterday']['revenue']}</div>
2629:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2630:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['yesterday']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2631:                             </div>
2632:                         </td>
2633: 
2634:                         <td class='gaddon-results-summary-data'>
2635:                             <div class='gaddon-results-summary-data-box'>
2636:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['last30']['revenue']}</div>
2637:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2638:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['last30']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . "</div>
2639:                             </div>
2640:                         </td>
2641:                         <td class='gaddon-results-summary-data'>
2642:                             <div class='gaddon-results-summary-data-box'>
2643:                                 <div class='gaddon-results-summary-primary'>{$data['summary']['total']['revenue']}</div>
2644:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['subscriptions']} " . esc_html__( 'subscriptions', 'gravityforms' ) . "</div>
2645:                                 <div class='gaddon-results-summary-secondary'>{$data['summary']['total']['orders']} " . esc_html__( 'orders', 'gravityforms' ) . '</div>
2646:                             </div>
2647:                         </td>
2648: 
2649:                     </tr>
2650:                  </table>';
2651: 
2652:         if ( $data['row_count'] == '0' ) {
2653:             $html .= "<div class='updated' style='padding:20px; margin-top:40px;'>" . esc_html__( "There aren't any transactions that match your criteria.", 'gravityforms' ) . '</div>';
2654:         } else {
2655:             $chart_data = $this->get_chart_data( $data );
2656:             $html .= $this->get_sales_chart( $chart_data );
2657: 
2658:             //Getting sales table markup
2659:             $sales_table = new GFPaymentStatsTable( $data['table']['header'], $data['data'], $data['row_count'], $data['page_size'] );
2660:             $sales_table->prepare_items();
2661:             ob_start();
2662:             $sales_table->display();
2663:             $html .= ob_get_clean();
2664:         }
2665: 
2666:         $html .= '</form>';
2667: 
2668:         return $html;
2669:     }
2670: 
2671:     public function get_chart_data( $data ) {
2672:         $hAxis_column = $data['chart']['hAxis']['column'];
2673:         $vAxis_column = $data['chart']['vAxis']['column'];
2674: 
2675:         $chart_data = array();
2676:         foreach ( $data['data'] as $row ) {
2677:             $hAxis_value                = $row[ $hAxis_column ];
2678:             $chart_data[ $hAxis_value ] = $row[ $vAxis_column ];
2679:         }
2680: 
2681:         return array(
2682:             'hAxis_title' => $data['chart']['hAxis']['label'],
2683:             'vAxis_title' => $data['chart']['vAxis']['label'],
2684:             'data'        => $chart_data
2685:         );
2686:     }
2687: 
2688:     public static function get_sales_chart( $sales_data ) {
2689:         $markup = '';
2690: 
2691:         $data_table   = array();
2692:         $data_table[] = array( $sales_data['hAxis_title'], $sales_data['vAxis_title'] );
2693: 
2694:         foreach ( $sales_data['data'] as $key => $value ) {
2695:             $data_table[] = array( (string) $key, $value );
2696:         }
2697: 
2698:         $chart_options = array(
2699:             'series' => array(
2700:                 '0' => array(
2701:                     'color'           => '#66CCFF',
2702:                     'visibleInLegend' => 'false',
2703:                 ),
2704:             ),
2705:             'hAxis'  => array(
2706:                 'title' => $sales_data['hAxis_title'],
2707:             ),
2708:             'vAxis'  => array(
2709:                 'title' => $sales_data['vAxis_title'],
2710:             )
2711:         );
2712: 
2713:         $data_table_json = json_encode( $data_table );
2714:         $options_json    = json_encode( $chart_options );
2715:         $div_id          = 'gquiz-results-chart-field-score-frequencies';
2716:         $markup .= "<div class='gresults-chart-wrapper' style='width:100%;height:250px' id='{$div_id}'></div>";
2717:         $markup .= "<script>
2718:                         jQuery('#{$div_id}')
2719:                             .data('datatable',{$data_table_json})
2720:                             .data('options', {$options_json})
2721:                             .data('charttype', 'column');
2722:                     </script>";
2723: 
2724:         return $markup;
2725: 
2726:     }
2727: 
2728:     public function results_data( $form, $fields, $search_criteria, $state_array ) {
2729: 
2730:         $summary = $this->get_sales_summary( $form['id'] );
2731: 
2732:         $data = $this->get_sales_data( $form['id'], $search_criteria, $state_array );
2733: 
2734:         return array(
2735:             'entry_count' => $data['row_count'],
2736:             'row_count'   => $data['row_count'],
2737:             'page_size'   => $data['page_size'],
2738:             'status'      => 'complete',
2739:             'summary'     => $summary,
2740:             'data'        => $data['rows'],
2741:             'chart'       => $data['chart'],
2742:             'table'       => $data['table'],
2743:         );
2744:     }
2745: 
2746:     private function get_mysql_tz_offset() {
2747:         $tz_offset = get_option( 'gmt_offset' );
2748: 
2749:         //add + if offset starts with a number
2750:         if ( is_numeric( substr( $tz_offset, 0, 1 ) ) ) {
2751:             $tz_offset = '+' . $tz_offset;
2752:         }
2753: 
2754:         return $tz_offset . ':00';
2755:     }
2756: 
2757:     public function get_sales_data( $form_id, $search, $state ) {
2758:         global $wpdb;
2759: 
2760:         $data = array(
2761:             'chart' => array(
2762:                 'hAxis' => array(),
2763:                 'vAxis' => array(
2764:                     'column' => 'revenue',
2765:                     'label'  => esc_html__( 'Revenue', 'gravityforms' )
2766:                 )
2767:             ),
2768:             'table' => array(
2769:                 'header' => array(
2770:                     'orders'             => esc_html__( 'Orders', 'gravityforms' ),
2771:                     'subscriptions'      => esc_html__( 'Subscriptions', 'gravityforms' ),
2772:                     'recurring_payments' => esc_html__( 'Recurring Payments', 'gravityforms' ),
2773:                     'refunds'            => esc_html__( 'Refunds', 'gravityforms' ),
2774:                     'revenue'            => esc_html__( 'Revenue', 'gravityforms' )
2775:                 )
2776:             ),
2777:             'rows'  => array()
2778:         );
2779: 
2780:         $tz_offset = $this->get_mysql_tz_offset();
2781: 
2782:         $page_size = 10;
2783:         $group     = strtolower( rgpost( 'group' ) );
2784:         switch ( $group ) {
2785: 
2786:             case 'weekly' :
2787:                 $select        = "concat(left(transaction.week,4), ' - ', right(transaction.week,2)) as week";
2788:                 $select_inner1 = "yearweek(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "')) week";
2789:                 $select_inner2 = "yearweek(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) week";
2790:                 $group_by      = 'week';
2791:                 $order_by      = 'week desc';
2792:                 $join          = 'lead.week = transaction.week';
2793: 
2794:                 $data['chart']['hAxis']['column'] = 'week';
2795:                 $data['chart']['hAxis']['label']  = esc_html__( 'Week', 'gravityforms' );
2796:                 $data['table']['header']          = array_merge( array( 'week' => esc_html__( 'Week', 'gravityforms' ) ), $data['table']['header'] );
2797: 
2798:                 $current_period_format = 'o - W';
2799:                 $decrement_period = 'week';
2800:                 $result_period = 'week';
2801:                 break;
2802: 
2803:             case 'monthly' :
2804:                 $select        = "date_format(transaction.inner_month, '%%Y') as year, date_format(transaction.inner_month, '%%c') as month, '' as month_abbrev, '' as month_year";
2805:                 $select_inner1 = "date_format(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') inner_month";
2806:                 $select_inner2 = "date_format(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "'), '%%Y-%%m-01') inner_month";
2807:                 $group_by      = 'inner_month';
2808:                 $order_by      = 'year desc, (month+0) desc';
2809:                 $join          = 'lead.inner_month = transaction.inner_month';
2810: 
2811:                 $data['chart']['hAxis']['column'] = 'month_year';
2812:                 $data['chart']['hAxis']['label']  = esc_html__( 'Month', 'gravityforms' );
2813:                 $data['table']['header']          = array_merge( array( 'month_year' => esc_html__( 'Month', 'gravityforms' ) ), $data['table']['header'] );
2814: 
2815:                 $current_period_format = 'n'; // Numeric representation of a month, without leading zeros
2816:                 $decrement_period = 'month';
2817:                 $result_period = 'month';
2818:                 break;
2819: 
2820:             default : //daily
2821:                 $select        = "transaction.date, date_format(transaction.date, '%%c') as month, day(transaction.date) as day, dayname(transaction.date) as day_of_week, '' as month_day";
2822:                 $select_inner1 = "date(CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "')) as date";
2823:                 $select_inner2 = "date(CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) as date";
2824:                 $group_by      = 'date';
2825:                 $order_by      = 'date desc';
2826:                 $join          = 'lead.date = transaction.date';
2827: 
2828:                 $data['chart']['hAxis']['column'] = 'month_day';
2829:                 $data['chart']['hAxis']['label']  = esc_html__( 'Day', 'gravityforms' );
2830:                 $data['table']['header']          = array_merge( array(
2831:                     'date'        => esc_html__( 'Date', 'gravityforms' ),
2832:                     'day_of_week' => esc_html__( 'Day', 'gravityforms' )
2833:                 ), $data['table']['header'] );
2834: 
2835:                 $current_period_format = 'Y-m-d';
2836:                 $decrement_period = 'day';
2837:                 $result_period = 'date';
2838:                 break;
2839:         }
2840: 
2841:         $lead_date_filter        = '';
2842:         $transaction_date_filter = '';
2843:         if ( isset( $search['start_date'] ) ) {
2844:             $lead_date_filter        = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.payment_date, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] );
2845:             $transaction_date_filter = $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) >= 0", $search['start_date'] );
2846:         }
2847: 
2848:         if ( isset( $search['end_date'] ) ) {
2849:             $search['end_date']      .= ' 23:59:59';
2850:             $lead_date_filter        .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(l.payment_date, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] );
2851:             $transaction_date_filter .= $wpdb->prepare( " AND timestampdiff(SECOND, %s, CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "')) <= 0", $search['end_date'] );
2852:         }
2853: 
2854:         $payment_method        = rgpost( 'payment_method' );
2855:         $payment_method_filter = '';
2856:         if ( ! empty( $payment_method ) ) {
2857:             $payment_method_filter = $wpdb->prepare( ' AND l.payment_method=%s', $payment_method );
2858:         }
2859: 
2860:         $current_page = rgempty( 'paged' ) ? 1 : absint( rgpost( 'paged' ) );
2861:         $offset       = $page_size * ( $current_page - 1 );
2862: 
2863:         $entry_table_name = self::get_entry_table_name();
2864: 
2865:         $sql = $wpdb->prepare(
2866:             " SELECT SQL_CALC_FOUND_ROWS {$select}, lead.orders, lead.subscriptions, transaction.refunds, transaction.recurring_payments, transaction.revenue
2867:                                 FROM (
2868:                                   SELECT  {$select_inner1},
2869:                                           sum( if(transaction_type = 1,1,0) ) as orders,
2870:                                           sum( if(transaction_type = 2,1,0) ) as subscriptions
2871:                                   FROM {$entry_table_name} l
2872:                                   WHERE l.status='active' AND form_id=%d {$lead_date_filter} {$payment_method_filter}
2873:                                   GROUP BY {$group_by}
2874:                                 ) AS lead
2875: 
2876:                                 RIGHT OUTER JOIN(
2877:                                   SELECT  {$select_inner2},
2878:                                           sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue,
2879:                                           sum( if(t.transaction_type = 'refund', 1, 0) ) as refunds,
2880:                                           sum( if(t.transaction_type = 'payment' AND t.is_recurring = 1, 1, 0) ) as recurring_payments
2881:                                   FROM {$wpdb->prefix}gf_addon_payment_transaction t
2882:                                   INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
2883:                                   WHERE l.status='active' AND l.form_id=%d {$lead_date_filter} {$transaction_date_filter} {$payment_method_filter}
2884:                                   GROUP BY {$group_by}
2885: 
2886:                                 ) AS transaction on {$join}
2887:                                 ORDER BY {$order_by}
2888:                                 LIMIT $page_size OFFSET $offset
2889:                                 ", $form_id, $form_id
2890:         );
2891: 
2892:         GFCommon::log_debug( "sales sql: {$sql}" );
2893: 
2894:         $results = $wpdb->get_results( $sql, ARRAY_A );
2895: 
2896:         $display_results = array();
2897:         $current_period = date( $current_period_format );
2898: 
2899:         if ( isset( $search['start_date'] ) || isset( $search['end_date'] ) ) {
2900:             foreach ( $results as &$result ) {
2901:                 $result['orders']             = intval( $result['orders'] );
2902:                 $result['subscriptions']      = intval( $result['subscriptions'] );
2903:                 $result['refunds']            = intval( $result['refunds'] );
2904:                 $result['recurring_payments'] = intval( $result['recurring_payments'] );
2905:                 $result['revenue']            = floatval( $result['revenue'] );
2906: 
2907:                 $result = $this->format_chart_h_axis( $result );
2908: 
2909:             }
2910: 
2911:             $data['row_count'] = $wpdb->get_var( 'SELECT FOUND_ROWS()' );
2912:             $data['page_size'] = $page_size;
2913: 
2914:             $data['rows'] = $results;
2915: 
2916:         } else {
2917:             $current_date = date( 'Y-m-d' );
2918:             $current_period_timestamp = strtotime( $current_date );
2919:             for ( $i = 1;  $i <= 10 ; $i++ ) {
2920:                 $result_for_date = false;
2921:                 foreach ( $results as $result ) {
2922:                     if ( $result[ $result_period ] == $current_period ) {
2923:                         $display_result = $result;
2924:                         $result_for_date = true;
2925:                         break;
2926:                     }
2927:                 }
2928:                 if ( ! $result_for_date ) {
2929:                     $display_result = array(
2930:                         $result_period      => $current_period,
2931:                         'month'              => date( 'm', $current_period_timestamp ),
2932:                         'day'                => date( 'd', $current_period_timestamp ),
2933:                         'day_of_week'        => date( 'l', $current_period_timestamp ),
2934:                         'month_day'          => '',
2935:                         'year' => date( 'Y', $current_period_timestamp ),
2936:                         'month_abbrev' => '',
2937:                         'orders'             => '0',
2938:                         'subscriptions'      => '0',
2939:                         'refunds'            => '0',
2940:                         'recurring_payments' => '0',
2941:                         'revenue'            => '0.00',
2942:                     );
2943:                 }
2944: 
2945:                 $display_result['orders']             = intval( $display_result['orders'] );
2946:                 $display_result['subscriptions']      = intval( $display_result['subscriptions'] );
2947:                 $display_result['refunds']            = intval( $display_result['refunds'] );
2948:                 $display_result['recurring_payments'] = intval( $display_result['recurring_payments'] );
2949:                 $display_result['revenue']            = floatval( $display_result['revenue'] );
2950:                 $display_result = $this->format_chart_h_axis( $display_result );
2951: 
2952:                 $display_results[] = $display_result;
2953: 
2954:                 $decremented_date = $current_date . ' ' . ( $i * -1 ) . ' ' . $decrement_period;
2955: 
2956:                 $current_period_timestamp = strtotime( $decremented_date );
2957: 
2958:                 $current_period = date( $current_period_format, $current_period_timestamp );
2959: 
2960:             }
2961:             $data['row_count'] = $page_size;
2962:             $data['page_size'] = $page_size;
2963: 
2964:             $data['rows'] = $display_results;
2965:         }
2966: 
2967:         return $data;
2968: 
2969:     }
2970: 
2971:     public function format_chart_h_axis( $result ) {
2972:         $months = array(
2973:             esc_html__( 'Jan', 'gravityforms' ),
2974:             esc_html__( 'Feb', 'gravityforms' ),
2975:             esc_html__( 'Mar', 'gravityforms' ),
2976:             esc_html__( 'Apr', 'gravityforms' ),
2977:             esc_html__( 'May', 'gravityforms' ),
2978:             esc_html__( 'Jun', 'gravityforms' ),
2979:             esc_html__( 'Jul', 'gravityforms' ),
2980:             esc_html__( 'Aug', 'gravityforms' ),
2981:             esc_html__( 'Sep', 'gravityforms' ),
2982:             esc_html__( 'Oct', 'gravityforms' ),
2983:             esc_html__( 'Nov', 'gravityforms' ),
2984:             esc_html__( 'Dec', 'gravityforms' ),
2985:         );
2986: 
2987:         if ( isset( $result['month_abbrev'] ) ) {
2988:             $result['month_abbrev'] = $months[ intval( $result['month'] ) - 1 ];
2989:             $result['month_year']   = $months[ intval( $result['month'] ) - 1 ] . ', ' . $result['year'];
2990: 
2991:             return $result;
2992:         } elseif ( isset( $result['month_day'] ) ) {
2993:             $result['month_day'] = $months[ intval( $result['month'] ) - 1 ] . ' ' . $result['day'];
2994: 
2995:             return $result;
2996:         }
2997: 
2998:         return $result;
2999:     }
3000: 
3001:     public function get_sales_summary( $form_id ) {
3002:         global $wpdb;
3003: 
3004:         $tz_offset = $this->get_mysql_tz_offset();
3005:         $entry_table_name = self::get_entry_table_name();
3006: 
3007:         $summary = $wpdb->get_results(
3008:             $wpdb->prepare(
3009:                 "
3010:                     SELECT transaction.date, lead.orders, lead.subscriptions, transaction.revenue
3011:                     FROM (
3012:                        SELECT  date( CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "') ) as date,
3013:                                sum( if(transaction_type = 1,1,0) ) as orders,
3014:                                sum( if(transaction_type = 2,1,0) ) as subscriptions
3015:                        FROM {$entry_table_name}
3016:                        WHERE status='active' AND form_id = %d AND datediff(now(), CONVERT_TZ(payment_date, '+00:00', '" . $tz_offset . "') ) <= 30
3017:                        GROUP BY date
3018:                      ) AS lead
3019: 
3020:                      RIGHT OUTER JOIN(
3021:                        SELECT  date( CONVERT_TZ(t.date_created, '+00:00', '" . $tz_offset . "') ) as date,
3022:                                sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue
3023:                        FROM {$wpdb->prefix}gf_addon_payment_transaction t
3024:                          INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
3025:                        WHERE l.form_id=%d AND l.status='active'
3026:                        GROUP BY date
3027:                      ) AS transaction on lead.date = transaction.date
3028:                     ORDER BY date desc", $form_id, $form_id
3029:             ), ARRAY_A
3030:         );
3031: 
3032:         $total_summary = $wpdb->get_results(
3033:             $wpdb->prepare(
3034:                 "
3035:                     SELECT sum( if(transaction_type = 1,1,0) ) as orders,
3036:                            sum( if(transaction_type = 2,1,0) ) as subscriptions
3037:                     FROM {$entry_table_name}
3038:                     WHERE form_id=%d AND status='active'", $form_id
3039:             ), ARRAY_A
3040:         );
3041: 
3042:         $total_revenue = $wpdb->get_var(
3043:             $wpdb->prepare(
3044:                 "
3045:                     SELECT sum( if(t.transaction_type = 'refund', abs(t.amount) * -1, t.amount) ) as revenue
3046:                     FROM {$wpdb->prefix}gf_addon_payment_transaction t
3047:                     INNER JOIN {$entry_table_name} l ON l.id = t.lead_id
3048:                     WHERE l.form_id=%d AND status='active'", $form_id
3049:             )
3050:         );
3051: 
3052: 
3053:         $result = array(
3054:             'today'     => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),
3055:             'yesterday' => array( 'revenue' => GFCommon::to_money( 0 ), 'orders' => 0, 'subscriptions' => 0 ),
3056:             'last30'    => array( 'revenue' => 0, 'orders' => 0, 'subscriptions' => 0 ),
3057:             'total'     => array(
3058:                 'revenue'       => GFCommon::to_money( $total_revenue ),
3059:                 'orders'        => $total_summary[0]['orders'],
3060:                 'subscriptions' => $total_summary[0]['subscriptions']
3061:             )
3062:         );
3063: 
3064:         $local_time = GFCommon::get_local_timestamp();
3065:         $today      = gmdate( 'Y-m-d', $local_time );
3066:         $yesterday  = gmdate( 'Y-m-d', strtotime( '-1 day', $local_time ) );
3067: 
3068:         foreach ( $summary as $day ) {
3069:             if ( $day['date'] == $today ) {
3070:                 $result['today']['revenue']       = GFCommon::to_money( $day['revenue'] );
3071:                 $result['today']['orders']        = $day['orders'];
3072:                 $result['today']['subscriptions'] = $day['subscriptions'];
3073:             } elseif ( $day['date'] == $yesterday ) {
3074:                 $result['yesterday']['revenue']       = GFCommon::to_money( $day['revenue'] );
3075:                 $result['yesterday']['orders']        = $day['orders'];
3076:                 $result['yesterday']['subscriptions'] = $day['subscriptions'];
3077:             }
3078: 
3079:             $is_within_30_days = strtotime( $day['date'] ) >= strtotime( '-30 days', $local_time );
3080:             if ( $is_within_30_days ) {
3081:                 $result['last30']['revenue'] += floatval( $day['revenue'] );
3082:                 $result['last30']['orders'] += floatval( $day['orders'] );
3083:                 $result['last30']['subscriptions'] += floatval( $day['subscriptions'] );
3084:             }
3085:         }
3086: 
3087:         $result['last30']['revenue'] = GFCommon::to_money( $result['last30']['revenue'] );
3088: 
3089:         return $result;
3090:     }
3091: 
3092:     public function results_filter_ui( $filter_ui, $form_id, $page_title, $gf_page, $gf_view ) {
3093: 
3094:         if ( $gf_view == "gf_results_{$this->_slug}" ) {
3095:             unset( $filter_ui['fields'] );
3096:         }
3097: 
3098:         $view_markup = "<div>
3099:                     <select id='gaddon-sales-group' name='group'>
3100:                         <option value='daily' " . selected( 'daily', rgget( 'group' ), false ) . '>' . esc_html__( 'Daily', 'gravityforms' ) . "</option>
3101:                         <option value='weekly' " . selected( 'weekly', rgget( 'group' ), false ) . '>' . esc_html__( 'Weekly', 'gravityforms' ) . "</option>
3102:                         <option value='monthly' " . selected( 'monthly', rgget( 'group' ), false ) . '>' . esc_html__( 'Monthly', 'gravityforms' ) . '</option>
3103:                     </select>
3104:                   </div>';
3105:         $view_filter = array(
3106:             'view' => array(
3107:                 'label'   => esc_html__( 'View', 'gravityforms' ),
3108:                 'tooltip' => '<h6>' . esc_html__( 'View', 'gravityforms' ) . '</h6>' . esc_html__( 'Select how you would like the sales data to be displayed.', 'gravityforms' ),
3109:                 'markup'  => $view_markup
3110:             )
3111:         );
3112: 
3113:         $payment_methods = $this->get_payment_methods( $form_id );
3114: 
3115:         $payment_method_markup = "
3116:                 <div>
3117:                     <select id='gaddon-sales-group' name='payment_method'>
3118:                         <option value=''>" . esc_html__( _x( 'Any', 'regarding a payment method', 'gravityforms' ) ) . '</option>';
3119: 
3120:         foreach ( $payment_methods as $payment_method ) {
3121:             $payment_method_markup .= "<option value='" . esc_attr( $payment_method ) . "' " . selected( $payment_method, rgget( 'payment_method' ), false ) . '>' . $payment_method . '</option>';
3122:         }
3123:         $payment_method_markup .= '
3124:                     </select>
3125:                  </div>';
3126: 
3127:         $payment_method_filter = array(
3128:             'payment_method' => array(
3129:                 'label'   => esc_html__( 'Payment Method', 'gravityforms' ),
3130:                 'tooltip' => '',
3131:                 'markup'  => $payment_method_markup
3132:             )
3133:         );
3134: 
3135: 
3136:         $filter_ui = array_merge( $view_filter, $payment_method_filter, $filter_ui );
3137: 
3138:         return $filter_ui;
3139: 
3140:     }
3141: 
3142:     public function get_payment_methods( $form_id ) {
3143:         global $wpdb;
3144: 
3145:         $entry_table_name = self::get_entry_table_name();
3146: 
3147:         $payment_methods = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT payment_method FROM {$entry_table_name} WHERE form_id=%d", $form_id ) );
3148: 
3149:         return array_filter( $payment_methods, array( $this, 'array_filter_non_blank' ) );
3150:     }
3151: 
3152:     public function array_filter_non_blank( $value ) {
3153:         if ( empty( $value ) || $value == 'null' ) {
3154:             return false;
3155:         }
3156: 
3157:         return true;
3158:     }
3159: 
3160:     /**
3161:      * Get name for entry table.
3162:      *
3163:      * @since  2.3
3164:      * @access public
3165:      *
3166:      * @uses   GFFormsModel::get_entry_table_name()
3167:      * @uses   GFFormsModel::get_lead_table_name()
3168:      * @uses   GFPayPalPaymentsPro::get_gravityforms_db_version()
3169:      *
3170:      * @return string
3171:      */
3172:     public static function get_entry_table_name() {
3173: 
3174:         return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_table_name() : GFFormsModel::get_entry_table_name();
3175: 
3176:     }
3177: 
3178:     /**
3179:      * Get name for entry meta table.
3180:      *
3181:      * @since  2.3
3182:      * @access public
3183:      *
3184:      * @uses   GFFormsModel::get_entry_meta_table_name()
3185:      * @uses   GFFormsModel::get_lead_meta_table_name()
3186:      * @uses   GFPayPalPaymentsPro::get_gravityforms_db_version()
3187:      *
3188:      * @return string
3189:      */
3190:     public static function get_entry_meta_table_name() {
3191: 
3192:         return version_compare( self::get_gravityforms_db_version(), '2.3-dev-1', '<' ) ? GFFormsModel::get_lead_meta_table_name() : GFFormsModel::get_entry_meta_table_name();
3193: 
3194:     }
3195: 
3196:     /**
3197:      * Get version of Gravity Forms database.
3198:      *
3199:      * @since  2.3
3200:      * @access public
3201:      *
3202:      * @uses   GFFormsModel::get_database_version()
3203:      *
3204:      * @return string
3205:      */
3206:     public static function get_gravityforms_db_version() {
3207: 
3208:         return method_exists( 'GFFormsModel', 'get_database_version' ) ? GFFormsModel::get_database_version() : GFForms::$version;
3209: 
3210:     }
3211: 
3212:     //-------- Uninstall ---------------------
3213:     public function uninstall() {
3214:         global $wpdb;
3215: 
3216:         $entry_meta_table_name = self::get_entry_meta_table_name();
3217: 
3218:         // deleting transactions
3219:         $sql = $wpdb->prepare(
3220:             "DELETE FROM {$wpdb->prefix}gf_addon_payment_transaction
3221:                                 WHERE lead_id IN
3222:                                    (SELECT lead_id FROM {$entry_meta_table_name} WHERE meta_key='payment_gateway' AND meta_value=%s)", $this->_slug
3223:         );
3224:         $wpdb->query( $sql );
3225: 
3226:         // deleting callback log
3227:         $sql = $wpdb->prepare( "DELETE FROM {$wpdb->prefix}gf_addon_payment_callback WHERE addon_slug=%s", $this->_slug );
3228:         $wpdb->query( $sql );
3229: 
3230:         //clear cron
3231:         wp_clear_scheduled_hook( $this->_slug . '_cron' );
3232: 
3233:         parent::uninstall();
3234:     }
3235: 
3236:     //-------- Scripts -----------------------
3237:     public function scripts() {
3238:         $min     = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
3239:         $scripts = array(
3240:             array(
3241:                 'handle'  => 'gaddon_payment',
3242:                 'src'     => $this->get_gfaddon_base_url() . "/js/gaddon_payment{$min}.js",
3243:                 'version' => GFCommon::$version,
3244:                 'strings' => array(
3245:                     'subscriptionCancelWarning' => __( "Warning! This subscription will be canceled. This cannot be undone. 'OK' to cancel subscription, 'Cancel' to stop", 'gravityforms' ),
3246:                     'subscriptionCancelNonce'   => wp_create_nonce( 'gaddon_cancel_subscription' ),
3247:                     'subscriptionCanceled'      => __( 'Canceled', 'gravityforms' ),
3248:                     'subscriptionError'         => __( 'The subscription could not be canceled. Please try again later.', 'gravityforms' )
3249:                 ),
3250:                 'enqueue' => array(
3251:                     array( 'admin_page' => array( 'form_settings' ), 'tab' => $this->_slug ),
3252:                     array( 'admin_page' => array( 'entry_view' ) ),
3253:                 )
3254:             ),
3255:             array(
3256:                 'handle'    => 'gaddon_token',
3257:                 'src'       => $this->get_gfaddon_base_url() . "/js/gaddon_token{$min}.js",
3258:                 'version'   => GFCommon::$version,
3259:                 'deps'      => array( 'jquery' ),
3260:                 'in_footer' => false,
3261:                 'enqueue'   => array(
3262:                     array( $this, 'enqueue_creditcard_token_script' )
3263:                 )
3264:             ),
3265:             array(
3266:                 'handle'  => 'gform_form_admin',
3267:                 'enqueue' => array(
3268:                     array( 'admin_page' => array( 'entry_edit' ) ),
3269:                 ),
3270:             ),
3271:         );
3272: 
3273:         return array_merge( parent::scripts(), $scripts );
3274:     }
3275: 
3276: 
3277:     //----- Javascript Credit Card Tokens ----
3278:     /**
3279:      * Override to support creating credit card tokens via Javascript.
3280:      *
3281:      * @access public
3282:      *
3283:      * @param mixed $form
3284:      *
3285:      * @return array
3286:      */
3287:     public function creditcard_token_info( $form ) {
3288: 
3289:         return array();
3290: 
3291:     }
3292: 
3293:     /**
3294:      * Add input field for credit card token response.
3295:      *
3296:      * @access public
3297:      *
3298:      * @param string $content
3299:      * @param GF_Field $field
3300:      * @param string $value
3301:      * @param string $entry_id
3302:      * @param string $form_id
3303:      *
3304:      * @return string
3305:      */
3306:     public function add_creditcard_token_input( $content, $field, $value, $entry_id, $form_id ) {
3307: 
3308:         if ( ! $this->has_feed( $form_id ) || GFFormsModel::get_input_type( $field ) != 'creditcard' ) {
3309:             return $content;
3310:         }
3311: 
3312:         $form = GFAPI::get_form( $form_id );
3313:         if ( ! $this->creditcard_token_info( $form ) ) {
3314:             return $content;
3315:         }
3316: 
3317:         $slug = str_replace( 'gravityforms', '', $this->_slug );
3318:         $content .= '<input type=\'hidden\' name=\'' . $slug . '_response\' id=\'gf_' . $slug . '_response\' value=\'' . rgpost( $slug . '_response' ) . '\' />';
3319: 
3320:         return $content;
3321: 
3322:     }
3323: 
3324:     /**
3325:      * Enables AJAX for forms that create credit card tokens via Javascript.
3326:      *
3327:      * @access public
3328:      *
3329:      * @param array $args
3330:      *
3331:      * @return array
3332:      */
3333:     public function force_ajax_for_creditcard_tokens( $args ) {
3334: 
3335:         $form = GFAPI::get_form( rgar( $args, 'form_id' ) );
3336: 
3337:         $args['ajax'] = $this->enqueue_creditcard_token_script( $form ) ? true : $args['ajax'];
3338: 
3339:         return $args;
3340: 
3341:     }
3342: 
3343:     /**
3344:      * Determines if GFToken script should be enqueued.
3345:      *
3346:      * @access public
3347:      *
3348:      * @param array $form
3349:      *
3350:      * @return bool
3351:      */
3352:     public function enqueue_creditcard_token_script( $form ) {
3353: 
3354:         return $form && $this->has_feed( $form['id'] ) && $this->creditcard_token_info( $form );
3355: 
3356:     }
3357: 
3358:     /**
3359:      * Prepare Javascript for creating credit card tokens.
3360:      *
3361:      * @access public
3362:      *
3363:      * @param array $form
3364:      * @param array $field_values
3365:      * @param bool $is_ajax
3366:      *
3367:      * @return void
3368:      */
3369:     public function register_creditcard_token_script( $form, $field_values, $is_ajax ) {
3370: 
3371:         if ( ! $this->enqueue_creditcard_token_script( $form ) ) {
3372:             return;
3373:         }
3374: 
3375:         /* Prepare GFToken object. */
3376:         $gftoken = array(
3377:             'callback'      => 'GF_' . str_replace( ' ', '', $this->_short_title ),
3378:             'feeds'         => $this->creditcard_token_info( $form ),
3379:             'formId'        => rgar( $form, 'id' ),
3380:             'hasPages'      => GFCommon::has_pages( $form ),
3381:             'pageCount'     => GFFormDisplay::get_max_page_number( $form ),
3382:             'responseField' => '#gf_' . str_replace( 'gravityforms', '', $this->_slug ) . '_response'
3383:         );
3384: 
3385:         /* Get needed fields. */
3386:         $gftoken['fields'] = $this->get_creditcard_token_entry_fields( $gftoken['feeds'] );
3387: 
3388:         $script = 'new GFToken( ' . json_encode( $gftoken ) . ' );';
3389:         GFFormDisplay::add_init_script( $form['id'], 'GFToken', GFFormDisplay::ON_PAGE_RENDER, $script );
3390: 
3391:     }
3392: 
3393:     /**
3394:      * Get needed fields for creating credit card tokens.
3395:      *
3396:      * @access public
3397:      *
3398:      * @param array $feeds
3399:      *
3400:      * @return array $fields
3401:      */
3402:     public function get_creditcard_token_entry_fields( $feeds ) {
3403: 
3404:         $fields = array();
3405: 
3406:         foreach ( $feeds as $feed ) {
3407:             foreach ( $feed['billing_fields'] as $billing_field ) {
3408:                 $fields[] = $billing_field;
3409:             }
3410:         }
3411: 
3412:         return array_unique( $fields );
3413: 
3414:     }
3415: 
3416:     //-------- Currency ----------------------
3417:     /**
3418:      * Override this function to add or remove currencies from the list of supported currencies
3419:      *
3420:      * @param $currencies - Currently supported currencies
3421:      *
3422:      * @return mixed - A filtered list of supported currencies
3423:      */
3424:     public function supported_currencies( $currencies ) {
3425:         return $currencies;
3426:     }
3427: 
3428:     /**
3429:      * Retrieve the currency object for the specified currency code.
3430:      *
3431:      * @param string $currency_code
3432:      *
3433:      * @return RGCurrency
3434:      */
3435:     public function get_currency( $currency_code = '' ) {
3436:         if ( empty( $currency_code ) ) {
3437:             $currency_code = GFCommon::get_currency();
3438:         }
3439: 
3440:         return new RGCurrency( $currency_code );
3441:     }
3442: 
3443:     /**
3444:      * Format the amount for export to the payment gateway.
3445:      *
3446:      * Removes currency symbol and if required converts the amount to the smallest unit required by the gateway (e.g. dollars to cents).
3447:      *
3448:      * @param int|float $amount The value to be formatted.
3449:      * @param string $currency_code The currency code.
3450:      *
3451:      * @return int|float
3452:      */
3453:     public function get_amount_export( $amount, $currency_code = '' ) {
3454:         $currency = $this->get_currency( $currency_code );
3455:         $amount   = $currency->to_number( $amount );
3456: 
3457:         if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) {
3458:             return (int) round( $amount * 100 );
3459:         }
3460: 
3461:         return $amount;
3462:     }
3463: 
3464:     /**
3465:      * If necessary convert the amount back from the smallest unit required by the gateway (e.g cents to dollars).
3466:      *
3467:      * @param int|float $amount The value to be formatted.
3468:      * @param string $currency_code The currency code.
3469:      *
3470:      * @return int|float
3471:      */
3472:     public function get_amount_import( $amount, $currency_code = '' ) {
3473:         $currency = $this->get_currency( $currency_code );
3474: 
3475:         if ( $this->_requires_smallest_unit && ! $currency->is_zero_decimal() ) {
3476:             return $amount / 100;
3477:         }
3478: 
3479:         return $amount;
3480:     }
3481: 
3482:     /**
3483:      * If necessary formats the amount as currency and adds it to the action array.
3484:      *
3485:      * @since 2.3.6.6
3486:      *
3487:      * @param array  $action        The payment or subscription properties.
3488:      * @param string $currency_code The currency code.
3489:      *
3490:      * @return array
3491:      */
3492:     public function maybe_add_action_amount_formatted( $action, $currency_code = '' ) {
3493:         if ( ! empty( $action['amount'] ) && empty( $action['amount_formatted'] ) ) {
3494:             $action['amount_formatted'] = GFCommon::to_money( $action['amount'], $currency_code );
3495:         }
3496: 
3497:         return $action;
3498:     }
3499: 
3500: 
3501:     //-------- Cancel Subscription -----------
3502:     public function entry_info( $form_id, $entry ) {
3503: 
3504:         //abort if subscription cancellation isn't supported by the addon or if it has already been canceled
3505:         if ( ! $this->payment_method_is_overridden( 'cancel' ) ) {
3506:             return;
3507:         }
3508: 
3509:         // adding cancel subscription button and script to entry info section
3510:         $cancelsub_button = '';
3511:         if ( $entry['transaction_type'] == '2' && $entry['payment_status'] <> 'Cancelled' && $this->is_payment_gateway( $entry['id'] ) ) {
3512:             ?>
3513:             <input id="cancelsub" type="button" name="cancelsub"
3514:                    value="<?php esc_html_e( 'Cancel Subscription', 'gravityforms' ) ?>" class="button"
3515:                    onclick="cancel_subscription(<?php echo absint( $entry['id'] ); ?>);"
3516:                    onkeypress="cancel_subscription(<?php echo absint( $entry['id'] ); ?>);"/>
3517:             <img src="<?php echo GFCommon::get_base_url() ?>/images/spinner.gif" id="subscription_cancel_spinner"
3518:                  style="display: none;"/>
3519: 
3520:             <script type="text/javascript">
3521: 
3522:             </script>
3523: 
3524:             <?php
3525:         }
3526:     }
3527: 
3528:     /**
3529:      * Target of gform_delete_lead hook. Deletes all transactions and callbacks when an entry is deleted.
3530:      *
3531:      * @param $entry_id . ID of entry that is being deleted
3532:      */
3533:     public function entry_deleted( $entry_id ) {
3534:         global $wpdb;
3535: 
3536:         //deleting from transaction table
3537:         $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_transaction", array( 'lead_id' => $entry_id ), array( '%d' ) );
3538: 
3539:         //deleting from callback table
3540:         $wpdb->delete( "{$wpdb->prefix}gf_addon_payment_callback", array( 'lead_id' => $entry_id ), array( '%d' ) );
3541:     }
3542: 
3543:     public function ajax_cancel_subscription() {
3544:         check_ajax_referer( 'gaddon_cancel_subscription', 'gaddon_cancel_subscription' );
3545: 
3546:         $entry_id = $_POST['entry_id'];
3547: 
3548:         $this->log_debug( __METHOD__ . '(): Processing request for entry #' . $entry_id );
3549: 
3550:         $entry = GFAPI::get_entry( $entry_id );
3551:         $form  = GFAPI::get_form( $entry['form_id'] );
3552:         $feed  = $this->get_payment_feed( $entry, $form );
3553: 
3554:         //This addon does not have a payment feed. Abort.
3555:         if ( empty ( $feed ) ) {
3556:             $this->log_debug( __METHOD__ . '(): Aborting. Entry does not have a feed.' );
3557: 
3558:             return;
3559:         }
3560: 
3561:         if ( $this->cancel( $entry, $feed ) ) {
3562:             $this->cancel_subscription( $entry, $feed );
3563:             die( '1' );
3564:         } else {
3565:             $this->log_debug( __METHOD__ . '(): Aborting. Unable to cancel subscription.' );
3566:             die( '0' );
3567:         }
3568: 
3569:     }
3570: 
3571:     /**
3572:      * Target of gform_before_delete_field hook. Sets relevant payment feeds to inactive when the credit card field is
3573:      * deleted.
3574:      *
3575:      * @param int $form_id   ID of the form being edited.
3576:      * @param int $field_id  ID of the field being deleted.
3577:      */
3578:     public function before_delete_field( $form_id, $field_id ) {
3579:         if ( $this->_requires_credit_card ) {
3580:             $form  = GFAPI::get_form( $form_id );
3581:             $field = $this->get_credit_card_field( $form );
3582: 
3583:             if ( is_object( $field ) && $field->id == $field_id ) {
3584:                 $feeds = $this->get_feeds( $form_id );
3585:                 foreach ( $feeds as $feed ) {
3586:                     if ( $feed['is_active'] ) {
3587:                         $this->update_feed_active( $feed['id'], 0 );
3588:                     }
3589:                 }
3590:             }
3591:         }
3592:     }
3593: 
3594: 
3595:     // # HELPERS
3596: 
3597:     private function payment_method_is_overridden( $method_name, $base_class = 'GFPaymentAddOn' ) {
3598:         return parent::method_is_overridden( $method_name, $base_class );
3599:     }
3600: 
3601:     public function authorization_error( $error_message ) {
3602:         return array( 'error_message' => $error_message, 'is_success' => false, 'is_authorized' => false );
3603:     }
3604: 
3605:     public function remove_spaces_from_card_number( $card_number ) {
3606:         $card_number = str_replace( array( "\t", "\n", "\r", ' ' ), '', $card_number );
3607: 
3608:         return $card_number;
3609:     }
3610: 
3611:     public function get_supports_callback(){
3612:         return $this->_supports_callbacks;
3613:     }
3614: }
3615: 
3616: if ( ! class_exists( 'WP_List_Table' ) ) {
3617:     require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
3618: }
3619: 
3620: 
3621: class GFPaymentStatsTable extends WP_List_Table {
3622: 
3623:     private $_rows = array();
3624:     private $_page_size = 10;
3625:     private $_total_items = 0;
3626: 
3627:     function __construct( $columns, $rows, $total_count, $page_size ) {
3628:         $this->_rows        = $rows;
3629:         $this->_total_items = $total_count;
3630:         $this->_page_size   = $page_size;
3631: 
3632:         $this->_column_headers = array(
3633:             $columns,
3634:             array(),
3635:             array(),
3636:             rgar( array_values( $columns ), 2 ),
3637:         );
3638: 
3639:         parent::__construct(
3640:             array(
3641:                 'singular' => esc_html__( 'sale', 'gravityforms' ),
3642:                 'plural'   => esc_html__( 'sales', 'gravityforms' ),
3643:                 'ajax'     => false,
3644:                 'screen'   => 'gaddon_sales',
3645:             )
3646:         );
3647:     }
3648: 
3649:     function prepare_items() {
3650:         $this->items = $this->_rows;
3651: 
3652:         $this->set_pagination_args( array( 'total_items' => $this->_total_items, 'per_page' => $this->_page_size ) );
3653:     }
3654: 
3655:     function no_items() {
3656:         esc_html_e( "There hasn't been any sales in the specified date range.", 'gravityforms' );
3657:     }
3658: 
3659:     function get_columns() {
3660:         return $this->_column_headers[0];
3661:     }
3662: 
3663:     function column_default( $item, $column ) {
3664:         return rgar( $item, $column );
3665:     }
3666: 
3667:     function column_revenue( $item ) {
3668:         return GFCommon::to_money( $item['revenue'] );
3669:     }
3670: 
3671:     function pagination( $which ) {
3672:         if ( empty( $this->_pagination_args ) ) {
3673:             return;
3674:         }
3675: 
3676:         $total_items = $this->get_pagination_arg( 'total_items' );
3677:         $total_pages = $this->get_pagination_arg( 'total_pages' );
3678: 
3679:         $output = '<span class="displaying-num">' . sprintf( _n( '1 item', '%s items', $total_items, 'gravityforms' ), number_format_i18n( $total_items ) ) . '</span>';
3680: 
3681:         $current = $this->get_pagenum();
3682: 
3683:         $page_links = array();
3684: 
3685:         $disable_first = $disable_last = '';
3686:         if ( $current == 1 ) {
3687:             $disable_first = ' disabled';
3688:         }
3689:         if ( $current == $total_pages ) {
3690:             $disable_last = ' disabled';
3691:         }
3692: 
3693:         $page_links[] = sprintf(
3694:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"1\"); gresults.getResults();' onkeypress='gresults.setCustomFilter(\"paged\", \"1\"); gresults.getResults();'>%s</a>",
3695:             'first-page' . $disable_first,
3696:             esc_attr__( 'Go to the first page', 'gravityforms' ),
3697:             '&laquo;'
3698:         );
3699: 
3700:         $page_links[] = sprintf(
3701:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");' onkeypress='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3702:             'prev-page' . $disable_first,
3703:             esc_attr__( 'Go to the previous page', 'gravityforms' ),
3704:             max( 1, $current - 1 ),
3705:             max( 1, $current - 1 ),
3706:             '&lsaquo;'
3707:         );
3708: 
3709: 
3710:         $html_current_page = $current;
3711: 
3712:         $html_total_pages = sprintf( "<span class='total-pages'>%s</span>", number_format_i18n( $total_pages ) );
3713:         $page_links[]     = '<span class="paging-input">' . sprintf( esc_html_x( '%1$s of %2$s', 'paging', 'gravityforms' ), $html_current_page, $html_total_pages ) . '</span>';
3714: 
3715:         $page_links[] = sprintf(
3716:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");' onkeypress='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3717:             'next-page' . $disable_last,
3718:             esc_attr__( 'Go to the next page', 'gravityforms' ),
3719:             min( $total_pages, $current + 1 ),
3720:             min( $total_pages, $current + 1 ),
3721:             '&rsaquo;'
3722:         );
3723: 
3724:         $page_links[] = sprintf(
3725:             "<a class='%s' title='%s' style='cursor:pointer;' onclick='gresults.setCustomFilter(\"paged\", \"%s\"); gresults.getResults(); gresults.setCustomFilter(\"paged\", \"1\");'>%s</a>",
3726:             'last-page' . $disable_last,
3727:             esc_attr__( 'Go to the last page', 'gravityforms' ),
3728:             $total_pages,
3729:             '&raquo;'
3730:         );
3731: 
3732:         $pagination_links_class = 'pagination-links';
3733:         if ( ! empty( $infinite_scroll ) ) {
3734:             $pagination_links_class = ' hide-if-js';
3735:         }
3736:         $output .= "\n<span class='$pagination_links_class'>" . join( "\n", $page_links ) . '</span>';
3737: 
3738:         if ( $total_pages ) {
3739:             $page_class = $total_pages < 2 ? ' one-page' : '';
3740:         } else {
3741:             $page_class = ' no-pages';
3742:         }
3743: 
3744:         $this->_pagination = "<div class='tablenav-pages{$page_class}'>$output</div>";
3745: 
3746:         echo $this->_pagination;
3747:     }
3748: 
3749: }
3750: 
Gravity Forms API API documentation generated by ApiGen 2.8.0