<?php
/**
 * The MailChimp class
 *
 * Used to define MailChimp related functions
 *
 *
 * @since      2.0
 * @package    CartBounty Pro - Save and recover abandoned carts for WooCommerce
 * @subpackage CartBounty Pro - Save and recover abandoned carts for WooCommerce/includes
 * @author     Streamline.lv
 */
class CartBounty_Pro_Mailchimp{
	
	/**
	 * The admin handler that manages the plugin's settings, options, and backend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Admin    $admin    Provides methods to control and extend the plugin's admin area.
	 */
	protected $admin = null;

	/**
	 * The public handler that manages the plugin's settings, options, and frontend functionality.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Public    $public    Provides methods to control and extend the plugin's public area.
	 */
	protected $public = null;

	/**
	 * The coupons manager responsible for handling coupons.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_Coupons    $coupons    Provides methods to create, apply, and manage discount coupons.
	 */
	protected $coupons = null;

	/**
	 * The API connector responsible for handling communication with external services.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      CartBounty_Pro_API_Connector    $api    Provides methods to send and receive data via API requests.
	 */
	protected $api = null;

	/**
	 * The API access credentials used to authenticate requests with the external service.
	 *
	 * @since    10.9
	 * @access   protected
	 * @var      array    $api_access    Contains authentication data such as API key, token, or other required parameters.
	 */
	protected $api_access = null;

	/**
	 * Get the admin handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Admin
	 */
	protected function admin(){
		
		if( $this->admin === null ){
			$this->admin = new CartBounty_Pro_Admin( CARTBOUNTY_PRO_PLUGIN_NAME_SLUG, CARTBOUNTY_PRO_VERSION_NUMBER );
		}

		return $this->admin;
	}

	/**
	 * Get the public handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Public
	 */
	protected function public(){
		
		if( $this->public === null ){
			$this->public = new CartBounty_Pro_Public( CARTBOUNTY_PRO_PLUGIN_NAME_SLUG, CARTBOUNTY_PRO_VERSION_NUMBER );
		}

		return $this->public;
	}

	/**
	 * Get the coupon handler (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_Coupons
	 */
	protected function coupons(){
		
		if( $this->coupons === null ){
			$this->coupons = new CartBounty_Pro_Coupons();
		}

		return $this->coupons;
	}

	/**
	 * Get the API connector (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return CartBounty_Pro_API_Connector
	 */
	protected function api(){
		
		if( $this->api === null ){
			$this->api = new CartBounty_Pro_API_Connector();
		}

		return $this->api;
	}

	/**
	 * Get the API access creentials (lazy-loaded).
	 * Creates the connector on first use and then reuses the same instance.
	 *
	 * @since 10.9
	 * @access protected
	 * @return array
	 */
	protected function api_access(){
		
		if( $this->api_access === null ){
			$this->api_access = $this->get_api_access();
		}

		return $this->api_access;
	}

	/**
	* Retrieve MailChimp recovery settings
	*
	* @since    10.1
	* @return   array
	* @param    string     $value                Value to return
	*/
	public function get_settings( $value = false ){
		$saved_options = get_option( 'cartbounty_pro_mailchimp_settings' );
		$defaults = array(
			'key' 							=> '',
			'list_id' 						=> '',
			'store_id' 						=> '',
			'merge_fields_created' 			=> false,
			'first_sync_done' 				=> false,
			'product_create_batch_id' 		=> '',
			'product_update_batch_id' 		=> '',
			'promo_rule_create_batch_id' 	=> '',
			'promo_code_create_batch_id' 	=> '',
			'promo_rule_update_batch_id' 	=> '',
			'promo_code_update_batch_id' 	=> '',
			'merge_field_create_batch_id' 	=> '',
			'merge_field_update_batch_id' 	=> '',
			'merge_field_delete_batch_id' 	=> '',
			'cart_batch_id' 				=> '',
			'cart_delete_batch_id' 			=> '',
		);

		if( is_array( $saved_options ) ){
			$settings = array_merge( $defaults, $saved_options ); //Merging default settings with saved options

		}else{
			$settings = $defaults;
		}

		if( $value ){ //If a single value should be returned
			
			if( isset( $settings[$value] ) ){ //Checking if value exists
				$settings = $settings[$value];
			}
		}

		return $settings;
	}

	/**
	* Update MailChimp settings
	*
	* @since    10.1
	* @return   array
	* @param    array     $new_value           	New value
	* @param    array     $old_value            Existing value
	*/
	public function handle_settings_update( $new_value, $old_value ){

		if( isset( $_POST['cartbounty_pro_mailchimp_settings'] ) ){ //If option update coming from saving a form
			$new_value = $_POST['cartbounty_pro_mailchimp_settings'];
		}

		if( is_array( $old_value ) && is_array( $new_value ) ){
			$settings = array_merge( $old_value, $new_value );

		}else{
			$settings = $new_value;
		}

		return $settings;
	}

	/**
	 * Method that runs all of the MailChimp business
	 *
	 * @since    2.0
	 */
	public function run(){
		$this->create_store();
	}

