en+966-11-473-5411
·
[email protected]
·
Sunday - Thursday 09:00-17:00
Free Consultation

<?php

/**

  • Gravity Forms Amazon Payfort Add-On.
    *
  • @since 1.0
  • @package GravityForms
  • @author Rocketgenius
  • @copyright Copyright (c) 2018, Rocketgenius
    */

defined( ‘ABSPATH’ ) or die();

// Include the Payment Add-On framework.
GFForms::include_payment_addon_framework();

/**

  • Class GF_AmazonPayfort
    *
  • Primary class to manage the Amazon Payfort Add-On.
    *
  • @since 1.0
    *
  • @uses GFPaymentAddOn
    */

class GF_AmazonPayfort extends GFPaymentAddOn {

/**
 * Version of this add-on which requires reauthentication with the API.
 *
 * Anytime updates are made to this class that requires a site to reauthenticate Gravity Forms with Zoho, this
 * constant should be updated to the value of GFForms::$version.
 *
 * @since 2.0.2
 *
 * @see GFForms::$version
 */
const LAST_REAUTHENTICATION_VERSION = '2.0';

/**
 * Contains an instance of this class, if available.
 *
 * @since  1.0
 * @access private
 *
 * @used-by GF_AmazonPayfort::get_instance()
 *
 * @var object $_instance If available, contains an instance of this class.
 */
private static $_instance = null;

/**
 * Defines the version of the Amazon Payfort Add-On.
 *
 * @since  1.0
 * @access protected
 *
 * @used-by GF_AmazonPayfort::scripts()
 *
 * @var string $_version Contains the version, defined from amazonpayfort.php
 */
protected $_version = GF_AMAZON_PAYFORT_VERSION;

/**
 * Defines the minimum Gravity Forms version required.
 *
 * @since  1.0
 * @access protected
 *
 * @var string $_min_gravityforms_version The minimum version required.
 */
protected $_min_gravityforms_version = '2.2';

/**
 * Defines the plugin slug.
 *
 * @since  1.0
 * @access protected
 *
 * @var string $_slug The slug used for this plugin.
 */
protected $_slug = 'gravityformsamazonpf';

/**
 * Defines the main plugin file.
 *
 * @since  1.0
 * @access protected
 *
 * @var string $_path The path to the main plugin file, relative to the plugins folder.
 */
protected $_path = 'amazon-payfort-gf/amazon-payfort-gf.php';

/**
 * Defines the full path to this class file.
 *
 * @since  1.0
 * @access protected
 *
 * @var string $_full_path The full path.
 */
protected $_full_path = __FILE__;

/**
 * Defines the URL where this Add-On can be found.
 *
 * @since  1.0
 * @access protected
 *
 * @var string $_url The URL of the Add-On.
 */
protected $_url = 'https://siyanaweb.com';

/**
 * Defines the title of this Add-On.
 *
 * @since  1.0
 * @access protected
 *
 * @var string $_title The title of the Add-On.
 */
protected $_title = 'Gravity Forms Amazon Payfort Add-On';

/**
 * Defines the short title of the Add-On.
 *
 * @since  1.0
 * @access protected
 *
 * @var string $_short_title The short title.
 */
protected $_short_title = 'Amazon Payfort';

/**
 * Defines if Add-On should use Gravity Forms servers for update data.
 *
 * @since  1.0
 * @access protected
 *
 * @var bool $_enable_rg_autoupgrade true
 */
protected $_enable_apf_autoupgrade = false;

/**
 * Defines if user will not be able to create feeds for a form until a credit card field has been added.
 *
 * @since  1.0
 * @since  2.0 set to false in favor of the Amazon Payfort field.
 * @access protected
 *
 * @var bool $_requires_credit_card true.
 */

//protected $_requires_credit_card = false;
protected $_requires_credit_card = true;

/**
 * Defines if callbacks/webhooks/IPN will be enabled and the appropriate database table will be created.
 *
 * @since  2.0
 * @access protected
 *
 * @var bool $_supports_callbacks true
 */
protected $_supports_callbacks = true;

/**
 * Defines the capability needed to access the Add-On settings page.
 *
 * @since  1.4.3
 * @access protected
 * @var    string $_capabilities_settings_page The capability needed to access the Add-On settings page.
 */
protected $_capabilities_settings_page = 'gravityforms_amazonpayfort';

/**
 * Defines the capability needed to access the Add-On form settings page.
 *
 * @since  1.4.3
 * @access protected
 * @var    string $_capabilities_form_settings The capability needed to access the Add-On form settings page.
 */
protected $_capabilities_form_settings = 'gravityforms_amazonpayfort';

/**
 * Defines the capability needed to uninstall the Add-On.
 *
 * @since  1.4.3
 * @access protected
 * @var    string $_capabilities_uninstall The capability needed to uninstall the Add-On.
 */
protected $_capabilities_uninstall = 'gravityforms_amazonpayfort_uninstall';

/**
 * Defines the capabilities needed for the Amazon Payfort Add-On
 *
 * @since  1.0
 * @access protected
 * @var    array $_capabilities The capabilities needed for the Add-On
 */
protected $_capabilities = array( 'gravityforms_amazonpayfort', 'gravityforms_amazonpayfort_uninstall' );

/**
 * Contains an instance of the Amazon Payfort API library, if available.
 *
 * @since  1.0
 * @since  2.0 API contains only one instance for production, it is no longer an array.
 * @access protected
 * @var    GF_AmazonPayfort_API $api If available, contains an instance of the Amazon Payfort API library.
 */
protected $api = null;

/**
 * Contains the nonce that is used to verify 3DSecure success callback.
 *
 * @since 1.3
 *
 * @var string
 */

protected $success_nonce = null;
//protected $redirect_url = 'http://localhost/woocommerce/gf-post.php';

/**
 * Get an instance of this class.
 *
 * @since  1.0
 * @access public
 *
 * @uses GF_AmazonPayfort::$_instance
 *
 * @return GF_AmazonPayfort
 */

public static function get_instance() {

    if ( null === self::$_instance ) {
        self::$_instance = new self;
    }

    return self::$_instance;

}//end of member function

//No Use of this function
public function pre_init() {

    add_filter( 'gform_is_delayed_pre_process_feed', array( $this, 'gform_is_delayed_pre_process_feed' ), 10, 4 );
    add_filter( 'gform_entry_post_save', array( $this, 'gfamazonpayfort_entry_post_save' ), 11, 2 );

    parent::pre_init();

    //Added load on init to display the Amazon Payfort form
    if (class_exists( 'GF_Field' ) ) {
        require_once 'includes/class-gf-field-amazonpayfort-creditcard.php';
    }

}//end of member function


public function feed_list_no_item_message() {

    $settings = $this->get_plugin_settings();
    if ( ! rgar( $settings, 'gf_amazonpayfort_configured' ) ) {
        return sprintf( esc_html__( 'To get started, please configure your %sBorgun Settings%s!', 'gravityformsborgun' ), '<a href="' . admin_url( 'admin.php?page=gf_settings&subview=' . $this->_slug ) . '">', '</a>' );
    } else {
        return parent::feed_list_no_item_message();
    }

}//end of function

/**
* Setup fields for feed settings.
*
* @since  1.0
* @access public
*
* @uses   GFAddOn::remove_field()
* @uses   GFFeedAddOn::feed_settings_fields()
*
* @return array $settings
*/


public function feed_settings_fields() {

    // Get feed settings fields.
    $settings = parent::feed_settings_fields();

    //return GFRecurly_Feed_Settings_Fields::instance()->do_feed_settings_fields();
    require_once 'amazon-payfort-gf/classes/class-gf-amazonpayfort-feed-settings-fields.php';

    $settings = GFAmazonPayfort_Feed_Settings_Fields::instance()->do_feed_settings_fields();

    return $settings; 


    // Prepare customer information fields.
    /*

    $settings   = $this->add_field_before( 'recurringAmount', $subscription_name_field, $settings );

    // Remove trial field.
    $settings = $this->remove_field( 'trial', $settings );

    return $settings;

     */

}//end of member function


/**
 * Loads the Amazon Payfort field.
 *
 * @since 2.0
 *
 * @return void
 */


//Entry Post Save
public function gfamazonpayfort_entry_post_save( $entry, $form ) {
    // echo "<pre>"; print_r($entry);
    // echo "<pre>"; print_r($form); exit();
    $amount_field_id = '';
    $is_gfpayfort = false;
    foreach($form['fields'] as $fields){
        if($fields->type==='total'){
            $amount_field_id = $fields->id;
            continue;
        }
    }
    foreach($entry as $key=>$single_entry){
        if($single_entry === 'Amazon PayFort'){
            $is_gfpayfort = true;
            continue;
        }
    }

    if(
        $is_gfpayfort 
        && (!in_array('subscribed', wp_get_current_user()->roles) && !in_array('gold_member', wp_get_current_user()->roles) && !in_array('platinum_member', wp_get_current_user()->roles))
        //$this->has_amazonpayfort_card_field( $form )

    ){
        require_once 'includes/class-gf-amazonpayfort-entry-post-save.php';

        if( isset($_POST['input_5_2']) ) $item_price = substr( $_POST['input_5_2'], 1 );
        if( isset($_POST['input_3_6_apf_cardNo']) ) $apf_cardNo = $_POST['input_3_6_apf_cardNo'];
        if( isset($_POST['input_3_6_apf_expDate']) ) $apf_expDate = $_POST['input_3_6_apf_expDate'];
        if( isset($_POST['input_3_6_apf_cvv']) ) $apf_cvv = $_POST['input_3_6_apf_cvv'];
        if( isset($_POST['input_3']) ) $customer_email = $_POST['input_3'];


        $merchant_reference = $entry["id"];                                               //Form Entry Id

        $settings = $this->get_plugin_settings();

        $merchant_identifier = $settings['merchant_identifier'];
        $access_code = $settings['access_code'];
        $shaRequestPhrase = $settings['sha_request_phrase'];
        $shaResponsePhrase = $settings['sha_response_phrase'];


        $command     = 'PURCHASE';                                                         //PURCHASE
        $language  = 'en';

        $return_url = home_url('/') . 'wp-content/plugins/amazon-payfort-gf/amazon-payfort-return.php';    
        $source_url = $entry["source_url"]; 


        if( $settings['apiMode'] == 'sandbox' )
        $Url = 'https://sbcheckout.payfort.com/FortAPI/paymentPage';                       //Sandbox
        else
        $Url = 'https://checkout.payfort.com/FortAPI/paymentPage';                         //Production


        $to = '[email protected]';
        $subject = 'GF Entry Submission Content';
        $body = json_encode($entry);
        $headers = array('Content-Type: text/html; charset=UTF-8');
        wp_mail( $to, $subject, $body, $headers );  


            $first_name = rgar( $entry, '371.3' );
            $last_name = rgar( $entry, '371.6' );

            if( !empty($first_name) )
            $name = $first_name. ' '.$last_name;
            else
            $name = 'Itmam Customer';

            if( empty($entry["373"]) )
            $customer_email = '[email protected]';
            else
            $customer_email = $entry["373"];

            $amount = $entry[$amount_field_id];
            /* if( $entry["form_id"] == 1 ){                                                           //Non-Disclosure Agreement - One Way
            $amount = $entry["100"];
            }elseif( $entry["form_id"] == 2 ){                                                     //Promotional Agreement
            $amount = $entry["65"];
            }elseif( $entry["form_id"] == 3 ){                                                     //Promotional Agreement
            $amount = $entry["65"];
            }elseif( $entry["form_id"] == 5 ){                                                     //Non-Disclosure Agreement - One Way
            $amount = $entry["115"];
            }elseif( $entry["form_id"] == 7 ){                                                     //Partnership Agreement
            $amount = $entry["157"];
            }elseif( $entry["form_id"] == 8 ){                                                     //Non-Disclosure Agreement - Two Way
            $amount = $entry["96"];
            }elseif( $entry["form_id"] == 9 ){                                                     //Share Sale & Purchase Agreement
            $amount = $entry["202"];
            }elseif( $entry["form_id"] == 10 ){
            $amount = $entry["164"];
            }elseif( $entry["form_id"] == 11 ){
            $amount = $entry["164"];
            }elseif( $entry["form_id"] == 13 ){                                                        //Novation Agreement
            $amount = $entry["198"];
            }elseif( $entry["form_id"] == 15 ){                                                        //Services Agreement
            $amount = $entry["228"];
            }elseif( $entry["form_id"] == 16 ){                                                        //Business Licensing Agreement
            $arr = explode( ' ', $entry["211.2"] );
            $amount = $arr['0'];
            }elseif( $entry["form_id"] == 20 ){                                                        //Employment Agreement – Fixed Term (Non Renewable)
            $amount = $entry["242"];
            }elseif( $entry["form_id"] == 18 ){                                                        //Employment Agreement – Fixed Term (Non Renewable)
            $amount = $entry["242"];
            }else if( $entry["form_id"] == 49 ){   
            $amount = $entry["242"];
            }else if( $entry["form_id"] == 17 ){                                                   //Employment Agreement – Fixed Term (Renewable)
            $amount = $entry["211"];
            }else if( $entry["form_id"] == 21 ){                                                   //Assets Transfer Agreement
            $amount = $entry["211"];
            }else if( $entry["form_id"] == 35 ){                                                   //مذكرة تفاهم Memorandum of Understanding
            $amount = $entry["117"];
            }else if( $entry["form_id"] == 37 ){                                                   //اتفاقية استحقاق Entitlement Agreement
            $amount = $entry["160"];
            }else if( $entry["form_id"] == 50 ){                                                   //Commercial Agency Agreement (Non-Exclusive)
            $amount = $entry["298"];
            }else if( $entry["form_id"] == 51 ){                                                   
            $amount = $entry["350"];
            }else if( $entry["form_id"] == 52 ){                                                   
            $amount = $entry["335"];
            }else{
            $amount = $entry["350"];
            } */

            $requestParams = array(

                'amount'              => $amount * 100,
                   'currency'            => 'SAR',
                'merchant_identifier' => $merchant_identifier,
                'access_code'         => $access_code,
                'merchant_reference'  => $merchant_reference,
                'customer_email'      => $customer_email,
                'customer_name'       => $name,
                'command'             => $command,
                'language'            => $language,
                'return_url'          => $return_url,
            );


        $SHARequestPhrase =    $settings['sha_request_phrase'];
        $SHAResponsePhrase = $settings['sha_response_phrase'];

        $SHAType = 'sha256';
        $shaString = '';

        ksort( $requestParams );
        foreach ( $requestParams as $k => $v ) {

            $shaString .= "$k=$v";

        }//end of foreach loop

        $shaString = $SHARequestPhrase . $shaString . $SHARequestPhrase;
        $signature = hash( $SHAType, $shaString );

        $requestParams['signature'] = $signature;

            $apf_args_array = array();
               foreach( $requestParams as $key => $value )
            {
                $apf_args_array[] = "<input type='hidden' name='$key' value='$value'/>";
            }


            $html_form = '
                        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
                         <form action="' . $Url . '" method="post" id="apf_payment_form">' 
                           . implode( '', $apf_args_array ) 
                           . '  <script type="text/javascript">
                             $("#apf_payment_form").submit();
                             </script>
                           </form>';

            echo $html_form;

            return GFAmazonPayFort_Entry_Post_Save::instance( $this )->gfamazonpayfort_entry_post_save( $entry, $form );
    }


}//end of member function


// ====================================================================================================================================
/**
 * This function handles the form confirmation by first trying to make the payment using paystation, then either returning the url
 * to redirect the user's browser to so they see the payment screen, or the error message is returned.
 * @param mixed $confirmation text or redirect for form submission
 * @param array $form the form submission data
 * @param array $entry the form entry
 * @param bool $ajax form submission via AJAX
 * @return mixed
 */
// ====================================================================================================================================

public function gformConfirmation( $confirmation, $form, $entry, $ajax ) {


    if (in_array('subscribed', wp_get_current_user()->roles) || in_array('gold_member',  wp_get_current_user()->roles) || in_array('platinum_member',  wp_get_current_user()->roles)) {
        return $confirmation;
    }


    // Return if not the current form.
    if (RGForms::post('gform_submit') != $form['id']) {
        return $confirmation;
    }

    // Get feed mapping form fields to payment request, return if not set as need the feed to actually do anything.
    $feed = $this->getFeed($form['id']);
    if (!$feed) {
        return $confirmation;
    }

    // --------------------------------------
    // Record payment gateway.
    gform_update_meta( $entry['id'], 'payment_gateway', 'amazon-payfort-gf' );

    // --------------------------------------
    // Get the data posted from the form and build the payment request by creating a Paystation payment request object,
    // setting it's properties, then calling the processPayment() method.
    $formData = $this->getFormData($form);
    $paymentReq = null;

    // If a paystation override id has been specified on the form from dropdown to override etc then create new payment object with that Paystation id
    // Otherwise default to the normal paystation_id in the main settings. This feature allows money to be paid in to different paystation accounts
    // based on user selection of something on the form such as branch, region, country etc.
    if ((isset($formData->PaystationOverrideId)) && ($formData->PaystationOverrideId)) {
        $paymentReq = new GFPaystationPayment($formData->PaystationOverrideId, $this->options['gatewayId'], $this->options['testMode'], $this->securityHash);
    }
    else {
        $paymentReq = new GFPaystationPayment($this->options['paystationId'], $this->options['gatewayId'], $this->options['testMode'], $this->securityHash);
    }

    $paymentReq->amount = (int) bcmul($formData->total, 100); // We multiply by 100 because the amount sent to the gateawy must be an int, so cents not a float.
    $paymentReq->currency = GFCommon::get_currency(); // Get the currency from gravity forms.
    $paymentReq->merchantSession = $this->buildSessionId($entry['id']); // Call function in this class to create the merchantSession id.

    $paymentReq->merchantReference = $formData->MerchantReference;
    $paymentReq->customerDetails = $formData->CustomerDetails;
    $paymentReq->orderDetails = $formData->OrderDetails;

    // --------------------------------------
    // Try making the payment, if successful the redirect the user to the payment URL, if not then catch error.
    try {
        $response = $paymentReq->processPayment();  // Call processPayment function in PaystationPayment class, it will do the POST to the paystation gateway.

        // If digitalOrder is populated then there were no issues with the transaction request, so update the status of the
        // the lead record and then set the confirmation to the url of the payment screen.
        if ($response->digitalOrder) {
            GFFormsModel::update_lead_property($entry['id'], 'payment_status', 'Processing');
            GFFormsModel::update_lead_property($entry['id'], 'transaction_id', $response->transactionId);

            // NB: GF handles redirect via JavaScript if headers already sent, or AJAX.
            $confirmation = array('redirect' => $response->digitalOrder);
        }
        else {
            // There was an error with the payment initiation, an error message will be set so
            // throw it so that the exception code below will update the status to failed
            // and cause the user to see the error emssage.
            throw new GFPaystationException($response->errorMessage);
        }
    } catch (GFPaystationException $e) {
        // Update the status to failed.
        GFFormsModel::update_lead_property($entry['id'], 'payment_status', 'Failed');
        // If the there is a failure url set the confirmation to the failure message so it is displayed to the user.
        // When there is an error like this the form is not displayed, so we need to wrap the error div in the gform
        // wrapper div in order for the validation error style to actually be applied.
        $confirmation = "<div class='gform_wrapper'><div class='validation_error'>" . nl2br($e->getMessage()) . "</div></div>";
    }

    return $confirmation;

}//end of member function


/**
 * Get success URL for 3DSecure flow.
 *
 * @since 2.0
 *
 * @param int $form_id Form ID.
 * @param int $feed_id Feed ID.
 *
 * @return string
 */

private function get_success_url( $form_id, $feed_id ) {

    /**
     * Filters 2Checkout success URL, which is the URL that users will be sent to after completing 3DSecure confirmation successfully.
     *
     * @since 2.0
     *
     * @param string $url     The URL to be filtered.
     * @param int    $form_id The ID of the form being submitted.
     * @param int    $feed_id The ID of the feed being processed.
     */

    return apply_filters(
        'gform_amazonpayfort_success_url',
        add_query_arg( 'gf_amazonpayfort_3ds_success', $this->get_success_nonce(), $this->get_page_url() ), $form_id, $feed_id
    );

}//end of member function


public function redirect_url( $feed, $submission_data, $form, $entry ) {




    global $wp_version;






    //Don't process redirect url if request is a Borgun return
    if ( ! rgempty( 'gf_borgun_return', $_GET ) ) {
        return false;
    }

    //updating lead's payment_status to Processing
    GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Processing' );

    //Getting Url (Production or Sandbox)
    $url = $feed['meta']['mode'] == 'production' ? $this->production_url : $this->sandbox_url;

    $settings = $this->get_plugin_settings();

    $gf_borgun_merchant_id        = ! empty( $settings['gf_borgun_merchant_id'] ) ? $settings['gf_borgun_merchant_id'] : '';
    $gf_borgun_payment_gateway_id = ! empty( $settings['gf_borgun_payment_gateway_id'] ) ? $settings['gf_borgun_payment_gateway_id'] : '';
    $gf_borgun_secret_key         = ! empty( $settings['gf_borgun_secret_key'] ) ? $settings['gf_borgun_secret_key'] : '';
    $gf_borgun_language           = ! empty( $settings['gf_borgun_language'] ) ? $settings['gf_borgun_language'] : 'en';
    $gf_borgun_notification_email = ! empty( $settings['gf_borgun_notification_email'] ) ? $settings['gf_borgun_notification_email'] : get_option( 'admin_email' );

    if ( empty( $gf_borgun_merchant_id ) && empty( $gf_borgun_secret_key ) && empty( $gf_borgun_payment_gateway_id ) ) {
        return false;
    }

    $line_items     = rgar( $submission_data, 'line_items' );
    $discounts      = rgar( $submission_data, 'discounts' );
    $payment_amount = rgar( $submission_data, 'payment_amount' );

    $currency       = rgar( $entry, 'currency' );

    $return = $this->return_url( $form['id'], $entry['id'], $payment_amount );

    $hash[] = $gf_borgun_merchant_id;
    $hash[] = $return;
    $hash[] = $return;
    $hash[] = $entry['id'];
    $hash[] = number_format( $payment_amount, 2, '.', '' );

    //print_r( $hash );
    $hash[]           = $currency;
    $message          = implode( '|', $hash );
    $CheckHashMessage = utf8_encode( trim( $message ) );
    $hash             = hash_hmac( 'sha256', $CheckHashMessage, $gf_borgun_secret_key );

    //Customer fields
    $customer_fields = $this->customer_query_string( $feed, $entry );

    $borgun_args = array(
        'merchantid'       => $gf_borgun_merchant_id,
        'paymentgatewayid' => $gf_borgun_payment_gateway_id,
        'checkhash'        => $hash,
        'orderid'          => $entry['id'],
        'currency'         => $currency,
        'language'         => $gf_borgun_language,
        'SourceSystem'     => 'GF-' . $wp_version . ' -BRG-' . $entry['id'],
        'buyeremail'       => $customer_fields['email'],
        'amount'           => number_format( $payment_amount, 2, '.', '' ),
        'pagetype'         => '0',
        //If set as 1 then cardholder is required to insert email,mobile number,address.
        'skipreceiptpage'  => '1',
        'merchantemail'    => $gf_borgun_notification_email,
    );

    $item_loop = 0;
    //work on products
    if ( is_array( $line_items ) ) {
        foreach ( $line_items as $item ) {
            $product_name                                   = $item['name'];
            $quantity                                       = $item['quantity'];
            $price                                          = $item['unit_price'];
            $borgun_args[ 'itemdescription_' . $item_loop ] = html_entity_decode( $product_name, ENT_NOQUOTES, 'UTF-8' );
            $borgun_args[ 'itemcount_' . $item_loop ]       = $quantity;

            $options     = rgar( $item, 'options' );
            $is_shipping = rgar( $item, 'is_shipping' );

            if ( ! $is_shipping ) {

                if ( ! empty( $options ) && is_array( $options ) ) {
                    $option_index   = 1;
                    $options_string = ' (';
                    foreach ( $options as $option ) {
                        $field_label    = html_entity_decode( $option["field_label"], ENT_NOQUOTES, 'UTF-8' );
                        $option_name    = html_entity_decode( $option["option_name"], ENT_NOQUOTES, 'UTF-8' );
                        $price          += GFCommon::to_number( $option["unit_price"] );
                        $amount         = GFCommon::to_number( $option['unit_price'] );
                        $field_label    = str_replace( '+', ' ', $field_label );
                        $option_name    = str_replace( '+', ' ', $option_name );
                        $options_string .= $field_label . ':' . $option_name . '(' . $currency . ' ' . $amount . ') , ';
                        $option_index ++;
                    }

                    $options_string                                 .= rtrim( $options_string, ', ' );
                    $product_name                                   .= $options_string . ' )';
                    $borgun_args[ 'itemdescription_' . $item_loop ] = html_entity_decode( $product_name, ENT_NOQUOTES, 'UTF-8' );
                    $borgun_args[ 'itemcount_' . $item_loop ]       = $quantity;
                    $borgun_args[ 'itemunitamount_' . $item_loop ]  = $price;
                    $borgun_args[ 'itemamount_' . $item_loop ]      = $price * $quantity;

                } else {

                    $borgun_args[ 'itemunitamount_' . $item_loop ] = number_format( $price, 2, '.', '' );
                    $borgun_args[ 'itemamount_' . $item_loop ]     = number_format( $price * $quantity, 2, '.', '' );

                }
            }

            $item_loop ++;
        }

        //look for discounts to pass in the item_name
        if ( is_array( $discounts ) ) {

            foreach ( $discounts as $discount ) {

                $product_name                                   = $discount['name'];
                $quantity                                       = $discount['quantity'];
                $price                                          = $discount['price'];
                $borgun_args[ 'itemdescription_' . $item_loop ] = html_entity_decode( $product_name, ENT_NOQUOTES, 'UTF-8' );
                $borgun_args[ 'itemcount_' . $item_loop ]       = $quantity;
                $borgun_args[ 'itemunitamount_' . $item_loop ]  = $price;
                $borgun_args[ 'itemamount_' . $item_loop ]      = $price * $quantity;

            }

        }

    }

    $url = $url . '?' . http_build_query( $borgun_args );

    $url = $url . '&returnurlsuccess=' . $return . '&returnurlsuccessserver' . $return
           . '&returnurlcancel=' . $return . '&returnurlerror=' . $return . '&merchantemail=' . $gf_borgun_notification_email;
    $url = gf_apply_filters( 'gform_borgun_request', $form['id'], $url, $form, $entry, $feed, $submission_data );
    $this->log_debug( __METHOD__ . "(): Sending to Borgun: {$url}" );



    return $url;

}//end of function

public function custom_confirmation( $confirmation, $form, $entry, $ajax ) {

    if( $form['id'] == '1' ) {
        $confirmation = array( 'redirect' => 'http://localhost/woocommerce/gf-post.php' );
    }
    return $confirmation;

}//end of member function


//Get 'Current Feed'
public function getCurrentFeed() {

    return $this->current_feed;

}//end of member function

//Get Authorization
public function getAuthorization() {

    return $this->authorization;

}//end of member function


private function redirect_to_amazon_payfort( $entry ) {

    $payment_details = gform_get_meta( $entry['id'], 'amazonpayfort_payment_details' );

    $this->log_debug( '--Returned Payment Details---' );
    $this->log_debug( print_r( $payment_details, true ) );

    if ( ! is_array( $payment_details ) || empty( $payment_details['PaymentMethod']['Authorize3DS']['Href'] ) ) {
        return;
    }

    gform_update_meta( $entry['id'], '3dsecure_success_nonce', wp_hash( $this->get_success_nonce() ) );

    $redirect_url = add_query_arg(
        rgar( $payment_details['PaymentMethod']['Authorize3DS'], 'Params', array() ),
        $payment_details['PaymentMethod']['Authorize3DS']['Href']
    );

    $this->log_debug( '3DS Redirect URL: ' . $redirect_url );

    header( 'location: ' . $redirect_url );
    exit();


}//end of member function


//Creates BluePay left nav menu under Forms
public static function create_menu( $menus ){

    // Adding submenu if user has access
    $permission = self::has_access("gravityforms_amazon_payfort");

    if(!empty($permission))

        $menus[] = array( "name" => "gf_amazon_payfort", "label" => __("Amazon Payfort", "gravity-forms-amazonpayfort"), 
        "callback" =>  array("GFAmazonPayfort", "amazon_payfort_page"), "permission" => $permission );

    return $menus;

}//end of member function

//——————– VALIDATION STEP ——————————————————-

public static function amazon_payfort_validation($validation_result){

    print_r( $validation_result );

    $config = self::is_ready_for_capture($validation_result);
    $form = $validation_result["form"];

    /*
    if(!$config)
        return $validation_result;
    */

    //getting submitted data from fields
    $form_data = self::get_form_data($form, $config);

    $initial_payment_amount = $form_data["amount"] + absint(rgar($form_data,"fee_amount"));

    //don't process payment if initial payment is 0, but act as if the transaction was successful
    if($initial_payment_amount == 0){

        self::log_debug("Amount is 0. No need to authorize payment, but act as if transaction was successful");

        self::process_free_product($form, $config, $validation_result);
    }
    else {

        $card_field = self::get_creditcard_field($form);

        if($card_field && rgpost("input_{$card_field["id"]}_1") && rgpost("input_{$card_field["id"]}_2") && rgpost("input_{$card_field["id"]}_3") && rgpost("input_{$card_field["id"]}_5") ){

            self::log_debug("Initial payment of {$initial_payment_amount}. Credit card authorization required.");

            //authorizing credit card and setting self::$transaction_response variable
            $validation_result = self::authorize_credit_card($form_data, $config, $validation_result);
        }else{

            self::log_debug("Initial payment of {$initial_payment_amount}. ACH authorization required.");
            //authorizing credit card and setting self::$transaction_response variable
            $validation_result = self::authorize_ach($form_data, $config, $validation_result);
        }
    }

    return $validation_result;

}//end of member function


//-------------------- SUBMISSION STEP -------------------------------------------------------
public static function amazon_payfort_save_field_value( $value, $lead, $field, $form ){

    if(empty(self::$transaction_response))
        return $value;

    $config = self::$transaction_response["config"];

    if($field['id'] == $config['meta']['customer_fields']['routing_number']){
        return '######'.substr($value, 6);
    }

    if($field['id'] == $config['meta']['customer_fields']['account_number']){
        return '######'.substr($value, 6);
    }

    return $value;

}//end of member validation


//Is Delayed Pre Process Feed?
public function gform_is_delayed_pre_process_feed( $is_delayed, $form, $entry, $slug ) {

    require_once 'includes/class-gf-amazonpayfort-gform-is-delayed-pre-process-feed.php';

return GFAmazonPayfort_Is_Delayed_Pre_Process_Feed::instance( $this )->gform_is_delayed_pre_process_feed( $is_delayed, $form, $entry, $slug );

}//end of member function


//GFPaymentAddOn Process Feed
public function process_feed( $feed, $entry, $form ) {

        require_once 'amazon-payfort-gf/classes/class-gf-amazonpayfort-process-feed.php';

        return GFAmazonPayfort_Process_Feed::instance( $this )->process_feed( $feed, $entry, $form );

}//end of member function


/**
 * Retrieves an entry by looking up for the success nonce that was saved before sending the user to 3DSecure challenge.
 *
 * @since 2.0
 *
 * @param string $nonce The success token.
 *
 * @return false|array The entry if found, or false.
 */

//No Use of this function
/*
private function get_entry_by_3dsecure_nonce( $nonce ) {

    if ( empty( $nonce ) ) {
        return false;
    }

    $entries = GFAPI::get_entries( 0,
        array(

            'field_filters' => array(

                array(

                    'key'   => '3dsecure_success_nonce',
                    'value' => wp_hash( $nonce ),

                ),

            ),

        )


    );

    if ( is_wp_error( $entries ) || ! is_array( $entries ) || count( $entries ) < 1 ) {
        return false;
    }

    return $entries[0];

}//end of member function
*/

/**
 * Completes processing the entry payment information after successful 3DSecure flow.
 *
 * @since 2.0
 *
 * @param array $entry Current entry object being processed.
 *
 * @return array The entry array after updating its payment information.
 */

//No Use of this function
/*
private function process_entry_after_successful_3dsecure( $entry ) {

// Only process entry if it is still in pending status.
// Sometimes IPN is sent and handled before the user is redirected back to the confirmation page, check status to prevent processing the entry twice.
if ( $entry[‘payment_status’] !== ‘Pending’ ) {
return $entry;
}

    $order_details = gform_get_meta( $entry['id'], 'order_details' );
    $order_type    = gform_get_meta( $entry['id'], 'order_type' );
    $form          = GFAPI::get_form( $entry['form_id'] );

    if ( $order_type === 'subscription' ) {

        $entry = parent::process_subscription(

            array(

                'subscription' => array(

                    'subscription_id' => $order_details['RefNo'],
                    'is_success'      => true,
                    'amount'          => $order_details['NetPrice'],

                ),

            ),

            array(),
            array(),

            $form,
            $entry

        );

    } else {

        parent::complete_authorization( $entry,

            array(

                'amount'         => $order_details['NetPrice'],
                'transaction_id' => $order_details['RefNo'], ) );

    }

    return $entry;


}//end of member function
*/


/**
 * Handles displaying/processing entry confirmation after successful 3DSecure flow.
 *
 * @since 2.0
 *
 * @param array $entry Current entry object being processed.
 */

private function handle_confirmation( $entry ) {

    if ( ! class_exists( 'GFFormDisplay' ) ) {

        require_once( GFCommon::get_base_path() . '/form_display.php' );
    }

    $form         = GFAPI::get_form( $entry['form_id'] );
    $confirmation = GFFormDisplay::handle_confirmation( $form, $entry, false );

    if ( is_array( $confirmation ) && isset( $confirmation['redirect'] ) ) {
        header( "Location: {$confirmation['redirect']}" );
        exit;
    }

    GFFormDisplay::$submission[ $entry['form_id'] ] = array(

        'is_confirmation'      => true,
        'confirmation_message' => $confirmation,
        'form'                 => $form,
        'lead'                 => $entry,

    );

}//end of member function

/**
 * Initialize the frontend hooks.
 *
 * @since  1.0
 * @access public
 *
 * @uses GF_AmazonPayfort::register_init_scripts()
 * @uses GF_AmazonPayfort::populate_credit_card_last_four()
 * @uses GFPaymentAddOn::init()
 *
 * @return void
 */

public function init() {

    //add_filter( 'gform_field_content', array( $this, 'add_amazonpayfort_token' ), 10, 5 );
    add_filter( 'gform_register_init_scripts', array( $this, 'register_init_scripts' ), 10, 3 );

    //Commented by Me
    //add_action( 'admin_notices', array( $this, 'add_upgrade_connection_notice' ) );

    // Supports frontend feeds.
    $this->_supports_frontend_feeds = true;

    parent::init();


}//end of member function


/**
 * Return the scripts which should be enqueued.
 *
 * @since  1.0
 * @since  2.0 Use 2pay.js library instead of deprecated 2co.js
 * @access public
 *
 * @uses   GF_AmazonPayfort::frontend_script_callback()
 * @uses   GFAddOn::get_base_url()
 * @uses   GFAddOn::get_short_title()
 * @uses   GFAddOn::get_version()
 * @uses   GFCommon::get_base_url()
 * @uses   GFPaymentAddOn::scripts()
 *
 * @return array
 */

public function scripts() {

    $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';
    $scripts = array(

        array(

            'handle'  => '2pay.js',
            'src'     => 'https://2pay-js.amazonpayfort.com/v1/2pay.js',
            'version' => $this->get_version(),
            'deps'    => array(),
        ),

        /*
        array(
            'handle'    => 'gform_amazonpayfort_frontend',
            'src'       => $this->get_base_url() . "/js/frontend{$min}.js",
            'version'   => $this->get_version(),
            'deps'      => array( 'jquery', '2pay.js', 'wp-a11y' ),
            'in_footer' => false,
            'enqueue'   => array(
                array( $this, 'frontend_script_callback' ),
            ),
        ),
        */

    );

    return array_merge( parent::scripts(), $scripts );

}//end of member function


/***
 * Return the styles that need to be enqueued.
 *
 * @since  2.0
 *
 * @return array Returns an array of styles and when to enqueue them
 */


public function styles() {

    $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG || isset( $_GET['gform_debug'] ) ? '' : '.min';

    $styles = array(

        array(

            'handle'    => 'gforms_amazonpayfort_frontend',
            'src'       => $this->get_base_url() . "/css/frontend{$min}.css",
            'version'   => $this->_version,
            'in_footer' => false,

            'enqueue'   => array(
                array( $this, 'frontend_script_callback' ),
            ),

        ),

        array(

            'handle'    => 'gforms_amazonpayfort_plugin_settings',
            'src'       => $this->get_base_url() . "/css/plugin_settings{$min}.css",
            'version'   => $this->_version,
            'in_footer' => false,

            'enqueue'   => array(
                array(
                    'admin_page' => array( 'form_settings', 'form_editor' ),
                ),

            ),

        ),


    );

    return array_merge( parent::styles(), $styles );

}//end of member function


/**
 * Check if the form has an active Amazon Payfort feed and a credit card field.
 *
 * @since  1.0
 * @since  2.0 Check if Amazon Payfort field exists instead of default credit card field.
 * @access public
 *
 * @used-by GF_AmazonPayfort::scripts()
 * @uses    GFFeedAddOn::has_feed()
 * @uses    GFPaymentAddOn::has_credit_card_field()
 *
 * @param array $form The form currently being processed.
 *
 * @return bool
 */


public function frontend_script_callback( $form ) {

    return ! is_admin() && $form && $this->has_feed( $form['id'] ) && $this->has_amazonpayfort_card_field( $form );

}//end of member function


/**
 * Add required Amazon Payfort token hidden input in case of a multi-page form.
 *
 * @since  2.0
 *
 * @param string  $content The field content to be filtered.
 * @param object  $field   The field that this input tag applies to.
 * @param string  $value   The default/initial value that the field should be pre-populated with.
 * @param integer $lead_id When executed from the entry detail screen, $lead_id will be populated with the Entry ID.
 * @param integer $form_id The current Form ID.
 *
 * @return string $content HTML formatted content.
 */

//Not Useful
public function add_amazonpayfort_token( $content, $field, $value, $lead_id, $form_id ) {

    // If this form does not have a Amazon Payfort feed or if this is not a Amazon Payfort field, return field content.
    if ( ! $this->has_feed( $form_id ) || $field->get_input_type() !== 'amazonpayfort_creditcard' ) {
        return $content;
    }

    // Populate Amazon Payfort token to hidden fields if it exists.
    $token = sanitize_text_field( rgpost( 'amazonpayfort_response' ) );

    if ( $token ) {
        $content .= '<input type="hidden" name="amazonpayfort_response" value="' . esc_attr( $token ) . '" />';
    }

    return $content;

}//end of member function

// # PLUGIN SETTINGS -----------------------------------------------------------------------------------------------
/**
 * Prepare plugin settings fields.
 *
 * @since  1.0
 * @since  2.0 Use merchant code & secret and deprecate sandbox credentials.
 * @access public
 *
 * @return array
 * @uses   GF_AmazonPayfort::initialize_api()
 */

public function plugin_settings_fields() {

    $tooltip =

        sprintf(
            // Translators: 1. Open anchor tag 2. close anchor tag.
            esc_html__( 'Enter your Amazon Payfort API credentials below. For more information about these settings, check out the %1$sGravity Forms documentation.%2$s', 'amazon-payfort-gf' ), '<a href="https://docs.gravityforms.com/setting-up-the-amazonpayfort-add-on/" target="_blank" title="Setting up the Amazon Payfort Add-on">', '</a>' );

$set_array = array(

        array(

            'title'  => esc_html__( 'Amazon Payfort API Mode', 'amazon-payfort-gf' ),
            'fields' => array(

                array(

                    'name'          => 'apiMode',
                    'label'         => esc_html__( 'Amazon PayFort API Mode', 'amazon-payfort-gf' ),
                    'type'          => 'radio',
                    'required'      => true,
                    'horizontal'    => true,

                    'default_value' => 'sandbox',

                    'choices'       => array(

                        array(

                            'label' => esc_html__( 'Production', 'amazon-payfort-gf' ),
                            'value' => 'production',
                        ),

                        array(

                            'label' => esc_html__( 'Sandbox', 'amazon-payfort-gf' ),
                            'value' => 'sandbox',

                        ),

                    ),

                ),

            ),

        ),


        array(

            'title'   => esc_html__( 'Amazon Payfort API Credentials', 'amazon-payfort-gf' ),
            'tooltip' => $tooltip,
            'fields'  => array(

                array(

                    'name'              => 'merchant_identifier',
                    'label'             => esc_html__( 'Merchant Identifier', 'amazon-payfort-gf' ),
                    'type'              => 'text',
                    'class'             => 'medium',
                    //'feedback_callback' => array( $this, 'validate_production_credentials' ),
                    'required'          => true,
                ),

                array(

                    'name'              => 'access_code',
                    'label'             => esc_html__( 'Access Code ', 'amazon-payfort-gf' ),
                    'type'              => 'text',
                    'class'             => 'medium',
                    //'feedback_callback' => array( $this, 'validate_production_credentials' ),
                    'required'          => true,
                ),

                array(

                    'name'  => 'sha_request_phrase',
                    'label' => esc_html__( 'SHA Request Phrase', 'amazon-payfort-gf' ),
                    'type'  => 'text',
                    'class' => 'medium',
                ),

                array(

                    'name'  => 'sha_response_phrase',
                    'label' => esc_html__( 'SHA Response Phrase', 'amazon-payfort-gf' ),
                    'type'  => 'text',
                    'class' => 'medium',
                ),


                /*
                array(

                    'name'        => 'IPN_enabled',
                    'label'       => esc_html__( 'IPN Configured?', 'amazon-payfort-gf' ),
                    'type'        => 'checkbox',
                    'horizontal'  => true,
                    'required'    => true,
                    'description' => $this->get_ipn_section_description(),

                    'choices'     => array(

                        array(
        'label' => esc_html__( 'I have enabled the Gravity Forms IPN URL in my Amazon Payfort account.', 'amazon-payfort-gf' ),
                            'value' => 1,
                            'name'  => 'IPN_enabled',

                        ),

                    ),

                ),
               */

            ),

        ),


    );


    return array(

        array(

            'title'  => esc_html__( 'Amazon PayFort API Mode', 'amazon-payfort-gf' ),
            'fields' => array(

                array(

                    'name'          => 'apiMode',
                    'label'         => esc_html__( 'API Mode', 'amazon-payfort-gf' ),
                    'type'          => 'radio',
                    'required'      => true,
                    'horizontal'    => true,
                    'default_value' => 'sandbox',

                    'choices'       => array(

                        array(

                            'label' => esc_html__( 'Production', 'amazon-payfort-gf' ),
                            'value' => 'production',

                        ),

                        array(

                            'label' => esc_html__( 'Sandbox', 'amazon-payfort-gf' ),
                            'value' => 'sandbox',

                        ),

                    ),

                ),

            ),

        ),

        array(

            'title'   => esc_html__( 'Amazon PayFort API Credentials', 'amazon-payfort-gf' ),
            'tooltip' => $tooltip,
            'fields'  => array(

                array(

                    'name'              => 'merchant_identifier',
                    'label'             => esc_html__( 'Merchant Identifier', 'amazon-payfort-gf' ),
                    'type'              => 'text',
                    'class'             => 'medium',
                    //'feedback_callback' => array( $this, 'validate_production_credentials' ),
                    'required'          => true,

                ),

                array(

                    'name'              => 'access_code',
                    'label'             => esc_html__( 'Access Code ', 'amazon-payfort-gf' ),
                    'type'              => 'text',
                    'class'             => 'medium',
                    //'feedback_callback' => array( $this, 'validate_production_credentials' ),
                    'required'          => true,

                ),

                array(

                    'name'  => 'sha_request_phrase',
                    'label' => esc_html__( 'SHA Request Phrase', 'amazon-payfort-gf' ),
                    'type'  => 'text',
                    'class' => 'medium',

                ),

                array(

                    'name'  => 'sha_response_phrase',
                    'label' => esc_html__( 'SHA Response Phrase', 'amazon-payfort-gf' ),
                    'type'  => 'text',
                    'class' => 'medium',

                ),

                /*

                array(
                    'name'        => 'IPN_enabled',
                    'label'       => esc_html__( 'IPN Configured?', 'amazon-payfort-gf' ),
                    'type'        => 'checkbox',
                    'horizontal'  => true,
                    'required'    => true,
                    'description' => $this->get_ipn_section_description(),

                    'choices'     => array(

                        array(
        'label' => esc_html__( 'I have enabled the Gravity Forms IPN URL in my Amazon Payfort account.', 'amazon-payfort-gf' ),
                            'value' => 1,
                            'name'  => 'IPN_enabled',

                        ),

                    ),

                ),

               */

            ),

        ),


    );


}//end of member function


/**
 * Return the plugin's icon for the plugin/form settings menu.
 *
 * @since 1.7
 *
 * @return string
 */

public function get_menu_icon() {

    //return file_get_contents( $this->get_base_path() . '/images/menu-icon.svg' );
    return 'gform-icon--credit-card';

}//end of member function

/**
 * Validate production API credentials.
 *
 * @since      1.0
 * @access  public
 *
 * @used-by GF_AmazonPayfort::plugin_settings_fields()
 * @uses    GF_AmazonPayfort::initialize_api()
 *
 * @return bool|null
 */

public function validate_production_credentials() {

    // Capture API response.
    $api_response = $this->initialize_api( 'production' );

    return is_a( $api_response, 'GF_AmazonPayfort_API' ) ? true : $api_response;

}//end of member function

/**
 * Validate sandbox API credentials.
 *
 * @since      1.0
 * @deprecated 2.0 No longer used by internal code.
 * @access  public
 *
 * @used-by GF_AmazonPayfort::plugin_settings_fields()
 * @uses    GF_AmazonPayfort::initialize_api()
 *
 * @return bool|null
 */

public function validate_sandbox_credentials() {

    //Capture API response.
    $api_response = $this->initialize_api( 'sandbox' );
    return is_a( $api_response, 'GF_AmazonPayfort_API' ) ? true : $api_response;

}//end of member function


// # FEED SETTINGS -------------------------------------------------------------------------------------------------
/**
 * Set feed creation control.
 *
 * @since  1.0
 * @since  2.0 Check if a Amazon Payfort field exists.
 * @access public
 *
 * @uses   GF_AmazonPayfort::initialize_api()
 *
 * @return bool
 */

public function can_create_feed() {

    return is_a( $this->initialize_api(), 'GF_AmazonPayfort_API' ) && $this->has_amazonpayfort_card_field();

}//end of member function


/**
 * Get the require Amazon Payfort field message.
 *
 * @since 2.0
 *
 * @return false|string
 */

public function feed_list_message() {

    $form = $this->get_current_form();

    // If settings are not yet configured, display default message.
    if ( ! is_a( $this->initialize_api(), 'GF_AmazonPayfort_API' ) ) {
        return GFFeedAddOn::feed_list_message();
    }

    // If form doesn't have a Amazon Payfort field, display require message.
    if ( ! $this->has_amazonpayfort_card_field( $form ) ) {
        return $this->requires_amazonpayfort_card_message();
    }

    return GFFeedAddOn::feed_list_message();

}//end of member function

/**
 * Display require Amazon Payfort field message.
 *
 * @since 2.0
 *
 * @return string
 */

public function requires_amazonpayfort_card_message() {

    $url = add_query_arg(

        array(

            'view'    => null,
            'subview' => null,
        )

    );

return sprintf( esc_html__( “You must add an Amazon Payfort field to your form before creating a feed. Let’s go %1\$sadd one%2\$s!”, ‘amazon-payfort-gf’ ), ““, ‘‘ );

}//end of member function


/**
 * Prepare a list of needed billing information fields.
 *
 * @since  1.0
 * @access public
 *
 * @return array
 */

public function billing_info_fields() {

    $fields = array(

        array(

            'name'     => 'email',
            'label'    => esc_html__( 'Email', 'amazon-payfort-gf' ),
            'required' => true,
        ),

        array(

            'name'     => 'address',
            'label'    => esc_html__( 'Address', 'amazon-payfort-gf' ),
            'required' => true,
        ),

        array(

            'name'     => 'address2',
            'label'    => esc_html__( 'Address 2', 'amazon-payfort-gf' ),
            'required' => false,
        ),

        array(

            'name'     => 'city',
            'label'    => esc_html__( 'City', 'amazon-payfort-gf' ),
            'required' => true,

        ),

        array(

            'name'     => 'state',
            'label'    => esc_html__( 'State', 'amazon-payfort-gf' ),
            'required' => true,

        ),

        array(

            'name'     => 'zip',
            'label'    => esc_html__( 'Zip', 'amazon-payfort-gf' ),
            'required' => true,

        ),

        array(

            'name'     => 'country',
            'label'    => esc_html__( 'Country', 'amazon-payfort-gf' ),
            'required' => true,

        ),

        array(

            'name'     => 'phone',
            'label'    => esc_html__( 'Phone Number', 'amazon-payfort-gf' ),
            'required' => true,

        ),


    );

    return $fields;

}//end of member function


/**
 * Define the choices available in the billing cycle drop downs.
 *
 * @since   1.0
 * @access  public
 *
 * @used-by GFPaymentAddOn::settings_billing_cycle()
 *
 * @return array
 */

public function supported_billing_intervals() {

    return array(

        'week'  => array(

            'label' => esc_html__( 'week(s)', 'amazon-payfort-gf' ),
            'min'   => 1,
            'max'   => 12,

        ),

        'month' => array(

            'label' => esc_html__( 'month(s)', 'amazon-payfort-gf' ),
            'min'   => 1,
            'max'   => 12,

        ),

        'year'  => array(

            'label' => esc_html__( 'year(s)', 'amazon-payfort-gf' ),
            'min'   => 1,
            'max'   => 1,

        ),


    );


}//end of member function

/**
 * Define the option choices available.
 *
 * @since   1.0
 * @access  public
 *
 * @used-by GFPaymentAddOn::other_settings_fields()
 *
 * @return array
 */

public function option_choices() {

    return array();

}//end of member function


// # FRONTEND ------------------------------------------------------------------------------------------------------
/**
 * Register Amazon Payfort script when displaying form.
 *
 * @since   1.0
 * @access  public
 *
 * @param array $form         Form object.
 * @param array $field_values Current field values. Not used.
 * @param bool  $is_ajax      If form is being submitted via AJAX.
 *
 * @used-by GF_AmazonPayfort::init()
 * @uses    GFAddOn::get_plugin_settings()
 * @uses    GFFeedAddOn::has_feed()
 * @uses    GFFormDisplay::add_init_script()
 * @uses    GFFormDisplay::ON_PAGE_RENDER
 * @uses    GFPaymentAddOn::get_credit_card_field()
 */

public function register_init_scripts( $form, $field_values, $is_ajax ) {

    // Get Amazon Payfort field.
    $cc_field = $this->get_amazonpayfort_card_field( $form );

    // If form does not have a Amazon Payfort feed and does not have a credit card field, exit.
    if ( ! $this->has_feed( $form['id'] ) || ! $cc_field || ! $this->initialize_api() ) {
        return;
    }

    // Get plugin settings.
    $settings = $this->get_plugin_settings();

    // Prepare Amazon Payfort Javascript arguments.
    $args = array(

        'apiMode'      => $settings['apiMode'],
        'formId'       => $form['id'],

        'ccFieldId'    => $cc_field->id,

        'ccPage'       => $cc_field->pageNumber,
        'isAjax'       => $is_ajax,

        'merchantIdentifier' => $settings['merchant_identifier'],
        'accessCode'    => $settings['access_code'],
        'shaRequestPhrase'    => $settings['sha_request_phrase'],
        'shaResponsePhrase'    => $settings['sha_response_phrase'],

    );


    // get all Amazon Payfort feeds.
    $feeds = $this->get_feeds_by_slug( $this->_slug, $form['id'] );

    foreach ( $feeds as $feed ) {

        if ( rgar( $feed, 'is_active' ) === '0' ) {
            continue;
        }

        // Get feed settings to pass them to JS object.
        $feed_settings = array(
            'feedId' => $feed['id'],
        );

        if ( rgars( $feed, 'meta/transactionType' ) === 'product' ) {
            $feed_settings['paymentAmount'] = rgars( $feed, 'meta/paymentAmount' );
        }

        $args['feeds'][ $feed['id'] ] = $feed_settings;

    }//end of foreach loop

    // Initialize Amazon Payfort script.
    $script = 'new GFAmazon Payfort( ' . json_encode( $args ) . ' );';

    // Add Amazon Payfort script to form scripts.
    GFFormDisplay::add_init_script( $form['id'], 'amazonpayfort', GFFormDisplay::ON_PAGE_RENDER, $script );

}//end of member function


/**
 * Gets the payment validation result.
 *
 * @since  2.0
 *
 * @param array $validation_result Contains the form validation results.
 * @param array $authorization_result Contains the form authorization results.
 *
 * @return array The validation result for the credit card field.
 */

public function get_validation_result( $validation_result, $authorization_result ) {

    if ( empty( $authorization_result['error_message'] ) ) {
        return $validation_result;
    }

    $credit_card_page = 0;

    foreach ( $validation_result['form']['fields'] as &$field ) {

        if ( $field->type === 'amazonpayfort_creditcard' ) {

            $field->failed_validation  = true;
            $field->validation_message = $authorization_result['error_message'];
            $credit_card_page          = $field->pageNumber;
            break;

        }

    }//end of foreach loop

    $validation_result['credit_card_page'] = $credit_card_page;
    $validation_result['is_valid']         = false;

    return $validation_result;

}//end of member function

// # TRANSACTIONS --------------------------------------------------------------------------------------------------
/**
 * Initialize authorizing the transaction for the product & services type feed or return the 2co.js error.
 *
 * @since  1.0
 * @access public
 *
 * @param array $feed            The Feed object currently being processed.
 * @param array $submission_data The customer and transaction data.
 * @param array $form            The Form object currently being processed.
 * @param array $entry           The Entry object currently being processed.
 *
 * @uses   GF_AmazonPayfort::authorize_product()
 * @uses   GF_AmazonPayfort::get_amazonpayfort_js_error()
 * @uses   GF_AmazonPayfort::initialize_api()
 * @uses   GFPaymentAddOn::authorization_error()
 *
 * @return array
 */

public function authorize( $feed, $submission_data, $form, $entry ) {

    // Initialize API.
    if ( ! is_a( $this->initialize_api(), 'GF_AmazonPayfort_API' ) ) {
        return $this->authorization_error( esc_html__( 'Unable to initialize API.', 'amazon-payfort-gf' ) );
    }

    $validation_errors = $this->validate_submission( $submission_data, $form, $entry, $feed );

    // If there were validation errors, return them.
    if ( is_array( $validation_errors ) ) {
        return $validation_errors;
    }

    // Authorize product.
    return $this->authorize_product( $feed, $submission_data, $form, $entry );

}//end of member function

/**
 * Create the Amazon Payfort sale authorization and return any authorization errors which occur.
 *
 * @since  1.0
 * @since  2.0 Use API version 6.
 * @access public
 *
 * @param array $feed            The Feed object currently being processed.
 * @param array $submission_data The customer and transaction data.
 * @param array $form            The Form object currently being processed.
 * @param array $entry           The Entry object currently being processed.
 *
 * @used-by GF_AmazonPayfort::authorize()
 * @uses    GFAddOn::get_field_map_fields()
 * @uses    GFAddOn::get_field_value()
 * @uses    GFAddOn::log_debug()
 * @uses    GFAddOn::log_error()
 * @uses    GF_AmazonPayfort::prepare_sale()
 * @uses    GF_AmazonPayfort::validate_customer_info()
 * @uses    GF_AmazonPayfort_API::create_sale()
 * @uses    GFPaymentAddOn::authorization_error()
 *
 * @return array
 */

public function authorize_product( $feed, $submission_data, $form, $entry ) {

    // Create order object.
    $order_object = $this->create_order_object( $submission_data, $form, $entry, $feed );
    $this->log_debug( __METHOD__ . '(): Order to be created; ' . print_r( $order_object, true ) );

    // Create & validate order.
    $order                   = $this->api->create_order( $order_object );
    $order_validation_errors = $this->validate_order_details( $order );

    if ( is_array( $order_validation_errors ) ) {
        return $order_validation_errors;
    }

    $this->log_debug( __METHOD__ . '(): Order created Successfully;  ' . print_r( $order, true ) );

    // Prepare authorization response.
    return array(

        'is_authorized'   => true,
        'transaction_id'  => $order['RefNo'],
        'payment_details' => $order['PaymentDetails'],

    );


}//end of member function


/**
 * Capture the Amazon Payfort charge which was authorized during validation.
 *
 * @since  1.0
 * @since  2.0 Use API version 6.
 * @access public
 *
 * @param array $auth            Contains the result of the authorize() function.
 * @param array $feed            The Feed object currently being processed.
 * @param array $submission_data The customer and transaction data.
 * @param array $form            The Form object currently being processed.
 * @param array $entry           The Entry object currently being processed.
 *
 * @uses   GF_AmazonPayfort::initialize_api()
 * @uses   GF_AmazonPayfort_API::detail_sale()
 * @uses   GFPaymentAddOn::authorization_error()
 *
 * @return array
 */

public function capture( $auth, $feed, $submission_data, $form, $entry ) {

    // Initialize API.
    if ( ! is_a( $this->initialize_api(), 'GF_AmazonPayfort_API' ) ) {
        return $this->authorization_error( esc_html__( 'Unable to initialize API.', 'amazon-payfort-gf' ) );
    }

    // Validate order was successfully authorized.
    if ( ! $auth['is_authorized'] || empty( $auth['transaction_id'] ) ) {
        return array(
            'is_success'    => false,
            'error_message' => $auth['error_message'],
        );
    }

    // Get order details.
    $order_details = $this->api->get_order( $auth['transaction_id'] );

    if ( is_wp_error( $order_details ) ) {

        $this->log_error( __METHOD__ . '(): Could not retrieve order; ' . $order_details->get_error_message() . ' (' . $order_details->get_error_code() . ')' );

        return array(

            'is_success'    => false,
            'error_message' => $order_details->get_error_message(),
            'orderDetails'  => array(),

        );

    }

    // Store order details for later use.
    $this->log_debug( 'Retrieved order: ' . print_r( $order_details, true ) );

    gform_update_meta( $entry['id'], 'amazonpayfort_payment_details', $auth['payment_details'] );
    gform_update_meta( $entry['id'], 'order_details', $order_details );
    gform_update_meta( $entry['id'], 'order_type', 'product' );

    // If order is completed already, return payment, otherwise return empty array to complete authorization.
    if ( $order_details['Status'] === 'AUTHRECEIVED' || $order_details['Status'] === 'PENDING' ) {

        return array();

    } elseif ( $order_details['Status'] === 'COMPLETE' ) {

        return array(

            'is_success'     => true,
            'transaction_id' => $auth['transaction_id'],
            'amount'         => $order_details['NetPrice'],
            'payment_method' => $order_details['PaymentDetails']['Type'],
            'orderDetails'   => $order_details,

        );

    }//end of If Else block

    // If status is not pending or completed, then order is cancelled.
    return array(

        'is_success'    => false,
        'error_message' => __( 'Order was cancelled', 'amazon-payfort-gf' ),

    );

}//end of member function

/**
 * Complete authorization (mark entry as pending and create note) for the pending orders.
 *
 * @since 2.0
 *
 * @param array $entry  Entry data.
 * @param array $action Authorization data.
 *
 * @return bool
 */

public function complete_authorization( &$entry, $action ) {

    $order_details = gform_get_meta( $entry['id'], 'order_details' );

    if ( empty( $order_details ) ) {
        return false;
    }

    $this->update_entry_credit_card_details( $entry, $order_details );
    $this->set_pending_payment_status( $entry, $order_details );
    $this->maybe_redirect_to_3dsecure( $entry );

    $action['amount']         = $order_details['NetPrice'];
    $action['transaction_id'] = $order_details['RefNo'];

    return parent::complete_authorization( $entry, $action );


}//end of member function


/**
 * Subscribe the user to a Amazon Payfort recurring sale.
 *
 * @since  1.0
 * @since  2.0 Use API version 6.
 *
 * @param array $feed            The Feed object currently being processed.
 * @param array $submission_data The customer and transaction data.
 * @param array $form            The Form object currently being processed.
 * @param array $entry           The Entry object currently being processed.
 *
 * @return array
 */


public function subscribe( $feed, $submission_data, $form, $entry ) {

    // Initialize API.
    if ( ! is_a( $this->initialize_api(), 'GF_AmazonPayfort_API' ) ) {
        return $this->authorization_error( esc_html__( 'Unable to initialize API.', 'amazon-payfort-gf' ) );
    }

    // If there were validation errors, return them.
    $validation_errors = $this->validate_submission( $submission_data, $form, $entry, $feed );

    if ( is_array( $validation_errors ) ) {
        return $validation_errors;
    }

    // Create order object.
    $order_object = $this->create_order_object( $submission_data, $form, $entry, $feed );
    $this->log_debug( __METHOD__ . '(): Order to be created; ' . print_r( $order_object, true ) );

    // Create & validate order.
    $order                   = $this->api->create_order( $order_object );
    $order_validation_errors = $this->validate_order_details( $order );
    if ( is_array( $order_validation_errors ) ) {
        return $order_validation_errors;
    }

    $this->log_debug( __METHOD__ . '(): Subscription Order created; ' . print_r( $order, true ) );

    // Prepare authorization response.
    return array(

        'is_success'      => true,
        'subscription_id' => $order['RefNo'],
        'amount'          => $submission_data['payment_amount'],
        'payment_method'  => $order['PaymentDetails']['Type'],
        'payment_details' => $order['PaymentDetails'],
        'order_details'   => $order,

    );


}//end of member function


/**
 * Updates entry values for Amazon Payfort field with CC details returned from API.
 *
 * @since 2.0
 *
 * @param array $entry          Current entry object being processed.
 * @param array $order_details  Order details returned from Amazon Payfort API.
 *
 * @return void
 */


private function update_entry_credit_card_details( &$entry, $order_details ) {

    $form  = GFAPI::get_form( $entry['form_id'] );

    $field = $this->get_amazonpayfort_card_field( $form );
    // Update entry with credit card data.
    $entry[ $field['id'] . '.1' ] = empty( $order_details['PaymentDetails']['PaymentMethod']['LastDigits'] ) ? '' : 'XXXX XXXXX XXXXX ' . $order_details['PaymentDetails']['PaymentMethod']['LastDigits'];
    $entry[ $field['id'] . '.4' ] = empty( $order_details['PaymentDetails']['PaymentMethod']['CardType'] ) ? '' : $order_details['PaymentDetails']['PaymentMethod']['CardType'];

    GFAPI::update_entry( $entry );


}//end of member function


/**
 * Check if the returned order array contains a request to start the 3DSecure flow, redirect the user to the url if so.
 *
 * @since 2.0
 *
 * @param array $entry Current entry being processed.
 */

private function maybe_redirect_to_3dsecure( $entry ) {

    $payment_details = gform_get_meta( $entry['id'], 'amazonpayfort_payment_details' );

    $this->log_debug( '--Returned Payment Details---' );
    $this->log_debug( print_r( $payment_details, true ) );

    if ( ! is_array( $payment_details ) || empty( $payment_details['PaymentMethod']['Authorize3DS']['Href'] ) ) {
        return;
    }

    gform_update_meta( $entry['id'], '3dsecure_success_nonce', wp_hash( $this->get_success_nonce() ) );

    $redirect_url = add_query_arg(
        rgar( $payment_details['PaymentMethod']['Authorize3DS'], 'Params', array() ),
        $payment_details['PaymentMethod']['Authorize3DS']['Href']
    );

    $this->log_debug( '3DS Redirect URL: ' . $redirect_url );

    header( 'location: ' . $redirect_url );
    exit();


}//end of member function


/**
 * Sets the entry payment status as pending.
 *
 * @since 2.0
 *
 * @param array $entry         Current entry object being processed.
 * @param array $order_detail  Order details array returned from Amazon Payfort API.
 */


private function set_pending_payment_status( $entry, $order_detail ) {

    GFAPI::update_entry_property( $entry['id'], 'payment_status', 'Pending' );
    GFAPI::update_entry_property( $entry['id'], 'payment_method', 'Amazon Payfort' );
    GFAPI::update_entry_property( $entry['id'], 'transaction_id', $order_detail['RefNo'] );


}//end of member function


// # FORM SETTINGS -------------------------------------------------------------------------------------------------

/**
 * Add supported notification events.
 *
 * @since 2.0
 *
 * @param array $form The form currently being processed.
 *
 * @return array|false The supported notification events. False if feed cannot be found within $form.
 */

public function supported_notification_events( $form ) {

    // If this form does not have a Amazon Payfort feed, return false.
    if ( ! $this->has_feed( $form['id'] ) ) {
        return false;
    }

    // Return Amazon Payfort notification events.
    return array(

        'complete_payment'          => esc_html__( 'Payment Completed', 'amazon-payfort-gf' ),
        'refund_payment'            => esc_html__( 'Payment Refunded', 'amazon-payfort-gf' ),
        'fail_payment'              => esc_html__( 'Payment Failed', 'amazon-payfort-gf' ),
        'create_subscription'       => esc_html__( 'Subscription Created', 'amazon-payfort-gf' ),
        'add_subscription_payment'  => esc_html__( 'Subscription Payment Added', 'amazon-payfort-gf' ),
        'fail_subscription_payment' => esc_html__( 'Subscription Payment Failed', 'amazon-payfort-gf' ),

    );


}//end of member function


// # HELPER METHODS ------------------------------------------------------------------------------------------------


/**
 * Initializes Amazon Payfort API if credentials are valid.
 *
 * @since  1.0
 * @since  2.0    Validate credentials by trying to generate a session id.
 * @since  2.0    api_mode is always set to production as it does not affect initializing the API.
 * @access public
 *
 * @param string $api_mode Amazon Payfort API mode (production or sandbox).
 *
 * @uses   GFAddOn::get_current_settings()
 * @uses   GFAddOn::get_plugin_settings()
 * @uses   GFAddOn::get_slug()
 * @uses   GFAddOn::is_plugin_settings()
 * @uses   GFAddOn::log_debug()
 * @uses   GFAddOn::log_error()
 * @uses   GF_AmazonPayfort_API::detail_company_info()
 *
 * @return GF_AmazonPayfort_API|false An API wrapper instance or false.
 */


public function initialize_api( $api_mode = '' ) {

    // Get the plugin settings.
    $settings = $this->is_plugin_settings( $this->get_slug() ) ? $this->get_current_settings() : $this->get_plugin_settings();

    // If API is already initialized, return.
    if ( ! is_null( $this->api ) ) {
        return $this->api;
    }

    // Load the API library.
    if ( ! class_exists( 'GF_AmazonPayfort_API' ) ) {
        require_once( 'includes/class-gf-amazonpayfort-api.php' );
    }

    // If API credentials are empty, return.
if (  !rgars($settings, 'merchant_identifier') || !rgars($settings, 'access_code') || !rgars($settings, 'sha_request_phrase') 
        || !rgars($settings, 'sha_response_phrase')  ) {

        return false;

    }//end of If block

    // Log validation step.
    $this->log_debug( __METHOD__ . '(): Validating API Info.' );

    // Initialize a new Amazon Payfort API object.
    //$twocheckout = new GF_AmazonPayfort_API( $api_mode, $settings['merchant_code'], $settings['secret_key'] );

$amazonpayfort = new GF_AmazonPayfort_API( $api_mode, $settings[‘merchant_identifier’], $settings[‘access_code’], $settings[‘sha_request_phrase’], $settings[‘sha_response_phrase’] );

    if ( ! empty( $amazonpayfort->generate_session_id() ) ) {

        // Log that authentication test passed.
        $this->log_debug( __METHOD__ . '(): API credentials are valid.' );

        // Assign API instance to class.
        $this->api = $amazonpayfort;

        // Remove upgrade notice now that the settings have been set.
        if ( get_option( 'gf_amazonpayfort_upgrade_notice' ) ) {
            update_option( 'gf_amazonpayfort_upgrade_notice', false );
        }

        // Update the reauthentication version.
        $settings['reauth_version'] = self::LAST_REAUTHENTICATION_VERSION;
        $this->update_plugin_settings( $settings );

        return $this->api;

    }//end of If block

    // Log that authentication test failed.
    $this->log_error( __METHOD__ . '(): API credentials are invalid' );

    return false;


}//end of member function


/**
 * Response from 2co.js is posted to the server as 'amazonpayfort_response'.
 *
 * @since   1.0
 * @access  public
 *
 * @used-by GF_AmazonPayfort::add_amazonpayfort_inputs()
 * @uses    GFAddOn::maybe_decode_json()
 *
 * @return array|null
 */


public function get_amazonpayfort_js_response() {

    // Get response.
    $response = rgpost( 'amazonpayfort_response' );

    return $this->maybe_decode_json( $response );


}//end of member function


/**
 * Validate customer information.
 *
 * @since  1.0
 * @access public
 *
 * @param array $sale      The Sale object currently being processed.
 * @param array $form      The Form object currently being processed.
 * @param array $field_map Billing Information field map.
 *
 * @uses   GF_AmazonPayfort::validate_billing_address()
 * @uses   GFFormsModel::get_field()
 * @uses   GFPaymentAddOn::authorization_error()
 *
 * @return array|bool
 */


public function validate_customer_info( $sale = array(), $form = array(), $field_map = array() ) {

    // Validate name.
    if ( ! rgars( $sale, 'billingAddr/name' ) ) {
        return $this->authorization_error( esc_html__( "You must provide the cardholder's name.", 'amazon-payfort-gf' ) );
    }

    // Validate email address.
    if ( ! rgars( $sale, 'billingAddr/email' ) ) {
        return $this->authorization_error( esc_html__( 'You must provide your email address.', 'amazon-payfort-gf' ) );
    }

    // Validate phone number.
    if ( ! rgars( $sale, 'billingAddr/phoneNumber' ) ) {
        return $this->authorization_error( esc_html__( 'You must provide your phone number.', 'amazon-payfort-gf' ) );
    }

    // Validate billing address.
    if ( ! $this->validate_billing_address( $sale['billingAddr'], GFFormsModel::get_field( $form, $field_map['country'] ) ) ) {
        return $this->authorization_error( esc_html__( 'You must provide a valid billing address.', 'amazon-payfort-gf' ) );
    }


    return true;


}//end of member function


/**
 * Checks if current form has a amazonpayfort field.
 *
 * @since 2.0
 *
 * @param array $form The form currently being processed.
 *
 * @return bool
 */


public function has_amazonpayfort_card_field( $form = null ) {

    return false !== $this->get_amazonpayfort_card_field( $form );

}//end of member function 


/**
 * Retrieves amazonpayfort field from current form.
 *
 * @since 2.0
 *
 * @param array $form The form currently being processed.
 *
 * @return bool|GF_Field_AmazonPayfort_CreditCard he Amazon Payfort field object, if found. Otherwise, false.
 */


public function get_amazonpayfort_card_field( $form = null ) {

    if ( is_null( $form ) ) {
        $form = $this->get_current_form();

    }

    $fields = GFAPI::get_fields_by_type( $form, array( 'amazonpayfort_creditcard' ) );

    return empty( $fields ) ? false : $fields[0];


}//end of member function


/**
 * Validates submitted data contains required values to create a Amazon Payfort sale.
 *
 * @since 2.0
 *
 * @param array $submission_data      The submitted data.
 * @param array $form                 The Form object currently being processed.
 * @param array $entry                The Entry object currently being processed.
 * @param array $feed                 The Feed object currently being processed.
 *
 * @return bool|array Authorization error array or true if data is valid.
 */


public function validate_submission( $submission_data, $form, $entry, $feed ) {
    // Validate name.
    $ccField = $this->get_amazonpayfort_card_field( $form );
    $name    = empty( $entry[ $ccField->id . '.5' ] ) ? array() : explode( ' ', $entry[ $ccField->id . '.5' ] );

    if ( count( $name ) < 2 ) {
        return $this->authorization_error( esc_html__( "You must provide the cardholder's full name.", 'amazon-payfort-gf' ) );
    }

    // Get field mapping information and validate submission data.
    $field_map = $this->get_field_map_fields( $feed, 'billingInformation' );

    // Validate Address.
    if ( empty( $this->get_field_value( $form, $entry, $field_map['address'] ) )
        || empty( $this->get_field_value( $form, $entry, $field_map['city'] ) )
        || empty( $this->get_field_value( $form, $entry, $field_map['state'] ) )
        || empty( $this->get_field_value( $form, $entry, $field_map['zip'] ) )
        || empty( $this->get_field_value( $form, $entry, $field_map['country'] ) )
    ) {

        return $this->authorization_error( esc_html__( 'You must provide a valid billing address.', 'amazon-payfort-gf' ) );

    }

    // Validate email address.
    if ( empty( $this->get_field_value( $form, $entry, $field_map['email'] ) ) ) {
        return $this->authorization_error( esc_html__( 'You must provide your email address.', 'amazon-payfort-gf' ) );
    }

    // Validate phone number.
    if ( empty( $this->get_field_value( $form, $entry, $field_map['phone'] ) ) ) {
        return $this->authorization_error( esc_html__( 'You must provide your phone number.', 'amazon-payfort-gf' ) );
    }

    return true;


}//end of member function


/**
 * Creates a Amazon Payfort Order object.
 *
 * @since 2.0
 *
 * @param array $submission_data   The customer and transaction data.
 *                                 or recurring billing information if this is a subscription order.
 * @param array $form              The Form object currently being processed.
 * @param array $entry             The Entry object currently being processed.
 * @param array $feed              The Feed object currently being processed.
 *
 * @return Object Created order object.
 */

public function create_order_object( $submission_data, $form, $entry, $feed ) {

    // Create order object.
    $order            = new stdClass();
    $order->Currency  = GFCommon::get_currency();
    $order->LocalTime = gmdate( 'Y-m-d H:i:s' );
    $order->Items     = array();

    // Add line items.
    if ( $feed['meta']['transactionType'] != 'subscription' ) {
        $order = $this->prepare_single_payment( $order, $submission_data, $feed );
    } else {
        $order = $this->prepare_subscription( $order, $submission_data, $feed );
    }

    // Add Name and billing information.
    $order = $this->prepare_billing( $order, $submission_data, $entry, $feed, $form );

    // Add payment details.
    $order = $this->prepare_payment_details( $order, $feed );

    return $order;


}//end of member function


/**
 * Validates order object was created successfully.
 *
 * @since 2.0
 *
 * @param object|WP_Error $order Order object returned from API or WP_Error.
 *
 * @return array|bool Authorization error array or true.
 */

public function validate_order_details( $order ) {

    if ( is_wp_error( $order ) ) {

$this->log_error( __METHOD__ . '(): Could not create order; ' . $order->get_error_message() . ' (' . $order->get_error_code() . ')' );

        return $this->authorization_error( $order->get_error_message() );

    } elseif ( ! empty( $order['Errors'] ) && is_array( $order['Errors'] ) ) {

        $error = array_pop( $order['Errors'] );
        $this->log_error( __METHOD__ . '(): Order created with an error; ' . $error );

        return $this->authorization_error( $error );

    }

    return true;

}//end of member function


/**
 * Adds line items and discounts to single payment order object.
 *
 * @since 2.0
 *
 * @param object $order           Amazon Payfort order object.
 * @param array  $submission_data Submitted form data.
 * @param array  $feed            Current feed object being processed.
 *
 * @return object Amazon Payfort Order object.
 */

private function prepare_single_payment( $order, $submission_data, $feed ) {

    // Add line items.
    foreach ( $submission_data['line_items'] as $line_item ) {

        $item                = new stdClass();

        $item->Code          = null;
        $item->Quantity      = $line_item['quantity'];

        $item->PurchaseType  = 'PRODUCT';
        $item->Tangible      = false;
        $item->IsDynamic     = true;
        $item->Price         = new stdClass();
        $item->Price->Amount = $line_item['unit_price'];

        $item->Price->Type   = 'CUSTOM';
        $item->Name          = $line_item['name'];
        $item->Description   = $line_item['description'];
        $order->Items[]      = $item;

    }//end of foreach loop

    // Add discounts.
    if ( is_array( $submission_data['discounts'] ) ) {

        foreach ( $submission_data['discounts'] as $discount ) {

            $discount_item                = new stdClass();
            $discount_item->Name          = $discount['name'];
            $discount_item->PurchaseType  = 'COUPON';
            $discount_item->IsDynamic     = true;
            $discount_item->Quantity      = (int) $discount['quantity'];
            $discount_item->Price         = new stdClass();
            $discount_item->Price->Amount = (float) ( $discount['unit_price'] * -1 );
            $order->Items[]               = $discount_item;

        }//end of foreach loop

    }//end of If block

    return $order;

}//end of member function


/**
 * Adds billing and delivery information to order object.
 *
 * @since 2.0
 *
 * @param object $order           Amazon Payfort order object.
 * @param array  $submission_data Submitted form data.
 * @param array  $entry           The Entry object currently being processed.
 * @param array  $feed            Current feed object being processed.
 * @param array  $form            Current form object being processed.
 *
 * @return object Amazon Payfort Order object.
 */

private function prepare_billing( $order, $submission_data, $entry, $feed, $form ) {

    // Get field mapping information and validate submission data.
    $field_map = $this->get_field_map_fields( $feed, 'billingInformation' );
    $ccField   = $this->get_amazonpayfort_card_field( $form );
    $name      = explode( ' ', $entry[ $ccField->id . '.5' ] );

    // Add billing information.
    $order->BillingDetails              = new stdClass();
    $order->BillingDetails->Address1    = $this->get_field_value( $form, $entry, $field_map['address'] );
    $order->BillingDetails->Address2    = $this->get_field_value( $form, $entry, $field_map['address2'] );
    $order->BillingDetails->City        = $this->get_field_value( $form, $entry, $field_map['city'] );
    $order->BillingDetails->State       = $this->get_field_value( $form, $entry, $field_map['state'] );
    $order->BillingDetails->CountryCode = GFCommon::get_country_code( $this->get_field_value( $form, $entry, $field_map['country'] ) );
    $order->BillingDetails->Phone       = $this->get_field_value( $form, $entry, $field_map['phone'] );
    $order->BillingDetails->Email       = $this->get_field_value( $form, $entry, $field_map['email'] );
    $order->BillingDetails->FirstName   = $name[0];
    $order->BillingDetails->LastName    = $name[1];
    $order->BillingDetails->Zip         = $this->get_field_value( $form, $entry, $field_map['zip'] );

    // Add delivery information ( same as billing ).
    $order->DeliveryDetails = $order->BillingDetails;

    return $order;
}

/**
 * Adds payment information to order object.
 *
 * @since 2.0
 *
 * @param object $order Amazon Payfort order object.
 * @param array  $feed  Current feed object being processed.
 *
 * @return object Amazon Payfort Order object.
 */
private function prepare_payment_details( $order, $feed ) {

    $settings = $this->get_plugin_settings();
    $api_mode = $settings['apiMode'];
    $form     = $this->get_current_form();

    // Add payment details token.
    $order->PaymentDetails                                    = new stdClass();
    $order->PaymentDetails->Type                              = $api_mode === 'sandbox' ? 'TEST' : 'EES_TOKEN_PAYMENT';
    $order->PaymentDetails->Currency                          = GFCommon::get_currency();
    $order->PaymentDetails->PaymentMethod                     = new stdClass();
    $order->PaymentDetails->PaymentMethod->EesToken           = $this->get_amazonpayfort_js_response();
    $order->PaymentDetails->PaymentMethod->Vendor3DSReturnURL = $this->get_success_url( rgar( $form, 'id' ), rgar( $feed, 'id' ) );
    $order->PaymentDetails->PaymentMethod->Vendor3DSCancelURL = $this->get_cancel_url( rgar( $form, 'id' ), rgar( $feed, 'id' ) );

    if ( $feed['meta']['transactionType'] === 'subscription' ) {
        $order->PaymentDetails->PaymentMethod->RecurringEnabled = true;
    }

    return $order;

}//end of member function


/**
 * Define the markup to be displayed for the IPN section description.
 *
 * @since 2.0
 *
 * @return string HTML formatted IPN description.
 */

public function get_ipn_section_description() {

    ob_start();

    ?>
    <p> 
        <a href="javascript:void(0);" onclick="tb_show('IPN Instructions', '#TB_inline?width=500&inlineId=ipn-instructions', '');" onkeypress="tb_show('IPN Instructions', '#TB_inline?width=500&inlineId=ipn-instructions', '');">
<?php esc_html_e( 'View Instructions', 'amazon-payfort-gf' ); ?>
        </a>
    </p>

    <div id="ipn-instructions" style="display:none;">

        <ol class="ipn-instructions">

            <li>
<?php esc_html_e( 'Click the following link and log in to access your Amazon Payfort IPN management page:', 'amazon-payfort-gf' ); ?>
                <br/>

        <a href="https://secure.avangate.com/cpanel/index.php" target="_blank">https://secure.avangate.com/cpanel/index.php</a>
            </li>
            <li><?php esc_html_e( 'Navigate to Dashboard → Integrations → Webhooks and API.', 'amazon-payfort-gf' ); ?></li>
            <li><?php esc_html_e( 'Click on the IPN Settings tab.', 'amazon-payfort-gf' ); ?></li>
            <li>
                <?php esc_html_e( 'Click on the Add IPN URL button and add the following IPN URL.', 'amazon-payfort-gf' ); ?>
                <?php echo esc_url( home_url( '/', 'https' ) . '?callback=' . $this->_slug ); ?>
            </li>
<li><?php esc_html_e( 'Click Add IPN button.', 'amazon-payfort-gf' ); ?></li>
<li><?php esc_html_e( 'Scroll down to the triggers section and make sure Completed orders, Cancelled orders and Reversed and refund orders are checked.', 'amazon-payfort-gf' ); ?></li>
            <li><?php esc_html_e( 'Scroll down to Response tags and click select all.', 'amazon-payfort-gf' ); ?></li>
            <li><?php esc_html_e( 'Update the settings.', 'amazon-payfort-gf' ); ?></li>

        </ol>

    </div>

    <?php

    return ob_get_clean();

}//end of member function


/**
 * Get cancel URL for Amazon Payfort 3DSecure flow.
 *
 * @since 2.0
 *
 * @param int $form_id Form ID.
 *
 * @return string
 */

private function get_cancel_url( $form_id ) {

    /**
     * Filters Amazon Payfort cancel URL, which is the URL that users will be sent to after cancelling/failing 3DSecure confirmation.
     *
     * @since 2.0
     *
     * @param string $url     The URL to be filtered.
     * @param int    $form_id The ID of the form being submitted.
     */

    return apply_filters( 'gform_amazonpayfort_cancel_url', $this->get_page_url(), $form_id );

}//end of function


/**
 * Build the URL of the current page.
 *
 * @since 2.0
 *
 * @return string
 */


private function get_page_url() {

    $page_url = GFCommon::is_ssl() ? 'https://' : 'http://';

    /**
     * Set the Amazon Payfort URL port if it's not 80.
     *
     * @since 2.0
     *
     * @param string Default server port.
     */

    $server_port = apply_filters( 'gform_amazonpayfort_url_port', $_SERVER['SERVER_PORT'] );

    if ( $server_port != '80' && $server_port != '443' ) {
        $page_url .= $_SERVER['SERVER_NAME'] . ':' . $server_port . $_SERVER['REQUEST_URI'];
    } else {
        $page_url .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
    }

    return $page_url;


}//end of function

/**
 * Generates a cryptographic token that is used later to verify the redirected user after successfully passing 3DS challenge.
 *
 * @since 2.0
 *
 * @return string
 */

private function get_success_nonce() {

    if ( ! $this->success_nonce ) {
        $this->success_nonce = wp_generate_password( 12, false );
    }

    return $this->success_nonce;

}//end of member function

/*
public function upgrade( $previous_version ) {
if ( version_compare( $previous_version, ‘2.0-beta-1’, ‘<‘ ) && ! $this->initialize_api() ) {
update_option( ‘gf_amazonpayfort_upgrade_notice’, true );
}

}//end of member function

*/

// # IPN Notifications ——————————————————————————————–

/**
 * If the Amazon Payfort IPN belongs to a valid entry process the raw response into a standard Gravity Forms $action.
 *
 * @since 2.0
 *
 * @return array|bool Return a valid GF $action or bool if the webhook can't be processed or no action needs to be done.
 */

public function callback() {

    if ( ! $this->is_gravityforms_supported() ) {
        return false;
    }

    // Log all request data.
    $this->log_debug( '--- Start logging IPN request data --- ' );
    $this->log_debug( print_r( $_REQUEST, true ) );

    // Maybe there is a difference.
    $this->log_debug( '--- Start logging input stream data --- ' );
    $request_body = trim( file_get_contents( 'php://input' ) );
    $this->log_debug( print_r( $request_body, true ) );

    // Hash will be unset later so cache it here.
    $hash = sanitize_text_field( $_POST['HASH'] );

    $order_reference_number = sanitize_text_field( $_POST['REFNO'] );
    $ipn_date               = sanitize_text_field( $_POST['IPN_DATE'] );
    $order_total            = sanitize_text_field( $_POST['IPN_TOTALGENERAL'] );
    $order_status           = sanitize_text_field( $_POST['ORDERSTATUS'] );

    if ( ! $this->verify_ipn() ) {
        $this->log_error( __METHOD__ . '(): Invalid IPN HASH, so it was not created by Gravity Forms. Aborting.' );
        return false;
    }
    $entry_id = $this->get_entry_by_transaction_id( $order_reference_number );

    if ( ! $entry_id ) {
        $this->log_error( __METHOD__ . '(): Could not find entry. Aborting.' );
        return false;
    }

    $this->output_ipn_read_receipt( $_POST );
    $entry  = GFAPI::get_entry( $entry_id );

    $action = array(

        'id'             => $hash,
        'entry_id'       => $entry_id,
        'transaction_id' => $order_reference_number,
        'amount'         => $order_total,

    );
    // Prevent already processed entries from being processed again.
    if ( gform_get_meta( $entry_id, 'IPN_' . $order_status . '_PROCESSED' ) === '1' ) {
        $this->log_debug( 'IPN ALREADY PROCESSED, ABORTING' );
        return false;
    } else {

        $this->log_debug( __METHOD__ . '(): IPN request received. Starting to process => ' . print_r( $_POST, true ) );
    }

    // Mark entry as processed for this particular event.
    gform_update_meta( $entry_id, 'IPN_' . $order_status . '_PROCESSED', '1' );

    // If this is a subscription event.
    $order_type = gform_get_meta( $entry_id, 'order_type' );

    if ( $order_type === 'subscription' ) {

        $action['subscription_id'] = $order_reference_number;
        return $this->process_subscription_ipn( $entry, $action );

    }

    return $this->process_product_ipn( $entry, $action );

}//end of member function


/**
 * Verifies that the IPN notification is a valid IPN request generated from Amazon Payfort.
 *
 * @since 2.0
 *
 * @return bool
 */

private function verify_ipn() {

    if ( ! isset( $_POST['HASH'] ) ) {

        $this->log_debug( 'no hash found in post' );
        return false;

    }

    $hash = sanitize_text_field( $_POST['HASH'] );
    unset( $_POST['HASH'] );
    $string = '';

    foreach ( $_POST as $key => $value ) {
        $string .= $this->expand_array( (array) $value );
    }

    $this->log_debug( 'Hash string:' . $string );
    $signature = hash_hmac( 'md5', $string, $this->get_plugin_setting( 'secret_key' ) );
    $this->log_debug( 'Signature:' . $signature );

    return $signature === $hash;


}//end of member function


/**
 * Converts IPN request array into a string formatted to generate the IPN signature.
 *
 * @since 2.0
 *
 *  @param array $array An array to be converted into a string.
 *
 * @return string
 */

private function expand_array( $array ) {

    $retval = '';

    foreach ( $array as $i => $value ) {

        if ( is_array( $value ) ) {
            $retval .= $this->expand_array( $value );
        } else {
            $size    = strlen( $value );
            $retval .= $size . $value;
        }

    }//end of foreach loop

    return $retval;

}//end member function


/**
 * Output IPN read receipt string.
 *
 * @since 2.0
 *
 * @param array $ipn_request The IPN request body.
 */

private function output_ipn_read_receipt( $ipn_request ) {

    $base_string = $this->expand_array(
        array(
            $ipn_request['IPN_PID'][0],
            $ipn_request['IPN_PNAME'][0],
            $ipn_request['IPN_DATE'],
            $ipn_request['IPN_DATE'],
        )
    );

    $this->log_debug( 'Read receipt base string: ' . $base_string );
    $hash = hash_hmac( 'md5', $base_string, $this->get_plugin_setting( 'secret_key' ) );
    echo '<EPAYMENT>' . $ipn_request['IPN_DATE'] . '|' . $hash . '</EPAYMENT>' . PHP_EOL;

}//end of member function

// # Deprecated METHODS ——————————————————————————————–

public function populate_credit_card_last_four( $form ) {

    if ( ! $this->is_payment_gateway ) {
        return;
    }
    // If response was an error, exit.
    if ( $this->get_amazonpayfort_js_error() ) {
        return;
    }

    // Get the credit card field.
    $cc_field = $this->get_credit_card_field( $form );

    // Get the Amazon Payfort response.
    $response = $this->get_amazonpayfort_js_response();

    $_POST[ 'input_' . $cc_field->id . '_1' ] = 'XXXXXXXXXXXX' . substr( $response['response']['paymentMethod']['cardNum'], -4 );

}//end of member function


/**
 * Validate billing address.
 *
 * @since  1.0
 * @deprecated 2.0
 *
 * @access public
 *
 * @param array  $address Billing address.
 * @param object $field   Address field.
 *
 * @uses   GF_Field_Address::get_country_code()
 *
 * @return bool
 */


public function validate_billing_address( $address, $field ) {

    // If address line 1, city or country are missing, return false.
    if ( ! rgar( $address, 'addrLine1' ) || ! rgar( $address, 'city' ) || ! rgar( $address, 'country' ) ) {
        return false;
    }
    // Prepare list of countries requiring state and zip code.
    //$state_zip_required = array( 'AR', 'AU', 'BG', 'CA', 'CN', 'CY', 'EG', 'ES', 'FR', 'GB', 'ID', 'IN', 'IT', 'JP', 'MX', 'MY', 'NL', 'PA', 'PH', 'PL', 'RO', 'RU', 'RS', 'SE', 'SG', 'TH', 'TR', 'US', 'ZA' );
    $state_zip_required = array( 'JOD', 'KWD', 'OMR', 'TND', 'BHD', 'LYD', 'IQD', 'SAR' );
    // Get country code.
    $country_code = $field->get_country_code( $address['country'] );
    // If state or zip code is missing, return false.
    if ( in_array( $country_code, $state_zip_required ) && ( ! rgar( $address, 'state' ) || ! rgar( $address, 'zipCode' ) ) ) {
        return false;
    }
    return true;

}//end of member function

}//end of GF_AmazonPayfort class