Jump to content
  • Checkout
  • Login
  • Get in touch

osCommerce

The e-commerce.

Official PayPal IPN Support Thread


Mark Evans

Recommended Posts

Hi,

 

After a lot of study, I succeeded in getting Paypal_IPN working.

I discovered errors in the file /catalog/paypal_ipn.php that was added august 2009 to contribution number 2679.

 

Below is the whole file in the form which I use.

Please read my release notes in the file header.

 

I also made the language files for Dutch, ask me if you want them.

 

 

The original (English) Install instructions are not included in most of the packages; I found them in the package "PayPal IPN Module 2.3.4.7" of 4 Mar 2009.

Later versions include only a small text in German.

 

Eveline

 

<?php
/*
$Id: paypal_ipn.php,v 1.1.2.2 2005/08/04 06:05:08 Michael Sasek Exp $

osCMax Power E-Commerce
http://oscdox.com

Copyright  2004 osCommerce

Released under the GNU General Public License
20091207 Denkster. 	Removed double code, added comment and code formatting; 
					Replaced hard coded German texts by variable names in email texts;  
					Corrected unknown variable in definition for $products_ordered .= ; Changed $currencies->display_price2() to $currencies->display_price()
					Note: general.php is supposed to have the function tep_decode_specialchars()
*/

class paypal_ipn {
var $code, $title, $description, $enabled, $identifier;

 // class constructor
 function paypal_ipn() {
global $order;

$this->code = 'paypal_ipn';
$this->title = MODULE_PAYMENT_PAYPAL_IPN_TEXT_TITLE;
$this->description = MODULE_PAYMENT_PAYPAL_IPN_TEXT_DESCRIPTION;
$this->sort_order = MODULE_PAYMENT_PAYPAL_IPN_SORT_ORDER;
$this->enabled = ((MODULE_PAYMENT_PAYPAL_IPN_STATUS == 'True') ? true : false);
$this->identifier = 'osCommerce PayPal IPN v1.0';

if ((int)MODULE_PAYMENT_PAYPAL_IPN_PREPARE_ORDER_STATUS_ID > 0) {
	$this->order_status = MODULE_PAYMENT_PAYPAL_IPN_PREPARE_ORDER_STATUS_ID;
}

if (is_object($order)) $this->update_status();
$this->email_footer = MODULE_PAYMENT_PAYPAL_TEXT_EMAIL_FOOTER;

if (MODULE_PAYMENT_PAYPAL_IPN_GATEWAY_SERVER == 'Live') {
	$this->form_action_url = 'https://www.paypal.com/cgi-bin/webscr';
} else {
	$this->form_action_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
}	//end if (is_object
 }	//end function paypal_ipn()


 // class methods
 function update_status() {
global $order;

if ( ($this->enabled == true) && ((int)MODULE_PAYMENT_PAYPAL_IPN_ZONE > 0) ) {
	$check_flag = false;
	$check_query = tep_db_query("select zone_id from " . TABLE_ZONES_TO_GEO_ZONES . " where geo_zone_id = '" . MODULE_PAYMENT_PAYPAL_IPN_ZONE . "' and zone_country_id = '" . $order->billing['country']['id'] . "' order by zone_id");
	while ($check = tep_db_fetch_array($check_query)) {
		if ($check['zone_id'] < 1) {
			$check_flag = true;
			break;
		} elseif ($check['zone_id'] == $order->billing['zone_id']) {
			$check_flag = true;
			break;
			}
		}

		if ($check_flag == false) {
			$this->enabled = false;
		}
	}	//end while
}	//end if ( ($this->enabled

function javascript_validation() {
	return false;
}

/*function selection() {
return array('id' => $this->code,
'module' => $this->title);
}*/
function selection() {
  global $cart_PayPal_Standard_ID;

     if (tep_session_is_registered('cart_PayPal_Standard_ID')) {
       $order_id = substr($cart_PayPal_Standard_ID, strpos($cart_PayPal_Standard_ID, '-')+1);

       $check_query = tep_db_query('select orders_id from ' . TABLE_ORDERS_STATUS_HISTORY . ' where orders_id = "' . (int)$order_id . '" limit 1');

       if (tep_db_num_rows($check_query) < 1) {
         tep_db_query('delete from ' . TABLE_ORDERS . ' where orders_id = "' . (int)$order_id . '"');
         tep_db_query('delete from ' . TABLE_ORDERS_TOTAL . ' where orders_id = "' . (int)$order_id . '"');
         tep_db_query('delete from ' . TABLE_ORDERS_STATUS_HISTORY . ' where orders_id = "' . (int)$order_id . '"');
         tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS . ' where orders_id = "' . (int)$order_id . '"');
         tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . ' where orders_id = "' . (int)$order_id . '"');
         tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS_DOWNLOAD . ' where orders_id = "' . (int)$order_id . '"');

         tep_session_unregister('cart_PayPal_Standard_ID');
       }
     }
     return array('id' => $this->code,
                  'module' => $this->title);
 	}	//end function selection()



  /*function pre_confirmation_check() {
  return false;
  }*/
  function pre_confirmation_check() {
	  global $cartID, $cart;

	  if (empty($cart->cartID)) {
		$cartID = $cart->cartID = $cart->generate_cart_id();
	  }

	  if (!tep_session_is_registered('cartID')) {
		tep_session_register('cartID');
	  }
  }	//end function pre_confirmation_check()



function confirmation() {
 global $cartID, $cart_PayPal_Standard_ID, $customer_id, $languages_id, $order, $order_total_modules;

 //if (tep_session_is_registered('cartID')) {
 if (array_key_exists('cartID', $_SESSION)) {
 	$insert_order = false;

 	if (tep_session_is_registered('cart_PayPal_Standard_ID')) {
     $order_id = substr($cart_PayPal_Standard_ID, strpos($cart_PayPal_Standard_ID, '-')+1);

  $curr_check = tep_db_query("select currency from " . TABLE_ORDERS . " where orders_id = '" . (int)$order_id . "'");
  $curr = tep_db_fetch_array($curr_check);

  if ( ($curr['currency'] != $order->info['currency']) || ($cartID != substr($cart_PayPal_Standard_ID, 0, strlen($cartID))) ) {
	$check_query = tep_db_query('select orders_id from ' . TABLE_ORDERS_STATUS_HISTORY . ' where orders_id = "' . (int)$order_id . '" limit 1');

	if (tep_db_num_rows($check_query) < 1) {
		tep_db_query('delete from ' . TABLE_ORDERS . ' where orders_id = "' . (int)$order_id . '"');
		tep_db_query('delete from ' . TABLE_ORDERS_TOTAL . ' where orders_id = "' . (int)$order_id . '"');
		tep_db_query('delete from ' . TABLE_ORDERS_STATUS_HISTORY . ' where orders_id = "' . (int)$order_id . '"');
		tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS . ' where orders_id = "' . (int)$order_id . '"');
		tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS_ATTRIBUTES . ' where orders_id = "' . (int)$order_id . '"');
		tep_db_query('delete from ' . TABLE_ORDERS_PRODUCTS_DOWNLOAD . ' where orders_id = "' . (int)$order_id . '"');
	}	//end if (tep_db_num_rows

	$insert_order = true;
  }	//end if ( ($curr
 	} else {
  $insert_order = true;
 	}	//end if tep_session_is_registered

