/**
  * This is the frontend or backend method used directly
  * @see cbpaidPaymentBasket::store()
  *
  * If table key (id) is NULL : inserts a new row
  * otherwise updates existing row in the database table
  *
  * Can be overridden or overloaded by the child class
  *
  * @param  boolean  $updateNulls  TRUE: null object variables are also updated, FALSE: not.
  * @return boolean                TRUE if successful otherwise FALSE
  */
 public function store($updateNulls = false)
 {
     global $_CB_framework, $_CB_database;
     // 1) check:
     if (!in_array($this->payment_status, array('Pending', 'Refunded', 'NotInitiated'))) {
         $this->setError(CBPTXT::T("This payment basket is not pending."));
         return false;
     }
     if ($this->txn_id == '') {
         $this->txn_id = 'None';
         // needed for updatePayment to generate payment record.
     }
     $paymentBasket = new cbpaidPaymentBasket($_CB_database);
     $paymentBasket->load($this->id);
     if (!$paymentBasket->gateway_account) {
         $this->setError(CBPTXT::T("This payment basket has no gateway associated so can not be paid manually."));
         return false;
     }
     $ipn = new cbpaidPaymentNotification($_CB_database);
     $ipn->bindObjectToThisObject($paymentBasket, 'id');
     $ipn->mc_currency = $this->mc_currency;
     $ipn->mc_gross = $this->mc_gross;
     if (!preg_match('/^[1-9][0-9]{3}-[01][0-9]-[0-3][0-9]/', $this->time_completed)) {
         $this->time_completed = Application::Database()->getUtcDateTime();
     }
     $paymentBasket->time_completed = $this->time_completed;
     $ipn->payment_type = $this->payment_type;
     $paymentBasket->payment_type = $this->payment_type;
     $ipn->txn_id = $this->txn_id;
     $paymentBasket->txn_id = $this->txn_id;
     $ipn->payment_status = 'Completed';
     $ipn->txn_type = 'web_accept';
     $ipn->payment_method = $this->payment_method;
     $ipn->gateway_account = $this->gateway_account;
     $ipn->log_type = 'P';
     $ipn->time_received = $_CB_database->getUtcDateTime();
     $ipn->payment_date = gmdate('H:i:s M d, Y T', $this->time_completed ? cbpaidTimes::getInstance()->strToTime($this->time_completed) : cbpaidTimes::getInstance()->startTime());
     // paypal-style				//TBD FIXME: WE SHOULD CHANGE THIS OLD DATE STYLE ONCE WITH UTC timezone inputed
     $ipn->payment_basket_id = $this->id;
     $ipn->raw_result = 'manual';
     $ipn->raw_data = '';
     $ipn->ip_addresses = cbpaidRequest::getIPlist();
     $ipn->user_id = Application::MyUser()->getUserId();
     $ipn->txn_id = $this->txn_id;
     $ipn->payment_type = $this->payment_type;
     $ipn->charset = $_CB_framework->outputCharset();
     //TBD
     /*
     		$paymentBasket->first_name	= $ipn->first_name	= cbGetParam( $_POST, 'txtBTitle' );
     		$paymentBasket->first_name		= $ipn->first_name		= cbGetParam( $_POST, 'txtBFirstName' );
     		$paymentBasket->last_name		= $ipn->last_name		= cbGetParam( $_POST, 'txtBLastName' );
     		$paymentBasket->address_street	= $ipn->address_street	= cbGetParam( $_POST, 'txtBAddr1' );
     		$paymentBasket->address_zip		= $ipn->address_zip		= cbGetParam( $_POST, 'txtBZipCode' );
     		$paymentBasket->address_city	= $ipn->address_city	= cbGetParam( $_POST, 'txtBCity' );
     		$paymentBasket->address_country	= $ipn->address_country	= cbGetParam( $_POST, 'txtBCountry' );
     		//TBD? $paymentBasket->phone	= $ipn->phone			= cbGetParam( $_POST, 'txtBTel' );
     		//TBD? $paymentBasket->fax		= $ipn->fax				= cbGetParam( $_POST, 'txtBFax' );
     		$paymentBasket->payer_email		= $ipn->payer_email		= cbGetParam( $_POST, 'txtBEmail' );
     */
     if (!$_CB_database->insertObject($ipn->getTableName(), $ipn, $ipn->getKeyName())) {
         trigger_error('store error:' . htmlspecialchars($_CB_database->getErrorMsg()), E_USER_ERROR);
         //TBD also in paypal: error code 500 !!!
     }
     $payAccount = cbpaidControllerPaychoices::getInstance()->getPayAccount($paymentBasket->gateway_account);
     if (!$payAccount) {
         $this->setError(CBPTXT::T("This payment basket's associated gateway account is not active, so can not be paid manually."));
         return false;
     }
     $payClass = $payAccount->getPayMean();
     $payClass->updatePaymentStatus($paymentBasket, 'web_accept', 'Completed', $ipn, 1, 0, 0, 'singlepayment');
     return true;
 }
 /**
  * loads the basket of this payment item: cbSubscription + ->reason from payment_items (N=New, R=Renewal)
  * caches in object
  *
  * @return cbpaidPaymentBasket   if subscription is loaded or already loaded, or NULL otherwise
  */
 public function loadBasket()
 {
     if ($this->payment_basket_id) {
         $basket = new cbpaidPaymentBasket($this->_db);
         if ($basket->load((int) $this->payment_basket_id)) {
             return $basket;
         }
     }
     $false = false;
     return $false;
 }
 /**
  * If table key (id) is NULL : inserts a new row
  * otherwise updates existing row in the database table
  *
  * Can be overridden or overloaded by the child class
  *
  * @param  boolean  $updateNulls  TRUE: null object variables are also updated, FALSE: not.
  * @return boolean                TRUE if successful otherwise FALSE
  */
 public function store($updateNulls = false)
 {
     if (!cbpaidApp::authoriseAction('cbsubs.refunds')) {
         $this->setError(CBPTXT::T("Not authorized"));
         return false;
     }
     // 1) check:
     if (!in_array($this->payment_status, array('Completed', 'Pending', 'Partially-Refunded'))) {
         $this->setError(CBPTXT::T("This payment is not completed, pending or partially refunded."));
         return false;
     }
     if ($this->txn_id == '') {
         $this->txn_id = 'None';
         // needed for updatePayment to generate payment record.
     }
     $payment = new cbpaidPayment();
     if (!$payment->load((int) $this->id)) {
         $this->setError(CBPTXT::T("This payment does not exist."));
         return false;
     }
     $paymentBasket = new cbpaidPaymentBasket();
     if (!$paymentBasket->load($this->payment_basket_id)) {
         $this->setError(CBPTXT::T("This payment has no associated payment basket and cannot be refunded from here. Maybe from your PSP online terminal ?"));
         return false;
     }
     if (!$this->gateway_account) {
         $this->setError(CBPTXT::T("This payment has no gateway associated so can not be refunded."));
         return false;
     }
     $payAccount = cbpaidControllerPaychoices::getInstance()->getPayAccount($this->gateway_account);
     if (!$payAccount) {
         $this->setError(CBPTXT::T("This payment's payment basket's associated gateway account is not active, so can not be refunded from here."));
         return false;
     }
     $payClass = $payAccount->getPayMean();
     $returnText = null;
     $amount = sprintf('%.2f', (double) $this->refund_gross);
     if (is_callable(array($payClass, 'refundPayment'))) {
         $success = $payClass->refundPayment($paymentBasket, $payment, null, $this->refund_is_last, $amount, $this->refund_reason, $returnText);
     } else {
         $success = false;
     }
     $user = CBuser::getUserDataInstance($paymentBasket->user_id);
     $username = $user ? $user->username : '******';
     $replacements = array('[REFUNDAMOUNT]' => $payment->mc_currency . ' ' . $amount, '[PAYMENTID]' => $payment->id, '[PAYMENTAMOUNT]' => $payment->mc_currency . ' ' . $payment->mc_gross, '[BASKETID]' => $paymentBasket->id, '[ORDERID]' => $paymentBasket->sale_id, '[FULLNAME]' => $paymentBasket->first_name . ' ' . $paymentBasket->last_name, '[USERNAME]' => $username, '[USERID]' => $paymentBasket->user_id, '[PAYMENTMETHOD]' => $payClass->getPayName(), '[TXNID]' => $payment->txn_id, '[AUTHID]' => $payment->auth_id, '[ERRORREASON]' => $paymentBasket->reason_code);
     if ($success) {
         // Success Message ?
         // $returnText	=	CBPTXT::P("Refunded [REFUNDAMOUNT] for payment id [PAYMENTID] of [PAYMENTAMOUNT] for basket id [BASKETID], Order id [ORDERID] of [FULLNAME] (username [USERNAME] - user id [USERID]) using [PAYMENTMETHOD] with txn_id [TXNID] and auth_id [AUTHID].", $replacements );
     } else {
         $this->setError(CBPTXT::T($payClass->getErrorMSG()) . '. ' . CBPTXT::P("Refund request of [REFUNDAMOUNT] for payment id [PAYMENTID] of [PAYMENTAMOUNT] for basket id [BASKETID], Order id [ORDERID] of [FULLNAME] (username [USERNAME] - user id [USERID]) using [PAYMENTMETHOD] with txn_id [TXNID] and auth_id [AUTHID] failed for reason: [ERRORREASON].", $replacements));
         return false;
     }
     return true;
 }
	/**
	* Handles the gateway-specific result of payments (redirects back to this site and gateway notifications). WARNING: unchecked access !
	*
	* @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
	* @param  string               $result         result= get parameter, other than 'notify', 'success' or 'cancel'.
	* @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
	*/
	protected function handleOtherResult( $paymentBasket, $postdata, $result )
	{
		global $_CB_framework;

		$privateVarsList = 'id user_id time_initiated time_completed ip_addresses mc_gross mc_currency '
						.	'quantity item_number item_name shared_secret payment_status '
						.	'invoice period1 period2 period3 mc_amount1 mc_amount2 mc_amount3';

		$ret = null;
		// $privateVarsList = 'id payment_method gateway_account user_id time_initiated time_completed ip_addresses mc_gross mc_currency quantity item_number item_name shared_secret payment_status';

		if ( $result == 'freetrial' ) {
			$paymentBasketId = (int) $this->_getReqParam( 'basket' );
			if ( $paymentBasketId ) {
				$exists = $paymentBasket->load( (int) $paymentBasketId );
				if ( $exists && ( $this->_getReqParam( 'cbpid' ) == $paymentBasket->shared_secret ) && ( $paymentBasket->payment_status == 'NotInitiated' ) ) {
					$isAnyAutoRecurring					=	$paymentBasket->isAnyAutoRecurring();
					if ( ( $isAnyAutoRecurring == 2 && ( ( $paymentBasket->period1 ) && ( $paymentBasket->mc_amount1 == 0 ) ) )
					|| ( ( $paymentBasket->mc_amount1 == 0 ) && ( $paymentBasket->mc_amount3 == 0 ) && ( $paymentBasket->mc_gross == 0 ) ) 
					) {
						// user-choice: no need to wait for payment basket completed to activate subscriptions:
						$paymentBasket->payment_method	=	$this->getPayName();
						// $paymentBasket->gateway_account = $this->getAccountParam( 'id' );
						// $ipn	=	null;
						$ipn							=	$this->_logNotification( 'P', $_CB_framework->now(), $paymentBasket );
						$paymentBasket->bindObjectToThisObject( $ipn, $privateVarsList );
						$this->updatePaymentStatus( $paymentBasket, 'web_accept', 'FreeTrial', $ipn, 1, 0, 0, true );
					}
				}
			}
		}
		return  $ret;
	}
	/**
	 * WARNING: UNCHECKED ACCESS! On purpose unchecked access for M2M operations
	 * Generates the HTML to display for a specific component-like page for the tab. WARNING: unchecked access !
	 * @param  TabTable|null  $tab       the tab database entry
	 * @param  UserTable      $user      the user being displayed
	 * @param  int            $ui        1 for front-end, 2 for back-end
	 * @param  array          $postdata  _POST data for saving edited tab content as generated with getEditTab
	 * @return mixed                     either string HTML for tab content, or false if ErrorMSG generated
	 */
	public function getTabComponent( /** @noinspection PhpUnusedParameterInspection */ $tab, $user, $ui, $postdata ) {
		global $_CB_database, $_CB_framework, $_POST;

		$return								=	'';
		$paid								=	false;

		$oldignoreuserabort = ignore_user_abort(true);

		$allowHumanHtmlOutput				=	true;			// this will be reverted in case of M2M server-to-server notifications

		$act								=	$this->base->_getReqParam( 'act' );
		$actPosted							=	isset($_POST[$this->base->_getPagingParamName('act')]);

		if ( $act === null ) {
			$act							=	$this->base->input( 'act', null, GetterInterface::COMMAND );
			$actPosted						=	$this->base->input( 'post/act', null, GetterInterface::COMMAND ) !== null;
		}

		$post_user_id						=	(int) cbGetParam( $_GET, 'user', 0 );

		if ( $actPosted && ( $post_user_id > 0 ) ) {
			$access							=	false;
			$myId							=	$_CB_framework->myId();
			if ( is_object( $user ) ) {
				if ( $myId == 0 ) {
					if ( in_array( $act, array( 'saveeditinvoiceaddress', 'saveeditbasketintegration', 'showbskt' ) ) ) {
						$access				=	true;
					} else {
						$paidsubsManager	=&	cbpaidSubscriptionsMgr::getInstance();
						if ( ! $paidsubsManager->checkExpireMe( __FUNCTION__, $user->id, false ) ) {
							// expired subscriptions: we will allow limited access to:
							if ( in_array( $act, array( 'upgrade', 'pay', 'reactivate', 'resubscribe', 'display_subscriptions' ) ) ) {
								$access		=	true;
							}
						}
					}
				} else {
					if ( ( $ui == 1 && ( $user->id == $myId ) )
						||	 ( cbpaidApp::authoriseAction( 'cbsubs.usersubscriptionmanage' ) ) ) {
						$access				=	true;
					}
				}
			} else {
				$return						=	CBPTXT::T("User does not exist") . '.';
			}
			if ( ! $access ) {
				$return						.=	'<br />' . CBPTXT::T("Not authorized action") . '.';
				return $return;
			}

			cbSpoofCheck( 'plugin' );		// anti-spoofing check


			// renew or upgrade subscription payment form:
			$params							=	$this->params;
			$now							=	$_CB_framework->now();
			$subscriptionsGUI				=	new cbpaidControllerUI();
			$subscriptionIds				=	$subscriptionsGUI->getEditPostedBoxes( 'id' );

			if ( $subscriptionIds == array( 0 ) ) {
				$subscriptionIds			=	array();
			}
			if ( $post_user_id && ( $user->id == $post_user_id ) ) {
				outputCbTemplate();
				$this->base->outputRegTemplate();
				outputCbJs();
				switch ( $act ) {
					case 'upgrade':		// upgrade an existing subscription
						// display basket and payment buttons or redirect for payment depending if multiple payment choices or intro text present:
						$chosenPlans		=	$subscriptionsGUI->getAndCheckChosenUpgradePlans( $postdata, $user, $now );
						if ( ( ! is_array( $chosenPlans ) ) || ( count( $chosenPlans ) == 0 ) ) {
							$subTxt			=	CBPTXT::T( $params->get( 'subscription_name', 'subscription' ) );
							$return			.=	( is_string( $chosenPlans ) ? $chosenPlans . '<br />' : '' )
								.	sprintf( CBPTXT::Th("Please press back button and select the %s plan to which you would like to upgrade."), $subTxt );
							break;
						}
						$introText			=	CBPTXT::Th( $params->get( 'intro_text_upgrade', null ) );
						//TBD: check if already exists (reload protection):
						$paymentBasket		=	cbpaidControllerOrder::createSubscriptionsAndPayment( $user, $chosenPlans, $postdata, $subscriptionIds, null, 'R', CBPTXT::T("Upgrade"), 'U' );
						if ( is_object( $paymentBasket ) ) {
							$return			=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, $introText );
						} else {
							$return			=	$paymentBasket;		// show messages as nothing to pay.
						}
						break;
					case 'pay':			// pay for an unpaid subscription
						// display basket and payment buttons or redirect for payment depending if multiple payment choices or intro text present:
						$plan				=	$this->base->_getReqParam( 'plan' );
						if ( ( ! $plan ) || ( ! isset( $subscriptionIds[$plan] ) ) || ( ! $subscriptionIds[$plan] ) ) {
							$subTxt			=	CBPTXT::T( $params->get( 'subscription_name', 'subscription' ) );
							$return			.=	sprintf( CBPTXT::Th("Please press back button and select a %s plan."), $subTxt );
							break;
						}
						$plansMgr			=&	cbpaidPlansMgr::getInstance();
						$chosenPlans		=	array();
						$chosenPlans[(int) $plan]		=	$plansMgr->loadPlan( (int) $plan );
						$introText			=	CBPTXT::Th( $params->get( 'intro_text', null ) );
						$paymentStatus		=	null;
						$return				=	cbpaidControllerOrder::showPaymentForm( $user, $chosenPlans, $introText, $subscriptionIds, $paymentStatus );
						break;
					case 'renew':		// renew a still valid subscription
					case 'reactivate':	// reactivate an expired subscription
					case 'resubscribe':	// resubscribe a cancelled subscription
						// display basket and payment buttons or redirect for payment depending if multiple payment choices or intro text present:
						$plan				=	$this->base->_getReqParam( 'plan' );
						if ( ( ! $plan ) || ( ! isset( $subscriptionIds[$plan] ) ) || ( ! $subscriptionIds[$plan] ) ) {
							$subTxt			=	CBPTXT::T( $params->get( 'subscription_name', 'subscription' ) );
							$return			.=	sprintf( CBPTXT::Th("Please press back button and select a %s plan."), $subTxt );
							break;
						}
						$plansMgr			=&	cbpaidPlansMgr::getInstance();
						$chosenPlans		=	array();
						$chosenPlans[(int) $plan]		=	$plansMgr->loadPlan( (int) $plan );

						$paidSomethingMgr	=&	cbpaidSomethingMgr::getInstance();
						$subscription		=	$paidSomethingMgr->loadSomething( $subscriptionIds[$plan][0], $subscriptionIds[$plan][1] );
						global $_PLUGINS;
						$_PLUGINS->loadPluginGroup( 'user', 'cbsubs.' );
						$_PLUGINS->loadPluginGroup('user/plug_cbpaidsubscriptions/plugin');
						$_PLUGINS->trigger( 'onCPayAfterPlanRenewalSelected', array( &$chosenPlans[(int) $plan], &$subscription, $act ) );
						if ( $_PLUGINS->is_errors() ) {
							$return			.=	$_PLUGINS->getErrorMSG();
							break;
						}

						$introText			=	CBPTXT::Th( $params->get( 'intro_text_renew', null ) );
						//TBD: check if already exists (reload protection):
						$paymentBasket		=	cbpaidControllerOrder::createSubscriptionsAndPayment( $user, $chosenPlans, $postdata, $subscriptionIds, null, null, CBPTXT::T("Renew"), 'R' );
						if ( is_object( $paymentBasket ) ) {
							$return			=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, $introText );
						} else {
							$return			=	$paymentBasket;		// show messages as nothing to pay.
						}
						break;
					case 'unsubscribe':	// request to unsubscribe an active subscription
						// display unsubscribe confirmation form:
						$plan				=	$this->base->_getReqParam( 'plan' );
						if ( ( ! $plan ) || ( ! isset( $subscriptionIds[$plan] ) ) || ( ! $subscriptionIds[$plan] ) ) {
							$subTxt			=	CBPTXT::T( $params->get( 'subscription_name', 'subscription' ) );
							$return			.=	sprintf( CBPTXT::Th("Please press back button and select a %s plan."), $subTxt );
							break;
						}
						$introText			=	CBPTXT::Th( $params->get( 'unsubscribe_intro_text' , null ) );
						$return				=	$subscriptionsGUI->showUnsubscribeForm( $user, $introText, (int) $plan, (int) $subscriptionIds[$plan][1] );

						break;
					case 'confirm_unsubscribe':	// confirm previous request to unsubscribe an active subscription
						// unsubscribe confirmed:
						$plan				=	$this->base->_getReqParam( 'plan' );
						if ( ( ! $plan ) || ( ! isset( $subscriptionIds[$plan] ) ) || ( ! $subscriptionIds[$plan] ) ) {
							$subTxt			=	CBPTXT::T( $params->get( 'subscription_name', 'subscription' ) );
							$return			.=	sprintf( CBPTXT::Th("Please press back button and select a %s plan."), $subTxt );
							break;
						}
						if ( ( $plan ) && ( count( $subscriptionIds ) == 1 ) ) {
							$unsubscribeConfText =	CBPTXT::Th( $params->get( 'unsubscribe_confirmation_text', null ) );
							$return			=	cbpaidControllerOrder::doUnsubscribeConfirm( $user, $unsubscribeConfText, (int) $plan, (int) $subscriptionIds[$plan][1] );
						}
						break;
					case 'display_subscriptions':
						// unsubscribe cancelled: display subscriptions:
						$return				=	$this->base->displayUserTab( $user );
						break;
					case 'showinvoice':
						// shows a particular user invoice:
						if ( $params->get( 'show_invoices', 1 ) ) {
							$invoiceNo		=	$this->base->_getReqParam( 'invoice' );
							$return			=	$this->showInvoice( $invoiceNo, $user );
						}
						break;
					case 'saveeditinvoiceaddress':
					case 'editinvoiceaddress':		// this is the case of reload of invoicing address
						$invoicingAddressQuery		=	$params->get( 'invoicing_address_query' );
						if ( $invoicingAddressQuery > 0 ) {
							$basketId				=	$this->base->_getReqParam( 'basket', 0 );
							$hashToCheck			=	$this->base->_getReqParam( 'bck' );
							$paymentBasket			=	new cbpaidPaymentBasket( $_CB_database );
							if ( $basketId && $paymentBasket->load( (int) $basketId ) && ( $paymentBasket->payment_status == 'NotInitiated' ) && ( $hashToCheck == $paymentBasket->checkHashUser( $hashToCheck ) ) ) {
								if ( ( $act == 'saveeditinvoiceaddress' ) && $this->base->input( 'actbutton', null, GetterInterface::COMMAND ) ) {				// IE7-8 will return text instead of value and IE6 will return button all the time http://www.dev-archive.net/articles/forms/multiple-submit-buttons.html
									$return			=	$paymentBasket->saveInvoicingAddressForm( $user );
									if ( $return === null ) {
										$paymentBasket->storeInvoicingDefaultAddress();
										$introText	=	CBPTXT::Th( $params->get( 'intro_text', null ) );
										$return		.=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, $introText );
									}
								} else {
									// invoice has reloaded itself (e.g. for country change):
									$return			=	$paymentBasket->renderInvoicingAddressForm( $user );
								}
							} else {
								$this->base->_setErrorMSG( CBPTXT::T("No unpaid payment basket found.") );
							}
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
						}

						break;
					case 'saverecordpayment':
					case 'editrecordpayment':		// this is the case of reload of the form
						$basketId				=	$this->base->_getReqParam( 'basket', 0 );
						$hashToCheck			=	$this->base->_getReqParam( 'bck' );
						$paymentBasket			=	new cbpaidPaymentBasket( $_CB_database );
						if ( $basketId && $paymentBasket->load( (int) $basketId ) && ( $paymentBasket->payment_status != 'Completed' ) && ( $hashToCheck == $paymentBasket->checkHashUser( $hashToCheck ) ) ) {
							if ( $paymentBasket->authoriseAction( 'cbsubs.recordpayments' ) ) {
								if ( ( $act == 'saverecordpayment' ) && $this->base->input( 'actbutton', null, GetterInterface::COMMAND ) ) {				// IE7-8 will return text instead of value and IE6 will return button all the time http://www.dev-archive.net/articles/forms/multiple-submit-buttons.html
									$return			=	cbpaidRecordBasketPayment::saveRecordPayment( $paymentBasket->id );
									if ( $return === null ) {
										$return		.=	CBPTXT::T("Payment recorded.")
											.	' <a href="' . $_CB_framework->userProfileUrl( $paymentBasket->user_id, true ) . '">'
											.	CBPTXT::Th("View user profile")
											.	'</a>';
									}
								} else {
									// invoice has reloaded itself (e.g. for country change):
									$return			=	cbpaidRecordBasketPayment::displayRecordPaymentForm( $paymentBasket->id );
								}
							} else {
								$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
							}
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("No unpaid payment basket found.") );
						}

						break;

					default:
						cbNotAuth();
						return '';
						break;
				}
			}

		} elseif ( $this->base->_getReqParam( 'account' ) && ( ( (int) cbGetParam( $_GET, 'user', 0 ) ) > 0 ) ) {

			$account					=	$this->base->_getReqParam( 'account' );
			$post_user_id				=	(int) cbGetParam( $_GET, 'user', 0 );
			$user						=	CBuser::getUserDataInstance( (int) $post_user_id );
			if ( $user->id ) {
				if ( isset( $_SESSION['cbsubs']['expireduser'] ) && ( $_SESSION['cbsubs']['expireduser'] == $user->id ) ) {
					// expired subscriptions of membership: show possibilities:
					$subscriptionsGUI		=	new cbpaidControllerUI();

					outputCbTemplate();
					$this->base->outputRegTemplate();
					outputCbJs();

					switch ( $account ) {
						case 'expired':
							$paidsubsManager		=&	cbpaidSubscriptionsMgr::getInstance();
							if ( ! $paidsubsManager->checkExpireMe( __FUNCTION__, $user->id, false ) ) {
								// no valid membership:
								$return				=	$subscriptionsGUI->getShowSubscriptionUpgrades( $user, true );
							}

							break;
						default:
							break;
					}
				} else {
					$return					=	CBPTXT::Th("Browser cookies must be enabled.");
				}
			}

		} elseif ( in_array( $act, array( 'setbsktpmtmeth', 'setbsktcurrency' ) ) ) {

			cbSpoofCheck( 'plugin' );		// anti-spoofing check
			$params							=	$this->params;
			outputCbTemplate();
			$this->base->outputRegTemplate();
			outputCbJs();

			$basketId				=	$this->base->_getReqParam( 'bskt', 0 );
			$hashToCheck			=	$this->base->_getReqParam( 'bck' );

			$paymentBasket			=	new cbpaidPaymentBasket( $_CB_database );
			if ( $basketId && $paymentBasket->load( (int) $basketId ) && ( $paymentBasket->payment_status == 'NotInitiated' ) && ( $hashToCheck == $paymentBasket->checkHashUser( $hashToCheck ) ) ) {

				switch ( $act ) {
					case 'setbsktpmtmeth':
						if ( $params->get( 'payment_method_selection_type' ) == 'radios' ) {
							$chosenPaymentMethod	=	cbGetParam( $_POST, 'payment_method' );
							$introText				=	CBPTXT::Th( $params->get( 'intro_text', null ) );
							$return					=	$paymentBasket->saveBasketPaymentMethodForm( $user, $introText, $chosenPaymentMethod );
							if ( $return === null ) {
								$return				.=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, $introText );
							}
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
						}
						break;

					case 'setbsktcurrency':
						if ( $params->get( 'allow_select_currency', '0' ) ) {
							$newCurrency			=	cbGetParam( $_POST, 'currency' );
							if ( $newCurrency ) {
								if ( in_array( $newCurrency, cbpaidControllerPaychoices::getInstance()->getAllCurrencies() ) ) {
									$paymentBasket->changeCurrency( $newCurrency );
								} else {
									$this->base->_setErrorMSG( CBPTXT::T("This currency is not allowed") );
								}
								$introText			=	CBPTXT::Th( $params->get( 'intro_text', null ) );
								$return				.=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, $introText );
							} else {
								$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
							}
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("Changes of currency of orders are not authorized") );
						}
						break;

					default:
						cbNotAuth();
						return '';
						break;
				}

			} else {
				$this->base->_setErrorMSG( CBPTXT::T("No unpaid payment basket found.") );
			}

		} elseif ( $act == 'cbsubsclass' ) {

			$pluginName						=	$this->base->_getReqParam( 'class' );
			if ( preg_match( '/^[a-z]+$/', $pluginName ) ) {
				$element					=	'cbsubs.' . $pluginName;
				global $_PLUGINS;
				$_PLUGINS->loadPluginGroup('user/plug_cbpaidsubscriptions/plugin', $element );
				$loadedPlugins				=&	$_PLUGINS->getLoadedPluginGroup( 'user/plug_cbpaidsubscriptions/plugin' );
				$params						=	$this->params;
				foreach ($loadedPlugins as $p ) {
					if ( $p->element == $element ) {
						$pluginId			=	$p->id;
						$args				=	array( &$user, &$params, &$postdata );
						/** @noinspection PhpUndefinedCallbackInspection */
						$return				=	$_PLUGINS->call( $pluginId, 'executeTask', 'getcbsubs' . $pluginName . 'Tab', $args, null );
						break;
					}
				}
			}

		} elseif ( $act && ( ! in_array( $act, array( 'showbskt', 'setbsktpmtmeth' ) ) ) && ( ( (int) cbGetParam( $_GET, 'user', 0 ) ) > 0 ) ) {

			if ( ! is_object( $user ) ) {
				return CBPTXT::T("User does not exist.");
			}

			$params								=	$this->params;

			$post_user_id						=	(int) cbGetParam( $_GET, 'user', 0 );
			if ( $post_user_id && ( ( $user->id == $post_user_id ) || ( cbpaidApp::authoriseAction( 'cbsubs.usersubscriptionmanage' ) ) ) ) {

				outputCbTemplate();
				$this->base->outputRegTemplate();
				outputCbJs();

				switch ( $act ) {
					case 'showinvoice':
						if ( $params->get( 'show_invoices', 1 ) ) {
							$invoiceNo			=	$this->base->_getReqParam( 'invoice', 0 );
							// This also checks for cbpaidApp::authoriseAction on cbsubs.sales or cbsubs.financial access permissions:
							$return				=	$this->showInvoice( $invoiceNo, $user );
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
						}
						break;
					case 'showinvoiceslist':
						$showInvoices			=	$params->get( 'show_invoices', 1 );
						$invoicesShowPeriod		=	$params->get( 'invoices_show_period', '0000-06-00 00:00:00' );
						$itsmyself				=	( $_CB_framework->myId() == $user->id );
						if ( $showInvoices && ( $itsmyself || ( cbpaidApp::authoriseAction( 'cbsubs.sales' ) || cbpaidApp::authoriseAction( 'cbsubs.financial' ) ) ) ) {
							$subscriptionsGUI	=	new cbpaidControllerUI();
							$invoices			=	$this->_getInvoices( $user, $invoicesShowPeriod, false );

							if ( $invoicesShowPeriod && ( $invoicesShowPeriod != '0000-00-00 00:00:00' ) ) {
								$cbpaidTimes	=&	cbpaidTimes::getInstance();
								$periodText		=	$cbpaidTimes->renderPeriod( $invoicesShowPeriod, 1, false );
							} else {
								$periodText		=	'';
							}
							$return				.=	$subscriptionsGUI->showInvoicesList( $invoices, $user, $itsmyself, $periodText );
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
						}
						break;
					case 'editinvoiceaddress':			// this is the case of the initial edit address link
						if ( $params->get( 'invoicing_address_query' ) > 0 ) {
							$basketId			=	$this->base->_getReqParam( 'basket', 0 );
							$hashToCheck		=	$this->base->_getReqParam( 'bck' );
							$paymentBasket		=	new cbpaidPaymentBasket( $_CB_database );
							if ( $basketId && $paymentBasket->load( (int) $basketId ) && ( $paymentBasket->payment_status == 'NotInitiated' ) && ( $hashToCheck == $paymentBasket->checkHashUser( $hashToCheck ) ) ) {
								$return			=	$paymentBasket->renderInvoicingAddressForm( $user );
							} else {
								$this->base->_setErrorMSG( CBPTXT::T("No unpaid payment basket found.") );
							}
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
						}
						break;
					case 'showrecordpayment':
						$paymentBasketId		=	$this->base->_getReqParam( 'recordpayment', 0 );
						if ( $paymentBasketId ) {
							$paymentBasket		=	new cbpaidPaymentBasket();
							if ( $paymentBasket->load( (int) $paymentBasketId ) && $paymentBasket->authoriseAction( 'cbsubs.recordpayments' ) ) {
								// Auto-loads class: and authorization is checked inside:
								$return				=	cbpaidRecordBasketPayment::displayRecordPaymentForm( $paymentBasketId );
							} else {
								$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
							}
						} else {
							$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
						}
						break;
					default:
						$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
						break;
				}
			}

		} elseif ( $act == 'showbskt' && ( ( ( (int) cbGetParam( $_GET, 'user', 0 ) ) > 0 ) ) || ( $this->base->_getReqParam( 'bskt', 0 ) && $this->base->_getReqParam( 'bck' ) ) ) {

			$basketId			=	$this->base->_getReqParam( 'bskt', 0 );
			$hashToCheck		=	$this->base->_getReqParam( 'bck' );

			// Basket integrations saving/editing url:
			if ( in_array($act, array( 'saveeditbasketintegration', 'editbasketintegration' ) ) ) {		// edit is the case of edit or reload of integration form
				$integration			=	$this->base->_getReqParam( 'integration' );
				$paymentBasket			=	new cbpaidPaymentBasket( $_CB_database );
				if ( preg_match( '/^[a-z]+$/', $integration ) && $basketId && $paymentBasket->load( (int) $basketId ) && ( $paymentBasket->payment_status == 'NotInitiated' ) && ( $hashToCheck == $paymentBasket->checkHashUser( $hashToCheck ) ) ) {
					global $_PLUGINS;
					$element			=	'cbsubs.' . $integration;
					$_PLUGINS->loadPluginGroup('user/plug_cbpaidsubscriptions/plugin', $element );
					$results		=	$_PLUGINS->trigger( 'onCPayEditBasketIntegration', array( $integration, $act, &$paymentBasket ) );
					$return			=	null;
					foreach ( $results as $r ) {
						if ( $r ) {
							$return	.=	$r;
						}
					}
					if ( $act == 'editbasketintegration' ) {
						if ( $return !== null ) {
							return $return;
						}
					}
				} else {
					$this->base->_setErrorMSG( CBPTXT::T("No unpaid payment basket found.") );
				}
			}


			$post_user_id							=	(int) cbGetParam( $_GET, 'user', 0 );
			if ( $post_user_id && ! ( ( is_object( $user ) && ( $user->id == $post_user_id ) ) ) ) {
				return CBPTXT::T("User does not exist.");
			}

			outputCbTemplate();
			$this->base->outputRegTemplate();
			outputCbJs();
			$params				=	$this->params;

			$paymentBasket		=	new cbpaidPaymentBasket( $_CB_database );
			if ( $basketId && $paymentBasket->load( (int) $basketId ) && ( $paymentBasket->payment_status == 'NotInitiated' ) ) {
				if ( ! $post_user_id ) {
					$cbUser		=&	CBuser::getInstance( (int) $paymentBasket->user_id );
					$user		=&	$cbUser->getUserData();
					if ( ( ! is_object( $user ) ) || ! $user->id ) {
						return CBPTXT::T("User does not exist.");
					}
				}
				if ( ( $hashToCheck && $hashToCheck == $paymentBasket->checkHashUser( $hashToCheck ) )
					|| ( ( ! $hashToCheck ) && $paymentBasket->user_id && ( $paymentBasket->user_id == $_CB_framework->myId() ) ) )
				{
					$introText	=	CBPTXT::Th( $params->get( 'intro_text', null ) );
					$return		.=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, $introText );
				} else {
					$this->base->_setErrorMSG( CBPTXT::T("Not authorized action") );
				}
			} else {
				$this->base->_setErrorMSG( CBPTXT::T("No unpaid payment basket found.") );
			}

			//	} elseif ( isset($_REQUEST['result']) && isset( $_REQUEST['user'] ) && ( $_REQUEST['user'] > 0 ) ) {
		} elseif ( isset($_REQUEST['result']) && ( $this->base->_getReqParam('method') || $this->base->_getReqParam('gacctno') ) ) {

			// don't check license here so initiated payments can complete !

			$params				=	$this->params;

			$method				=	$this->base->_getReqParam('method');

			if ( ( $method == 'freetrial' ) || ( $method == 'cancelpay' ) ) {
				cbpaidApp::import( 'processors.freetrial.freetrial' );
				cbpaidApp::import( 'processors.cancelpay.cancelpay' );
				$className		=	'cbpaidGatewayAccount' . $method;
				$payAccount		=	new $className( $_CB_database );
			} else {
				$gateAccount	=	$this->base->_getReqParam('gacctno');

				$payAccount		=	cbpaidControllerPaychoices::getInstance()->getPayAccount( $gateAccount );
				if ( ! $payAccount ) {
					return '';
				}
			}
			$payClass			=	$payAccount->getPayMean();
			$paymentBasket		=	new cbpaidPaymentBasket($_CB_database);

			if ( $payClass && ( ( $this->base->_getReqParam('method') == $payClass->getPayName() ) || ( $this->base->_getReqParam('method') == null ) ) && $payClass->hashPdtBackCheck( $this->base->_getReqParam('pdtback') ) ) {
				// output for resultNotification: $return and $allowHumanHtmlOutput
				$return			=	$payClass->resultNotification( $paymentBasket, $postdata, $allowHumanHtmlOutput );
			}

			if ( ! $paymentBasket->id ) {
				$this->base->_setErrorMSG(CBPTXT::T("No suitable basket found."));
			} else {
				$user			=&	CBuser::getUserDataInstance( (int) $paymentBasket->user_id );

				if ( $paymentBasket->payment_status == 'RegistrationCancelled' ) {
					// registration cancelled: delete payment basket and delete user after checking that he is not yet active:
					if ( $paymentBasket->load( (int) $paymentBasket->id ) ) {
						if ( $payClass->hashPdtBackCheck( $this->base->_getReqParam('pdtback') ) && ( ( $paymentBasket->payment_status == 'NotInitiated' ) || ( ( $paymentBasket->payment_status === 'Pending' ) && ( $paymentBasket->payment_method === 'offline' ) ) ) ) {

							$notification						=	new cbpaidPaymentNotification();
							$notification->initNotification( $payClass, 0, 'P', $paymentBasket->payment_status, $paymentBasket->payment_type, null, $_CB_framework->now(), $paymentBasket->charset );

							$payClass->updatePaymentStatus( $paymentBasket, 'web_accept', 'RegistrationCancelled', $notification, 0, 0, 0, true );

							// This is a notification or a return to site after payment, we want to log any error happening in third-party stuff in case:
							cbpaidErrorHandler::keepTurnedOn();
						}
					}
				}
				if ( $allowHumanHtmlOutput ) {
					// If frontend, we display result, otherwise, If Server-to-server notification: do not display any additional text here !
					switch ( $paymentBasket->payment_status ) {
						case 'Completed':
							// PayPal recommends including the following information with the confirmation:
							// - Item name
							// - Amount paid
							// - Payer email
							// - Shipping address
							$newMsg = sprintf( CBPTXT::Th("Thank you for your payment of %s for the %s %s."), $paymentBasket->renderPrice(),
								$paymentBasket->item_name,
								htmlspecialchars( $payClass->getTxtUsingAccount( $paymentBasket ) ) )		// ' using your paypal account ' . $paymentBasket->payer_email
								. ' ' . $payClass->getTxtNextStep( $paymentBasket );
							// . "Your transaction has been completed, and a receipt for your purchase has been emailed to you by PayPal. "
							// . "You may log into your account at www.paypal.com to view details of this transaction.</p>\n";
							if ( $params->get( 'show_invoices' ) ) {
								$itsmyself			=	( $_CB_framework->myId() == $user->id );
								$subscriptionsGUI	=	new cbpaidControllerUI();
								$newMsg				.=	'<p id="cbregviewinvoicelink">'
									.	$subscriptionsGUI->getInvoiceShowAhtml( $paymentBasket, $user, $itsmyself, CBPTXT::Th("View printable invoice") )
									.	'</p>'
								;
							}
							$paid = true;
							break;
						case 'Pending':
							$newMsg = sprintf( CBPTXT::Th("Thank you for initiating the payment of %s for the %s %s."), $paymentBasket->renderPrice(),
								$paymentBasket->item_name,
								htmlspecialchars( $payClass->getTxtUsingAccount( $paymentBasket ) ) )		// ' using your paypal account ' . $paymentBasket->payer_email
								. ' ' . $payClass->getTxtNextStep( $paymentBasket );
							// . "Your payment is currently being processed. "
							// . "A receipt for your purchase will be emailed to you by PayPal once processing is complete. "
							// . "You may log into your account at www.paypal.com to view status details of this transaction.</p>\n";
							break;
						case 'RegistrationCancelled':
							$newMsg		=	$payClass->getTxtNextStep( $paymentBasket );
							break;
						case 'FreeTrial':
							$newMsg = CBPTXT::Th("Thank you for subscribing to") . ' ' . $paymentBasket->item_name . '.'
								. ' ' . $payClass->getTxtNextStep( $paymentBasket );
							break;
						case null:
							$newMsg	= CBPTXT::T("Payment basket does not exist.");
							break;
						case 'NotInitiated':
							$newMsg	=	'';
							break;
						case 'RedisplayOriginalBasket':
							if ( $paymentBasket->load( (int) $paymentBasket->id ) && ( $paymentBasket->payment_status == 'NotInitiated' ) ) {
								$introText		=	CBPTXT::Th( $params->get( 'intro_text', null ) );
								$return			.=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, $introText );
							}
							$newMsg				=	'';
							break;
						case 'Processed':
						case 'Denied':
						case 'Reversed':
						case 'Refunded':
						case 'Partially-Refunded':
						default:
							$newMsg = $payClass->getTxtNextStep( $paymentBasket );
							// "<p>Your transaction is not cleared and has currently following status: <strong>" . $paymentBasket->payment_status . ".</strong></p>"
							// . "<p>You may log into your account at www.paypal.com to view status details of this transaction.</p>";
							break;
					}

					if ( in_array( $paymentBasket->payment_status, array( 'Completed', 'Pending' ) ) ) {
						$subscriptions = $paymentBasket->getSubscriptions();
						$texts		=	array();			// avoid repeating several times identical texts:
						if ( is_array( $subscriptions ) ) {
							foreach ( $subscriptions as $sub ) {
								/** @var $sub cbpaidSomething */
								$thankYouParam		=	( $paymentBasket->payment_status == 'Completed') ? 'thankyoutextcompleted' : 'thankyoutextpending';
								$thankYouText		=	$sub->getPersonalized( $thankYouParam, true );
								if ( $thankYouText && ! in_array( $thankYouText, $texts ) ) {
									$texts[]		=	$thankYouText;
									if ( strpos( $thankYouText, '<' ) === false ) {
										$msgTag		=	'p';
									} else {
										$msgTag		=	'div';
									}
									$newMsg			.=	'<' . $msgTag . ' class="cbregThanks" id="cbregThanks' . $sub->plan_id . '">' . $thankYouText . '</' . $msgTag . ">\n";
								}
							}
						}
					}
					if ( $newMsg ) {
						$return .= '<div>' . $newMsg . '</div>';
					}

					if ( $paid && ( $_CB_framework->myId() < 1 ) && ( cbGetParam( $_REQUEST, 'user', 0 ) == $paymentBasket->user_id ) ) {
						$_CB_database->setQuery( "SELECT * FROM #__comprofiler c, #__users u WHERE c.id=u.id AND c.id=".(int) $paymentBasket->user_id );
						if ( $_CB_database->loadObject( $user ) && ( $user->lastvisitDate == '0000-00-00 00:00:00' ) ) {
							$return = '<p>' . implode( '', getActivationMessage( $user, 'UserRegistration' ) ) . '</p>' . $return;
						}
					}
				}
			}

		} else {
			cbNotAuth();
			return ' ' . CBPTXT::T("No result.");
		}

		if ( $allowHumanHtmlOutput ) {
			$allErrorMsgs	=	$this->base->getErrorMSG( '</div><div class="error">' );
			if ( $allErrorMsgs ) {
				$errorMsg	=	'<div class="error">' . $allErrorMsgs . '</div>';
			} else {
				$errorMsg	=	null;
			}

			/** @var string $return */
			if ( ( $return == '' ) && ( $errorMsg ) ) {
				$this->base->outputRegTemplate();
				$return		=	$errorMsg . '<br /><br />' . $return;
				$return		.=	cbpaidControllerOrder::showBasketForPayment( $user, $paymentBasket, '' );
			} else {
				$return		=	$errorMsg . $return;
			}
		}

		if ( ! is_null( $oldignoreuserabort ) ) {
			ignore_user_abort($oldignoreuserabort);
		}

		return $return;
	}
	/**
	 * The user got redirected back from the payment service provider with a success message: let's see how successfull it was
	 *
	 * @param  cbpaidPaymentBasket  $paymentBasket       New empty object. returning: includes the id of the payment basket of this callback (strictly verified, otherwise untouched)
	 * @param  array                $requestdata         Data returned by gateway
	 * @param  string               $type                Type of return ('R' for PDT, 'I' for INS, 'A' for Autorecurring payment (Vault) )
     * @param  array                $additionalLogData   Additional strings to log with IPN
	 * @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 _returnParamsHandler( $paymentBasket, $requestdata, $type, $additionalLogData = null )
	{
		global $_CB_framework, $_GET, $_POST;

		$ret													=	null;
		$paymentBasketId										=	(int) cbGetParam( $requestdata, 'USER1', null );

		if ( $paymentBasketId ) {
			$exists												=	$paymentBasket->load( (int) $paymentBasketId );

			if ( $exists && ( ( cbGetParam( $requestdata, $this->_getPagingParamName( 'id' ), 0 ) == $paymentBasket->shared_secret ) && ( ! ( ( ( $type == 'R' ) || ( $type == 'I' ) ) && ( $paymentBasket->payment_status == 'Completed' ) ) ) ) ) {
				// Log the return record:
				$log_type										=	$type;
				$reason											=	null;
				$paymentStatus									=	$this->_mapPaymentStatus( $requestdata, $reason );
				$paymentType									=	$this->_getPaymentType( $requestdata );
				$paymentTime									=	$_CB_framework->now();

				if ( $paymentStatus == 'Error' ) {
					$errorTypes									=	array( 'I' => 'D', 'R' => 'E', '3' => 'V', '4' => 'V' );

					if ( isset( $errorTypes[$type] ) ) {
						$log_type								=	$errorTypes[$type];
					}
				}

				$ipn											=&	$this->_prepareIpn( $log_type, $paymentStatus, $paymentType, $reason, $paymentTime, 'utf-8' );

				if ( $paymentStatus == 'Refunded' ) {
					// in case of refund we need to log the payment as it has same TnxId as first payment: so we need payment_date for discrimination:
					$ipn->payment_date							=	date( 'H:i:s M d, Y T', $paymentTime ); // paypal-style
				}

				$ipn->test_ipn									=	( $this->getAccountParam( 'normal_gateway' ) == '0' ? 1 : 0 );
				$ipn->raw_data									=	'$message_type="' . ( $type == 'R' ? 'RETURN_TO_SITE' : ( $type == 'I' ? 'NOTIFICATION' : ( $type == '3' ? 'REFUND' : ( $type == '4' ? 'PARTIAL_REFUND' : 'UNKNOWN' ) ) ) ) . '";' . "\n";

				if ( $additionalLogData ) {
					foreach ( $additionalLogData as $k => $v ) {
						$ipn->raw_data							.=	'$' . $k . '="' . var_export( $v, true ) . '";' . "\n";
					}
				}

				$ipn->raw_data									.=	/* cbGetParam() not needed: we want raw info */ '$requestdata=' . var_export( $requestdata, true ) . ";\n"
																.	/* cbGetParam() not needed: we want raw info */ '$_GET=' . var_export( $_GET, true ) . ";\n"
																.	/* cbGetParam() not needed: we want raw info */ '$_POST=' . var_export( $_POST, true ) . ";\n";

				if ( $paymentStatus == 'Error' ) {
					$paymentBasket->reason_code					=	$reason;

					$this->_storeIpnResult( $ipn, 'ERROR:' . $reason );
					$this->_setLogErrorMSG( 4, $ipn, $this->getPayName() . ': ' . $reason, CBPTXT::T( 'Sorry, the payment server replied with an error.' ) . ' ' . CBPTXT::T( 'Please contact site administrator to check payment status and error log.' ) );

					$ret										=	false;
				} else {
					$ipn->bindBasket( $paymentBasket );

					$ipn->sale_id								=	$paymentBasketId;

					$insToIpn									=	array(	'sale_id' => 'PPREF',
																			'txn_id' => 'PNREF',
																			'first_name' => 'FIRSTNAME',
																			'last_name' => 'LASTNAME',
																			'address_street' => 'ADDRESS',
																			'address_zip' => 'ZIP',
																			'address_city' => 'CITY',
																			'address_country' => 'COUNTRY',
																			'address_state' => 'STATE',
																			'payer_email' => 'EMAIL',
																			'contact_phone' => 'PHONE',
																			'payer_id' => 'PAYERID',
																			'auth_id' => 'CORRELATIONID',
																			'tax' => 'TAXAMT',
																			'mc_fee' => 'FEEAMT',
																			'mc_gross' => 'AMT'
																		);

					foreach ( $insToIpn as $k => $v ) {
						$ipn->$k								=	cbGetParam( $requestdata, $v );
					}

					$ipn->mc_currency							=	$paymentBasket->mc_currency;
					$ipn->user_id								=	(int) $paymentBasket->user_id;

					$recurring									=	( ( cbGetParam( $requestdata, 'USER4' ) == 'R' ) && ( cbGetParam( $requestdata, 'TENDER' ) != 'P' ) ? true : false );

					if ( $recurring ) {
						if ( ( $paymentStatus == 'Completed' ) && ( ! $paymentBasket->subscr_id ) ) {
							if ( $this->hasPaypalPayflow() ) {
								list( /*$p3 */, $t3, $start )	=	$this->_paypalPeriodsLimits( explode( ' ', $paymentBasket->period3 ), $paymentTime );

								if ( $paymentBasket->period1 ) {
									list( /*$p2*/, /*$t2*/, $start )	=	$this->_paypalPeriodsLimits( explode( ' ', $paymentBasket->period1 ), $paymentTime );
								}

								$request						=	array(	'PARTNER' => 'PayPal',
																			'VENDOR' => $this->getAccountParam( 'paypal_payflow_vendor' ),
																			'USER' => $this->getAccountParam( 'paypal_payflow_user' ),
																			'PWD' => $this->getAccountParam( 'paypal_payflow_password' ),
																			'TENDER' => cbGetParam( $requestdata, 'TENDER' ),
																			'TRXTYPE' => 'R',
																			'ACTION' => 'A',
																			'PROFILENAME' => $paymentBasket->item_name,
																			'AMT' => sprintf( '%.2f', $paymentBasket->mc_amount3 ),
																			'START' => date( 'mdY', $start ),
																			'TERM' => ( $paymentBasket->recur_times ? $paymentBasket->recur_times : 0 ),
																			'PAYPERIOD' => $t3,
																			'ORIGID' => $ipn->txn_id
																		);

								$formUrl						=	array();

								foreach ( $request as $k => $v ) {
									$formUrl[$k]				=	$k . '=' . $v;
								}

								$formUrl						=	implode( '&', $formUrl );

								$results						=	array();
								$response						=	null;
								$status							=	null;
								$error							=	$this->_httpsRequest( $this->gatewayUrl( 'psp' ), $formUrl, 105, $response, $status, 'post', 'normal' );

								if ( $response ) {
									parse_str( $response, $results );
								}

								if ( $error || ( $status != 200 ) || ( ! $response ) ) {
									$ipn->txn_type				=	'web_accept';

									$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' HTTPS POST request to payment gateway server failed.', CBPTXT::T( "Submitted refund request didn't return an error but didn't complete." ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
								} else {
									if ( cbGetParam( $results, 'RESULT' ) == 0 ) {
										$ipn->txn_type			=	'subscr_signup';
										$ipn->subscr_id			=	cbGetParam( $results, 'PROFILEID' );
										$ipn->subscr_date		=	$ipn->payment_date;
									} else {
										$ipn->txn_type			=	'web_accept';

										$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' Paypal Payflow error returned. ERROR: ' . cbGetParam( $results, 'RESPMSG' ), CBPTXT::T( 'Please contact site administrator to check error log.' ) );
									}
								}
							} else {
								$ipn->txn_type					=	'web_accept';

								$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' Needed Paypal Payflow vendor, user and password not set.', CBPTXT::T( 'Needed Paypal Payflow vendor, user and password not set.' ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
							}
						} elseif ( $paymentStatus == 'Denied' ) {
							if ( ( $paymentBasket->reattempts_tried + 1 ) <= cbpaidScheduler::getInstance( $this )->retries ) {
								$ipn->txn_type					=	'subscr_failed';
							} else {
								$ipn->txn_type					=	'subscr_cancel';
							}
						} elseif ( in_array( $paymentStatus, array( 'Completed', 'Processed', 'Pending' ) ) ) {
							$ipn->txn_type						=	'subscr_payment';
						}
					} else {
						$ipn->txn_type							=	'web_accept';
					}

					// validate payment from PDT or IPN
					$valid										=	$this->_validateIPN( $ipn, $paymentBasket );

					if ( $valid === true ) {
						if ( in_array( $ipn->payment_status, array( 'Completed', 'Processed', 'Pending', 'Refunded', 'Denied' ) ) ) {
							$this->_storeIpnResult( $ipn, 'SUCCESS' );

							$autorecurring_type					=	( in_array( $ipn->txn_type, array( 'subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ? 2 : 0 );
							$autorenew_type						=	( $autorecurring_type ? ( ( ( $this->getAccountParam( 'enabled', 0 ) == 3 ) && ( $paymentBasket->isAnyAutoRecurring() == 2 ) ) ? 1 : 2 ) : 0 );

							if ( $autorecurring_type && ( $ipn->txn_type == 'subscr_signup' ) && ( ( $paymentBasket->period1 ) && ( $paymentBasket->mc_amount1 == 0 ) ) && ( $ipn->payment_status == '' ) ) {
								$ipn->payment_status			=	'Completed';
							}

							if ( ( $ipn->payment_status == 'Refunded' ) && ( $paymentBasket->mc_gross != ( - $ipn->mc_gross ) ) ) {
								$ipn->payment_status			=	'Partially-Refunded';
							}

							$this->_bindIpnToBasket( $ipn, $paymentBasket );

							// add the gateway to the basket:
							$paymentBasket->payment_method		=	$this->getPayName();
							$paymentBasket->gateway_account		=	$this->getAccountParam( 'id' );

							$this->updatePaymentStatus( $paymentBasket, $ipn->txn_type, $ipn->payment_status, $ipn, 1, $autorecurring_type, $autorenew_type, false );

							if ( in_array( $ipn->payment_status, array( 'Completed', 'Processed', 'Pending' ) ) ) {
								$ret							=	true;
							}
						} else {
							$this->_storeIpnResult( $ipn, 'FAILED' );

							$paymentBasket->payment_status		=	$ipn->payment_status;

							$this->_setErrorMSG( '<div class="message">' . $this->getTxtNextStep( $paymentBasket ) . '</div>' );

							$paymentBasket->payment_status		=	'RedisplayOriginalBasket';
							$ret								=	false;
						}
					} else {
						$this->_storeIpnResult( $ipn, 'MISMATCH' );

						$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' Paypal IPN fraud attempt. ERROR: ' . $valid, CBPTXT::T( 'Invalid transaction.' ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );

						$ret									=	false;
					}
				}
			}
		} else {
			$this->_setLogErrorMSG( 3, null, $this->getPayName() . ': USER1 is missing in the return URL: ' . var_export( $_GET, true ), CBPTXT::T( 'Please contact site administrator to check error log.' ) );
		}

		if ( ( $type == 'R' ) && in_array( $this->getAccountParam( 'template_layout', 'MINLAYOUT' ), array( 'MINLAYOUT', 'MOBILE' ) ) ) {
			$js													=	"if ( top != self ) {"
																.		"document.body.style.display = 'none';"
																.		"parent.location = '" . addslashes( ( $ret ? $this->getSuccessUrl( $paymentBasket ) : $this->getCancelUrl( $paymentBasket ) ) ) . "';"
																.	"}";

			echo '<script type="text/javascript">' . $js . '</script>';
		}

		return  $ret;
	}
	/**
	 * Handles a gateway notification
	 *
	 * @param cbpaidPaymentBasket $paymentBasket
	 * @param array $postdata
	 * @return bool
	 */
	protected function handleNotify( $paymentBasket, $postdata )
	{
		global $_CB_framework;

		$transactionId												=	cbGetParam( $postdata, 'txn_id', null );
		$subscriptionId												=	cbGetParam( $postdata, 'recurring_payment_id', null );
		$return														=	false;

		if ( $transactionId || $subscriptionId ) {
			if ( $this->hasPaypalApi() ) {
				$paymentStatus										=	cbGetParam( $postdata, 'payment_status', null );
				$paymentType										=	cbGetParam( $postdata, 'payment_type', null );

				$ipn												=&	$this->_prepareIpn( 'I', $paymentStatus, $paymentType, null, $_CB_framework->now(), 'utf-8' );

				if ( $subscriptionId ) {
					$exists											=	$paymentBasket->loadThisMatching( array( 'subscr_id' => $subscriptionId ) );
				} else {
					$custom											=	(int) cbGetParam( $postdata, 'custom', null );

					if ( $custom ) {
						$exists										=	$paymentBasket->load( $custom );
					} else {
						$exists										=	$paymentBasket->loadThisMatching( array( 'txn_id' => $transactionId ) );
					}
				}

				if ( $exists ) {
					$ipn->bindBasket( $paymentBasket );

					$ipn->user_id									=	(int) $paymentBasket->user_id;
				}

				$ipn->bind( $postdata );

				if ( $exists ) {
					$ipn->item_number								=	$paymentBasket->item_number;
				}

				if ( $subscriptionId ) {
					if ( ! $ipn->payment_status ) {
						$profileStatus								=	cbGetParam( $postdata, 'profile_status', null );

						if ( $profileStatus == 'Cancelled' ) {
							$ipn->payment_status					=	'Unsubscribed';
							$ipn->payment_date						=	cbGetParam( $postdata, 'time_created', null );
						} elseif ( $profileStatus == 'Active' ) {
							$ipn->payment_status					=	'Completed';
							$ipn->payment_date						=	cbGetParam( $postdata, 'time_created', null );
						} elseif ( $profileStatus == 'Expired' ) {
							$ipn->payment_status					=	'Unsubscribed';
							$ipn->payment_date						=	cbGetParam( $postdata, 'time_created', null );
						} elseif ( $profileStatus == 'Suspended' ) {
							$ipn->payment_status					=	'Denied';
							$ipn->payment_date						=	cbGetParam( $postdata, 'time_created', null );
						} elseif ( $profileStatus == 'Pending' ) {
							$ipn->payment_status					=	'Pending';
							$ipn->payment_date						=	cbGetParam( $postdata, 'time_created', null );
						}
					}

					$requestParams									=	array(	'METHOD' => 'GetRecurringPaymentsProfileDetails',
																				'PROFILEID' => $subscriptionId
																			);
				} else {
					$requestParams									=	array(	'METHOD' => 'GetTransactionDetails',
																				'TRANSACTIONID' => $transactionId
																			);
				}

				$this->_signRequestParams( $requestParams );

				$results											=	array();
				$response											=	null;
				$status												=	null;
				$error												=	$this->_httpsRequest( str_replace( 'www', 'api-3t', $this->gatewayUrl( 'psp' ) . '/nvp' ), $requestParams, 105, $response, $status, 'post', 'normal' );

				if ( $response ) {
					parse_str( $response, $results );
				}

				$rawData											=	'$response="' . preg_replace( '/([^\s]{100})/', '$1 ', $response ) . "\"\n"
																	.	'$results=' . var_export( $results, true ) . ";\n"
																	.	'$_GET=' . var_export( $_GET, true ) . ";\n"
																	.	'$_POST=' . var_export( $_POST, true ) . ";\n";

				$ipn->setRawData( $rawData );

				if ( $error || ( $status != 200 ) || ( ! $response ) ) {
					$ipn->log_type									=	'D';

					$ipn->setRawResult( 'COMMUNICATION ERROR' );

					$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' HTTPS POST request to payment gateway server failed.', CBPTXT::T( "Submitted transaction details request didn't return an error but didn't complete." ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
				} else {
					$insToIpn										=	array(	'address_street' => 'STREET',
																				'address_city' => 'CITY',
																				'address_state' => 'STATE',
																				'address_zip' => 'ZIP',
																				'address_country' => 'COUNTRY',
																				'address_country_code' => 'COUNTRYCODE',
																				'address_status' => 'ADDRESSSTATUS',
																				'first_name' => 'FIRSTNAME',
																				'last_name' => 'LASTNAME',
																				'payer_email' => 'EMAIL',
																				'payer_id' => 'PAYERID',
																				'payer_status' => 'PAYERSTATUS',
																				'auth_id' => 'CORRELATIONID',
																				'tax' => 'TAXAMT',
																				'mc_currency' => 'CURRENCYCODE',
																				'mc_fee' => 'FEEAMT',
																				'mc_gross' => 'AMT'
																			);

					if ( $ipn->payment_status == 'Refunded' ) {
						unset( $insToIpn['mc_fee'] );
						unset( $insToIpn['mc_gross'] );
					}

					foreach ( $insToIpn as $k => $v ) {
						$apiValue									=	cbGetParam( $results, $v );

						if ( $apiValue && ( ! in_array( $apiValue, array( '0.00', 'None' ) ) ) ) {
							$ipn->$k								=	$apiValue;
						}
					}

					switch ( $ipn->txn_type ) {
						case 'recurring_payment':
							$ipn->txn_type							=	'subscr_payment';
							break;
						case 'recurring_payment_profile_created':
							$ipn->txn_type							=	'subscr_signup';
							break;
						case 'recurring_payment_profile_cancel':
							$ipn->txn_type							=	'subscr_cancel';
							break;
						case 'recurring_payment_expired':
							$ipn->txn_type							=	'subscr_eot';
							break;
						case 'recurring_payment_skipped':
							$ipn->txn_type							=	'subscr_failed';
							break;
					}

					$valid											=	$this->_validateIPN( $ipn, $paymentBasket, cbGetParam( $_REQUEST, 'cbpid' ) );

					if ( $valid === true ) {
						if ( cbGetParam( $results, 'ACK' ) == 'Success' ) {
							if ( $exists ) {
								$ipn->setRawResult( 'SUCCESS' );

								if ( $ipn->txn_type != 'subscr_signup' ) {
									$autorecurring_type				=	( in_array( $ipn->txn_type, array( 'subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ? 2 : 0 );
									$autorenew_type					=	( $autorecurring_type ? ( ( ( $this->getAccountParam( 'enabled', 0 ) == 3 ) && ( $paymentBasket->isAnyAutoRecurring() == 2 ) ) ? 1 : 2 ) : 0 );

									if ( $autorecurring_type && ( $ipn->txn_type == 'subscr_signup' ) && ( ( $paymentBasket->period1 ) && ( $paymentBasket->mc_amount1 == 0 ) ) && ( $ipn->payment_status == '' ) ) {
										$ipn->payment_status		=	'Completed';
									}

									if ( ( $ipn->payment_status == 'Refunded' ) && ( $paymentBasket->mc_gross != ( - $ipn->mc_gross ) ) ) {
										$ipn->payment_status		=	'Partially-Refunded';
									}

									if ( in_array( $ipn->txn_type, array( 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ) {
										$autorecurring_type			=	0;
									}

									$this->updatePaymentStatus( $paymentBasket, $ipn->txn_type, $ipn->payment_status, $ipn, 1, $autorecurring_type, $autorenew_type, false );

									$return							=	true;

									$ipn->store();
								} else {
									$return							=	null;
								}
							} else {
								$ipn->log_type						=	'J';

								$ipn->setRawResult( 'FAILED' );
							}
						} else {
							$ipn->log_type							=	'M';

							$ipn->setRawResult( 'FAILED' );

							$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' Paypal API error returned. ERROR: ' . cbGetParam( $results, 'L_LONGMESSAGE0' ) . ' CODE: ' . cbGetParam( $results, 'L_ERRORCODE0' ), cbGetParam( $results, 'L_SHORTMESSAGE0' ) . '. ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
						}
					} else {
						$ipn->log_type								=	'O';

						$ipn->setRawResult( 'MISMATCH' );

						$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' Paypal IPN fraud attempt. ERROR: ' . $valid, CBPTXT::T( 'Invalid transaction.' ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
					}
				}
			} else {
				$this->_setLogErrorMSG( 3, null, $this->getPayName() . ' Needed Paypal API username, password and signature not set.' . "\n" . '$_GET=' . var_export( $_GET, true ) . "\n" . '$_POST=' . var_export( $_POST, true ) . "\n", CBPTXT::T( 'Needed Paypal API username, password and signature not set.' ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
			}
		} else {
			$this->_setLogErrorMSG( 3, null, $this->getPayName() . ' Needed Paypal Transaction ID (txn_id) missing.' . "\n" . '$_GET=' . var_export( $_GET, true ) . "\n" . '$_POST=' . var_export( $_POST, true ) . "\n", CBPTXT::T( 'Transaction not found.' ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
		}

		return $return;
	}
 /**
  * The user cancelled his payment
  *
  * @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 null
  */
 private function handleCancel($paymentBasket, $postdata)
 {
     global $_GET;
     // The user cancelled his payment (and registration):
     if ($this->hashPdtBackCheck($this->_getReqParam('pdtback'))) {
         $paymentBasketId = (int) $this->_getReqParam('basket');
         $exists = $paymentBasket->load((int) $paymentBasketId);
         if ($exists && $this->_getReqParam('id') == $paymentBasket->shared_secret && $paymentBasket->payment_status != 'Completed') {
             $paymentBasket->payment_status = 'RegistrationCancelled';
             $messageToUser = $this->_getCancelledErrorText($_GET);
             $this->_setErrorMSG($messageToUser);
         }
     }
     return null;
 }
	/**
	 * Handle Paypal PDT
	 *
	 * @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 handlePaypalPDT( $paymentBasket, /** @noinspection PhpUnusedParameterInspection */ $postdata )
	{
		global $_CB_framework, $_CB_database, $_GET, $_POST;

		$ret								=	null;
		// The user got redirected back from paypal with a success message:

		if ( isset( $_GET['tx'] ) && isset( $_GET['st'] ) && isset( $_GET['amt'] ) && isset( $_GET['cc'] ) ) {

			/// P D T :		Process Payment Data Transaction (PDT):

			// check if PDT not already processed:
			$pbTmp							=	new cbpaidPaymentBasket( $_CB_database );
			$paymentBasketId				=	(int) $this->_getReqParam('basket');
			if ( $paymentBasketId
				&& $pbTmp->load( (int) $paymentBasketId )
				&& ( $pbTmp->payment_status == cbGetParam( $_GET, 'st' ) )
				&& ( $pbTmp->txn_id == cbGetParam( $_GET, 'tx' ) )
				&& ( $pbTmp->shared_secret ==cbGetParam( $_GET, 'cbpid' ) ) )
			{
				// this PDT has already been treated...probably a Nth reload or bookmarked page:
				$paymentBasket->load( (int) $pbTmp->id );
			} else {
				$ipn						=	new cbpaidPaymentNotification($_CB_database);
				$ipn->payment_method		=	$this->getPayName();
				$ipn->gateway_account		=	$this->getAccountParam( 'id' );
				// done below: $ipn->log_type			= 'R';
				$ipn->time_received			=	date( 'Y-m-d H:i:s', $_CB_framework->now() );
				$ipn->raw_data				=	/* cbGetParam() not needed: we want raw info */ '$_GET=' . var_export( $_GET, true ) . ";\n";
				$ipn->raw_data				.=	/* cbGetParam() not needed: we want raw info */ '$_POST=' . var_export( $_POST, true ) . ";\n";
				$ipn->ip_addresses			=	cbpaidRequest::getIPlist();
				$ipn->user_id				=	$pbTmp->user_id;

				// post back to PayPal system to validate:
				$formvars					=	array(	'cmd'	=> '_notify-synch',
														'tx'	=> cbGetParam( $_REQUEST, 'tx', '' ),
														'at'	=> trim($this->getAccountParam('paypal_identity_token'))
													 );
				$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);			//FIXME: urldecode is done below already!

				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			.=	'$formvars=' . var_export( $formvars, true ) . ";\n";
					$ipn->raw_data			.=	'$transaction_info=\'' . $transaction_info . "';\n";
					$ipn->log_type			=	'E';
					$ipn->time_received		=	date( 'Y-m-d H:i:s', $_CB_framework->now() );
					$_CB_database->insertObject( $ipn->getTableName(), $ipn, $ipn->getKeyName() );

					$this->_setLogErrorMSG( 3, $ipn, 'Paypal: Error at notification received: could not reach Paypal gateway for notification check at ' . $this->_paypalUrl() . '. ' . $ipn->raw_result, null );
					$this->_setErrorMSG( sprintf( CBPTXT::T("Sorry no response for your payment from payment server (error %s). Please check your email and status later."), $error ) );
					$ret					=	false;
				} else {
					// echo $transaction_info;
					$input = explode("\n", $transaction_info);
					foreach ($input as $k => $in) {
						$input[$k]			=	trim( $in, "\n\r" );
					}
					$resultMessage = array_shift( $input );
					$output					=	array();
					foreach ($input as $in) {
						$posEqualSign		=	strpos($in, '=');
						if ($posEqualSign === false) {
							$output[]		=	$in;
						} else {
							$output[substr($in,0,$posEqualSign)]	=	substr($in, $posEqualSign+1);
						}
					}
					if ( isset( $output['charset'] ) && ( $resultMessage == 'SUCCESS' ) ) {
						if ( strtolower( $output['charset'] ) != strtolower( $_CB_framework->outputCharset() ) ) {
							foreach ($output as $k => $v ) {
								$output[$k]		=	$this->_charsetConv( $v, $output['charset'], $_CB_framework->outputCharset() );
							}
							$output['charset']	=	$_CB_framework->outputCharset();
						}			
					}
					$ipn->bind( $output );
					$ipn->raw_result 		=	$resultMessage;
					$ipn->raw_data			.=	'$transaction_info=\'' . $transaction_info . "';\n";
					$ipn->raw_data			.=	'$PDT_RESULT=' . var_export( $output, true ) . ";\n";
					$ipn->payment_basket_id	=	(int) $ipn->custom;
					/*
					if(!$_CB_database->updateObject( $ipn->_tbl, $ipn, $ipn->_tbl_key, false)) {
						echo 'update error:'.htmlspecialchars($_CB_database->stderr(true))."\n";
						exit();
					}
					*/
	
					if ( $resultMessage == 'SUCCESS' ) {
						$ipn->log_type							= 'R';
						$_CB_database->insertObject( $ipn->getTableName(), $ipn, $ipn->getKeyName() );

						$paymentBasketId						=	(int) $ipn->custom;
						$exists									=	$paymentBasket->load( (int) $paymentBasketId );
						if ( $exists ) {
							$this->_fixPayPalIpnBugs( $ipn, $paymentBasket );
							$noFraudCheckResult					=	$this->_checkNotPayPalFraud( $ipn, $paymentBasket, cbGetParam( $_REQUEST, 'cbpid', '' ) );
							if ( $noFraudCheckResult === true ) {
								$autorecurring_type				=	( in_array( $ipn->txn_type, array( 'subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ? 2 : 0 );
								$paypalUserChoicePossible		=	( ( $this->getAccountParam( 'enabled', 0 ) == 3 ) && ( $paymentBasket->isAnyAutoRecurring() == 2 ) );
								$autorenew_type					=	( $autorecurring_type ? ( $paypalUserChoicePossible ? 1 : 2 ) : 0 );
								$this->_bindIpnToBasket( $ipn, $paymentBasket );
								$paymentBasket->payment_method	=	$this->getPayName();
								$paymentBasket->gateway_account	=	$this->getAccountParam( 'id' );
								$this->updatePaymentStatus( $paymentBasket, $ipn->txn_type, $ipn->payment_status, $ipn, 1, $autorecurring_type, $autorenew_type, false );

							} else {
								$this->_setLogErrorMSG( 3, $ipn, 'Received back from paypal: ' . var_export( $ipn, true ), CBPTXT::T("Payment notification mismatch: ") . $noFraudCheckResult . '.' );
								$ret = false;
								//TBD: update notification record !
								$ipn->log_type					=	'G';		// PDT FRAUD detected
								$ipn->raw_result				=	$noFraudCheckResult;
								$_CB_database->updateObject( $ipn->getTableName(), $ipn, $ipn->getKeyName(), false);
							}
						}
					} elseif ( $resultMessage == 'FAIL' ) {
						$ipn->log_type							= 'L';
						$_CB_database->insertObject( $ipn->getTableName(), $ipn, $ipn->getKeyName() );

						$this->_setLogErrorMSG( 3, $ipn, 'Paypal: Error: Received FAIL result message from Paypal', CBPTXT::T("Sorry your payment has not been processed. Transaction result:")
							. $transaction_info
							. '. ' . CBPTXT::T("Please try again and notify system administrator.") );
						$ret = false;
					} else {
						$ipn->log_type							= 'N';
						$_CB_database->insertObject( $ipn->getTableName(), $ipn, $ipn->getKeyName() );

						$this->_setLogErrorMSG( 3, $ipn, 'Paypal: Error: Received following unknown result message from Paypal: ' . $resultMessage, CBPTXT::T("Sorry no response for your payment. Please check your email and status later.") );
						$ret = false;
					}
				}
			}
		} else {
			// result=success but not a PDT from paypal:

			// it could be a subscription with a free trial period: in that case, as there is no initial transaction, we get returned without txn_id.
			// we must either guess that user subscribed (if he is allowed for free trials, or must wait for IPN:

			$paymentBasketId											=	(int) $this->_getReqParam( 'basket' );
			if ( $paymentBasketId ) {
				if ( $paymentBasket->load( (int) $paymentBasketId ) ) {
					$cbpid												=	cbGetParam( $_REQUEST, 'cbpid', '' );
					if ( $cbpid == $paymentBasket->shared_secret ) {
						$enable_paypal									=	$this->getAccountParam( 'enabled', 0 );
						$isAnyAutoRecurring								=	$paymentBasket->isAnyAutoRecurring();
						$pay1subscribe2									=	$this->_getPaySubscribePossibilities( $enable_paypal, $paymentBasket );
						if ( $isAnyAutoRecurring && ( ( $pay1subscribe2 & 0x2 ) != 0 ) && $paymentBasket->period1 ) {
							// Free first period: Wait for IPN for 20 times 1 second:
							for ( $i = 0; $i < 20; $i++ ) {
								if ( $paymentBasket->load( (int) $paymentBasketId ) ) {
									if ( $paymentBasket->payment_status == 'Completed' ) {
										break;
									}
								} else {
									break;
								}
								sleep( 1 );
							}
							if ( $paymentBasket->payment_status != 'Completed' ) {
								if ( ( $isAnyAutoRecurring == 1 ) || ( ( $isAnyAutoRecurring == 2 ) && ( $paymentBasket->mc_amount1 != 0 ) ) )  {
									// 1: forced subscription: error if no IPN came to update payment basket:
									// 2: not forced subscription but no free initial value: we really need IPN to know status:
										$this->_setErrorMSG(CBPTXT::T("Sorry, payment has not been confirmed by Paypal (no IPN received). IPN must be enabled for auto-recurring payment subscriptions."));
								} else {
									// user-choice: no need to wait for payment basket completed to activate subscriptions:

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

									if ( $isAnyAutoRecurring == 2 )  {
										$autorecurring_type				=	2;
										$paypalUserChoicePossible		=	( ( $this->getAccountParam( 'enabled', 0 ) == 3 ) && ( $paymentBasket->isAnyAutoRecurring() == 2 ) );
										$autorenew_type					=	( $autorecurring_type ? ( $paypalUserChoicePossible ? 1 : 2 ) : 0 );
									} else {
										$autorecurring_type				=	0;
										$autorenew_type					=	0;
									}
									$paymentBasket->payment_method		=	$this->getPayName();
									$paymentBasket->gateway_account		=	$this->getAccountParam( 'id' );

									$this->updatePaymentStatus( $paymentBasket, 'web_accept', 'Completed', $ipn, 1, $autorecurring_type, $autorenew_type, false );
								}
							}
						}
					}
				}
			}


		}
		return $ret;
	}
 /**
  * The user got redirected back from the payment service provider with a success message: let's see how successfull it was
  *
  * @param  cbpaidPaymentBasket  $paymentBasket       New empty object. returning: includes the id of the payment basket of this callback (strictly verified, otherwise untouched)
  * @param  array                $requestdata         Data returned by gateway
  * @param  string               $type                Type of return ('R' for PDT, 'I' for INS, 'A' for Autorecurring payment (Vault) )
  * @param  array                $additionalLogData   Additional strings to log with IPN
  * @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 _returnParamsHandler($paymentBasket, $requestdata, $type, $additionalLogData = null)
 {
     global $_CB_framework, $_GET, $_POST;
     $oid = $requestdata["ordernumber"];
     $qp = new QuickpayApi();
     $qp->setOptions($this->api_key);
     if ($this->getAccountParam('enabled') == 2) {
         $qp->mode = 'subscriptions?order_id=';
     } else {
         $qp->mode = 'payments?order_id=';
     }
     // Commit the status request, checking valid transaction id
     $str = $qp->status($oid);
     $str["operations"][0] = array_reverse($str["operations"][0]);
     $qp_status = $str[0]["operations"][0]["qp_status_code"];
     $qp_type = strtolower($str[0]["type"]);
     $qp_status_msg = $str[0]["operations"][0]["qp_status_msg"];
     $qp_vars = $str[0]["variables"];
     $qp_id = $str[0]["id"];
     $qp_order_id = $str[0]["order_id"];
     $qp_aq_status_code = $str[0]["aq_status_code"];
     $qp_aq_status_msg = $str[0]["aq_status_msg"];
     $qp_cardtype = $str[0]["metadata"]["brand"];
     $qp_cardnumber = "xxxx-xxxxxx-" . $str[0]["metadata"]["last4"];
     $qp_amount = $str[0]["operations"][0]["amount"];
     $qp_currency = $str[0]["currency"];
     $qp_pending = $str[0]["pending"] == "true" ? " - pending " : "";
     $qp_expire = $str[0]["metadata"]["exp_month"] . "-" . $str[0]["metadata"]["exp_year"];
     $ret = null;
     $paymentBasketId = $requestdata["cbpbasket"];
     if ($paymentBasketId) {
         $exists = $paymentBasket->load((int) $paymentBasketId);
         if ($exists && ($requestdata["cbpid"] == $paymentBasket->shared_secret && !(($type == 'R' || $type == 'I') && $paymentBasket->payment_status == 'Completed'))) {
             // PDT doesn't return transacton information; lets request for it:
             /*				if ( $type == 'R' ) {
             					$requestdata = $str;
             
             					$formvars									=	array();
             
             					$formvars['protocol']						=	'3';
             
             					$formvars['msgtype']						=	'status';
             
             					$formvars['merchant']						=	$this->getAccountParam( 'pspid' );
             
             					$formvars['ordernumber']					=	$this->_prepareOrderNumber( $paymentBasket->id, true );
             
             					$formvars['splitpayment']					=	'0';
             
             
             
             					$this->_signRequestParams( $formvars );
             
             
             
             					$response									=	null;
             
             					$status										=	null;
             
             					$error										=	$this->_httpsRequest( $this->_pspApiUrl(), $formvars, 30, $response, $status, 'post', 'normal', '*/
             /*', true, 443, '', '', true, null );
             */
             /*
             					if ( ( ! $error ) && ( $status == 200 ) && $response ) {
             						$xml_response							=	$this->xmlTagValuesToArray( new SimpleXMLElement( $response, LIBXML_NONET | ( defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0 ) ) );
             						if ( $xml_response ) {
             							$requestdata						=	$xml_response;
             						}
             					}
             			}
             */
             // Log the return record:
             $log_type = $type;
             $reason = null;
             $paymentStatus = $this->_paymentStatus($qp_status, $this->reason);
             $paymentType = $qp_cardtype;
             $paymentTime = $_CB_framework->now();
             if ($paymentStatus == 'Error') {
                 $errorTypes = array('I' => 'D', 'R' => 'E');
                 if (isset($errorTypes[$type])) {
                     $log_type = $errorTypes[$type];
                 }
             }
             $ipn = $this->_prepareIpn($log_type, $paymentStatus, $paymentType, $this->reason, $paymentTime, 'utf-8');
             if ($qp_type == 'refund') {
                 // in case of refund we need to log the payment as it has same TnxId as first payment: so we need payment_date for discrimination:
                 $ipn->payment_date = gmdate('H:i:s M d, Y T', $paymentTime);
                 // paypal-style
             }
             $ipn->test_ipn = 0;
             $ipn->raw_data = '$message_type="' . ($type == 'R' ? 'RETURN_TO_SITE' : ($type == 'I' ? 'NOTIFICATION' : 'UNKNOWN')) . '";' . "\n";
             if ($additionalLogData) {
                 foreach ($additionalLogData as $k => $v) {
                     $ipn->raw_data .= '$' . $k . '="' . var_export($v, true) . '";' . "\n";
                 }
             }
             $ipn->raw_data .= '$requestdata=' . var_export($requestdata, true) . ";\n" . '$_GET=' . var_export($_GET, true) . ";\n" . '$_POST=' . var_export($_POST, true) . ";\n";
             if ($paymentStatus == 'Error') {
                 $paymentBasket->reason_code = $this->reason;
                 $this->_storeIpnResult($ipn, 'ERROR:' . $this->reason);
                 $this->_setLogErrorMSG(4, $ipn, $this->getPayName() . ': ' . $this->reason, CBPTXT::T('Sorry, the payment server replied with an error.') . ' ' . CBPTXT::T('Please contact site administrator to check payment status and error log.'));
                 $ret = false;
             } else {
                 $ipn->bindBasket($paymentBasket);
                 $ipn->sale_id = $paymentBasketId;
                 $insToIpn = array('txn_id' => $qp_id, 'mc_currency' => $qp_currency, 'receiver_email' => $qp_vars["merchant_email"], 'first_name' => $qp_vars['first_name'], 'last_name' => $qp_vars['last_name'], 'address_street' => $qp_vars['address_one'], 'address_zip' => $qp_vars['postal_code'], 'address_city' => $qp_vars['city'], 'address_country' => $qp_vars['country'], 'address_state' => $qp_vars['state_or_province'], 'contact_phone' => $qp_vars['phone'], 'payer_email' => $qp_vars['email']);
                 foreach ($insToIpn as $k => $v) {
                     $ipn->{$k} = $v;
                 }
                 $ipn->mc_gross = sprintf('%.2f', $qp_amount / 100);
                 $ipn->user_id = (int) $paymentBasket->user_id;
                 // check what type of purchase this is:
                 $recurring = in_array($qp_type, array('subscription', 'recurring')) ? true : false;
                 //subscription handling
                 // handle recurring subscriptions properly or default to single payment:
                 if ($recurring) {
                     $qp->mode = "subscriptions/";
                     $addlink = $qp_id . "/recurring/";
                     $process_parameters["amount"] = $qp_amount;
                     $process_parameters["order_id"] = $paymentBasket->item_number;
                     $process_parameters["auto_capture"] = TRUE;
                     $storder = $qp->createorder($qp_order_id, $qp_currency_code, $process_parameters, $addlink);
                     if ($paymentStatus == 'Completed' && !$paymentBasket->subscr_id) {
                         $ipn->txn_type = 'subscr_signup';
                         $ipn->subscr_id = $qp_id;
                         $ipn->subscr_date = $ipn->payment_date;
                     } elseif ($paymentStatus == 'Denied') {
                         if ($paymentBasket->reattempts_tried + 1 <= cbpaidScheduler::getInstance($this)->retries) {
                             $ipn->txn_type = 'subscr_failed';
                         } else {
                             $ipn->txn_type = 'subscr_cancel';
                         }
                     } elseif (in_array($paymentStatus, array('Completed', 'Processed', 'Pending'))) {
                         $ipn->txn_type = 'subscr_payment';
                     }
                 } else {
                     $ipn->txn_type = 'web_accept';
                 }
                 // validate payment from PDT or IPN
                 $apiReplyRaw = null;
                 $apiReplyArray = null;
                 if ($qp_status == 20000) {
                     $ipn->raw_data .= '$apiReplyRaw=\'' . str_replace(array('\\', '\''), array('\\\\', '\\\''), $apiReplyRaw) . "';\n" . '$apiReplyFormattedArray=' . var_export($apiReplyArray, true) . ";\n";
                     if ($paymentBasketId == $requestdata["cbpbasket"] && (sprintf('%.2f', $paymentBasket->mc_gross) == $ipn->mc_gross || $ipn->payment_status == 'Refunded') && $paymentBasket->mc_currency == $ipn->mc_currency) {
                         if (in_array($ipn->payment_status, array('Completed', 'Processed', 'Pending', 'Refunded', 'Denied'))) {
                             $this->_storeIpnResult($ipn, 'SUCCESS');
                             $this->_bindIpnToBasket($ipn, $paymentBasket);
                             // add the gateway to the basket:
                             $paymentBasket->payment_method = $this->getPayName();
                             $paymentBasket->gateway_account = $this->getAccountParam('id');
                             // 0: not auto-recurring, 1: auto-recurring without payment processor notifications, 2: auto-renewing with processor notifications updating $expiry_date:
                             $autorecurring_type = in_array($ipn->txn_type, array('subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed')) ? 2 : 0;
                             // 0: not auto-renewing (manual renewals), 1: asked for by user, 2: mandatory by configuration:
                             $autorenew_type = $autorecurring_type ? $this->getAccountParam('enabled', 0) == 3 && $paymentBasket->isAnyAutoRecurring() == 2 ? 1 : 2 : 0;
                             if ($recurring) {
                                 $paymentBasket->reattempt = 1;
                                 // we want to reattempt auto-recurring payment in case of failure
                             }
                             $this->updatePaymentStatus($paymentBasket, $ipn->txn_type, $ipn->payment_status, $ipn, 1, $autorecurring_type, $autorenew_type, false);
                             if (in_array($ipn->payment_status, array('Completed', 'Processed', 'Pending'))) {
                                 $ret = true;
                             }
                         } else {
                             $this->_storeIpnResult($ipn, 'FAILED');
                             $paymentBasket->payment_status = $ipn->payment_status;
                             $this->_setErrorMSG('<div class="message">' . $this->getTxtNextStep($paymentBasket) . '</div>');
                             $paymentBasket->payment_status = 'RedisplayOriginalBasket';
                             $ret = false;
                         }
                     } else {
                         $this->_storeIpnResult($ipn, 'MISMATCH');
                         $this->_setLogErrorMSG(3, $ipn, $this->getPayName() . ': amount or currency missmatch', CBPTXT::T('Sorry, the payment does not match the basket.') . ' ' . CBPTXT::T('Please contact site administrator to check error log.'));
                         $ret = false;
                     }
                 } else {
                     $this->_storeIpnResult($ipn, 'SIGNERROR');
                     $this->_setLogErrorMSG(3, $ipn, $this->getPayName() . ': Transaction does not match with gateway. Please check API Key setting', CBPTXT::T('The API Key is incorrect.') . ' ' . CBPTXT::T('Please contact site administrator to check error log.'));
                     $ret = false;
                 }
             }
         }
     } else {
         $this->_setLogErrorMSG(3, null, $this->getPayName() . ': ordernumber is missing in the return URL: ' . var_export($_GET, true), CBPTXT::T('Please contact site administrator to check error log.'));
     }
     return $ret;
 }
 /**
  * Handles the gateway-specific result of payments (redirects back to this site and gateway notifications). WARNING: unchecked access !
  *
  * @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
  * @param  string               $result         result= get parameter, other than 'notify', 'success' or 'cancel'.
  * @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
  */
 protected function handleOtherResult($paymentBasket, $postdata, $result)
 {
     global $_CB_framework, $_POST;
     $ret = null;
     // $privateVarsList = 'id payment_method gateway_account user_id time_initiated time_completed ip_addresses mc_gross mc_currency quantity item_number item_name shared_secret payment_status';
     switch ($result) {
         case 'showinstructions':
             //We need to display payment instructions:
             $paymentBasketId = (int) cbGetParam($_POST, 'custom', 0);
             if ($paymentBasketId == 0) {
                 $paymentBasketId = (int) cbGetParam($_GET, 'custom', 0);
             }
             $exists = $paymentBasket->load((int) $paymentBasketId);
             if ($exists && cbGetParam($_REQUEST, 'amount', 0.0) == $paymentBasket->mc_gross && cbGetParam($_REQUEST, 'currency_code', 0) == $paymentBasket->mc_currency) {
                 $ret = $this->_outputInstructions($paymentBasket);
                 if ($ret !== false) {
                     outputCbTemplate();
                     $this->_outputRegTemplate();
                     $ret = $paymentBasket->displayBasket() . $ret;
                 }
             } else {
                 $this->_setErrorMSG(CBPTXT::T("Payment basket does not login."));
                 $ret = false;
             }
             break;
         case 'showslip':
             // The user asked to see the payment slip.
             if (isset($_GET['user']) && isset($_GET['cbpid'])) {
                 $paymentBasketId = (int) cbGetParam($_GET, 'cbpid', 0);
                 $exists = $paymentBasket->load((int) $paymentBasketId);
                 if ($exists && (int) cbGetParam($_GET, 'user', 0) == $paymentBasket->user_id && (int) cbGetParam($_GET, 'cbpbhs') == md5($paymentBasket->shared_secret)) {
                     // log:
                     $ipn = $this->_logNotification('R', $_CB_framework->now(), $paymentBasket);
                     if ($paymentBasket->payment_status == 'NotInitiated' && $paymentBasket->payment_method == $this->getPayName() && $paymentBasket->gateway_account == $this->getAccountParam('id')) {
                         $privateVarsList = 'id user_id time_initiated time_completed ip_addresses mc_gross mc_currency ' . 'quantity item_number item_name shared_secret payment_status ' . 'invoice period1 period2 period3 mc_amount1 mc_amount2 mc_amount3';
                         $paymentBasket->bindObjectToThisObject($ipn, $privateVarsList);
                         $newPaymentState = $this->getAccountParam('pending_payment_state', 'Pending');
                         $this->updatePaymentStatus($paymentBasket, 'web_accept', $newPaymentState, $ipn, 1, 0, 0, true, false);
                     }
                     if (in_array($paymentBasket->payment_status, array('NotInitiated', 'Pending'))) {
                         $ret = $this->_outputSlip($paymentBasket);
                     }
                 }
             }
             break;
         default:
             $ret = CBPTXT::Th("Unexpected result");
             break;
     }
     return $ret;
 }