/**
	 * Handle Paypal IPN
	 *
	 * @param  cbpaidPaymentBasket  $paymentBasket  New empty object. returning: includes the id of the payment basket of this callback (strictly verified, otherwise untouched)
	 * @param  array                $postdata       _POST data for saving edited tab content as generated with getEditTab
	 * @return string                               HTML to display if frontend, text to return to gateway if notification, FALSE if registration cancelled and ErrorMSG generated, or NULL if nothing to display
	 */
	private function hanldePaypalIPN( $paymentBasket, $postdata )
	{
		global $_CB_framework, $_CB_database, $_GET, $_POST;

		$ret							=	null;
		if ( ( cbGetParam( $_GET, 'result' ) == 'notify' ) || ( ( cbGetParam( $_GET, 'result' ) == 'success') && isset( $postdata['txn_type'] ) ) ) {
			// cbGetParam( $_GET, 'result' ) == 'notify'	:	IPN : We got an Instant Payment Notification (IPN) from Paypal :
			// isset($postdata['txn_type'])	:	We got returned to website with a Post ! This means PDT is not enabled, or website is not public ! so we check anyway to make sure:

			/// I P N :		Process Instant Payment Notification (IPN):

			$ipn						=	new cbpaidPaymentNotification($_CB_database);
			$ipn->bind( $postdata );

			$getExport					=	var_export( $_GET, true );		/* cbGetParam() not needed: we want raw info */
			$postExport					=	var_export( $_POST, true );		/* cbGetParam() not needed: we want raw info */
			if ( $ipn->charset && ( $ipn->charset != $_CB_framework->outputCharset() ) ) {
				$getExport				=	$this->_charsetConv( $getExport, $ipn->charset, $_CB_framework->outputCharset() );
				$postExport				=	$this->_charsetConv( $postExport, $ipn->charset, $_CB_framework->outputCharset() );
			}

			$ipn->payment_method		=	$this->getPayName();
			$ipn->gateway_account		=	$this->getAccountParam( 'id' );
			$ipn->log_type				=	'I';
			$ipn->time_received			=	date('Y-m-d H:i:s', $_CB_framework->now() );
			$ipn->raw_data				=	/* cbGetParam() not needed: we want raw info */ '$_GET=' . $getExport . ";\n";
			$ipn->raw_data				.=	/* cbGetParam() not needed: we want raw info */ '$_POST=' . $postExport . ";\n";
			$ipn->ip_addresses			=	cbpaidRequest::getIPlist();
			$ipn->user_id				=	(int) cbGetParam( $_GET, 'user', 0 );
			$ipn->payment_basket_id		=	(int) $ipn->custom;
			
			$_CB_database->insertObject( $ipn->getTableName(), $ipn, $ipn->getKeyName());

			// read the post from PayPal system and add 'cmd' and post back to PayPal system to validate
			$formvars = array( 'cmd' => '_notify-validate' );
			foreach ($postdata as $key => $value) {
				$formvars[$key]			=	$this->cbGetUnEscaped( $value );
			}

			$results = null;
			$status = null;
			$error = $this->_httpsRequest( $this->_paypalUrl() . '/cgi-bin/webscr', $formvars, 30, $results, $status, 'post', 'normal', '*/*', true, 443, '', '', true, null );
			$transaction_info = urldecode($results);

			if ( $error || $status != 200 ) {
				$ipn->raw_result = 'COMMUNICATION ERROR';
//				$ipn->raw_data = 'Error: '. $error . ' Status: ' . $status . ' Transaction info: ' . $transaction_info;
				$ipn->raw_data			.=	'$error=\''. $error . "';\n";
				$ipn->raw_data			.=	'$status=\'' . $status . "';\n";
				$ipn->raw_data			.=	'$transaction_info=\'' . $transaction_info . "';\n";
				$ipn->log_type			=	'D';
				$_CB_database->updateObject( $ipn->getTableName(), $ipn, $ipn->getKeyName(), false );
				$this->_setLogErrorMSG( 3, $ipn, 'Paypal: Error at notification received: could not reach Paypal gateway for notification check at ' . $this->_paypalUrl() . '. ' . $ipn->raw_result, null );
				// Returns error 500 to paypal, so paypal can retry to reach us later
				header('HTTP/1.0 500 Internal Server Error');
				exit();
			} else {
				$ipn->raw_result		=	$transaction_info;
			}
			$_CB_database->updateObject( $ipn->getTableName(), $ipn, $ipn->getKeyName(), false);

			if (strcmp ($transaction_info, 'VERIFIED') == 0) {

				if ( isset( $ipn->charset ) ) {
					if ( strtolower( $ipn->charset ) != strtolower( $_CB_framework->outputCharset() ) ) {
						foreach ( array_keys( $postdata ) as $k ) {
							if ( isset( $ipn->$k ) && is_string( $ipn->$k ) ) {
								$ipn->$k					=	$this->_charsetConv( $ipn->$k, $ipn->charset, $_CB_framework->outputCharset() );
							}
						}
						$ipn->charset						=	$_CB_framework->outputCharset();
					}			
				}
				
				$paymentBasketId							=	(int) $ipn->custom;
				$autorecurring_type							=	( in_array( $ipn->txn_type, array( 'subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ? 2 : 0 );
				$exists = $paymentBasket->load( (int) $paymentBasketId );
				if ( $exists ) {		//  && ( ( $paymentBasket->payment_status != $ipn->payment_status ) || ( $autorecurring_type && ( $paymentBasket->txn_id != $ipn->txn_id ) ) ) ) {
					$this->_fixPayPalIpnBugs( $ipn, $paymentBasket );
					$noFraudCheckResult						=	$this->_checkNotPayPalFraud( $ipn, $paymentBasket, cbGetParam( $_REQUEST, 'cbpid', '' ) );
					if ( $noFraudCheckResult === true ) {
						if ( ( $ipn->txn_type == 'web_accept' ) || ( $ipn->txn_type == '' ) || $autorecurring_type ) {		// refunds don't have txn_type (but if we are here, the IPN is already VERIFIED !
							$paypalUserChoicePossible		=	( ( $this->getAccountParam( 'enabled', 0 ) == 3 ) && ( $paymentBasket->isAnyAutoRecurring() == 2 ) );
							$autorenew_type					=	( $autorecurring_type ? ( $paypalUserChoicePossible ? 1 : 2 ) : 0 );
							if ( $autorecurring_type && ( $ipn->txn_type == 'subscr_signup' ) && ( ( $paymentBasket->period1 ) && ( $paymentBasket->mc_amount1 == 0 ) ) && ( $ipn->payment_status == '' ) ) {
								$ipn->payment_status		=	'Completed';	// 'FreeTrial'
							}
							if ( ( $ipn->payment_status == 'Refunded' ) && ( $paymentBasket->mc_gross != - $ipn->mc_gross ) ) {
								// Fix a paypal server bug: we are receiving payment_status 'Refunded' instead of 'Partially-Refunded':
								// needed to be fixed as follows to avoid loosing the subscriptions:
								$ipn->payment_status		=	'Partially-Refunded';
								//TBD : if we have several partial refunds, we should still say 'Refunded' for the one refunding the last chunk...
							}
							$this->_bindIpnToBasket( $ipn, $paymentBasket );
							$paymentBasket->payment_method	=	$this->getPayName();
							$paymentBasket->gateway_account	=	$this->getAccountParam( 'id' );
// $this->_setLogErrorMSG( 6, $ipn, 'Paypal DEBUG1', ' autorecurring_type:' . var_export( $autorecurring_type, true ) . ' autorenew_type:' . var_export( $autorenew_type, true ) . ' txn_type:' . var_export( $ipn->txn_type, true ) );
							$this->updatePaymentStatus( $paymentBasket, $ipn->txn_type, $ipn->payment_status, $ipn, 1, $autorecurring_type, $autorenew_type, false );
							
							if ( cbGetParam( $_GET, 'result' ) == 'success' ) {		// in case of return to website with a POST, issue warning to enabel PDT (warn also for IPN as probably the setting is also wrong!)
								$this->_setLogErrorMSG( 4, $ipn, 'Paypal: Got POST successful return from Paypal', CBPTXT::T("Please tell sysadmin to enable IPN and PDT in his Paypal account.") );
							}
						} elseif ( $ipn->txn_type == 'new_case' ) {
							$ipn->log_type					=	'H';	// IPN New case
						} else {
							$ipn->log_type					=	'T';	// IPN Wrong TYPE
							if ( cbGetParam( $_GET , 'result' ) == 'success' ) {
								$errorText					=	CBPTXT::T("Error: this is not a payment yet, type received is: ") . htmlspecialchars( $ipn->txn_type ) . '. ' . CBPTXT::T("Please tell sysadmin to enable IPN and PDT in his Paypal account.");
								$this->_setLogErrorMSG( 3, $ipn, 'Paypal: Unexpected type received from Paypal', $errorText );
							}
						}
					} else {
						$ipn->log_type						=	'F';		// IPN FRAUD detected
						$ipn->raw_result					=	$noFraudCheckResult;
						if ( cbGetParam( $_GET , 'result' ) == 'success' ) {
							$this->_setLogErrorMSG( 3, $ipn, 'Paypal: Fraud attempt or Paypal value mismatch detected by the plugin: ' . $noFraudCheckResult, CBPTXT::T("Error") . ': ' . htmlspecialchars( $ipn->raw_result ) . '. ' . CBPTXT::T("Please tell sysadmin to enable IPN and PDT in his Paypal account.") );
						}
					}
				} else {
					$ipn->log_type							=	'J';
					if ( cbGetParam( $_GET , 'result' ) == 'success' ) {
						if ( ! $exists ) {
							$this->_setLogErrorMSG( 3, $ipn, 'Paypal', CBPTXT::T("Error") . ': ' . CBPTXT::T("Innexistant payment basket") . '. ' . CBPTXT::T("Please tell sysadmin to enable IPN and PDT in his Paypal account.") );
						} else {
							$this->_setLogErrorMSG( 3, $ipn, 'Paypal', CBPTXT::T("Payment basket payment status is already ") . htmlspecialchars( $paymentBasket->payment_status ) . '. ' . CBPTXT::T("Please tell sysadmin to enable PDT in his Paypal account.") );
						}
					}
				}
			} else if (strcmp ($transaction_info, 'INVALID') == 0) {
				// log for manual investigation:
				$ipn->log_type								=	'K';		// KO
				if ( cbGetParam( $_GET , 'result' ) == 'success' ) {
					$this->_setLogErrorMSG( 3, $ipn, 'Paypal: Fraud attempt or Paypal value mismatch detected by Paypal: ', CBPTXT::T("Error") . ': ' . CBPTXT::T("return information didn't validate with paypal. Please tell sysadmin to enable IPN and PDT in his Paypal account.") );
				}
				$_CB_database->updateObject( $ipn->getTableName(), $ipn, $ipn->getKeyName(), false );
				header('HTTP/1.0 500 Internal Server Error');
				exit('INVALID');
			} else {
				$ipn->log_type								=	'M';
			}
			$_CB_database->updateObject( $ipn->getTableName(), $ipn, $ipn->getKeyName(), false );
			
		}
		return $ret;
	}