if ($insert_order == true) {
  $order_totals = array();
  if (is_array($order_total_modules->modules)) {
	reset($order_total_modules->modules);
	while (list(, $value) = each($order_total_modules->modules)) {
		$class = substr($value, 0, strrpos($value, '.'));
		if ($GLOBALS[$class]->enabled) {
			for ($i=0, $n=sizeof($GLOBALS[$class]->output); $i<$n; $i++) {
			  if (tep_not_null($GLOBALS[$class]->output[$i]['title']) && tep_not_null($GLOBALS[$class]->output[$i]['text'])) {
				$order_totals[] = array('code' => $GLOBALS[$class]->code,
							'title' => $GLOBALS[$class]->output[$i]['title'],
							'text' => $GLOBALS[$class]->output[$i]['text'],
							'value' => $GLOBALS[$class]->output[$i]['value'],
							'sort_order' => $GLOBALS[$class]->sort_order);
			  }	//end if tep_not_null
			}	//end for
		}	//end if class enabled
	}	//end while
  }	//end if is array
// Vor CAO
  $sql_data_array = array('customers_id' => $customer_id,
	'customers_name' => $order->customer['firstname'] . ' ' . $order->customer['lastname'],
	'customers_company' => $order->customer['company'],
	'customers_street_address' => $order->customer['street_address'],
	'customers_suburb' => $order->customer['suburb'],
	'customers_city' => $order->customer['city'],
	'customers_postcode' => $order->customer['postcode'],
	'customers_state' => $order->customer['state'],
	'customers_country' => $order->customer['country']['title'],
	'customers_telephone' => $order->customer['telephone'],
	'customers_email_address' => $order->customer['email_address'],
	'customers_address_format_id' => $order->customer['format_id'],
	'delivery_name' => $order->delivery['firstname'] . ' ' . $order->delivery['lastname'],
	'delivery_company' => $order->delivery['company'],
	'delivery_street_address' => $order->delivery['street_address'],
	'delivery_suburb' => $order->delivery['suburb'],
	'delivery_city' => $order->delivery['city'],
	'delivery_postcode' => $order->delivery['postcode'],
	'delivery_state' => $order->delivery['state'],
	'delivery_country' => $order->delivery['country']['title'],
	'delivery_address_format_id' => $order->delivery['format_id'],
	'billing_name' => $order->billing['firstname'] . ' ' . $order->billing['lastname'],
	'billing_company' => $order->billing['company'],
	'billing_street_address' => $order->billing['street_address'],
	'billing_suburb' => $order->billing['suburb'],
	'billing_city' => $order->billing['city'],
	'billing_postcode' => $order->billing['postcode'],
	'billing_state' => $order->billing['state'],
	'billing_country' => $order->billing['country']['title'],
	'billing_address_format_id' => $order->billing['format_id'],
	'payment_method' => $order->info['payment_method'],
	'cc_type' => $order->info['cc_type'],
	'cc_owner' => $order->info['cc_owner'],
	'cc_number' => $order->info['cc_number'],
	'cc_expires' => $order->info['cc_expires'],
	'date_purchased' => 'now()',
	'orders_status' => $order->info['order_status'],
	'currency' => $order->info['currency'],
	'currency_value' => $order->info['currency_value']);

  tep_db_perform(TABLE_ORDERS, $sql_data_array);

  $insert_id = tep_db_insert_id();

  for ($i=0, $n=sizeof($order_totals); $i<$n; $i++) {
	$sql_data_array = array('orders_id' => $insert_id,
		'title' => $order_totals[$i]['title'],
		'text' => $order_totals[$i]['text'],
		'value' => $order_totals[$i]['value'],
		'class' => $order_totals[$i]['code'],
		'sort_order' => $order_totals[$i]['sort_order']);

	tep_db_perform(TABLE_ORDERS_TOTAL, $sql_data_array);
  }

  for ($i=0, $n=sizeof($order->products); $i<$n; $i++) {
	$sql_data_array = array('orders_id' => $insert_id,
		'products_id' => tep_get_prid($order->products[$i]['id']),
		'products_model' => $order->products[$i]['model'],
		'products_name' => $order->products[$i]['name'],
		'products_price' => $order->products[$i]['price'],
		'final_price' => $order->products[$i]['final_price'],
		'products_tax' => $order->products[$i]['tax'],
		'products_quantity' => $order->products[$i]['qty']);

	tep_db_perform(TABLE_ORDERS_PRODUCTS, $sql_data_array);

	$order_products_id = tep_db_insert_id();

//------insert customer choosen option to order--------
       $attributes_exist = '0';
       if (isset($order->products[$i]['attributes'])) {
             $attributes_exist = '1';
             for ($j=0, $n2=sizeof($order->products[$i]['attributes']); $j<$n2; $j++) {
               if (DOWNLOAD_ENABLED == 'true') {
                 $attributes_query = "select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix, pad.products_attributes_maxdays, pad.products_attributes_maxcount , pad.products_attributes_filename
                                      from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa
                                      left join " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad
                                      on pa.products_attributes_id=pad.products_attributes_id
                                      where pa.products_id = '" . $order->products[$i]['id'] . "'
                                      and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "'
                                      and pa.options_id = popt.products_options_id
                                      and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "'
                                      and pa.options_values_id = poval.products_options_values_id
                                      and popt.language_id = '" . $languages_id . "'
                                      and poval.language_id = '" . $languages_id . "'";
                 $attributes = tep_db_query($attributes_query);
               } else {
                 $attributes = tep_db_query("select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa where pa.products_id = '" . $order->products[$i]['id'] . "' and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "' and pa.options_id = popt.products_options_id and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "' and pa.options_values_id = poval.products_options_values_id and popt.language_id = '" . $languages_id . "' and poval.language_id = '" . $languages_id . "'");
               }
               $attributes_values = tep_db_fetch_array($attributes);

               $sql_data_array = array('orders_id' => $insert_id,
                                       'orders_products_id' => $order_products_id,
                                       'products_options' => $attributes_values['products_options_name'],
                                       'products_options_values' => $attributes_values['products_options_values_name'],
                                       'options_values_price' => $attributes_values['options_values_price'],
                                       'price_prefix' => $attributes_values['price_prefix']);

               tep_db_perform(TABLE_ORDERS_PRODUCTS_ATTRIBUTES, $sql_data_array);
               if ((DOWNLOAD_ENABLED == 'true') && isset($attributes_values['products_attributes_filename']) && tep_not_null($attributes_values['products_attributes_filename'])) {
			  /* BOF replacing for super download shop
                   $sql_data_array = array('orders_id' => $insert_id,
                                         'orders_products_id' => $order_products_id,
                                         'orders_products_filename' => $attributes_values['products_attributes_filename'],
                                         'download_maxdays' => $attributes_values['products_attributes_maxdays'],
                                         'download_count' => $attributes_values['products_attributes_maxcount']);
                   tep_db_perform(TABLE_ORDERS_PRODUCTS_DOWNLOAD, $sql_data_array);
			  */
			  if (DOWNLOADS_CONTROLLER_FILEGROUP_STATUS != 'Yes' || !strstr($attributes_values['products_attributes_filename'], 'Group_Files-')) {
				$sql_data_array = array(
					'orders_id' => $insert_id, 
					'orders_products_id' => $order_products_id, 
					'orders_products_filename' => $attributes_values['products_attributes_filename'], 
					'download_maxdays' => $attributes_values['products_attributes_maxdays'], 
					'download_count' => $attributes_values['products_attributes_maxcount']);
				tep_db_perform(TABLE_ORDERS_PRODUCTS_DOWNLOAD, $sql_data_array);
			  } else {
				$filegroup_array = explode('Group_Files-', $attributes_values['products_attributes_filename']);
				$filegroup_id = $filegroup_array[1];
				$groupfiles_query = tep_db_query(
					"select download_group_filename
					from " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD_GROUPS_FILES . "
					where download_group_id = '" . (int)$filegroup_id . "'");
				while ($groupfile_array = tep_db_fetch_array($groupfiles_query)) {
				  $sql_data_array = array(
				  	'orders_id' => $insert_id, 
					'orders_products_id' => $order_products_id, 
					'orders_products_filename' => $groupfile_array['download_group_filename'], 
					'download_maxdays' => $attributes_values['products_attributes_maxdays'], 
					'download_count' => $attributes_values['products_attributes_maxcount']);
				  tep_db_perform(TABLE_ORDERS_PRODUCTS_DOWNLOAD, $sql_data_array);
				}	//end while
			  }		//end if (DOWNLOADS_CONTROLLER_FILEGROUP_STATUS
			  // EOF replace for super download shop
               }		//end if ((DOWNLOAD_ENABLED
             }			//end for attributes
            }			//end if (isset
		//------insert customer choosen option eof ----
	}	//end for products

	// FS start
         $GLOBALS['cart_PayPal_Standard_ID'] = $cartID . '-' . $insert_id;
	// FS stop          
         tep_session_register('cart_PayPal_Standard_ID');
}	//end if ($insert_order
 }	//end if (array_key_exists

return false;
}	//end function before_process()