	/**
	 * Method that is used for cron synchronization jobs
	 *
	 * @since    2.0
	 */
	public function auto_sync(){
		$admin = $this->admin();

		if( !$admin->check_license() || !class_exists( 'WooCommerce' ) ) return; //Exit if license key not valid or if WooCommerce is not activated

		if( $this->api_valid() && $this->store_connected() ){ //If API key is valid and store has connected to a list
			$settings = $this->get_settings();

			if( !$settings['first_sync_done'] ){ //If this is first time synchronization is run
				//Not syncing carts for the first time because we must get products to MailChimp first and only then Carts can be synced
				$this->put_products();
				$this->put_coupons();
				$settings['first_sync_done'] = true;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );

			}else{
				$this->put_products();
				$this->sync_carts();
				$this->sync_merge_fields();
				$this->sync_promo_codes();
			}
		}
	}

	/**
	 * Method retrieves saved API credentials
	 *
	 * @since    10.9
	 * @return 	 Array
	 */
	public function get_api_access(){
		$api_access = array(
			'api_key' => '',
			'api_url' => 'https://'. $this->get_datacenter() .'.api.mailchimp.com/3.0/'
		);

		$mailchimp_settings = $this->get_settings();
		
		if( !empty( $mailchimp_settings ) ){
			$api_access['api_key'] = esc_attr( $mailchimp_settings['key'] ); //Retrieve MailChimp API key from database
		}

		return $api_access;
	}

	/**
	 * Method checks if a MailChimp list has been chosen
	 *
	 * @since    2.0
	 */
	public function list_chosen(){
		$chosen = false;
		$list_id = $this->get_settings( 'list_id' ); //Retrieve chosen MailChimp list ID

		if( !empty( $list_id ) ){
			$chosen = true;
		}

		return $chosen;
	}

	/**
	 * Method creates database table to save coupons for synchronizing with MailChimp
	 *
	 * @since    4.0
	 */
	public static function create_coupon_table(){
		global $wpdb;
		$admin = new CartBounty_Pro_Admin( CARTBOUNTY_PRO_PLUGIN_NAME_SLUG, CARTBOUNTY_PRO_VERSION_NUMBER );
		$coupon_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_COUPONS;
		$charset_collate = $wpdb->get_charset_collate();
		$misc_settings = $admin->get_settings( 'misc_settings' );

		$sql = "CREATE TABLE $coupon_table (
			coupon_id INT NOT NULL,
			last_updated DATETIME DEFAULT '0000-00-00 00:00:00',
			last_synced_rule DATETIME DEFAULT '0000-00-00 00:00:00',
			last_synced_code DATETIME DEFAULT '0000-00-00 00:00:00',
			PRIMARY KEY (coupon_id)
		) $charset_collate;";

		require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
		dbDelta( $sql );
		
		//Resets table Auto increment index to 1
		$sql = "ALTER TABLE $coupon_table AUTO_INCREMENT = 1";
		dbDelta( $sql );
		
		$misc_settings['coupon_table_exists'] = true;
		update_option( 'cartbounty_pro_misc_settings', $misc_settings ); //Updating status and telling that table has been created
		return;
	}
	
	/**
	 * Method creates unique Store ID or returns the value if it has been already created
	 *
	 * @since    2.0
	 * @return 	 string
	 */
	private function get_store_id(){
		$store_id = $this->get_settings( 'store_id' );
		return $store_id;
	}

	/**
	 * Method creates unique Store ID or returns the value if it has been already created
	 *
	 * @since    9.7.3
	 * @return 	 string
	 */
	private function create_store_id(){
		return uniqid( 'cartbounty_' ); //Uniqid() function generates unique identifier based on the current time in microseconds
	}

	/**
	 * Method creates unique Store ID or returns the value if it has been already created
	 *
	 * @since    2.0
	 * @return 	 string
	 */
	private function get_list_id(){
		return $this->get_settings( 'list_id' );
	}
	
	/**
	 * Method uses API key and strips the data centre from the end of it
	 *
	 * @since    2.0
	 * @return 	 Boolean
	 */
	private function get_datacenter(){
		$datacenter = 'us2'; //Setting default data center
		$api_key = $this->get_settings( 'key' ); //Retrieve MailChimp API key from database
		
		$parts = str_getcsv( $api_key, '-' ); //Splits the API key into 2 parts and uses the second part as a datacenter ID
		
		if( count( $parts ) == 2 ){
			$datacenter = $parts[1];
		}

		return $datacenter;
	}
	
	/**
	 * Method checks if API key valid
	 * Return True if API valid. Return False if not
	 *
	 * @since    2.0
	 * @return 	 Boolean
	 */
	public function api_valid(){
		$admin = $this->admin();
		$api = $this->api();
		$status = false;
		$api_access = $this->api_access();
		$api_key = $api_access['api_key'];

		if( !empty( $api_key ) ){ //If MailChimp API key is not empty

			$current_fingerprint = $admin->get_key_fingerprint( $api_key );
			$valid_fingerprint = $admin->get_cartbounty_transient( 'mailchimp_valid_fingerprint' ); //Last known valid fingerprint
			
			if( $valid_fingerprint === $current_fingerprint ){ //If API key has not changed and was already validated before
				$status = true;

			}else{

				$invalid_fp = $admin->get_cartbounty_transient( 'mailchimp_invalid_fingerprint' ); //Last known invalid fingerprint

				if( $invalid_fp !== $current_fingerprint ){ //If API key has changed

					$invalid_until = (int)$admin->get_cartbounty_transient( 'mailchimp_api_invalid_until' );

					if( $invalid_until && time() < $invalid_until && $invalid_fp === $current_fingerprint ){ //Exit and return false if credentials have already failed before and they have not changed
						return $status;
					}

					if( (int)$api->credentials_test( 'mailchimp', $api_access, $this ) ){ //In case we get a valid response from MailChimp
						$admin->set_cartbounty_transient( 'mailchimp_valid_fingerprint', $current_fingerprint, 60*60*48 ); //Cache valid API fingerprint for 48 hours
						$admin->delete_cartbounty_transient( 'mailchimp_api_times_failed' );
						$admin->delete_cartbounty_transient( 'mailchimp_api_invalid_until' );
						$admin->delete_cartbounty_transient( 'mailchimp_invalid_fingerprint' );
						$status = true;

					}else{ //If we have connection issues - try and save response to limit how many times we try to connect
						$fail_count = (int)$admin->get_cartbounty_transient( 'mailchimp_api_times_failed' );
						$fail_count++;
						$admin->set_cartbounty_transient( 'mailchimp_api_times_failed', $fail_count, 60*60 ); //Remember failures for 1 hour

						if( $fail_count >= 3 ){
							$admin->set_cartbounty_transient( 'mailchimp_api_invalid_until', time() + 60*15, 60*15 ); //Invalidate requests for 15 minutes
							$admin->set_cartbounty_transient( 'mailchimp_invalid_fingerprint', $current_fingerprint, 60*15 ); //Cache invalid API fingerprint for 15 minutes
							$admin->delete_cartbounty_transient( 'mailchimp_valid_fingerprint' );
						}
					}
				}
			}
		}

		return $status;
	}
	
	/**
	 * Method returns MailChimp API key status
	 *
	 * @since    2.0
	 * @return 	 Array
	 */
	public function api_status(){
		$admin = $this->admin();
		$api_access = $this->api_access();
		$api_key = $api_access['api_key'];
		$response = array();
		$settings = $this->get_settings();
		
		//Checking cached api Key status
		if( !empty( $api_key ) ){
			$api_valid = $this->api_valid();
			$store_connected = $this->store_connected();

			if( $api_valid && !$store_connected ){ //If API key is valid, but we have not selected list to connect with
				$response['status'] = '2';
				$response['result'] = '';
				$response['message'] =  '';

			}elseif( $api_valid && $store_connected ){ //If the API key is cahced and not changed and is valid and the MailChimp API field is not empty
				$response['status'] = '1';
				$response['result'] = '';
				$response['message'] =  esc_html__( 'Connected', 'woo-save-abandoned-carts' );

			}else{
				$response['status'] = '0';
				$response['result'] = esc_html__( 'Error', 'woo-save-abandoned-carts' );
				$response['message'] =  esc_html__( 'Please make sure your API key is correct', 'woo-save-abandoned-carts' );
				$settings['store_id'] = '';
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
			}

		}else{ //In case API key field is empty
			$response['status'] = '0';
			$response['result'] = '';
			$response['message'] = '';
			$settings['store_id'] = '';
			update_option( 'cartbounty_pro_mailchimp_settings', $settings );
			$admin->delete_cartbounty_transient( 'mailchimp_api_times_failed' );
			$admin->delete_cartbounty_transient( 'mailchimp_api_invalid_until' );
			$admin->delete_cartbounty_transient( 'mailchimp_valid_fingerprint' );
			$admin->delete_cartbounty_transient( 'mailchimp_invalid_fingerprint' );
		}

		return $response;
	}

	/**
	 * Method creates a store
	 * First try to check if MailChimp already has a store created before. If nothing found - create a new store
	 *
	 * @since    2.0
	 */
	private function create_store(){

		if( !$this->api_valid() ) return; //Exit if API key is not valid

		if( !$this->store_connected() ){ //If store is not connected
			$admin = $this->admin();
			$api = $this->api();
			$store_found = false;
			$settings = $this->get_settings();
			$settings['first_sync_done'] = false;
			update_option( 'cartbounty_pro_mailchimp_settings', $settings );

			//Try checking all stores on MailChimp to relink it in case it has been already linked
			$query = array(
				'fields'	=> 'stores.id,stores.list_id,stores.domain',
				'count'		=> 1000
			);
			$result = $api->connect( 'mailchimp', $this->api_access(), 'ecommerce/stores', 'GET', $query ); //Retrieving stores
			$response = $result['response'];

			if( $result['status_code'] == 200 ){
				$domain = get_bloginfo( 'url' );

				foreach( $response->stores as $key => $store ){
					
					if( $domain == $store->domain ){ //If domains match - we have found our store
						$settings['store_id'] = $store->id;
						$settings['list_id'] = $store->list_id;
						update_option( 'cartbounty_pro_mailchimp_settings', $settings );
						$store_found = true;
						break;
					}
				}

			}else{
				$this->log_mc_errors( $response, 'Get stores' );
			}

			//If linking not sucessful - create store
			if( !$store_found ){
				$this->add_store();
			}

			if( $admin->table_exists( 'product_table_exists' ) ){
				$this->reset_product_sync(); //Resetting products to be unsynchronized
			}

			if( $admin->table_exists( 'coupon_table_exists' ) ){
				$this->reset_coupon_sync(); //Resetting coupons to be unsynchronized
			}
		}
	}

	/**
	 * Add store to MailChimp
	 *
	 * @since    9.7.3
	 */
	private function add_store(){
		
		if( !$this->list_chosen() || !class_exists( 'WooCommerce' ) ) return;

		$api = $this->api();
		$settings = $this->get_settings();
		$list_id = $settings['list_id'];
		$store_id = $this->create_store_id();
		$store_location = new WC_Countries();

		//Support for WooCommerce versions prior 3.1.1 where method get_base_address didn't exist
		if( method_exists( $store_location, 'get_base_address' ) ){
			$address1 = $store_location->get_base_address();
			$address2 = $store_location->get_base_address_2();

		}else{
			$address1 = '';
			$address2 = '';
		}

		$data = array(
			'id' 					=> $store_id,
			'list_id' 				=> $list_id,
			'name' 					=> get_bloginfo( 'name' ),
			'platform' 				=> 'WooCommerce',
			'domain' 				=> get_bloginfo( 'url' ),
			'email_address' 		=>  get_bloginfo( 'admin_email' ),
			'currency_code' 		=> get_woocommerce_currency(),
			'money_format' 			=> get_woocommerce_currency_symbol(),
			'primary_locale' 		=> get_bloginfo( 'language' ),
			'timezone' 				=> get_option( 'timezone_string' ),
			'address' 				=> array(
				'address1' 			=> $address1,
				'address2' 			=> $address2,
				'city' 				=> $store_location->get_base_city(),
				'province_code' 	=> $store_location->get_base_state(),
				'postal_code' 		=> $store_location->get_base_postcode(),
				'country' 			=> WC()->countries->countries[$store_location->get_base_country()],
				'country_code' 		=> $store_location->get_base_country()
			)
		);
		
		$result = $api->connect( 'mailchimp', $this->api_access(), 'ecommerce/stores', 'POST', $data );
		$response = $result['response'];

		if( $result['status_code'] == 200 ){
			$settings['store_id'] = $store_id;
			update_option( 'cartbounty_pro_mailchimp_settings', $settings );

		}else{
			$this->log_mc_errors( $response, 'Add store' );
		}
	}

	/**
	 * Method checks if a store is connected to a list
	 *
	 * @since    2.0
	 * @return   Boolean
	 */
	public function store_connected(){
		$connected = false;
		$settings = $this->get_settings();
		$store_id = $settings['store_id'];
		$list_id = $settings['list_id'];

		if( $this->api_valid() ){

			if( !empty( $store_id ) && !empty( $list_id ) ){
				$connected = true;
			}
		}

		return $connected;
	}

	/**
	 * Method resets product table last_synced column in order to resync them
	 *
	 * @since    2.0
	 */
	private function reset_product_sync(){
		global $wpdb;
		$admin = $this->admin();
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		
		if( $admin->table_exists( 'product_table_exists' ) ){
			$wpdb->query(
				$wpdb->prepare(
					"UPDATE {$product_table}
					SET last_synced = %s",
					'0000-00-00 00:00:00'
				)
			);
		}
	}

	/**
	 * Method resets coupon table last_synced column in order to resync them
	 *
	 * @since    4.0
	 */
	private function reset_coupon_sync(){
		global $wpdb;
		$admin = $this->admin();
		$coupon_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_COUPONS;

		if( $admin->table_exists( 'coupon_table_exists' ) ){
			$wpdb->query(
				$wpdb->prepare(
					"UPDATE {$coupon_table}
					SET last_synced_rule = %s,
					last_synced_code = %s",
					'0000-00-00 00:00:00',
					'0000-00-00 00:00:00'
				)
			);
		}
	}

	/**
	 * Method that retrieves and displays existing lists from MailChimp
	 * If store has already been linked, lists are not displayed since MailChimp does not allow changing them after creating a store
	 *
	 * @since    2.0
	 */
	public function display_lists(){
		$admin = $this->admin();
		$api = $this->api();
		$list_id = $this->get_settings( 'list_id' );
		$input_disabled = false;

		if( !$list_id ){
			$list_id = 0;
		}

		$data = array(
			'fields'	=> 'lists.id,lists.name',
			'count'		=> 10000
		);
		$result = $api->connect( 'mailchimp', $this->api_access(), 'lists', 'GET', $data );
		$response = $result['response'];

		if( $result['status_code'] == 200 ){
			
			if( !empty( $response->lists ) ){

				if( $this->store_connected() ){
					$input_disabled = true;
				}?>

				<label for="cartbounty_pro_mailchimp_list_id"><?php esc_html_e( 'List to sync with', 'woo-save-abandoned-carts' ); ?></label>
				<select id="cartbounty_pro_mailchimp_list_id" class="cartbounty-pro-select" name="cartbounty_pro_mailchimp_settings[list_id]" <?php echo $admin->disable_field(); ?> <?php if( $input_disabled ) echo "disabled "; ?>autocomplete="off">
					<option value="0" <?php echo selected( $list_id, 0 ) ?>><?php esc_html_e( 'Choose a list', 'woo-save-abandoned-carts' );?></option>
					<?php foreach( $response->lists as $list ): ?>
						<option value="<?php echo esc_attr( $list->id ); ?>"<?php selected( $list_id, $list->id ); ?>><?php echo esc_html( $list->name ) ?></option>
					<?php endforeach; ?>
				</select>

				<?php
			}

		}elseif( !empty( $response ) ){
			echo '<strong>' . esc_html( $response->title ) . ':</strong> ' . esc_html( $response->detail );
		}
	}
	
	/**
	 * Method pushes / updates products to product table once the cart is added to the table
	 * Excluding abandoned carts that do not have an email address, cart contents are empty or the cart has already been synced
	 *
	 * @since    2.0
	 */
	private function put_products(){
		global $wpdb;
		$admin = $this->admin();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		$time = $admin->get_time_intervals();
		$products = array();

		if( !$admin->table_exists( 'product_table_exists' ) ){
			$admin->create_product_table();
		}
		
		//Retrieve from abandoned carts rows that have not been synced to MailChimp and are not older than 30 days
		$rows_to_product_table = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, cart_contents
				FROM $cart_table
				WHERE email != '' AND
				(type = %d OR type = %d) AND
				cart_contents != '' AND
				time != last_synced AND
				time > %s",
				$admin->get_cart_type( 'abandoned' ),
				$admin->get_cart_type( 'recovered_pending' ),
				$time['maximum_sync_period']
			)
		);

		if( $rows_to_product_table ){ //If we have abandoned carts
			foreach( $rows_to_product_table as $row ){
				//Values to pass to MailChimp
				$cart_contents = $admin->get_saved_cart_contents( $row->cart_contents );

				if( $cart_contents ){ //If abandoned cart row has products
					
					foreach( $cart_contents as $contents ){ //Looping through cart contents array to find if it has got products that are not in Products table

						if( isset( $contents['product_id'] ) ){ //After version 2.0
							$product_id = $contents['product_id']; //Product ID

						}else{ //Before version 2.0
							$product_id = $contents[2]; //Product ID
						}
						
						$product = wc_get_product( $product_id ); //Getting product

						if( !$product ){ //Stop in case the product is not found or does not exist
							$admin->log( 'notice', sprintf( 'MailChimp: WooCommerce product does not exist. ID: %d', esc_html( $product_id ) ) );

						}else{
							$product_last_updated_object = $product->get_date_modified(); //Returns WC_DateTime object
							$product_last_updated = $product_last_updated_object->date( 'c' ); //Date when product was last modified

							//Getting current date and time
							$current_date_object = date_create( current_time( 'c', true ) ); //Returns current WC_DateTime object
							$current_date = $current_date_object->format( 'c' );
							
							$current_product = array( 'product_id' => $product_id, 'last_updated' => $product_last_updated );

							//Checking if current product is already in products array, if is, then do not insert it again
							if( !in_array( $current_product, $products ) ){
								$products[] = $current_product; //Adding current product to products array
								
								//Handling product variations
								$variables = new WC_Product_Variable( $product_id );
								
								if( !empty( $variables->get_children() ) ){ //If current product has variations
									$variation_ids = json_encode( $variables->get_children() ); //Return a products child IDs

								}else{
									$variation_ids = NULL;
								}
								
								//Inserting / updating all products in product table
								$wpdb->query(
									$wpdb->prepare(
										"INSERT INTO {$product_table}
										(product_id, variations, first_created, last_updated)
										VALUES (%d, %s, %s, %s)
										ON DUPLICATE KEY
										UPDATE variations = %s,
										last_updated = %s",
										$product_id,
										$variation_ids,
										$current_date,
										$product_last_updated,
										$variation_ids,
										$product_last_updated
									)
								);
							}
						}
					}
				}
			}

			if( !empty( $products ) ){ //If products updated or added
				$this->sync_products();
			}
		}
	}

	/**
	 * Method populates coupon table with coupons
	 *
	 * @since    4.0
	 * @param    integer      $coupon_id			Coupon ID
	 */
	public function put_coupons( $coupon_id = '' ){
		$admin = $this->admin();

		if( !$admin->table_exists( 'coupon_table_exists' ) ){
			$this->create_coupon_table();
		}

		global $wpdb;
		$coupon_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_COUPONS;
		$args = array(
			'posts_per_page'	=> -1,
			'orderby'			=> 'title',
			'order'				=> 'asc',
			'post_type'			=> 'shop_coupon',
			'post_status'		=> array( 'publish', 'future' )
		);

		if( !empty( $coupon_id ) ){ //Retrieve single coupon in case single coupon created or updated
			$args['include'] = $coupon_id;
		}

		$coupons = get_posts( $args );

		if( !empty( $coupons ) ){
			foreach( $coupons as $coupon ){
				if( !$this->coupon_expired( $coupon ) ){ //Adding or updating coupons in coupon table
					$wpdb->query(
						$wpdb->prepare(
							"INSERT INTO {$coupon_table} (coupon_id, last_updated)
							VALUES (%d, %s)
							ON DUPLICATE KEY
							UPDATE last_updated = %s",
							$coupon->ID,
							$coupon->post_modified,
							$coupon->post_modified
						)
					);
				}
			}
			//Sending Promo rules over to MailChimp
			$this->sync_promo_rules();
		}
	}

	/**
	 * Method checks if coupon has expired or not
	 *
	 * @since    4.0
	 * @return   Boolean
	 * @param    object      $coupon			Coupon object
	 */
	private function coupon_expired( $coupon ){
		$expired = false;
		$coupon_object = new WC_Coupon( $coupon->ID );		
		$date_object = $coupon_object->get_date_expires();

		if( !empty( $date_object ) ){ //If we have expiry date
			$expires_at = strtotime( $date_object->date( 'c' ) );
			$current_date = date_create(current_time( 'c', true ) );
			$current_date = strtotime( $current_date->format( 'c' ) );
			
			if( $current_date >= $expires_at ){ //Checking if current date is not past the expiry date
				$expired = true;
			}
		}

		return $expired;
	}
	
	/**
	 * Method prepares products for pushing to MailChimp
	 *
	 * @since    2.0
	 * @return   array
	 * @param    array       $cart_products				Product array from abandoned cart
	 */
	private function prepare_products( $cart_products ){
		$admin = $this->admin();
		$public = $this->public();
		$products_array = array();
		
		if( $cart_products ){

			foreach( $cart_products as $cart_product ){
				$variants = array();
				$product_id = $cart_product['product_id'];
				$product_variation_ids = json_decode( $cart_product['variations'] );
				$product = wc_get_product( $product_id ); //Getting product data

				if( $product ){ //If product still exists and is not deleted
					$title = strip_tags( $product->get_name() );
					$url = get_permalink( $product->get_id() );
					$description = $product->get_short_description();
					$type = $product->get_type();
					$date_object = $product->get_date_modified(); //Returns WC_DateTime object
					$date_last_modified = $date_object->date( 'c' ); //Date when product was last modified
					$image_url = $admin->get_product_thumbnail_url( $cart_product );

					//Handling product variations
					if( $product_variation_ids ){ //Handling multiple variations
						foreach( $product_variation_ids as $product_variation_id ){ //Looping through Child ID values to retrieve data about distinct variations
							$single_variation = new WC_Product_Variation( $product_variation_id );
							$single_variation_data = $single_variation->get_data();

							//Handling variable product title output with attributes
							$product_variations = $single_variation->get_variation_attributes();
							$variation_attributes = $public->attribute_slug_to_title( $product_variations );
							$variation_title = $title.$variation_attributes; //Creating variation title output that consists of two parts. First part = Product title. Second part = product attributes
							
							$variation_url = $single_variation->get_permalink();
							$variation_sku = $single_variation->get_sku();
							$variation_price = $admin->get_product_price( $single_variation );
							$variation_quantity = $single_variation->get_stock_quantity();

							if( !isset( $variation_quantity ) ){ //If not managing stock quantity, then set quantity to 5000 just for the MailChimp to know that there is enough products
								$variation_quantity = 5000;
							}

							$variation_image_url = wp_get_attachment_image_src( $single_variation->get_image_id() , 'medium' );
							
							if( isset( $variation_image_url[0] ) ){
								$variation_image_url = $variation_image_url[0];

							}else{
								$variation_image_url = wc_placeholder_img_src( 'medium' );
							}
							
							$variants[] = array(
								'id' 					=> "$product_variation_id",
								'title' 				=> $variation_title,
								'url' 					=> $variation_url,
								'sku' 					=> $variation_sku,
								'price' 				=> $variation_price,
								'inventory_quantity' 	=> $variation_quantity,
								'image_url' 			=> $variation_image_url
							);
						}

					}else{ //Handling single variations
						$inventory_quantity = $product->get_stock_quantity();
						$price = $admin->get_product_price( $product );

						if( !isset( $inventory_quantity ) ){ //If not managing stock quantity, then set quantity to 5000 just for the MailChimp to know that there is enough products
							$inventory_quantity = 5000;
						}

						$variants[] = array(
							'id' 					=> "$product_id",
							'title' 				=> $title,
							'url' 					=> $url,
							'sku' 					=> $product->get_sku(),
							'price' 				=> $price,
							'inventory_quantity' 	=> $inventory_quantity,
							'image_url' 			=> $image_url
						);
					}
					
					//Building product array that is prepared for MailChimp
					$products_array[] = array(
						'id' 					=> "$product_id", //A unique identifier for the product
						'title' 				=> $title, //The title of a product
						'url' 					=> $url, //The URL for a product
						'description' 			=> $description, //The description of a product
						'type' 					=> $type, //The type of product
						'image_url' 			=> $image_url, //The image URL for a product
						'variants' 				=> $variants, //An array of the product's variants. At least one variant is required for each product. A variant can use the same id and title as the parent product.
						'published_at_foreign' 	=> $date_last_modified //The date and time the product was last modified
					);
				}
			}
		}

		return $products_array;
	}

	/**
	 * Method prepares carts for pushing to MailChimp
	 *
	 * @since    2.0
	 * @return   array
	 * @param    array      $carts			Carts array
	 */
	private function prepare_carts( $carts ){
		$admin = $this->admin();
		$coupons = $this->coupons();
		$carts_array = array();
		$store_id = $this->get_store_id();
		
		if( $carts ){
			foreach( $carts as $cart ){
				$lines = array();
				$cart_id = $this->get_cart_id( $store_id, $cart['id'] );
				$location_data = $admin->get_cart_location( $cart['location'] );
				$country = $location_data['country'];
				$city = $location_data['city'];
				$postcode = $location_data['postcode'];

				//Handling cart contents
				$line_id = 1;

				$cart_contents = $admin->get_saved_cart_contents( $cart['cart_contents'] );

				if( is_array( $cart_contents ) ){
					foreach( $cart_contents as $line ){
						$product_id = $line['product_id']; //Product ID
						$product_variant_id = $line['product_variation_id']; //Product Variant ID
						$inventory_quantity = $line['quantity']; //Quantity
						$product_price = $admin->get_product_price( $line );

						if( empty( $product_variant_id ) ){
							$product_variant_id = $product_id; //Setting variation ID for simple products because it can't be null
						}

						$line = array(
							'id' 					=> "$line_id",
							'product_id' 			=> "$product_id",
							'product_variant_id' 	=> "$product_variant_id",
							'quantity' 				=> $inventory_quantity,
							'price' 				=> $product_price
						);
						$lines[] = $line;
						$line_id++;
					}
				}

				$coupon = $coupons->get_coupon( 'mailchimp', 'cartbounty_pro_mailchimp_coupon', $step_nr = false, ( object )$cart ); //Coupon code creation

				$checkout_url = $admin->create_cart_url( $cart['email'], $cart['session_id'], $cart['id'], 'mailchimp' );
				
				//Building cart array that is prepared for MailChimp
				$carts_array[] = array(
					'id' 				=> $cart_id,
					'customer' 			=> array(
						'id' 				=> md5( $cart['email'] ), //Creating ID using MD5 Hash from users email address
						'email_address' 	=> $cart['email'],
						'opt_in_status' 	=> false, //Customers added with opt_in_status set to "false" will be added to the connected List with the status "transactional". Necessary for Abandoned cart automations
						'first_name' 		=> sanitize_text_field( wp_unslash( (string)( $cart['name'] ?? '' ) ) ),
						'last_name' 		=> sanitize_text_field( wp_unslash( (string)( $cart['surname'] ?? '' ) ) ),
						'address' 			=> array(
							'addr1' 			=> esc_html__( 'n/a', 'woo-save-abandoned-carts' ),
							'city' 				=> stripslashes( $city ),
							'state' 			=> esc_html__( 'n/a', 'woo-save-abandoned-carts' ),
							'zip' 				=> stripslashes( $postcode ),
							'country' 			=> $country
						),
					),
					'checkout_url' 		=> $checkout_url, //Link to cart checkout
					'currency_code' 	=> get_woocommerce_currency(),
					'order_total' 		=> $cart['cart_total'],
					'lines' 			=> $lines
				);
			}
		}

		return $carts_array;
	}

	/**
	 * Method prepares Promo rules for pushing to MailChimp
	 *
	 * @since    4.0
	 * @return   array
	 * @param    array       $coupons			Coupon array
	 */
	private function prepare_promo_rules( $coupons ){
		$promo_rule_array = array();

		if( $coupons ){
			foreach( $coupons as $coupon ){
				$coupon_id = $coupon['coupon_id'];
				$coupon_title = '';
				$coupon_post = get_post( $coupon_id );

				if( isset( $coupon_post->post_title ) ){
					$coupon_title = $coupon_post->post_title;

				}else{
					$this->remove_coupon( $coupon_id );
				}

				if( !empty( $coupon_title ) ){ //If Coupon has a name
					$coupon = new WC_Coupon( $coupon_title );
					$discount_type = $coupon->get_discount_type();
					$amount = $coupon->get_amount();

					if( $discount_type == 'fixed_cart' || $coupon->get_free_shipping() ){
						$discount_type = 'fixed';
						$target_type = 'total';

					}elseif( $discount_type == 'percent' ){
						$discount_type = 'percentage';
						$target_type = 'total';
						$amount = $amount/100;

					}elseif( $discount_type == 'fixed_product' ){
						$discount_type = 'fixed';
						$target_type = 'per_item';

					}else{
						$discount_type = '';
						$target_type = '';
					}

					if( $coupon_post->post_status == 'publish' || $coupon_post->post_status == 'future' ){
						$enabled = true;

					}else{
						$enabled = false;
					}

					$expires_at = '';
					$starts_at = '';
					$created_at_foreign = '';

					$date_object = new DateTime( $coupon_post->post_date );

					if( !empty( $date_object ) ){ //If we have date
						$starts_at = $date_object->format( 'c' );
					}

					$date_object = $coupon->get_date_expires();

					if( !empty( $date_object ) ){ //If we have date
						$expires_at = $date_object->date( 'c' );

					}else{//In case we do not have end date for coupon, we add default 10 years to it otherwise MailChimp does not display it
						$date_object = new DateTime( date( 'c' ) );
						$date_object = $date_object->add( new DateInterval( 'P1Y' ) ); //Adding 10 years since the time of creation
						$expires_at = $date_object->format( 'c' );
					}

					$date_object = new DateTime( $coupon_post->post_modified );

					if( !empty( $date_object ) ){ //If we have date
						$created_at_foreign = $date_object->format( 'c' );
					}

					//Building coupon array that is prepared for MailChimp
					$promo_rule_array[] = array(
						'id' 					=> "$coupon_id", //A unique identifier for the coupon
						'title' 				=> $coupon->get_code(), //The title of a coupon
						'description' 			=> $coupon->get_description(), //The description of a coupon
						'starts_at' 			=> $starts_at, //The date and time when the promotion is in effect
						'ends_at' 				=> $expires_at, //The date and time when the promotion ends
						'amount' 				=> $amount, //The amount of the promo code discount
						'type' 					=> $discount_type, //Type of discount. For free shipping set type to fixed. Possible Values: fixed, percentage
						'target' 				=> $target_type, //The target that the discount applies to. Possible Values: per_item, total, shipping
						'enabled' 				=> $enabled, //Whether the promo rule is currently enabled
						'created_at_foreign' 	=> $created_at_foreign //The date and time the coupon was last modified
					);
				}
			}
		}

		return $promo_rule_array;
	}

	/**
	 * Method prepares Promo codes for pushing to MailChimp
	 *
	 * @since    4.0
	 * @return   array
	 * @param    array      $coupons		Coupon array
	 */
	private function prepare_promo_codes( $coupons ){
		$promo_code_array = array();

		if( $coupons ){
			foreach( $coupons as $coupon ){
				$coupon_id = $coupon['coupon_id'];
				$coupon_post = get_post( $coupon_id );
				
				if( isset( $coupon_post->post_title ) ){ //If we have a coupon object - continue
					$coupon_title = $coupon_post->post_title;
					
					if( !empty( $coupon_title ) ){ //If Coupon has a name
						$coupon = new WC_Coupon( $coupon_title );
						$created_at_foreign = '';
						$enabled = false;

						if( $coupon_post->post_status == 'publish' || $coupon_post->post_status == 'future' ){
							$enabled = true;
						}

						$date_object = new DateTime( $coupon_post->post_modified );
						
						if( !empty( $date_object ) ){ //If we have date
							$created_at_foreign = $date_object->format( 'c' );
						}

						//Building coupon array that is prepared for MailChimp
						$promo_code_array[] = array(
							'id' 					=> "$coupon_id", //A unique identifier for the coupon
							'code' 					=> $coupon->get_code(), //The title of a coupon
							'redemption_url' 		=> wc_get_cart_url(), //The url that should be used in the promotion campaign
							'usage_count' 			=> $coupon->get_usage_count(), //Number of times promo code has been used.
							'enabled' 				=> $enabled, //Whether the promo code is currently enabled.
							'created_at_foreign' 	=> $created_at_foreign //The date and time the promotion was created
						);
					}
				}
			}
		}

		return $promo_code_array;
	}

	/**
	 * Preparing merge fields for pushing to MailChimp
	 *
	 * @since    9.4
	 * @return   array
	 * @param    array      $merge_fields		Merge fields array
	 */
	private function prepare_merge_fields( $merge_fields ){
		$merge_fields_array = array();
		
		if( $merge_fields ){
			foreach( $merge_fields as $field ){
				$merge_fields_array[$field['subscriber_hash']] = array( //Subscriber hash later is used for syncing each coupon to its contact
					'merge_fields' 		=> array(
						'CBCOUPON' 		=> strtoupper( $field['coupon_code'] ),
						'CBCOUPONXP' 	=> strtoupper( $field['coupon_expiration_date'] )
					)
				);
			}
		}

		return $merge_fields_array;
	}

	/**
	 * Method sends items to MailChimp in a batch operation
	 * With batch operations in the MailChimp API, you can complete more than one operation in just one call.
	 * Batch operations run in the background on MailChimp's servers.
	 *
	 * @since    4.0
	 * @return   boolean
	 * @param    array        $items				Items that must be sent
	 * @param    string       $item_type			Type of the item
	 * @param    string       $method				Method, e.g. GET, POST
	 */
	private function send_batch( $items, $item_type, $method ){
		$api = $this->api();
		$success = false;
		$item_batches = array();
		$subscriber_hash = false;
		
		if( $items ){
			foreach( $items as $key => $item ){
				
				if( $item_type == 'merge_fields' ){ //If working with merge fields
					$subscriber_hash = $key; //In case we are syncing merge fields - use the key field which holds member hash value required to build correct url for syncing each merge field
				}

				$send_items = array(
					'method' 	=> $method,
					'path' 		=> $this->get_path_or_save_batch( $item, $item_type, $method, $batch_id = false, $subscriber_hash ),
					'body' 		=> json_encode( $item )
				);
				
				array_push( $item_batches, $send_items );
			}
			$item_operations = array( 'operations' => $item_batches );
			$result = $api->connect( 'mailchimp', $this->api_access(), 'batches', 'POST', $item_operations ); //Send items to MailChimp
			$response = $result['response'];

			if( $result['status_code'] == 200 ){
				$this->get_path_or_save_batch( $item = false, $item_type, $method, $response->id );
				$success = true;

			}else{
				$this->log_mc_errors( $response, 'Send batch' );
			}
		}

		return $success;
	}

	/**
	 * Method returns path that is used to send data over to MailChimp or saves last batch ID in the database
	 *
	 * @since    4.0
	 * @return   false or string
	 * @param    string       $item					Item that must be sent
	 * @param    string       $item_type			Type of the item
	 * @param    string       $method				Method, e.g. GET, POST
	 * @param    string       $batch_id				Batch ID
	 * @param    string       $subscriber_hash		Subscriber hash value
	 */
	private function get_path_or_save_batch( $item, $item_type, $method, $batch_id, $subscriber_hash = false ){
		$store_id = $this->get_store_id();
		$list_id = $this->get_list_id();
		$settings = $this->get_settings();

		if( $item_type == 'carts' && $method == 'POST' ){
			
			if( $batch_id ){
				$settings['cart_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/carts';
			}
			
		}elseif( $item_type == 'products' && $method == 'POST' ){
			
			if( $batch_id ){
				$settings['product_create_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/products';
			}
			
		}elseif( $item_type == 'products' && $method == 'PATCH' ){
			
			if( $batch_id ){
				$settings['product_update_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/products/'. $item['id'];
			}
			
		}elseif( $item_type == 'promo_rules' && $method == 'POST' ){
			
			if( $batch_id ){
				$settings['promo_rule_create_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/promo-rules';
			}
			
		}elseif( $item_type == 'promo_rules' && $method == 'PATCH' ){
			
			if( $batch_id ){
				$settings['promo_rule_update_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/promo-rules/'. $item['id'];
			}
			
		}elseif( $item_type == 'promo_codes' && $method == 'POST' ){
			
			if( $batch_id ){
				$settings['promo_code_create_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/promo-rules/'. $item['id'] .'/promo-codes';
			}
			
		}elseif( $item_type == 'promo_codes' && $method == 'PATCH' ){
			
			if( $batch_id ){
				$settings['promo_code_update_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/promo-rules/'. $item['id'] . '/promo-codes/'. $item['id'];
			}

		}elseif( $item_type == 'create_merge_fields' && $method == 'POST' ){
			
			if( $batch_id ){
				$settings['merge_field_create_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'lists/' . $list_id . '/merge-fields';
			}

		}elseif( $item_type == 'merge_fields' && $method == 'PATCH' ){
			
			if( $batch_id ){
				$settings['merge_field_update_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $subscriber_hash ){
				return 'lists/'. $list_id .'/members/'. $subscriber_hash;
			}

		}elseif( $item_type == 'delete_merge_fields' && $method == 'DELETE' ){
			
			if( $batch_id ){
				$settings['merge_field_delete_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'lists/' . $list_id . '/merge-fields/' . $item['id'];
			}

		}elseif( $item_type == 'carts' && $method == 'DELETE' ){
			
			if( $batch_id ){
				$settings['cart_delete_batch_id'] = $batch_id;
				update_option( 'cartbounty_pro_mailchimp_settings', $settings );
				return;
			}

			if( $item ){
				return 'ecommerce/stores/'. $store_id .'/carts/'. $item['cart_id'];
			}
		}
	}

	/**
	 * Method displays results of the last MailChimp batch operations
	 * Cart creation, Product creation, Product updating batches
	 *
	 * @since    3.2
	 */
	public function export_last_batch_results(){
		$admin = $this->admin();
		
		//Checking if the Export MailChimp button was submited and the user has rights to do that
		if( empty( $_POST['cartbounty_pro_export_mailchimp'] ) || 'cartbounty_pro_export_m' != $_POST['cartbounty_pro_export_mailchimp'] ) return;

		if( !$admin->user_is_admin() ) return; //Checking if the user has rights to Export tables

		//Retrieving last batch ID values from database
		$csv_rows = array();
		$settings = $this->get_settings();

		$items = array(
			'Create carts' 			=> $settings['cart_batch_id'],
			'Create products' 		=> $settings['product_create_batch_id'],
			'Update products' 		=> $settings['product_update_batch_id'],
			'Create promo rules' 	=> $settings['promo_rule_create_batch_id'],
			'Create promo codes' 	=> $settings['promo_code_create_batch_id'],
			'Update promo rules' 	=> $settings['promo_rule_update_batch_id'],
			'Update promo codes' 	=> $settings['promo_code_update_batch_id'],
			'Create merge fields' 	=> $settings['merge_field_create_batch_id'],
			'Update merge fields' 	=> $settings['merge_field_update_batch_id'],
			'Delete merge fields' 	=> $settings['merge_field_delete_batch_id'],
			'Delete carts' 			=> $settings['cart_delete_batch_id'],
		);

		foreach ( $items as $key => $item ){
			
			if( !empty( $item ) ){
				$csv_rows[$key] = $this->get_batch_result( $key, $item );
			}
		}
		
		//Checking if we have any results to output
		if( !empty( $csv_rows ) ){
			$csv_header_row = array( 'type', 'status', 'operations', 'completed', 'incomplete', 'completed_at', 'batch_response' );
			$admin->generate_csv_file( $csv_header_row, $csv_rows, 'mailchimp' );
		}

		die();
	}

	/**
	 * Getting result of previous batch operation
	 *
	 * @since    9.7.3
	 * @return   array
	 * @param    string       $name					Name of the operation
	 * @param    string       $batch_id				Batch ID
	 */
	private function get_batch_result( $name, $batch_id ){
		$api = $this->api();
		$row = array();
		$result = $api->connect( 'mailchimp', $this->api_access(), 'batches/' . $batch_id, 'GET' );
		$response = $result['response'];
		
		if( $result['status_code'] == 200 ){ //If we have a successful result
			$cart_results = array( 
				'task' 			=> $name,
				'status' 		=> $response->status,
				'operations' 	=> $response->total_operations,
				'completed' 	=> $response->finished_operations,
				'incomplete' 	=> $response->errored_operations,
				'completed_at' 	=> $response->completed_at,
				'batch' 		=> $response->response_body_url 
			);
			$row = $cart_results;

		}else{
			$this->log_mc_errors( $response, 'Get batch result' );
		}

		return $row;
	}
	
	/**
	 * Method synchronizes products to MailChimp
	 * Products are sent to MailChimp as soon as they are in the Abandoned carts table
	 *
	 * @since    2.0
	 */
	private function sync_products(){
		global $wpdb;
		$admin = $this->admin();
		$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
		
		if( !$admin->check_license() ) return; //Exit if license key not valid

		//Retrieve from database products that have not been synced or have updated
		$products = $wpdb->get_results(
			"SELECT product_id, variations, last_updated, last_synced
			FROM {$product_table}
			WHERE last_updated != last_synced"
		);
		
		if( $products ){ //If we have new or updated products
			$new_products = array();
			$updated_products = array();
			
			foreach( $products as $product ){
				
				if( $product->last_synced == '0000-00-00 00:00:00' ){ //If we have a new product that has not been synced yet
					//Prepare array to pass as a Batch for creating new products on MailChimp
					$new_products[] = array(
						'product_id' 	=> $product->product_id,
						'variations' 	=> $product->variations
					);

				}else{
					//Prepare array to pass as a Batch for updating products on MailChimp
					$updated_products[] = array(
						'product_id' 	=> $product->product_id,
						'variations' 	=> $product->variations
					);
				}
			}

			//Prepare products for sending in a batch				
			$new_prepared_products = $this->prepare_products( $new_products );
			$new_updated_products = $this->prepare_products( $updated_products );

			//Send products to MailChimp
			$batch_create_result = $this->send_batch( $new_prepared_products, 'products', 'POST' ); //Batch Create new products.
			$batch_update_result = $this->send_batch( $new_updated_products, 'products', 'PATCH' ); //Batch Update changed products.
			
			if( $batch_create_result || $batch_update_result ){ //If we have a successful batch create or update operation.
				//Update last_synced column to be the same as last_updated
				foreach( $products as $product ){
					$wpdb->query(
						$wpdb->prepare(
							"UPDATE {$product_table}
							SET last_synced = %s
							WHERE product_id = %d",
							$product->last_updated,
							$product->product_id
						)
					);
				}
			}
		}		
	}

	/**
	 * Method synchronizes carts to MailChimp
	 * Carts are sent to MailChimp once they are considered as abandoned
	 * Carts without email address are not synced to MailChimp
	 *
	 * @since    2.0
	 */
	private function sync_carts(){
		global $wpdb;
		$admin = $this->admin();
		
		if( !$admin->check_license() ) return;

		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$time = $admin->get_time_intervals();
		$email_consent_query = '';

		if( $admin->get_consent_settings( 'email' ) ){ //Check if email consent enabled to query only carts with given email usage consent
			$email_consent_query = 'AND email_consent = 1';
		}

		$carts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, name, surname, email, phone, location, cart_contents, cart_total, session_id, time, mc_synced, last_synced
				FROM {$cart_table}
				WHERE (type = %d OR type = %d) AND
				email != '' AND
				cart_contents != '' AND
				paused != 1 AND
				mc_excluded != 1 AND
				anonymized != 1 AND
				time != last_synced AND
				time < %s AND
				time > %s
				$email_consent_query",
				$admin->get_cart_type( 'abandoned' ),
				$admin->get_cart_type( 'recovered_pending' ),
				$time['mc_cart_sync_period'],
				$time['maximum_sync_period']
			)
		);
		
		if( $carts ){ //If we have new carts
			$new_carts = array();
			foreach( $carts as $key => $cart ){
				$cart_contents = $admin->get_saved_cart_contents( $cart->cart_contents );

				if( $this->product_exist_in_product_table( $cart_contents ) ){

					if( !$admin->has_excluded_items( $cart, 'mailchimp' ) ){ //If cart contents do not have excluded items
						$new_carts[] = array(
							'id' 				=> $cart->id,
							'name' 				=> $cart->name,
							'surname' 			=> $cart->surname,
							'email' 			=> $cart->email,
							'location' 			=> $cart->location,
							'cart_contents' 	=> $cart->cart_contents,
							'cart_total' 		=> $cart->cart_total,
							'session_id' 		=> $cart->session_id
						);

					}else{
						$admin->exclude_cart( 'mailchimp', $cart ); //Exclude cart from further recovery
					 	unset( $carts[$key] ); //Remove current cart from cart array since it includes products that are excluded
					}

				}else{
					unset( $carts[$key] ); //Remove current cart from cart array since it has a product that doesn't exist in product table
				}
			}
			
			//Prepare carts for sending in a batch
			$new_prepared_carts = $this->prepare_carts( $new_carts );

			//Send carts to MailChimp
			$batch_create_result = $this->send_batch( $new_prepared_carts, 'carts', 'POST' ); //Products must be created on MailChimp before this step

			if( $batch_create_result ){ //If we have a successful batch create operation.
				//Update mc_synced column to synced = 1
			
				foreach( $carts as $cart ){
					$wpdb->query(
						$wpdb->prepare(
							"UPDATE {$cart_table} 
							SET mc_synced = %d, last_synced = %s
							WHERE id = %d",
							1,
							$cart->time,
							$cart->id
						)
					);
				}
			}
		}
	}

	/**
	 * Method synchronizes recovered abandoned cart (creates an order) to MailChimp
	 * Syncronization will be executed only if the user arrives via MailChimp email link (abandoned cart recovery automation)
	 * mc_cid value is used to track and return the campaign from which the user has origintaed from
	 *
	 * @since    6.4
	 * @param    object     $cart            Abandoned cart that has just been turned into order
	 */
	public function sync_order( $cart ){
		$admin = $this->admin();
		$api = $this->api();

		if( !$admin->check_license() ) return; //Exit if license key not valid
		
		if( !$this->api_valid() || !$this->store_connected() ) return; //Exit if MailChimp is not enabled

		$refferal = $admin->get_refferals( $cart->refferals, 'mailchimp' );

		if( !$this->cart_is_synced( $cart ) ){ //If cart hasn't been synced to MailChimp (maybe updated before)
			
			if( empty( $refferal ) ) return; //Exit if user has not previously arrived from MailChimp email campaign
		}

		if( !empty( $refferal ) ){ //If user has arrived from MailChimp email and we have the refferal of the campaign - sync order
			$lines = array();
			$cart_id = $this->get_cart_id( $this->get_store_id(), $cart->id );
			$location_data = $admin->get_cart_location( $cart->location );
			$country = $location_data['country'];
			$city = $location_data['city'];
			$postcode = $location_data['postcode'];

			//Handling cart contents
			$line_id = 1;

			$cart_contents = $admin->get_saved_cart_contents( $cart->cart_contents );

			if( is_array( $cart_contents ) ){
				foreach( $cart_contents as $line ){
					$product_id = $line['product_id']; //Product ID
					$product_variant_id = $line['product_variation_id']; //Product Variant ID
					$inventory_quantity = $line['quantity']; //Quantity
					$product_price = $admin->get_product_price( $line );

					if( empty( $product_variant_id ) ){
						$product_variant_id = $product_id; //Setting variation ID for simple products because it can't be null
					}

					$line = array(
						'id' 					=> "$line_id",
						'product_id' 			=> "$product_id",
						'product_variant_id' 	=> "$product_variant_id",
						'quantity' 				=> $inventory_quantity,
						'price' 				=> $product_price
					);
					$lines[] = $line;
					$line_id++;
				}
			}

			//Building cart array that is prepared for MailChimp
			$cart_data = array(
				'id' 					=> $cart_id,
				'customer' 				=> array(
					'id' 				=> md5( $cart->email ), //Creating ID using MD5 Hash from users email address
					'email_address' 	=> $cart->email,
					'opt_in_status' 	=> false, //Customers added with opt_in_status set to "false" will be added to the connected List with the status "transactional". Necessary for Abandoned cart automations
					'first_name' 		=> sanitize_text_field( wp_unslash( (string)( $cart->name ?? '' ) ) ),
					'last_name' 		=> sanitize_text_field( wp_unslash( (string)( $cart->surname ?? '' ) ) ),
					'address' 			=> array(
						'addr1' 		=> esc_html__( 'n/a', 'woo-save-abandoned-carts' ),
						'city' 			=> stripslashes( $city ),
						'state' 		=> esc_html__( 'n/a', 'woo-save-abandoned-carts' ),
						'zip' 			=> stripslashes( $postcode ),
						'country' 		=> $country
					),
				),
				'financial_status' 		=> 'paid',
				'currency_code' 		=> get_woocommerce_currency(),
				'order_total' 			=> $cart->cart_total,
				'processed_at_foreign' 	=> $cart->time,
				'campaign_id' 			=> $refferal,
				'outreach' 				=> array(
					'id' 				=> $refferal
				),
				'lines' 				=> $lines
			);

			$result = $api->connect( 'mailchimp', $this->api_access(), 'ecommerce/stores/' . $this->get_store_id() . '/orders', 'POST', $cart_data );
		}

		$this->delete_cart_if_order_complete( $cart );
	}

	/**
	 * Method synchronizes generated coupon codes to MailChimp as merge fields
	 * First checking if merge fields themselves have been created on MailChimp - create them in case they do not exist
	 * Then sync merge fields in a batch operation to MailChimp once the contacts have been synced
	 *
	 * @since    9.4
	 */
	private function sync_merge_fields(){
		global $wpdb;
		$admin = $this->admin();
		$coupons = $this->coupons();
		
		if( !$admin->check_license() ) return; //Exit if license key not valid

		$coupon_enabled = $coupons->coupon_enabled( 'mailchimp', 'cartbounty_pro_mailchimp_coupon', $step_nr = false );

		if( !$coupon_enabled ){ //If coupon disabled - delete merge fields from MailChimp and stop sync
			$this->delete_coupon_code_merge_fields();
			return;
		}

		if( empty( $this->get_settings( 'merge_fields_created' ) ) ){ //Check if merge fields have been created over at MailChimp
			$created = $this->create_coupon_code_merge_fields();
			
			if( !$created ) return; //In case the merge field has not been created, exit coupon sync
		}

		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$cart_coupons = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, time, email, coupons
				FROM {$cart_table}
				WHERE mc_synced = %d AND
				time != mc_coupon_synced ORDER BY
				time ASC",
				1
			)
		);

		if( $cart_coupons ){ //If we have carts with unsynced coupons
			$merge_fields = array();

			foreach( $cart_coupons as $key => $coupon ){
				$coupon_data = maybe_unserialize( $coupon->coupons );
				$coupon_code = '';
				$email = '';

				if( isset( $coupon_data['mailchimp'] ) ){ //If a coupon for MailChimp exists
					$coupon_code = $coupon_data['mailchimp'];
				}

				if( isset( $coupon->email ) ){ //If email exists
					$email = $coupon->email;
				}

				$coupon_expiration_date = $coupons->get_coupon_expiration_date( $coupon_code );

				if( empty( $coupon_expiration_date ) ){ //If expiration date not set
					$coupon_expiration_date = '';
				}

				//Build coupons for syncing to MailChimp
				//Prepare array to pass as a Batch
				$merge_fields[] = array(
					'coupon_code' 				=> $coupon_code,
					'coupon_expiration_date' 	=> $coupon_expiration_date,
					'subscriber_hash' 			=> md5( $email ) //Creating ID using MD5 Hash from users email address
				);
			}
			
			//Prepare merge fields for sending in a batch
			$prepared_fields = $this->prepare_merge_fields( $merge_fields );

			//Send merge fields to MailChimp
			$batch_create_result = $this->send_batch( $prepared_fields, 'merge_fields', 'PATCH' ); //Contacts must be synces to MailChimp before this step

			if( $batch_create_result ){ //If we have a successful batch create operation
				foreach( $cart_coupons as $coupon ){ //Update coupon sync time to match the abandoned cart time so it is no longer synced
					$wpdb->query(
						$wpdb->prepare(
							"UPDATE {$cart_table} 
							SET mc_coupon_synced = %s
							WHERE id = %d",
							$coupon->time,
							$coupon->id
						)
					);
				}
			}
		}
	}

	/**
	 * Method synchronizes Promo rules to MailChimp
	 * Promo rules must be synced prior Promo codes
	 *
	 * @since    4.0
	 */
	public function sync_promo_rules(){
		$admin = $this->admin();

		if( !$this->store_connected() || !$admin->table_exists( 'coupon_table_exists' ) ) return; //Exiting in case store is not connected to MailChimp or Coupon table hasn't been created

		global $wpdb;
		$coupon_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_COUPONS;
		$promo_rules = $wpdb->get_results( //Retrieve from database coupons that have not been synced or have updated
			"SELECT coupon_id, last_updated, last_synced_rule
			FROM {$coupon_table}
			WHERE last_updated != last_synced_rule"
		);
		
		if( $promo_rules ){ //If we have new or updated coupons
			$new_coupons = array();
			$updated_coupons = array();
			
			foreach( $promo_rules as $rule ){
				
				if( $rule->last_synced_rule == '0000-00-00 00:00:00' ){ //If we have a new coupon that has not been synced yet
					//Prepare array to pass as a Batch for creating new coupons on MailChimp
					//Placing it in the new coupons array
					$new_coupons[] = array( 'coupon_id' => $rule->coupon_id );

				}else{
					//Prepare array to pass as a Batch for updating coupons on MailChimp
					//Add or update a coupon variant
					$updated_coupons[] = array( 'coupon_id' => $rule->coupon_id );
				}
			}
			
			//Prepare Promo rules for sending in a batch
			$new_prepared_promo_rules = $this->prepare_promo_rules( $new_coupons );
			$new_updated_promo_rules = $this->prepare_promo_rules( $updated_coupons );

			//Send Promo rules to MailChimp
			$batch_create_result = $this->send_batch( $new_prepared_promo_rules, 'promo_rules', 'POST' ); //Batch Create new Promo rules.
			$batch_update_result = $this->send_batch( $new_updated_promo_rules, 'promo_rules', 'PATCH' ); //Batch Update changed Promo rules.

			if( $batch_create_result || $batch_update_result ){ //If we have a successful batch create or update operation. In case if there is no internet connection we do not want to mark coupon as synced
				//Update last_synced_rule column to be the same as last_updated
				foreach( $promo_rules as $rule ){
					$wpdb->query(
						$wpdb->prepare(
							"UPDATE {$coupon_table} 
							SET last_synced_rule = %s 
							WHERE coupon_id = %d",
							$rule->last_updated,
							$rule->coupon_id
						)
					);
				}
			}
		}
	}

	/**
	 * Method synchronizes Promo codes to MailChimp
	 *
	 * @since    4.0
	 */
	public function sync_promo_codes(){
		global $wpdb;
		$admin = $this->admin();
		
		if( !$admin->check_license() ) return; //Exit if license key not valid

		$coupon_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_COUPONS;
		
		//Retrieve from database coupons that have not been synced or have updated
		$promo_codes = $wpdb->get_results(
			"SELECT coupon_id, last_updated, last_synced_code
			FROM {$coupon_table}
			WHERE last_updated != last_synced_code"
		);
		
		if( $promo_codes ){ //If we have new or updated coupons
			$new_coupons = array();
			$updated_coupons = array();
			
			foreach( $promo_codes as $promo_code ){
				
				if( $promo_code->last_synced_code == '0000-00-00 00:00:00' ){ //If we have a new coupon that has not been synced yet
					//Prepare array to pass as a Batch for creating new coupons on MailChimp
					//Placing it in the new coupons array
					$new_coupons[] = array( 'coupon_id' => $promo_code->coupon_id );

				}else{
					//Prepare array to pass as a Batch for updating coupons on MailChimp
					//Add or update a coupon variant
					$updated_coupons[] = array( 'coupon_id' => $promo_code->coupon_id );
				}
			}

			//Prepare Promo codes for sending in a batch
			$new_prepared_promo_codes = $this->prepare_promo_codes( $new_coupons );
			$new_updated_promo_codes = $this->prepare_promo_codes( $updated_coupons );

			//Send Promo codes to MailChimp
			$batch_create_result = $this->send_batch( $new_prepared_promo_codes, 'promo_codes', 'POST' ); //Batch Create new Promo codes.
			$batch_update_result = $this->send_batch( $new_updated_promo_codes, 'promo_codes', 'PATCH' ); //Batch update Promo codes.

			if( $batch_create_result || $batch_update_result ){ //If we have a successful batch create or update operation.
				//Update last_synced column to be the same as last_updated
				foreach( $promo_codes as $promo_code ){
					$wpdb->query(
						$wpdb->prepare(
							"UPDATE {$coupon_table} 
							SET last_synced_code = %s 
							WHERE coupon_id = %d",
							$promo_code->last_updated,
							$promo_code->coupon_id
						)
					);
				}
			}
		}
	}

	/**
	 * Method retrieves product IDs from Porduct table that are older than 5 minutes from the time of creation
	 * Method is created in order for carts to be syncronised and pushed to MailChimp only after the product has been synced to MailChimp. Otherwise we will not be able to syn the cart
	 *
	 * @since    4.0
	 * @return 	 Boolean
	 * @param    array      $cart_lines			Cart lines array
	 */
	public function product_exist_in_product_table( $cart_lines ){
		$admin = $this->admin();
		$product_exists = false;

		if( $admin->table_exists( 'product_table_exists' ) ){
			global $wpdb;
			$product_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_PRODUCTS;
			$time = $admin->get_time_intervals();

			if( $cart_lines ){
				foreach( $cart_lines as $line ){
					$product_id = $line['product_id'];
					$result = $wpdb->get_var(
						$wpdb->prepare(
							"SELECT * FROM $product_table
							WHERE product_id = %d AND
							first_created < %s",
							$product_id,
							$time['mc_cart_sync_period']
						)
					);

					if( $result > 0 ){
						$product_exists = true;
					}
				}
			}
		}

		return $product_exists;
	}

	/**
	 * Method returns cart ID
	 *
	 * @since    4.0
	 * @return 	 string
	 * @param    string      $store_id			Store ID
	 * @param    string      $cart_id			Cart ID
	 */
	private function get_cart_id( $store_id, $cart_id ){
		return $store_id . '_' . $cart_id;
	}

	/**
	 * Method Checks if current abandoned cart is synced to MailChimp
	 *
	 * @since    4.0
	 * @return   boolean
	 * @param    object     $cart            Abandoned cart data
	 */
	public function cart_is_synced( $cart ){
		$synced = false;
		
		if( !empty( $cart ) ){
			
			if( isset( $cart->mc_synced ) ){
				
				if( $cart->mc_synced ){
					$synced = true;
				}
			}
		}

		return $synced;
	}

	/**
	 * Method sends a delete request in case the abandoned cart has been synced of a given cart to MailChimp if the abandoned cart is turned into a complete order
	 *
	 * @since    5.0
	 * @param    object     $cart            Abandoned cart data
	 */
	public function delete_cart_if_order_complete( $cart ){
		global $wpdb;
		$api = $this->api();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;

		if( !$this->cart_is_synced( $cart ) ){ //If current cart has not been synced
			$wpdb->update( //Update cart row so that the last sync time would match the last update time and this cart would no longer sync
				$cart_table,
				array(
					'last_synced'	=> $cart->time
				),
				array(
					'id'			=> $cart->id
				),
				array( '%s' ),
				array( '%d' )
			);
			return;
		}

		$cart_id = $cart->id;
		$store_id = $this->get_store_id();
		$cart_id_on_mailchimp = $this->get_cart_id( $store_id, $cart_id );

		//Deleting cart
		$result = $api->connect( 'mailchimp', $this->api_access(), 'ecommerce/stores/' . $store_id . '/carts/' . $cart_id_on_mailchimp, 'DELETE' );

		$wpdb->update( //Update cart row so it would be considered as synced, but the setting mc_synved = 0 since the cart itself no longer exists on MailChimp
			$cart_table,
			array(
				'last_synced'	=> $cart->time,
				'mc_synced'		=> 0
			),
			array(
				'id'			=> $cart->id
			),
			array( '%s', '%d' ),
			array( '%d' )
		);
	}

	/**
	 * Method removes duplicate carts from MailChimp email automations
	 *
	 * @since    9.5
	 * @param    array      $carts          Cart array
	 */
	public function delete_duplicate_carts( $carts ){
		$admin = $this->admin();
		
		if( !$admin->check_license() ) return; //Exit if license key not valid

		if( !$this->api_valid() || !$this->store_connected() ) return; //Exit if MailChimp is not enabled

		if( empty( $carts ) ) return; //Exit in case no carts provided

		global $wpdb;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$store_id = $this->get_store_id();
		$duplicate_carts = array();
		$duplicate_cart_ids = array();

		foreach( $carts as $key => $cart ){
			
			if( $this->cart_is_synced( $cart ) ){ //If current cart has been synced - add it to batch
				$cart_id_on_mailchimp = $this->get_cart_id( $store_id, $key );
				$duplicate_carts[] = array( 'cart_id' => "$cart_id_on_mailchimp" );
				$duplicate_cart_ids[] = $key;
			}
		}

		if( empty( $duplicate_carts ) ){ //Exit if no duplicate carts were synced to MailChimp
			return;
		}

		$this->send_batch( $duplicate_carts, 'carts', 'DELETE' ); //Batch delete carts
		
		$ids = implode( ', ', $duplicate_cart_ids );
		$result = $wpdb->query( //Update all duplicate carts
			$wpdb->prepare(
				"UPDATE $cart_table
				SET mc_synced = %d
				WHERE id IN ($ids)",
				0
			)
		);
	}

	/**
	 * Method sends a delete request of current cart to MailChimp
	 *
	 * @since    4.0
	 * @param    string      $cart_id			Cart ID
	 */
	public function delete_cart( $cart_id ){
		
		if( !$this->api_valid() || !$this->store_connected() ) return; //Exit if MailChimp is not enabled

		if( empty( $cart_id ) ) return;

		if( is_array( $cart_id ) ){ //If deleting multiple lines from table
			$ids = implode( ', ', $cart_id );

		}else{
			$ids = $cart_id;
		}

		global $wpdb;
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$store_id = $this->get_store_id();
		$carts_to_delete = array();

		$carts = $wpdb->get_results( //Retrieve carts from database that require deletion
			"SELECT *
			FROM $cart_table
			WHERE id IN ($ids)"
		);

		foreach( $carts as $key => $cart ){
			
			if( $this->cart_is_synced( $cart ) ){ //If current cart has been synced - add it to batch
				$cart_id_on_mailchimp = $this->get_cart_id( $store_id, $cart->id );
				$carts_to_delete[] = array( 'cart_id' => "$cart_id_on_mailchimp" );
			}
		}

		if( !empty( $carts_to_delete ) ){
			$this->send_batch( $carts_to_delete, 'carts', 'DELETE' ); //Batch delete carts
		}
	}

	/**
	 * Method sends a delete request in case the abandoned cart has been previously synced, but has been updates so we would be able to sync it again
	 *
	 * @since    5.0
	 * @param    object     $cart            Abandoned cart data
	 */
	public function delete_updated_cart( $cart ){
		
		if( !$this->api_valid() || !$this->store_connected() ) return; //Exit if MailChimp is not enabled

		if( !isset( $cart['session_id'] ) ) return;

		global $wpdb;
		$api = $this->api();
		$cart_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME;
		$cart = $wpdb->get_row( //Retrieve latest abandoned cart
			$wpdb->prepare(
				"SELECT *
				FROM $cart_table
				WHERE session_id = %s
				ORDER BY time DESC",
				$cart['session_id']
			)
		);

		if( empty( $cart ) ) return; //If no cart has been found - exit

		if( !$this->cart_is_synced( $cart ) ) return; //Exit if current cart has not been synced

		$store_id = $this->get_store_id();
		$cart_id_on_mailchimp = $this->get_cart_id( $store_id, $cart->id );

		//Deleting cart
		$api->connect( 'mailchimp', $this->api_access(), 'ecommerce/stores/' . $store_id . '/carts/' . $cart_id_on_mailchimp, 'DELETE' );

		$wpdb->update( //Update cart row as unsynced
			$cart_table,
			array(
				'mc_synced'	=> 0
			),
			array(
				'id'		=> $cart->id
			),
			array( '%d' ),
			array( '%d' )
		);
	}

	/**
	 * Creating coupon merge fields on MailChimp
	 * Returns True in case the fields have been successfully created
	 *
	 * @since    9.4
	 * @return   boolean
	 */
	private function create_coupon_code_merge_fields(){
		$created = false;

		if( $this->api_valid() && $this->store_connected() ){ //If MailChimp is enabled
			$settings = $this->get_settings();
			$list_id = $settings['list_id'];

			if( $list_id ){ //If we have a list ID
				$merge_fields = array(
					array(
						'name' 		=> 'CartBounty coupon',
						'type' 		=> 'text',
						'tag' 		=> 'CBCOUPON',
						'required' 	=> false,
						'public' 	=> false,
						'options'	=> array(
							'size'	=> 40 
						)
					),
					array(
						'name' 		=> 'CartBounty coupon expiration',
						'type' 		=> 'date',
						'tag' 		=> 'CBCOUPONXP',
						'required' 	=> false,
						'public' 	=> false,
						'options'	=> array(
							'size'	=> 12 
						)
					)
				);

				$batch_create_result = $this->send_batch( $merge_fields, 'create_merge_fields', 'POST' ); //Send merge fields to MailChimp

				if( $batch_create_result ){
					$settings['merge_fields_created'] = true;
					update_option( 'cartbounty_pro_mailchimp_settings', $settings );
					$created = true;
				}
			}
		}

		return $created;
	}

	/**
	 * Method sends a delete request of coupon merge fields to MailChimp
	 *
	 * @since    9.4
	 */
	private function delete_coupon_code_merge_fields(){
		$settings = $this->get_settings();

		if( !$settings['merge_fields_created'] ) return; //Exit if merge fields have not been created

		$merge_fields_from_mailchimp = $this->get_cartbounty_merge_fields_from_mailchimp();
		$batch_create_result = $this->send_batch( $merge_fields_from_mailchimp, 'delete_merge_fields', 'DELETE' ); //Delete merge fields from MailChimp
		$settings['merge_fields_created'] = false;
		update_option( 'cartbounty_pro_mailchimp_settings', $settings );
	}

	/**
	* Get a list of CartBounty merge fields from MailChimp
	*
	* @since    9.9.2
	* @return   Array
	*/
	private function get_cartbounty_merge_fields_from_mailchimp(){
		$api = $this->api();
		$merge_fields_from_mailchimp = array();
		$cartbounty_merge_fields = array( 'CBCOUPON', 'CBCOUPONXP' );
		$list_id = $this->get_settings( 'list_id' );

		if( $list_id ){ //If we have a list ID
			$query = array(
				'count' => 1000
			);

			$result = $api->connect( 'mailchimp', $this->api_access(), 'lists/' . $list_id . '/merge-fields', 'GET', $query );
			$response = $result['response'];

			if( $result['status_code'] != 200 ){ //Exit in case of failure
				$this->log_mc_errors( $response, 'Get merge fields' );
			}

			if( is_array( $response->merge_fields ) ){ //If merge field sucessfuly found - loop through to find CartBounty coupon field that must be restored
				foreach( $response->merge_fields as $key => $merge_field ){
					
					if( isset( $merge_field->tag ) ){
						
						if( in_array( $merge_field->tag, $cartbounty_merge_fields ) ){ //If we find our merge fields - add their ID values to response array
							$merge_fields_from_mailchimp[] = array(
								'tag' 	=> $merge_field->tag,
								'id' 	=> $merge_field->merge_id
							);
						}
					}
				}
			}
		}

		return $merge_fields_from_mailchimp;
	}

	/**
	* Function handles MailChimp error array
	*
	* @since    9.7.3
	* @param    array     $errors				MailChimp error array
	* @param 	array     $extra				Additional information to output, optional
	*/
	public function log_mc_errors( $errors, $additional_data = '' ){
		$admin = $this->admin();

		if( is_array( $errors ) ){
			foreach( $errors as $key => $error ){
				$admin->log( 'notice', sprintf( 'MailChimp: %s - %s. %s', esc_html( $additional_data ), esc_html( $errors->title ), esc_html( $errors->detail ) ) );
			}
			
		}else{

			if( !empty( $errors ) ){
				$admin->log( 'notice', sprintf( 'MailChimp: %s - %s. %s', esc_html( $additional_data ), esc_html( $errors->title ), esc_html( $errors->detail ) ) );
			}
		}
	}

	/**
	* Function handles MailChimp error array
	*
	* @since    9.7.3
	* @param    integer   $coupon_id			Coupon ID
	*/
	private function remove_coupon( $coupon_id ){
		global $wpdb;
		$coupon_table = $wpdb->prefix . CARTBOUNTY_PRO_TABLE_NAME_COUPONS;
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM $coupon_table
				WHERE coupon_id = %d",
				intval( $coupon_id )
			)
		);
	}
}