function process_button() {
global $customer_id, $order, $languages_id, $currencies, $currency, $cart_PayPal_Standard_ID, $shipping;
global $sendto;

if (MODULE_PAYMENT_PAYPAL_IPN_CURRENCY == 'Selected Currency') {
	$my_currency = $currency;
} else {
	$my_currency = substr(MODULE_PAYMENT_PAYPAL_IPN_CURRENCY, 5);
}
if (!in_array($my_currency, array('AUD', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', 'JPY', 'NOK', 'NZD', 'PLN', 'SEK', 'SGD', 'USD'))) {
		$my_currency = 'USD';
}

$parameters = array();

if ( (MODULE_PAYMENT_PAYPAL_IPN_TRANSACTION_TYPE == 'Per Item') && (MODULE_PAYMENT_PAYPAL_IPN_EWP_STATUS == 'False') ) {
	$parameters['cmd'] = '_cart';
	$parameters['upload'] = '1';
	////////////
	//handling
	//Von Ihnen erhobene Bearbeitungsgebuehren. Dieser Betrag ist unabhngig von der Menge. Derselbe Betrag wird berechnet, egal wie viele Posten die Bestellung umfasst.
	//Standardwert: keine Bearbeitungsgebuehren

	if (MODULE_ORDER_TOTAL_PAYPAL_TAX_CLASS > 0) {
				$cod_tax = tep_get_tax_rate(MODULE_ORDER_TOTAL_PAYPAL_TAX_CLASS, $order->billing['country']['id'], $order->billing['zone_id']);
				$cod_tax_description = tep_get_tax_description(MODULE_ORDER_TOTAL_PAYPAL_TAX_CLASS, $order->billing['country']['id'], $order->billing['zone_id']);
	}


	if (MODULE_PAYMENT_PAYPAL_FIX_FEE != '0') {
		$handling_amount = MODULE_PAYMENT_PAYPAL_FIX_FEE;
		$handling = $handling_amount;
	} else {
		$handling_amount = ($order->info['subtotal'] * MODULE_PAYMENT_PAYPAL_FEE);
		$handling = round(($handling_amount - $order->info['subtotal']),2);
	}
	//
	$_handling_tax = '1.'.$cod_tax;
	$handling = ($handling * $_handling_tax);
	//
	$parameters['handling'] = $handling;  //Bearbeitungsgebhren

	//////////
	for ($i=0, $n=sizeof($order->products); $i<$n; $i++) {
		$item = $i+1;

		$_products_tax = ('1.'.$order->products[$i]['tax']);
		$tax_value = ($_products_tax * $order->products[$i]['final_price']) - $order->products[$i]['final_price'];
		$_bruttopreis_item = $_products_tax * $order->products[$i]['final_price'];

		$parameters['item_name_' . $item] = ' Art.nr.: ' . $order->products[$i]['model'] .' '. $order->products[$i]['name']. 'GEB:'.$parameters['handling'];
		$parameters['amount_' . $item] = number_format($_bruttopreis_item * $currencies->get_value($my_currency), $currencies->get_decimal_places($my_currency));
		// Tax not Display. It not funktion
		$parameters['quantity_' . $item] = $order->products[$i]['qty'];
		$parameters['handling'] = $handling;  //Bearbeitungsgebhren
		if ($i == 0) {
			if (DISPLAY_PRICE_WITH_TAX == 'true') {
				$shipping_cost = $order->info['shipping_cost'];
			} else {
				$module = substr($shipping['id'], 0, strpos($shipping['id'], '_'));
				$shipping_tax = tep_get_tax_rate($GLOBALS[$module]->tax_class, $order->delivery['country']['id'], $order->delivery['zone_id']);
				$shipping_cost = $order->info['shipping_cost'] + tep_calculate_tax($order->info['shipping_cost'], $shipping_tax);
			}
			$shipping_cost = $shipping_cost + $handling;
			$parameters['shipping_' . $item] = number_format($shipping_cost * $currencies->get_value($my_currency), $currencies->get_decimal_places($my_currency));
		}

		if (isset($order->products[$i]['attributes'])) {
			for ($j=0, $n2=sizeof($order->products[$i]['attributes']); $j<$n2; $j++) {
				if (DOWNLOAD_ENABLED == 'true') {
					$attributes_query = "select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix, pad.products_attributes_maxdays, pad.products_attributes_maxcount , pad.products_attributes_filename
						from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa
						left join " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad
						on pa.products_attributes_id=pad.products_attributes_id
						where pa.products_id = '" . $order->products[$i]['id'] . "'
						and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "'
						and pa.options_id = popt.products_options_id
						and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "'
						and pa.options_values_id = poval.products_options_values_id
						and popt.language_id = '" . $languages_id . "'
						and poval.language_id = '" . $languages_id . "'";
					$attributes = tep_db_query($attributes_query);
				} else {
					$attributes = tep_db_query("select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa where pa.products_id = '" . $order->products[$i]['id'] . "' and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "' and pa.options_id = popt.products_options_id and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "' and pa.options_values_id = poval.products_options_values_id and popt.language_id = '" . $languages_id . "' and poval.language_id = '" . $languages_id . "'");
				}
				$attributes_values = tep_db_fetch_array($attributes);

				// Unfortunately PayPal only accepts two attributes per product, so the
				// third attribute onwards will not be shown at PayPal
				$parameters['on' . $j . '_' . $item] = $attributes_values['products_options_name'];
				$parameters['os' . $j . '_' . $item] = $attributes_values['products_options_values_name'];

			}
		}
	}

	$parameters['num_cart_items'] = $item;
} else {
	$parameters['cmd'] = '_xclick';
	$parameters['item_name'] = STORE_NAME.' Kunden-Nr.: ' . $_SESSION['customer_id'];
	$parameters['item_name'] .= '  Kundenname: '.$order->customer['firstname'] . ' ' . $order->customer['lastname'];
	// handling
	// Von Ihnen erhobene Bearbeitungsgebuehren. Dieser Betrag ist unabhaengig von der Menge. Derselbe Betrag wird berechnet, egal wie viele Posten die Bestellung umfasst.
	//Standardwert: keine Bearbeitungsgebuehren 
	if (MODULE_PAYMENT_PAYPAL_FIX_FEE != '0') {
		$handling_amount = MODULE_PAYMENT_PAYPAL_FIX_FEE;
		$handling = $handling_amount;
	} else {
		$handling_amount = ($order->info['total'] * MODULE_PAYMENT_PAYPAL_FEE);
		$handling = round(($handling_amount - $order->info['total']),2);
	}
	$parameters['handling'] = $handling;  //Bearbeitungsgebuehren
	////
	// Versand ohne Zuschlag fuer zahlweise paypal
	$parameters['shipping'] = number_format($order->info['shipping_cost'] * $currencies->get_value($my_currency), $currencies->get_decimal_places($my_currency));
	//
	// Versand mit Zuschlag fuer zahlweise paypal
	if(MOVE_TAX_TO_TOTAL_AMOUNT == 'True') {
		// PandA.nl move tax to total amount
		$parameters['tax'] = 0;
	} else {
	// default
		$parameters['tax'] = number_format($order->info['tax'] * $currencies->get_value($my_currency), $currencies->get_decimal_places($my_currency));
	}
}

$parameters['business'] = MODULE_PAYMENT_PAYPAL_IPN_ID;
if(MOVE_TAX_TO_TOTAL_AMOUNT == 'True') {
	// PandA.nl move tax to total amount
	$parameters['amount'] = number_format((($order->info['total'] - $order->info['shipping_cost'] - $handling )  * $currencies->get_value($my_currency)), $currencies->get_decimal_places($my_currency));
} else {
	// default
	$parameters['amount'] = number_format((($order->info['total'] - $order->info['shipping_cost'] - $handling - $order->info['tax']) * $currencies->get_value($my_currency)), $currencies->get_decimal_places($my_currency));
}

/////
$parameters['currency_code'] = $my_currency;
$parameters['invoice'] = substr($cart_PayPal_Standard_ID, strpos($cart_PayPal_Standard_ID, '-')+1);
$parameters['custom'] = $customer_id;
$parameters['no_shipping'] = '1';
$parameters['no_note'] = '1';
$parameters['handling'] = $handling;  //Bearbeitungsgebhren
$parameters['notify_url'] = tep_href_link('ext/modules/payment/paypal_ipn/paypal_ipn.php', '', 'SSL', false, false);
$parameters['return'] = tep_href_link(FILENAME_CHECKOUT_PROCESS, '', 'SSL');
$parameters['cancel_return'] = tep_href_link(FILENAME_CHECKOUT_PAYMENT, '', 'SSL');
$parameters['bn'] = $this->identifier;

if (tep_not_null(MODULE_PAYMENT_PAYPAL_IPN_PAGE_STYLE)) {
	$parameters['page_style'] = MODULE_PAYMENT_PAYPAL_IPN_PAGE_STYLE;
}


//// Encryption
if (MODULE_PAYMENT_PAYPAL_IPN_EWP_STATUS == 'True') {
	$parameters['cert_id'] = MODULE_PAYMENT_PAYPAL_IPN_EWP_CERT_ID;

	$random_string = rand(100000, 999999) . '-' . $customer_id . '-';

	$data = '';
	while (list($key, $value) = each($parameters)) {
		$data .= $key . '=' . $value . "\n";
	}

	$fp = fopen(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'data.txt', 'w');
	fwrite($fp, $data);
	fclose($fp);

	unset($data);

	if (function_exists('openssl_pkcs7_sign') && function_exists('openssl_pkcs7_encrypt')) {
		openssl_pkcs7_sign(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'data.txt', MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt', file_get_contents(MODULE_PAYMENT_PAYPAL_IPN_EWP_PUBLIC_KEY), file_get_contents(MODULE_PAYMENT_PAYPAL_IPN_EWP_PRIVATE_KEY), array('From' => MODULE_PAYMENT_PAYPAL_IPN_ID), PKCS7_BINARY);

		unlink(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'data.txt');

		// remove headers from the signature
		$signed = file_get_contents(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt');
		$signed = explode("\n\n", $signed);
		$signed = base64_decode($signed[1]);

		$fp = fopen(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt', 'w');
		fwrite($fp, $signed);
		fclose($fp);

		unset($signed);

		openssl_pkcs7_encrypt(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt', MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'encrypted.txt', file_get_contents(MODULE_PAYMENT_PAYPAL_IPN_EWP_PAYPAL_KEY), array('From' => MODULE_PAYMENT_PAYPAL_IPN_ID), PKCS7_BINARY);

		unlink(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt');

		// remove headers from the encrypted result
		$data = file_get_contents(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'encrypted.txt');
		$data = explode("\n\n", $data);
		$data = '-----BEGIN PKCS7-----' . "\n" . $data[1] . "\n" . '-----END PKCS7-----';

		unlink(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'encrypted.txt');
	} else {
		exec(MODULE_PAYMENT_PAYPAL_IPN_EWP_OPENSSL . ' smime -sign -in ' . MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'data.txt -signer ' . MODULE_PAYMENT_PAYPAL_IPN_EWP_PUBLIC_KEY . ' -inkey ' . MODULE_PAYMENT_PAYPAL_IPN_EWP_PRIVATE_KEY . ' -outform der -nodetach -binary > ' . MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt');
		unlink(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'data.txt');

		exec(MODULE_PAYMENT_PAYPAL_IPN_EWP_OPENSSL . ' smime -encrypt -des3 -binary -outform pem ' . MODULE_PAYMENT_PAYPAL_IPN_EWP_PAYPAL_KEY . ' < ' . MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt > ' . MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'encrypted.txt');
		unlink(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'signed.txt');

		$fh = fopen(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'encrypted.txt', 'rb');
		$data = fread($fh, filesize(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'encrypted.txt'));
		fclose($fh);

		unlink(MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY . '/' . $random_string . 'encrypted.txt');
	}

	$process_button_string = tep_draw_hidden_field('cmd', '_s-xclick') .
	tep_draw_hidden_field('encrypted', $data);

	unset($data);
} else {
	while (list($key, $value) = each($parameters)) {
		echo tep_draw_hidden_field($key, $value);
	}
}

return $process_button_string;
 }	//end function process_button()




function before_process() {
 global $customer_id, $order, $order_totals, $sendto, $billto, $languages_id, $payment, $currencies, $cart, $cart_PayPal_Standard_ID;
 global $$payment;

 $order_id = substr($cart_PayPal_Standard_ID, strpos($cart_PayPal_Standard_ID, '-')+1);

 $check_query = tep_db_query("select orders_status from " . TABLE_ORDERS . " where orders_id = '" . (int)$order_id . "'");
 if (tep_db_num_rows($check_query)) {
$check = tep_db_fetch_array($check_query);

if ($check['orders_status'] == MODULE_PAYMENT_PAYPAL_IPN_PREPARE_ORDER_STATUS_ID) {
  $sql_data_array = array('orders_id' => $order_id,
						  'orders_status_id' => MODULE_PAYMENT_PAYPAL_IPN_PREPARE_ORDER_STATUS_ID,
						  'date_added' => 'now()',
						  'customer_notified' => '0',
						  'comments' => '');

  tep_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);
}
 }

 tep_db_query("update " . TABLE_ORDERS . " set orders_status = '" . (MODULE_PAYMENT_PAYPAL_IPN_ORDER_STATUS_ID > 0 ? (int)MODULE_PAYMENT_PAYPAL_IPN_ORDER_STATUS_ID : (int)DEFAULT_ORDERS_STATUS_ID) . "', last_modified = now() where orders_id = '" . (int)$order_id . "'");

 $sql_data_array = array('orders_id' => $order_id,
					  'orders_status_id' => (MODULE_PAYMENT_PAYPAL_IPN_ORDER_STATUS_ID > 0 ? (int)MODULE_PAYMENT_PAYPAL_IPN_ORDER_STATUS_ID : (int)DEFAULT_ORDERS_STATUS_ID),
					  'date_added' => 'now()',
					  'customer_notified' => (SEND_EMAILS == 'true') ? '1' : '0',
					  'comments' => $order->info['comments']);

 tep_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array);

// initialized for the email confirmation
 $products_ordered = '';
 $subtotal = 0;
 $total_tax = 0;

 for ($i=0, $n=sizeof($order->products); $i<$n; $i++) {
// Stock Update - Joao Correia
if (STOCK_LIMITED == 'true') {
  if (DOWNLOAD_ENABLED == 'true') {
	$stock_query_raw = "SELECT products_quantity, pad.products_attributes_filename
			FROM " . TABLE_PRODUCTS . " p
			LEFT JOIN " . TABLE_PRODUCTS_ATTRIBUTES . " pa
			ON p.products_id=pa.products_id
			LEFT JOIN " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad
			ON pa.products_attributes_id=pad.products_attributes_id
			WHERE p.products_id = '" . tep_get_prid($order->products[$i]['id']) . "'";
	// Will work with only one option for downloadable products
	// otherwise, we have to build the query dynamically with a loop
	$products_attributes = $order->products[$i]['attributes'];
	if (is_array($products_attributes)) {
	  $stock_query_raw .= " AND pa.options_id = '" . $products_attributes[0]['option_id'] . "' AND pa.options_values_id = '" . $products_attributes[0]['value_id'] . "'";
	}
	$stock_query = tep_db_query($stock_query_raw);
  } else {
	$stock_query = tep_db_query("select products_quantity from " . TABLE_PRODUCTS . " where products_id = '" . tep_get_prid($order->products[$i]['id']) . "'");
  }	//end if download_enabled
  if (tep_db_num_rows($stock_query) > 0) {
	$stock_values = tep_db_fetch_array($stock_query);
	// do not decrement quantities if products_attributes_filename exists
	if ((DOWNLOAD_ENABLED != 'true') || (!$stock_values['products_attributes_filename'])) {
	  $stock_left = $stock_values['products_quantity'] - $order->products[$i]['qty'];
	} else {
	  $stock_left = $stock_values['products_quantity'];
	}
	tep_db_query("update " . TABLE_PRODUCTS . " set products_quantity = '" . $stock_left . "' where products_id = '" . tep_get_prid($order->products[$i]['id']) . "'");
	if ( ($stock_left < 1) && (STOCK_ALLOW_CHECKOUT == 'false') ) {
	  tep_db_query("update " . TABLE_PRODUCTS . " set products_status = '0' where products_id = '" . tep_get_prid($order->products[$i]['id']) . "'");
	}
  }	//end if stock > 0
}	//end if stock_limited

// Update products_ordered (for bestsellers list)
 	tep_db_query("update " . TABLE_PRODUCTS . " set products_ordered = products_ordered + " . sprintf('%d', $order->products[$i]['qty']) . " where products_id = '" . tep_get_prid($order->products[$i]['id']) . "'");


//------insert customer choosen option to order--------
$attributes_exist = '0';
$products_ordered_attributes = '';
if (isset($order->products[$i]['attributes'])) {
	$attributes_exist = '1';
	for ($j=0, $n2=sizeof($order->products[$i]['attributes']); $j<$n2; $j++) {
	  if (DOWNLOAD_ENABLED == 'true') {
		$attributes_query = "select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix, pad.products_attributes_maxdays, pad.products_attributes_maxcount , pad.products_attributes_filename
			from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa
			left join " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad
			on pa.products_attributes_id=pad.products_attributes_id
			where pa.products_id = '" . $order->products[$i]['id'] . "'
			and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "'
			and pa.options_id = popt.products_options_id
			and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "'
			and pa.options_values_id = poval.products_options_values_id
			and popt.language_id = '" . $languages_id . "'
			and poval.language_id = '" . $languages_id . "'";
		$attributes = tep_db_query($attributes_query);
	  } else {
		$attributes = tep_db_query("select popt.products_options_name, poval.products_options_values_name, pa.options_values_price, pa.price_prefix from " . TABLE_PRODUCTS_OPTIONS . " popt, " . TABLE_PRODUCTS_OPTIONS_VALUES . " poval, " . TABLE_PRODUCTS_ATTRIBUTES . " pa where pa.products_id = '" . $order->products[$i]['id'] . "' and pa.options_id = '" . $order->products[$i]['attributes'][$j]['option_id'] . "' and pa.options_id = popt.products_options_id and pa.options_values_id = '" . $order->products[$i]['attributes'][$j]['value_id'] . "' and pa.options_values_id = poval.products_options_values_id and popt.language_id = '" . $languages_id . "' and poval.language_id = '" . $languages_id . "'");
	  }	//end if download_enabled
	  $attributes_values = tep_db_fetch_array($attributes);

	  $products_ordered_attributes .= "\n\t" . $attributes_values['products_options_name'] . ' ' . tep_decode_specialchars($order->products[$i]['attributes'][$j]['value']);
	}	//end for attributes
}	//end if isset

//------insert customer choosen option eof ----
$total_weight += ($order->products[$i]['qty'] * $order->products[$i]['weight']);
$total_tax += tep_calculate_tax($total_products_price, $products_tax) * $order->products[$i]['qty'];
$total_cost += $total_products_price;

$products_ordered .= $order->products[$i]['qty'] . ' x ' . $order->products[$i]['name'] . ' (' . $order->products[$i]['model'] . ') = ' . $currencies->display_price($order->products[$i]['final_price'], $order->products[$i]['tax'], $order->products[$i]['qty']) . $products_ordered_attributes .  "\n";

 }	//end for (loop through products)

 // lets start with the email confirmation
$email_order =		STORE_NAME 					. "\n" .
					EMAIL_SEPARATOR 			. "\n" .
					EMAIL_TEXT_ORDER_NUMBER 	. '' . $order_id . "\n" .
					EMAIL_TEXT_INVOICE_URL 		. ' ' . tep_href_link(FILENAME_ACCOUNT_HISTORY_INFO, 'order_id=' . $order_id, 'SSL', false) . "\n" .
					EMAIL_TEXT_DATE_ORDERED 	. ' ' . strftime(DATE_FORMAT_LONG) . "\n\n";
if ($order->info['comments']) {
	$email_order .= tep_db_output($order->info['comments']) . "\n\n";
}
$email_order .= 	EMAIL_TEXT_PRODUCTS 		. "\n" .
					EMAIL_SEPARATOR 			. "\n" .
					$products_ordered 			.
					EMAIL_SEPARATOR 			. "\n";

for ($i=0, $n=sizeof($order_totals); $i<$n; $i++) {
	$email_order .= strip_tags($order_totals[$i]['title']) . ' ' . 
					strip_tags($order_totals[$i]['text']) . "\n";
}

if ($order->content_type != 'virtual') {
	$email_order .= "\n" . 
					EMAIL_TEXT_DELIVERY_ADDRESS . "\n" .
					EMAIL_SEPARATOR 			. "\n" .
					tep_address_label($customer_id, $sendto, 0, '', "\n") . "\n";
}
$email_order .= 	"\n" . 
					EMAIL_TEXT_BILLING_ADDRESS 	. "\n" .
					EMAIL_SEPARATOR 			. "\n" .
					tep_address_label($customer_id, $billto, 0, '', "\n") . "\n\n";

if (is_object($$payment)) {
	$email_order .= EMAIL_TEXT_PAYMENT_METHOD 	. "\n" .
					EMAIL_SEPARATOR 			. "\n";
	$payment_class = $$payment;
	$email_order .= $payment_class->title 		. "\n\n";
	if ($payment_class->email_footer) {
		$email_order .= $payment_class->email_footer . "\n\n";
	}
}

tep_mail($order->customer['firstname'] . ' ' . $order->customer['lastname'], $order->customer['email_address'], EMAIL_TEXT_SUBJECT . EMAIL_TEXT_ORDER_NUMBER . ': ' . $order_id, $email_order, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS);

// send emails to other people
if (SEND_EXTRA_ORDER_EMAILS_TO != '') {
	tep_mail('', SEND_EXTRA_ORDER_EMAILS_TO, EMAIL_TEXT_SUBJECT . EMAIL_TEXT_ORDER_NUMBER . ': ' . $order_id, $email_order, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS);
}

// load the after_process function from the payment modules
$this->after_process();

$cart->reset(true);

// unregister session variables used during checkout
tep_session_unregister('sendto');
tep_session_unregister('billto');
tep_session_unregister('shipping');
tep_session_unregister('payment');
tep_session_unregister('comments');

tep_session_unregister('cart_PayPal_Standard_ID');

tep_redirect(tep_href_link(FILENAME_CHECKOUT_SUCCESS, '', 'SSL'));

}	//end function before_process()



function after_process() {
return false;
}	//end function after_process()

function output_error() {
return false;
}	//end function output_error()

function check() {
if (!isset($this->_check)) {
	$check_query = tep_db_query("select configuration_value from " . TABLE_CONFIGURATION . " where configuration_key = 'MODULE_PAYMENT_PAYPAL_IPN_STATUS'");
	$this->_check = tep_db_num_rows($check_query);
}
return $this->_check;
}	//end function check()

function install() {
$check_query = tep_db_query("select orders_status_id from " . TABLE_ORDERS_STATUS . " where orders_status_name = 'Preparing [PayPal IPN]' limit 1");

if (tep_db_num_rows($check_query) < 1) {
	$status_query = tep_db_query("select max(orders_status_id) as status_id from " . TABLE_ORDERS_STATUS);
	$status = tep_db_fetch_array($status_query);

	$status_id = $status['status_id']+1;

	$languages = tep_get_languages();

	foreach ($languages as $lang) {
		tep_db_query("insert into " . TABLE_ORDERS_STATUS . " (orders_status_id, language_id, orders_status_name) values ('" . $status_id . "', '" . $lang['id'] . "', 'Preparing [PayPal IPN]')");
	}
} else {
	$check = tep_db_fetch_array($check_query);

	$status_id = $check['orders_status_id'];
}

tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Enable PayPal IPN Module', 'MODULE_PAYMENT_PAYPAL_IPN_STATUS', 'False', 'Do you want to accept PayPal IPN payments?', '6', '3', 'tep_cfg_select_option(array(\'True\', \'False\'), ', now())");
// bof PandA.nl move tax to total amount
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Move tax to total amount', 'MOVE_TAX_TO_TOTAL_AMOUNT', 'True', 'Do you want to move the tax to the total amount? If true PayPal will allways show the total amount including tax. (needs Aggregate i.s.o. Per Item to function)', '6', '4', 'tep_cfg_select_option(array(\'True\', \'False\'), ', now())");
// eof PandA.nl move tax to total amount

// fr fee
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('PayPal Fee', 'MODULE_PAYMENT_PAYPAL_FEE" . $i."', '0', 'Insert a % PayPal feel - 1.04 = 4%', '6', '0', now())");

tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('PayPal Fix Fee', 'MODULE_PAYMENT_PAYPAL_FIX_FEE" . $i."', '0', 'Insert a PayPal fix feel', '6', '0', now())");
// Ende fr fee

tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('E-Mail Address', 'MODULE_PAYMENT_PAYPAL_IPN_ID', '', 'The e-mail address to use for the PayPal IPN service', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Transaction Currency', 'MODULE_PAYMENT_PAYPAL_IPN_CURRENCY', 'Selected Currency', 'The currency to use for transactions', '6', '6', 'tep_cfg_select_option(array(\'Selected Currency\',\'Only CHF\',\'Only CAD\',\'Only EUR\',\'Only GBP\',\'Only JPY\'), ', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Sort order of display.', 'MODULE_PAYMENT_PAYPAL_IPN_SORT_ORDER', '0', 'Sort order of display. Lowest is displayed first.', '6', '0', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, use_function, set_function, date_added) values ('Payment Zone', 'MODULE_PAYMENT_PAYPAL_IPN_ZONE', '0', 'If a zone is selected, only enable this payment method for that zone.', '6', '2', 'tep_get_zone_class_title', 'tep_cfg_pull_down_zone_classes(', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, use_function, date_added) values ('Set Preparing Order Status', 'MODULE_PAYMENT_PAYPAL_IPN_PREPARE_ORDER_STATUS_ID', '" . $status_id . "', 'Set the status of prepared orders made with this payment module to this value', '6', '0', 'tep_cfg_pull_down_order_statuses(', 'tep_get_order_status_name', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, use_function, date_added) values ('Set PayPal Acknowledged Order Status', 'MODULE_PAYMENT_PAYPAL_IPN_ORDER_STATUS_ID', '0', 'Set the status of orders made with this payment module to this value', '6', '0', 'tep_cfg_pull_down_order_statuses(', 'tep_get_order_status_name', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Transaction Type', 'MODULE_PAYMENT_PAYPAL_IPN_TRANSACTION_TYPE', 'Per Item', 'Send individual items to PayPal or aggregate all as one total item?', '6', '6', 'tep_cfg_select_option(array(\'Per Item\',\'Aggregate\'), ', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Gateway Server', 'MODULE_PAYMENT_PAYPAL_IPN_GATEWAY_SERVER', 'Testing', 'Use the testing (sandbox) or live gateway server for transactions?', '6', '6', 'tep_cfg_select_option(array(\'Testing\',\'Live\'), ', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Page Style', 'MODULE_PAYMENT_PAYPAL_IPN_PAGE_STYLE', '', 'The page style to use for the transaction procedure (defined at your PayPal Profile page)', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Debug E-Mail Address', 'MODULE_PAYMENT_PAYPAL_IPN_DEBUG_EMAIL', '', 'All parameters of an Invalid IPN notification will be sent to this email address if one is entered.', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, set_function, date_added) values ('Enable Encrypted Web Payments', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_STATUS', 'False', 'Do you want to enable Encrypted Web Payments?', '6', '3', 'tep_cfg_select_option(array(\'True\', \'False\'), ', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Your Private Key', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_PRIVATE_KEY', '', 'The location of your Private Key to use for signing the data. (*.pem)', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Your Public Certificate', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_PUBLIC_KEY', '', 'The location of your Public Certificate to use for signing the data. (*.pem)', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('PayPals Public Certificate', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_PAYPAL_KEY', '', 'The location of the PayPal Public Certificate for encrypting the data.', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Your PayPal Public Certificate ID', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_CERT_ID', '', 'The Certificate ID to use from your PayPal Encrypted Payment Settings Profile.', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('Working Directory', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY', '', 'The working directory to use for temporary files. (trailing slash needed)', '6', '4', now())");
tep_db_query("insert into " . TABLE_CONFIGURATION . " (configuration_title, configuration_key, configuration_value, configuration_description, configuration_group_id, sort_order, date_added) values ('OpenSSL Location', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_OPENSSL', '/usr/bin/openssl', 'The location of the openssl binary file.', '6', '4', now())");

}	//end function install()

function remove() {
tep_db_query("delete from " . TABLE_CONFIGURATION . " where configuration_key in ('" . implode("', '", $this->keys()) . "')");
}	//end function remove()

function keys() {
// PandA.nl move tax to total amount added: ", 'MOVE_TAX_TO_TOTAL_AMOUNT'"
return array('MODULE_PAYMENT_PAYPAL_IPN_STATUS', 'MOVE_TAX_TO_TOTAL_AMOUNT', 'MODULE_PAYMENT_PAYPAL_IPN_ID', 'MODULE_PAYMENT_PAYPAL_IPN_CURRENCY','MODULE_PAYMENT_PAYPAL_FEE', 'MODULE_PAYMENT_PAYPAL_FIX_FEE', 'MODULE_PAYMENT_PAYPAL_IPN_ZONE', 'MODULE_PAYMENT_PAYPAL_IPN_PREPARE_ORDER_STATUS_ID', 'MODULE_PAYMENT_PAYPAL_IPN_ORDER_STATUS_ID', 'MODULE_PAYMENT_PAYPAL_IPN_GATEWAY_SERVER', 'MODULE_PAYMENT_PAYPAL_IPN_TRANSACTION_TYPE', 'MODULE_PAYMENT_PAYPAL_IPN_PAGE_STYLE', 'MODULE_PAYMENT_PAYPAL_IPN_DEBUG_EMAIL', 'MODULE_PAYMENT_PAYPAL_IPN_SORT_ORDER', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_STATUS', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_PRIVATE_KEY', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_PUBLIC_KEY', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_PAYPAL_KEY', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_CERT_ID', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_WORKING_DIRECTORY', 'MODULE_PAYMENT_PAYPAL_IPN_EWP_OPENSSL');
}	//end function keys()
}	//end class Paypal_ipn


?>

Link to comment
Share on other sites

  • 2 weeks later...

Hello,

 

I've a problem with IPN. When I config payment and click on the button to come back on my shop, it says my order was successfully recorded.

It's the case but ...

Payment status is already on pending, no information in comments from paypal.

 

I've turned ON register globals and add a php.ini file in the paypal ipn ext folder but it's the same thing.

 

Configuration :

- Local installation (Windows XP)

- Wamp server with : Apache 2.2.11 // PHP 5.2.5 // MySQL 5.1.36

- OSC 2.2rc2a

- Paypal IPN v2.3.4.6

 

Thanks for help !

Edited by theclem35
Link to comment
Share on other sites

Payment status is already on pending, no information in comments from paypal.

 

This means that IPN is not reaching Your site.As IPN code is supposed to update order status and send email.

 

The probable reasons:

Improper IPN path.

IPN profile setting in Paypal disabled.

 

SO first step is make sure IPN are reaching where they should.

 

Satish

Ask/Skype for Free osCommerce value addon/SEO suggestion tips for your site.

 

Check My About US For who am I and what My company does.

Link to comment
Share on other sites

  • 3 weeks later...

Hi, I'm using the Paypal IPN 2.3.4.7. I think I installed in correctly, but when I get to the paypal page, it shows a zero total and asks for an amount to be entered. Has anyone else encountered this issue and is there a fix? Basically, what am I doing wrong?

 

I am having the same problem. Any fix?

Link to comment
Share on other sites

  • 3 weeks later...

hi there,

 

i have just installed the paypal ipn addon version 2.3.4.7 (4th march 09) it works and i can recieve payments but the order status is not updating and not sending out emails.

 

i have searched the forums and there are many similar posts.

 

how can i fix this? i have no idea whats causing the problem. when i look at the IPN history in my paypal account and then message id i get retrying errors and the Notification URL is showing up as :https://www.mysite.co.uk/ipn.php?language=english i thought the ipn.php was supposed to be loacted in the ext/etc/etc?

 

i also have a dedicated ssl on the site if that makes a difference? the encrypted option is set to off.

 

thanks in advance

 

dan

Link to comment
Share on other sites

  • 2 weeks later...

Having the same problem as in the post above. Not receiving any e-mails and the Comments field also remains blank. This is a serious problem for my shop.

 

As for my previous problem, fixed it by changing the Transaction Type.

Link to comment
Share on other sites

Hi,

I having problems downloading tha latest version of this addon.

 

Paypal_IPN 3.0 next Version GER

 

It keeps coming up as blocked file and i can not extract. The other files i can download and extract as normal. Unblocking the file is not working as well.

 

Any suggestions.

Link to comment
Share on other sites

Never mind, i found that PayPal IPN Module 2.3.4.7 is the latest for english language. The later ones are germen only.

 

I'm a bit confused. osCrc2a has a Paypal module (catalog>ext>modules>payment>paypal>standard_ipn.php). Isn't this all that's needed for IPN?

 

Ken

Link to comment
Share on other sites

I'm a bit confused. osCrc2a has a Paypal module (catalog>ext>modules>payment>paypal>standard_ipn.php). Isn't this all that's needed for IPN?

 

Ken

 

 

Ken - your confusion is understandable, but your assumption is wrong.

 

How it works is like this -

 

- Once the customer has entered checkout and chosen shipping, and shipping options (gift wrap, insurance etc), the next step is the payment method

 

- checkout_payments.php checks the customer address against the tax zones (geographic areas) for each payment method you have installed

- if the customer is in the allowed geo-zone for PayPal, it offers payPal as a payment method

- if the customer chooses PayPal, and goes to order confirmation, then checkout_confirmation.php assembles all the customer and order details and basically asks the customer if it is all correct, or if they want to edit anything. If they confirm it, then all that data is passed to /includes/modules/paypal_ipn.php

- /includes/modules/paypal_ipn.php then passes that data to PayPal and redirects the customer to the PayPal website to sign in and choose funding source etc. This part of the IPN also passes to PayPal the data return path for confirming payment has been transferred to the merchant's PayPal account.

- once the customer has done what's needed, and PayPal have moved the money to the Merchant's PayPal account, then the PayPal IPN server squirts a data stream back to your site - but the returned data goes to /ext/.../paypal_ipn.php NOT to /includes/modules/paypal_ipn.php

 

- /ext/.../paypal_ipn.php then records the payment details into the database and updates the order status and transaction record, I think it also confirms back to PayPal that the payment data was received from them, and failing to do so is one common cause of payment failures.

 

So the IPN has two parts -

- the first paypal_ipn.php which is the payment methods module, and sends the customer and order details to PayPal, this is in /includes/modules/

- the second paypal_ipn.php which is the response receiver and order updater, this one is in the /ext.../ folder

 

There is a third file in /includes/languages/{language-name}/modules/payments/paypal_ipn.php which controls the displayed language strings for the checkout pages and the admin payment modules page.

 

So .... nope, the /ext/.../paypal_ipn.php is not all that osC needs - it needs all three files for any individual payment processor's IPN in order for that payment processor's IPN to function - regardless of which payment processor you're using (e.g. MoneyBookers, NoChex, AlertPay, ppPay etc.n Google Checkout is a little different, as is Amazon Payments, but the idea is similar). Each set of files will be named after the payment processor they handle - e.g. MoneyBookers.php and so on.

 

Hope that helps?

 

Gaz

 

 

 

If any of the above is wrong, can someone post the correct process or logic - I think there's a few people get stuck with how the IPN's work.

Wearing a seatbelt prevents head injuries when the computer crashes - - - Yeah Right!!! - not in this office.

Link to comment
Share on other sites

Ken - your confusion is understandable, but your assumption is wrong.

 

How it works is like this -

 

- if the customer chooses PayPal, and goes to order confirmation, then checkout_confirmation.php assembles all the customer and order details and basically asks the customer if it is all correct, or if they want to edit anything. If they confirm it, then all that data is passed to /includes/modules/paypal_ipn.php

 

Whoa - stop right there! In osC2rc2a there is no such code file as /includes/modules/paypal_ipn.php! So, having installed paypal website payments standard, I appear to have everything set up? All I appear to need to do is set up my profile to return to catalog/ext/modules/payment/paypal/standard_ipn.php?

 

Ken

Link to comment
Share on other sites

Whoa - stop right there! In osC2rc2a there is no such code file as /includes/modules/paypal_ipn.php! So, having installed paypal website payments standard, I appear to have everything set up? All I appear to need to do is set up my profile to return to catalog/ext/modules/payment/paypal/standard_ipn.php?

 

Ken

 

OK, fair call - my bad, I was using generally understood shorthand pathing ... try this - /includes/modules/payment/paypal_ipn.php

 

Just for the record in RC2a ...

 

catalog/ext/modules/payment/paypal/standard_ipn.php

hooks up with

catalog/includes/modules/payment/paypal_standard.php

and

catalog/includes/languages/english/modules/payment/paypal_standard.php

 

Therefore as I originally answered - No your premise of the single file in /ext/.... is still incorrect, and my original answer SHOULD have given you enough information to track down the other two files of the trio. The overall process I described remains the same for the standard_ipn in RC2a, it's just handled slightly differently by the code.

 

Lastly, there perfectly well could be an /includes/modules/paypal_ipn.php in RC2a on a NON STANDARD install, which is actually the norm for osC sites - i.e. NON-standard (nobody uses it straight from the box and runs with it that way as a production site - that's just begging for hackers to visit your install).

 

All I appear to need to do is set up my profile to return to catalog/ext/modules/payment/paypal/standard_ipn.php?

 

You could do - if you only have one website, will only ever have one website, and that one website will only ever be the sole source of payments into your PayPal account (i.e. no instant payment buttons, no auction sites, no WordPress eShops etc etc etc) - otherwise, read the help files and the instructions - within osC, and at PayPal - enter your /ext/... path in there and EVERY IPN or PayPal Express callback confirmation from your PayPal account will be sent to your website, many will be irresolvable (because they did not originate from the IPN on your site) and could cause server resource abuse due to SQL query loops and timeouts, and your hosting account could get suspended (I've heard several stories of it happening).

 

If you ask for advice, listen to experience, and read between the lines if it's not an exact fit with your situation. Adapt what you're told to what you're experiencing ... and I do hope that's not "teaching granny to suck eggs"?

 

Alternatively - follow the osC forum credo - and post your full site and server spec with version numbers, plus screen shots of errors etc, then people who help you are not firing blindly and generically when they offer that help.

Wearing a seatbelt prevents head injuries when the computer crashes - - - Yeah Right!!! - not in this office.

Link to comment
Share on other sites

I build a shop for one of my customer. Now this shop receives 300-400 small orders each day. Many orders are done through paypal.

I am using paypal ipn latest version but have a big problem which is a real issue when receiving so many orders;

I know that we can not prevend a customer closing the paypal window after payment has been made.

But there must be a way that directly after paypals displays the screen where it confirms that the payment has been made the shop also

receives a message for this payment. The IPN notification is only send when customer goes back to the shop.

There must be something that can be done about this because manual check of all payments is impossible.

Hope someone can help.

Link to comment
Share on other sites

OK, fair call - my bad, I was using generally understood shorthand pathing ... try this - /includes/modules/payment/paypal_ipn.php

 

There is no file paypal_ipn.php anywhere on the site.

 

Just for the record in RC2a ...

 

catalog/ext/modules/payment/paypal/standard_ipn.php

hooks up with

catalog/includes/modules/payment/paypal_standard.php

and

catalog/includes/languages/english/modules/payment/paypal_standard.php

 

Yes, that's all in position.

 

Lastly, there perfectly well could be an /includes/modules/paypal_ipn.php in RC2a on a NON STANDARD install, which is actually the norm for osC sites

 

There isn't on the two test sites I've set up using the full installation as downloaded.

 

You could do - if you only have one website, will only ever have one website, and that one website will only ever be the sole source of payments into your PayPal account (i.e. no instant payment buttons, no auction sites, no WordPress eShops etc etc etc) - otherwise, read the help files and the instructions - within osC, and at PayPal - enter your /ext/... path in there and EVERY IPN or PayPal Express callback confirmation from your PayPal account will be sent to your website, many will be irresolvable (because they did not originate from the IPN on your site) and could cause server resource abuse due to SQL query loops and timeouts, and your hosting account could get suspended (I've heard several stories of it happening).

 

According to paypal, if the calling program sends return URLs in the variables "notify_url" and "return" these will override the default return URLs set up in "Profile". This seems to work ok in paypal sandbox test environment for the two test osC sites I'm playing with at the moment.

 

As to "teaching granny to suck eggs", no problem. I'm always happy to learn from anybody with more experience than I. I hope you don't mind me pursuing what seems to be a bit anomalous.

Link to comment
Share on other sites

 

According to paypal, if the calling program sends return URLs in the variables "notify_url" and "return" these will override the default return URLs set up in "Profile". This seems to work ok in paypal sandbox test environment for the two test osC sites I'm playing with at the moment.

 

 

I've now tried this with two live sites accessing the same Paypal account and with the variables "reply" and "notify_url" properly passed from each site. Absolutely perfect - no confusion. Both return to the correct checkout process and orders are correctly allocated and the shopping baskets cleared.

 

Hope this helps.

 

Ken

Link to comment
Share on other sites

I have two OS Commerce shopping carts, both running Paypal IPN. One works fine. When the customer picks Paypal, they are given a split screen at Paypal.com where they can either enter there credit card info, or log into there Paypal account.

 

On the other cart, same setup but no matter what I do the customer can only log into Paypal.

 

Both Paypal accounts are business accounts, both can take credit cards.

 

I called Paypal on the not working cart and they said its something in my shopping cart, not on there end but I can't find anything wrong.

 

What am I missing here?

 

Thanks guys.

Link to comment
Share on other sites

I have two OS Commerce shopping carts, both running Paypal IPN. One works fine. When the customer picks Paypal, they are given a split screen at Paypal.com where they can either enter there credit card info, or log into there Paypal account.

 

On the other cart, same setup but no matter what I do the customer can only log into Paypal.

 

Both Paypal accounts are business accounts, both can take credit cards.

 

I called Paypal on the not working cart and they said its something in my shopping cart, not on there end but I can't find anything wrong.

 

What am I missing here?

 

Thanks guys.

 

Log into your PayPal Account

 

Go To Profile>> Website Payment Preferences >> PayPal Account Optional. This should be checked "On"

 

Martin

Live shop Phoenix 1.0.8.4 on PHP 7.4 Working my way up the versions.

Link to comment
Share on other sites

Log into your PayPal Account

 

Go To Profile>> Website Payment Preferences >> PayPal Account Optional. This should be checked "On"

 

Martin

 

I totally agree with what you say Martin. However, isn't it strange that one site was showing the split screen? I'd have thought that if "Paypal Account Optional" was 'off' then neither of his sites would have seen the split screen?

 

Ken

Link to comment
Share on other sites

Hello forum.

 

I have a fairly big problem with getting the order status updates happening automatic. I am using the standard build in paypal module. And suddenly on 1 september at 12:10 the order updates stopped working. I have been testing a lot now and come to the following facts:

 

the ipn.php writes a ipn.txt with all the data from paypal which seems correct

 

in ipn history on paypal site all the notifications cause a http response code 500

when i discard the following check in ipn.php:(by adding || 1 == 1 to the if condition)

 

//Check both the receiver_email and business ID fields match

if (!$ipn->validateReceiverEmail(MODULE_PAYMENT_PAYPAL_ID,MODULE_PAYMENT_PAYPAL_BUSINESS_ID)) $ipn->dienice('500');

 

the notification does not cause the http response 500 error.

 

but still the order status is not updating automatic.

 

This is still displaying at the order status:

 

No PayPal Transaction Information Available(d4c201db922b26f9113c616dxxxxxxx)

 

I hope someone has encountered this problem before and can help me on my way with solving it.

 

Thanks for your time,

 

Chemist Hull

Link to comment
Share on other sites

I totally agree with what you say Martin. However, isn't it strange that one site was showing the split screen? I'd have thought that if "Paypal Account Optional" was 'off' then neither of his sites would have seen the split screen?

 

Ken

 

Yes Ken, but the OP infers that he has 2 PayPal accounts so perhaps one has the incorrect settings.

 

Martin

Live shop Phoenix 1.0.8.4 on PHP 7.4 Working my way up the versions.

Link to comment
Share on other sites

I have installed this contribution, but I can't quite seem to get it all figured out.

 

When I process the initial transaction and transfer to Paypal, I can see where an order is being created in my orders list - say order #190 for .01 in "preparing [paypal ipn] status. Then, I walk through the Paypal screens and when I click on the "confirm order" button in Paypal, a new order record is generated for me - order #191 fo .01. Everything about order #191 is identical to #190 including the preparing status.

 

If anyone has any ideas about what might be happening, or even thoughts about where I can begin looking, I would really appreciate it. I'm really pulling my hair out, and I really don't have that much more to spare!

 

Thanks!

Link to comment
Share on other sites

Yes Ken, but the OP infers that he has 2 PayPal accounts so perhaps one has the incorrect settings.

 

Martin

 

Ah yes, of course. I missed the implication. Been so involved in settings for two carts accessing the same Paypal account my mind was pre-focussed. :blush:

 

Ken

Edited by oldcelt
Link to comment
Share on other sites

I have installed this contribution, but I can't quite seem to get it all figured out.

 

When I process the initial transaction and transfer to Paypal, I can see where an order is being created in my orders list - say order #190 for .01 in "preparing [paypal ipn] status. Then, I walk through the Paypal screens and when I click on the "confirm order" button in Paypal, a new order record is generated for me - order #191 fo .01. Everything about order #191 is identical to #190 including the preparing status.

 

If anyone has any ideas about what might be happening, or even thoughts about where I can begin looking, I would really appreciate it. I'm really pulling my hair out, and I really don't have that much more to spare!

 

Thanks!

 

 

Strange - I thought the confirm order button was on catalogue/checkout_confirmation.php - not at PayPal

 

I say that because it sounds like the checkout_confirmation order button is being activated twice or the checkout_confirmation.php page is being loaded twice (osC commits the order to the database when that page is loaded - including at a page refresh or if the back button is used to escape paypal). That's the most common cause of multiple records and order numbers for the same order.

 

Example - if your site "home currency" is not the same as the transaction currency, nor the "home currency" for your PayPal account, then rounding errors occur during the currency conversions, and even if only a few pennies, some customers will use the back button to check that the PayPal total is truly not the same as the one shown by checkout_confirmation - that will cause two orders to be logged on their account, in the database, and on the admin/Customers/orders page.

 

Other than that, I'd recommend you increase the level of server error logging to try and capture more information to point you to the culprit.

 

Sorry I can't be more helpful.

 

Gaz

Wearing a seatbelt prevents head injuries when the computer crashes - - - Yeah Right!!! - not in this office.

Link to comment
Share on other sites

Strange - I thought the confirm order button was on catalogue/checkout_confirmation.php - not at PayPal

 

I say that because it sounds like the checkout_confirmation order button is being activated twice or the checkout_confirmation.php page is being loaded twice (osC commits the order to the database when that page is loaded - including at a page refresh or if the back button is used to escape paypal). That's the most common cause of multiple records and order numbers for the same order.

 

Example - if your site "home currency" is not the same as the transaction currency, nor the "home currency" for your PayPal account, then rounding errors occur during the currency conversions, and even if only a few pennies, some customers will use the back button to check that the PayPal total is truly not the same as the one shown by checkout_confirmation - that will cause two orders to be logged on their account, in the database, and on the admin/Customers/orders page.

 

Other than that, I'd recommend you increase the level of server error logging to try and capture more information to point you to the culprit.

 

Sorry I can't be more helpful.

 

Gaz

 

 

That was actually really helpful. As of my posting, I didn't even have a clue where to begin looking.... I will now dive into checkout_confirmation.php and see where I am going wrong.

 

Thank you very much for the tip!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...