/**
	 * Get list of plan_id of all items purchased simultaneously
	 * 
	 * @param  cbpaidPaymentBasket  $paymentBasket
	 * @return array of int planids array( plan_id => quantity )
	 */
	public static function getPlansQuantityofBasket( $paymentBasket ) {
		$sameTimePurchasingPlansIds						=	array();
		foreach ( $paymentBasket->loadPaymentItems() as $item ) {
			if ( ! isset( $sameTimePurchasingPlansIds[$item->plan_id] ) ) {
				$sameTimePurchasingPlansIds[$item->plan_id]		=	0;
			}
			$sameTimePurchasingPlansIds[$item->plan_id]			+=	$item->quantity;
		}
		return $sameTimePurchasingPlansIds;
	}
	/**
	 * Check if $this Something is in a Basket which is in Pending payment state, but which is not $notBasketId
	 *
	 * @param  int  $notBasketId
	 * @return boolean
	 */
	public function hasPendingPayment( $notBasketId ) {
		$paymentBasket		=	new cbpaidPaymentBasket( $this->_db );
		$basketLoaded		=	$paymentBasket->loadLatestBasketOfUserPlanSubscription( $this->user_id, $this->plan_id, $this->id, 'Pending', $notBasketId );
		return $basketLoaded;
	}
 /**
  * Updates payment status of basket and of corresponding subscriptions if there is a change in status
  *
  * @param  cbpaidPaymentBasket        $paymentBasket         Basket
  * @param  string                     $eventType             type of event (paypal type): 'web_accept', 'subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed'
  * @param  string                     $paymentStatus         new status (Completed, RegistrationCancelled)
  * @param  cbpaidPaymentNotification  $notification          notification object of the payment
  * @param  int                        $occurrences           renewal occurrences
  * @param  int                        $autorecurring_type    0: not auto-recurring, 1: auto-recurring without payment processor notifications, 2: auto-renewing with processor notifications updating $expiry_date
  * @param  int                        $autorenew_type        0: not auto-renewing (manual renewals), 1: asked for by user, 2: mandatory by configuration
  * @param  boolean|string             $txnIdMultiplePaymentDates  FALSE: unique txn_id for each payment, TRUE: same txn_id can have multiple payment dates, additionally: 'SINGLEPAYMENT' will not look at txn_id at all
  * @param  boolean                    $storePaymentRecord   TRUE: normal case, create payment record if needed. FALSE: offline case where pending payment should not create a payment record.
  * @return void
  */
 public function updatePaymentStatus($paymentBasket, $eventType, $paymentStatus, &$notification, $occurrences, $autorecurring_type, $autorenew_type, $txnIdMultiplePaymentDates, $storePaymentRecord = true)
 {
     global $_CB_framework, $_PLUGINS;
     $pluginsLoaded = false;
     $basketUpdateNulls = false;
     $previousUnifiedStatus = $this->mapPaymentStatus($paymentBasket->payment_status);
     $unifiedStatus = $this->mapPaymentStatus($paymentStatus);
     // get all related subscriptions being paid by this basket:
     $subscriptions = $paymentBasket->getSubscriptions();
     $thisIsReferencePayment = false;
     $user = CBuser::getUserDataInstance((int) $paymentBasket->user_id);
     if ($paymentBasket->payment_status != $paymentStatus || $unifiedStatus == 'Partially-Refunded' || $autorecurring_type) {
         if ($paymentStatus && (in_array($eventType, array('web_accept', 'subscr_payment', 'subscr_signup')) || in_array($unifiedStatus, array('Reversed', 'Refunded', 'Partially-Refunded')))) {
             $paymentBasket->payment_status = $paymentStatus;
         }
         if (in_array($eventType, array('subscr_payment', 'subscr_signup'))) {
             $paymentBasket->recurring = 1;
         }
         if ($autorecurring_type == 0 && in_array($unifiedStatus, array('Completed', 'Processed', 'FreeTrial'))) {
             $paymentBasket->mc_amount1 = null;
             $paymentBasket->mc_amount3 = null;
             $paymentBasket->period1 = null;
             $paymentBasket->period3 = null;
             $basketUpdateNulls = true;
         }
         // if (count($subscriptions) >= 1) {
         $now = $_CB_framework->now();
         $completed = false;
         $thisIsReferencePayment = false;
         $reason = null;
         switch ($unifiedStatus) {
             case 'FreeTrial':
             case 'Completed':
             case 'Processed':
                 // this includes Canceled_Reversal !!! :
                 if ($unifiedStatus == 'FreeTrial') {
                     $paymentBasket->payment_status = 'Completed';
                 }
                 if ($unifiedStatus == 'FreeTrial' || $unifiedStatus == 'Completed') {
                     if ($notification->payment_date) {
                         $time_completed = cbpaidTimes::getInstance()->gmStrToTime($notification->payment_date);
                     } else {
                         $time_completed = $now;
                     }
                     $paymentBasket->time_completed = Application::Database()->getUtcDateTime($time_completed);
                     $completed = true;
                 }
                 if ($paymentStatus == 'Canceled_Reversal') {
                     $paymentBasket->payment_status = 'Completed';
                 }
                 if (is_object($notification) && isset($notification->txn_id)) {
                     // real payment with transaction id: store as reference payment if not already stored:
                     $thisIsReferencePayment = $this->_storePaymentOnce($paymentBasket, $notification, $now, $txnIdMultiplePaymentDates, 'Updating payment record because of new status of payment basket: ' . $unifiedStatus . ($paymentStatus != $unifiedStatus ? ' (new gateway-status: ' . $paymentStatus . ')' : '') . ' because of event received: ' . $eventType . '. Previous status was: ' . $previousUnifiedStatus);
                 } else {
                     // Free trials don't have a notification:
                     $thisIsReferencePayment = true;
                 }
                 if ($thisIsReferencePayment) {
                     // payment not yet processed:
                     $autorenewed = $paymentBasket->recurring == 1 && $unifiedStatus == 'Completed' && $previousUnifiedStatus == 'Completed';
                     for ($i = 0, $n = count($subscriptions); $i < $n; $i++) {
                         $reason = $autorenewed ? 'R' : $subscriptions[$i]->_reason;
                         $subscriptions[$i]->activate($user, $now, $completed, $reason, $occurrences, $autorecurring_type, $autorenew_type, $autorenewed ? 1 : 0);
                     }
                 }
                 break;
             case 'RegistrationCancelled':
             case 'Reversed':
             case 'Refunded':
             case 'Unsubscribed':
                 if ($unifiedStatus == 'RegistrationCancelled') {
                     if (!($previousUnifiedStatus == 'NotInitiated' || $previousUnifiedStatus === 'Pending' && $paymentBasket->payment_method === 'offline')) {
                         return;
                     }
                 }
                 for ($i = 0, $n = count($subscriptions); $i < $n; $i++) {
                     $reason = $subscriptions[$i]->_reason;
                     if ($reason != 'R' || in_array($unifiedStatus, array('Reversed', 'Refunded'))) {
                         // Expired and Cancelled as well as Partially-Refunded are not reverted !		//TBD: really revert on refund everything ? a plan param would be nice here
                         if (!in_array($previousUnifiedStatus, array('Pending', 'In-Progress', 'Denied', 'Reversed', 'Refunded')) && in_array($subscriptions[$i]->status, array('A', 'R', 'I')) && !$subscriptions[$i]->hasPendingPayment($paymentBasket->id)) {
                             // not a cancelled or denied renewal:
                             $subscriptions[$i]->revert($user, $unifiedStatus);
                         }
                     }
                 }
                 if ($unifiedStatus == 'RegistrationCancelled') {
                     $paymentBasket->historySetMessage('Payment basket deleted because the subscriptions and payment got cancelled');
                     $paymentBasket->delete();
                     // deletes also payment_Items
                 }
                 $paidUserExtension = cbpaidUserExtension::getInstance($paymentBasket->user_id);
                 $subscriptionsAnyAtAll = $paidUserExtension->getUserSubscriptions('');
                 $params = cbpaidApp::settingsParams();
                 $createAlsoFreeSubscriptions = $params->get('createAlsoFreeSubscriptions', 0);
                 if (count($subscriptionsAnyAtAll) == 0 && !$createAlsoFreeSubscriptions) {
                     $user = new UserTable();
                     $id = (int) cbGetParam($_GET, 'user');
                     $user->load((int) $id);
                     if ($user->id && $user->block == 1) {
                         $user->delete(null);
                     }
                 }
                 break;
             case 'Denied':
             case 'Pending':
                 if ($unifiedStatus == 'Denied') {
                     // In fact when denied, it's the case as if the user attempted payment but failed it: He should be able to re-try: So just store the payment as denied for the records.
                     if ($eventType == 'subscr_failed' || $eventType == 'subscr_cancel' && $autorecurring_type != 2) {
                         // special case of a failed attempt:
                         // or this is the final failed attempt of a basket with notifications:
                         break;
                     }
                 }
                 if ($previousUnifiedStatus == 'Completed') {
                     return;
                     // do not change a Completed payment as it cannot become Pending again. If we get "Pending" after "Completed", it is a messages chronological order mistake.
                 }
                 break;
             case 'In-Progress':
             case 'Partially-Refunded':
             default:
                 break;
         }
         if ($eventType == 'subscr_cancel') {
             if (!in_array($unifiedStatus, array('Denied', 'Reversed', 'Refunded', 'Unsubscribed'))) {
                 for ($i = 0, $n = count($subscriptions); $i < $n; $i++) {
                     $subscriptions[$i]->autorecurring_cancelled($user, $unifiedStatus, $eventType);
                 }
             }
         }
         for ($i = 0, $n = count($subscriptions); $i < $n; $i++) {
             $subscriptions[$i]->notifyPaymentStatus($unifiedStatus, $previousUnifiedStatus, $paymentBasket, $notification, $now, $user, $eventType, $paymentStatus, $occurrences, $autorecurring_type, $autorenew_type);
         }
         if (in_array($unifiedStatus, array('Denied', 'Reversed', 'Refunded', 'Partially-Refunded', 'Pending', 'In-Progress'))) {
             $thisIsReferencePayment = $this->_storePaymentOnce($paymentBasket, $notification, $now, $txnIdMultiplePaymentDates, 'Updating payment record because of new status of payment basket: ' . $unifiedStatus . ($paymentStatus != $unifiedStatus ? ' (new gateway-status: ' . $paymentStatus . ')' : '') . ' because of event received: ' . $eventType . '. Previous status was: ' . $previousUnifiedStatus);
         }
         // }
         foreach ($paymentBasket->loadPaymentTotalizers() as $totalizer) {
             $totalizer->notifyPaymentStatus($thisIsReferencePayment, $unifiedStatus, $previousUnifiedStatus, $paymentBasket, $notification, $now, $user, $eventType, $paymentStatus, $occurrences, $autorecurring_type, $autorenew_type, $txnIdMultiplePaymentDates);
         }
         if (!in_array($unifiedStatus, array('RegistrationCancelled'))) {
             if ($thisIsReferencePayment && in_array($unifiedStatus, array('Completed', 'Processed'))) {
                 $paymentBasket->setPaidInvoiceNumber($reason);
             }
             $paymentBasket->historySetMessage('Updating payment basket ' . ($paymentStatus !== null ? 'status: ' . $unifiedStatus . ($paymentStatus != $unifiedStatus ? ' (new gateway-status: ' . $paymentStatus . ')' : '') : '') . ' because of event received: ' . $eventType . ($paymentStatus !== null ? '. Previous status was: ' . $previousUnifiedStatus : ''));
             $paymentBasket->store($basketUpdateNulls);
         } else {
             //TDB ? : $paymentBasket->delete(); in case of RegistrationCancelled done above, but should be done in case of FreeTrial ? (could be a param in future)
         }
         if (!in_array($unifiedStatus, array('Completed', 'Processed')) || $thisIsReferencePayment) {
             $_PLUGINS->loadPluginGroup('user', 'cbsubs.');
             $_PLUGINS->loadPluginGroup('user/plug_cbpaidsubscriptions/plugin');
             $pluginsLoaded = true;
             $_PLUGINS->trigger('onCPayAfterPaymentStatusChange', array(&$user, &$paymentBasket, &$subscriptions, $unifiedStatus, $previousUnifiedStatus, $occurrences, $autorecurring_type, $autorenew_type));
         }
     }
     if (!in_array($unifiedStatus, array('Completed', 'Processed')) || $thisIsReferencePayment) {
         if (!$pluginsLoaded) {
             $_PLUGINS->loadPluginGroup('user', 'cbsubs.');
             $_PLUGINS->loadPluginGroup('user/plug_cbpaidsubscriptions/plugin');
         }
         $_PLUGINS->trigger('onCPayAfterPaymentStatusUpdateEvent', array(&$user, &$paymentBasket, &$subscriptions, $unifiedStatus, $previousUnifiedStatus, $eventType, &$notification));
     }
 }
	/**
	 * Binds notification record from basket
	 *
	 * @param  cbpaidPaymentNotification  $ipn
	 * @param  cbpaidPaymentBasket              $paymentBasket
	 */
	protected function _bindNotificationToBasket( $ipn, &$paymentBasket ) {
		$privateVarsList = 'id user_id time_initiated time_completed ip_addresses mc_gross mc_currency '
			.	'quantity item_number item_name shared_secret payment_date payment_status '
			.	'invoice period1 period2 period3 mc_amount1 mc_amount2 mc_amount3';
		$paymentBasket->bindObjectToThisObject( $ipn, $privateVarsList );
		if ( $ipn->payment_status === 'Completed' ) {
			$paymentBasket->payment_date	=	$ipn->payment_date;
		}
	}
 /**
  * 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;
 }
	/**
	 * Get the most recent payment basket, checking for time-out, and deleting if timed out !
	 *
	 * if returned cbpaidPaymentBasket has ->id != null then it's existing !
	 * @static
	 *
	 * @param  int|null $userid             User Id
	 * @param  boolean  $generateNewBasket  Delete expired basket and Generate new basket systematically
	 * @param  boolean  $generateWarning    true: sets error message with warning "A payment invoice exists already: Please check below if it is correct. If not correct, click on the cancel link below, and select your choice again."
	 * @return cbpaidPaymentBasket          Payment basket
	 */
	public static function & getInstanceBasketOfUser( $userid, $generateNewBasket, $generateWarning = true ) {
		global $_CB_framework, $_CB_database;

		$paymentBasket						=	new cbpaidPaymentBasket( $_CB_database );
		if ( $paymentBasket->loadLatestBasketOfUserPlanSubscription( $userid ) ) {

			// auto-expire basket of more than 30 minutes:
			$cbpaidTimes					=&	cbpaidTimes::getInstance();
			$initiatedAt					=	$cbpaidTimes->strToTime( $paymentBasket->time_initiated );

			if ( $generateNewBasket || ( $initiatedAt < ( $_CB_framework->now() - 1800 ) ) ) {
				// auto-expire basket of more than 30 minutes:
				$paymentBasket->delete();
				$paymentBasket				=	new cbpaidPaymentBasket( $_CB_database );
			} else {
				// otherwise return existing basket
				if ( $generateWarning ) {
					cbpaidApp::getBaseClass()->_setErrorMSG( CBPTXT::T("A payment invoice exists already: Please check below if it is correct. If not correct, click on the cancel link below, and select your choice again.") );
				}
			}
		}
		return $paymentBasket;
	}
 /**
  * Renders a $variable for an $output
  *
  * @param  string       $variable  Variable to render
  * @param  string       $output    'html': HTML rendering, 'text': TEXT rendering
  * @param  boolean      $rounded   Round column values ?
  * @return string|null
  */
 public function renderColumn($variable, $output = 'html', $rounded = false)
 {
     $html = $output == 'html';
     switch ($variable) {
         case 'rate':
         case 'original_rate':
         case 'tax_amount':
             $ret = $this->renderItemRate($variable, $html, $rounded);
             break;
         case 'first_rate':
         case 'first_original_rate':
         case 'first_tax_amount':
             if (property_exists($this, $variable)) {
                 $ret = cbpaidMoney::getInstance()->renderPrice($this->{$variable}, $this->currency, $html, $rounded);
             } else {
                 $ret = null;
             }
             break;
         case 'validity_period':
             if ($this->start_date && $this->stop_date && $this->start_date != '0000-00-00 00:00:00' && $this->stop_date != '0000-00-00 00:00:00') {
                 $startDate = cbFormatDate($this->start_date, 0, false);
                 $stopDate = cbFormatDate($this->stop_date, 0, false);
                 $ret = htmlspecialchars($startDate);
                 if ($startDate != $stopDate) {
                     $ret .= ($html ? '&nbsp;-&nbsp;' : ' - ') . htmlspecialchars($stopDate);
                 }
             } else {
                 $ret = null;
             }
             break;
         case 'tax_rule_id':
             if ($this->tax_rule_id && is_callable(array('cbpaidTaxRule', 'getInstance'))) {
                 $ret = cbpaidTaxRule::getInstance((int) $this->tax_rule_id)->getShortCode();
             } else {
                 $ret = null;
             }
             break;
         case 'ordering':
             if ($this->payment_item_id) {
                 $paymItem = $this->_paymentBasket->getPaymentItem($this->payment_item_id);
                 if ($paymItem) {
                     $ret = htmlspecialchars($paymItem->ordering);
                 } else {
                     $ret = null;
                 }
             } else {
                 $ret = null;
             }
             break;
         case 'discount_amount':
         case 'first_discount_amount':
             $ret = null;
             break;
         case 'quantity':
         case 'artnum':
         case 'description':
         case 'discount_text':
         default:
             $ret = htmlspecialchars($this->get($variable));
             break;
     }
     return $ret;
 }
	/**
	 * perform anti fraud checks on ipn values
	 *
	 * @param cbpaidPaymentNotification $ipn
	 * @param cbpaidPaymentBasket $paymentBasket
	 * @return bool|string
	 */
	private function _validateIPN( $ipn, $paymentBasket )
	{
		global $_CB_database;

		$matching						=	true;

		if ( in_array( $ipn->payment_status, array( 'Completed', 'Processed', 'Canceled_Reversal' ) ) ) {
			if ( $ipn->txn_type == 'subscr_payment' ) {
				$payments				=	$paymentBasket->getPaymentsTotals( $ipn->txn_id );

				if ( ( $paymentBasket->mc_amount1 != 0 ) && ( $payments->count == 0 ) ) {
					$amount				=	$paymentBasket->mc_amount1;
				} else {
					$amount				=	$paymentBasket->mc_amount3;
				}

				if ( sprintf( '%.2f', $ipn->mc_gross ) != sprintf( '%.2f', $amount ) ) {
					if ( ( sprintf( '%.2f', $ipn->mc_gross ) < sprintf( '%.2f', $amount ) ) || ( sprintf( '%.2f', ( $ipn->mc_gross - $ipn->tax ) ) != sprintf( '%.2f', $amount ) ) ) {
						if ( ( ! ( ( $paymentBasket->mc_amount1 != 0 ) && ( $payments->count == 0 ) ) ) && ( ( (float) sprintf( '%.2f', ( $ipn->mc_gross - abs( $ipn->tax ) ) ) ) < ( (float) sprintf( '%.2f', $amount ) ) ) ) {
							$matching	=	CBPTXT::P( 'amount mismatch on recurring_payment: $amount: [amount] != IPN mc_gross: [gross] or IPN mc_gross - IPN tax: [net] where IPN tax = [tax]', array( '[amount]' => $amount, '[net]' => ( $ipn->mc_gross - $ipn->tax ), '[gross]' => $ipn->mc_gross, '[tax]' => $ipn->tax ) );
						}
					}
				}
			} else {
				if ( sprintf( '%.2f', $ipn->mc_gross ) != sprintf( '%.2f', $paymentBasket->mc_gross ) ) {
					if ( ( sprintf( '%.2f', $ipn->mc_gross ) < sprintf( '%.2f', $paymentBasket->mc_gross ) ) || ( sprintf( '%.2f', $ipn->mc_gross - $ipn->tax ) != sprintf( '%.2f', $paymentBasket->mc_gross ) ) ) {
						$matching		=	CBPTXT::P( 'amount mismatch on webaccept: BASKET mc_gross: [basket_gross] != IPN mc_gross: [gross] or IPN mc_gross - IPN tax: [net] where IPN tax = [tax]', array( '[basket_gross]' => $paymentBasket->mc_gross, '[net]' => ( $ipn->mc_gross - $ipn->tax ), '[gross]' => $ipn->mc_gross, '[tax]' => $ipn->tax ) );
					}
				}
			}
		}

		if ( in_array( $ipn->txn_type, array( 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed', 'subscr_payment' ) ) ) {
			if ( ! $paymentBasket->isAnyAutoRecurring() ) {
				$matching				=	CBPTXT::P( 'paypal subscription IPN type [txn_type] for a basket without auto-recurring items', array( '[txn_type]' => $ipn->txn_type ) );
			}
		}

		if ( ! in_array( $ipn->txn_type, array( 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ) {
			if ( ( $ipn->txn_id === '' ) || ( $ipn->txn_id === 0 ) || ( $ipn->txn_id === null ) ) {
				$matching				=	CBPTXT::T( 'illegal transaction id' );
			} else {
				$countBaskets			=	$paymentBasket->countRows( "txn_id = '" . $_CB_database->getEscaped( $ipn->txn_id ) . "' AND payment_status = 'Completed'" );

				if ( ( $countBaskets == 1 ) && ( $paymentBasket->txn_id != $ipn->txn_id ) || ( $countBaskets > 1 ) ) {
					$matching			=	CBPTXT::P( 'transaction already used for [count] other already completed payment(s)', array( '[count]' => $countBaskets ) );
				}
			}
		}

		return $matching;
	}
 /**
  * Compounds rates of this compounder added with addRate() method
  *
  * @param  cbpaidPaymentBasket       $paymentBasket
  * @param  cbpaidPaymentTotalizerCompoundable[]  $taxableTotalizers
  * @param  string                    $totalizerType
  * @return void
  */
 public function computeAllTaxes($paymentBasket, &$taxableTotalizers, $totalizerType)
 {
     $currency = $paymentBasket->mc_currency;
     $anyAutoRecurringInBasket = $paymentBasket->isAnyAutoRecurringPossibleWithThisBasket();
     foreach ($this->ratesToCompound as $ratesOfPriorityItem) {
         // handle first taxes-inclusive items:
         //TODO		$this->_getItemsTaxesInclusiveToExclusive( $ratesOfPriorityItem, $currency );
         $itemsTaxesToAdd = array();
         foreach ($ratesOfPriorityItem as $ratesOfItem) {
             $item = $ratesOfItem['item'];
             // item can have first_rate and first_period SOMETIMES, but only of $anyAutoRecurringInBasket
             // totalizer is same rules as basket: if basket has first_period then totalizer follows basket.
             // So:
             // Now $first.... below is refering to the $item :
             $extraAmountBefore = 0;
             $extraPercents = 0;
             $extraAmountAfter = 0;
             $firstExtraAmountBefore = 0;
             $firstExtraPercents = 0;
             $firstExtraAmountAfter = 0;
             $itemHasReallyFirstRate = isset($item->first_validity) && $item->first_validity || $item->first_rate;
             // when $item is cbpaidPaymentTotalizer then it doesn't have first_validity
             $itemHasFirstRate = $itemHasReallyFirstRate || $item->first_discount_amount || $item->first_tax_amount;
             $amountTaxExcl = $this->_getItemAmount_first_incl($item, false, false);
             $amount = $this->_getItemAmount_first_incl($item, true, false);
             if ($itemHasFirstRate) {
                 $firstAmountTaxExcl = $this->_getItemAmount_first_incl($item, false, true, $itemHasReallyFirstRate);
                 $firstAmount = $this->_getItemAmount_first_incl($item, true, true, $itemHasReallyFirstRate);
             } else {
                 $firstAmountTaxExcl = null;
                 $firstAmount = null;
             }
             // first handles the item:
             $totalizerRatesOfItem = array();
             // first period first, as some totalizers differ after use (e.g. wallet use), then the normal period:
             foreach ($ratesOfItem['ratestots'] as $k => $rateTotalizer) {
                 /** @var $rate cbpaidTotalizertypeCompoundable */
                 list($rate, $totalizer) = $rateTotalizer;
                 $rate->setBasket($paymentBasket);
                 $rate->setPaymentItem($item);
                 /* NOT NOW, MAYBE LATER DIFFERENTLY:
                 					if ( $item->getPlanParam( 'tax_taxing_date', 1 ) == 2 ) {
                 						$tax_taxing_date_ratio	=	$item->getPlanParam( 'tax_taxing_date_ratio', 0.0 );
                 						// we have a non-linear taxation on this item:
                 		$item = NEW cbpaidPaymentItem();
                 						// Does this taxRate start after the start (or stops before the stop) of the initiating time of the basket ? :
                 						$itemStartDay			=	cbpaidTimes::getInstance()->localDate( 'Y-m-d', cbpaidTimes::getInstance()->strToTime( $item->start_date ) );
                 						$taxRate_stop_date		=	( $rate->stop_date == '0000-00-00' ? '9999-99-99' : $rate->stop_date );
                 						if ( ( $rate->start_date <= $itemStartDay ) && ( $taxRate_stop_date >= $itemStartDay ) ) {
                 							// The item starts during the validity period of this tax rate: we need to fix the ratios:
                 							
                 						} else {
                 							$proraterFactor		=	( 100.0 - (float) $tax_taxing_date_ratio ) / 100;
                 						}
                 	
                 					} else {
                 						$proraterFactor			=	1;
                 					}
                 */
                 //			if ( $totalizer->first_rate !== null ) {
                 /** @var $totalizer cbpaidPaymentTotalizerCompoundable */
                 $firstPeriodProrater = $totalizer->proRatePeriod($item, true);
                 $totalizerRatesOfItem[$k]['fr'] = $firstPeriodProrater;
                 if ($itemHasFirstRate) {
                     $firstExtraAmountBefore += $totalizerRatesOfItem[$k]['fb'] = $rate->getAmountBeforePercents($firstAmount, $firstAmountTaxExcl, $firstPeriodProrater, true, $currency);
                     $firstExtraPercents += $totalizerRatesOfItem[$k]['fp'] = $rate->getPercents($firstAmount, $firstAmountTaxExcl, $firstPeriodProrater, true);
                     $firstExtraAmountAfter += $totalizerRatesOfItem[$k]['fa'] = $rate->getAmountAfterPercents($firstAmount, $firstAmountTaxExcl, $firstPeriodProrater, true, $currency);
                 } else {
                     $firstExtraAmountBefore += $totalizerRatesOfItem[$k]['fb'] = $rate->getAmountBeforePercents($amount, $amountTaxExcl, $firstPeriodProrater, true, $currency);
                     $firstExtraPercents += $totalizerRatesOfItem[$k]['fp'] = $rate->getPercents($amount, $amountTaxExcl, $firstPeriodProrater, true);
                     $firstExtraAmountAfter += $totalizerRatesOfItem[$k]['fa'] = $rate->getAmountAfterPercents($amount, $amountTaxExcl, $firstPeriodProrater, true, $currency);
                 }
                 //			}
             }
             // Now the normal recurring period:
             if ($anyAutoRecurringInBasket && $item->autorecurring > 0) {
                 foreach ($ratesOfItem['ratestots'] as $k => $rateTotalizer) {
                     list($rate, $totalizer) = $rateTotalizer;
                     if ($totalizer->rate !== null) {
                         $periodProrater = $totalizer->proRatePeriod($item, false);
                         $totalizerRatesOfItem[$k]['r'] = $periodProrater;
                         $extraAmountBefore += $totalizerRatesOfItem[$k]['b'] = $rate->getAmountBeforePercents($amount, $amountTaxExcl, $periodProrater, false, $currency);
                         $extraPercents += $totalizerRatesOfItem[$k]['p'] = $rate->getPercents($amount, $amountTaxExcl, $periodProrater, false);
                         $extraAmountAfter += $totalizerRatesOfItem[$k]['a'] = $rate->getAmountAfterPercents($amount, $amountTaxExcl, $periodProrater, false, $currency);
                     }
                 }
             }
             // Now adds to the item:
             if ($itemHasFirstRate) {
                 $first_tax_amount = ($firstAmount + $firstExtraAmountBefore) * (1 + $firstExtraPercents) + $firstExtraAmountAfter - $firstAmount;
             } else {
                 $first_tax_amount = ($amount + $firstExtraAmountBefore) * (1 + $firstExtraPercents) + $firstExtraAmountAfter - $amount;
             }
             $second_tax_amount = ($amount + $extraAmountBefore) * (1 + $extraPercents) + $extraAmountAfter - $amount;
             if ($anyAutoRecurringInBasket && $item->autorecurring > 0) {
                 // We cannot do this now, otherwise same-priority taxes will be compounded:
                 // $item->first_tax_amount	+=	$first_tax_amount;
                 // So store that for after all rates totalizers of this priority have been handled:
                 if ($first_tax_amount != 0 && ($itemHasFirstRate || $first_tax_amount != $second_tax_amount)) {
                     if (!$itemHasFirstRate) {
                         // As the following step will create the first rate for the item, we need to copy current discount from regular discount amount:
                         $item->first_discount_amount = $item->discount_amount;
                         $item->first_tax_amount = $item->tax_amount;
                     }
                     // Now prepare following statement but deferred:
                     // $item->first_discount_amount (or ->first_tax_amount)		+=	$first_tax_amount;
                     $itemsTaxesToAdd[] = array($item, $this->_getItemTotalizerColumnName(true), $first_tax_amount);
                 } else {
                     $second_tax_amount = ($amount + $firstExtraAmountBefore) * (1 + $firstExtraPercents) + $firstExtraAmountAfter - $amount;
                 }
                 if ($second_tax_amount != 0) {
                     // Now prepare following statement but deferred:
                     // $item->discount_amount (or ->tax_amount)		+=	$tax_amount;
                     $itemsTaxesToAdd[] = array($item, $this->_getItemTotalizerColumnName(false), $second_tax_amount);
                 }
             } else {
                 $itemsTaxesToAdd[] = array($item, $this->_getItemTotalizerColumnName(false), $first_tax_amount);
             }
             // then, now that totals for this item in this tax priority are known, handles the totalizers (of that priority, for that item):
             foreach ($ratesOfItem['ratestots'] as $k => $rateTotalizer) {
                 list(, $totalizer) = $rateTotalizer;
                 if ($totalizer->first_rate !== null) {
                     if ($itemHasFirstRate) {
                         $totalizer->first_original_rate += $firstAmount * $totalizerRatesOfItem[$k]['fr'];
                         $totalizer->first_rate += $totalizerRatesOfItem[$k]['fb'] + (($firstAmount + $firstExtraAmountBefore) * (0 + $totalizerRatesOfItem[$k]['fp']) + $totalizerRatesOfItem[$k]['fa']);
                     } else {
                         // Basket and totalizer have first rate but the item doesn't have one: still add item's non-first tax to totalizer's first tax:
                         $totalizer->first_original_rate += $amount * $totalizerRatesOfItem[$k]['fr'];
                         $totalizer->first_rate += $totalizerRatesOfItem[$k]['fb'] + (($amount + $firstExtraAmountBefore) * (0 + $totalizerRatesOfItem[$k]['fp']) + $totalizerRatesOfItem[$k]['fa']);
                     }
                     $totalizer->first_original_rate = round($totalizer->first_original_rate, 2);
                     $totalizer->first_rate = round($totalizer->first_rate, 2);
                 }
                 if ($totalizer->rate !== null && (!$anyAutoRecurringInBasket || $anyAutoRecurringInBasket && $item->autorecurring > 0)) {
                     if ($anyAutoRecurringInBasket) {
                         $totalizer->original_rate += $amount * $totalizerRatesOfItem[$k]['r'];
                         $totalizer->rate += $totalizerRatesOfItem[$k]['b'] + (($amount + $extraAmountBefore) * (0 + $totalizerRatesOfItem[$k]['p']) + $totalizerRatesOfItem[$k]['a']);
                     } else {
                         $totalizer->original_rate += $amount * $totalizerRatesOfItem[$k]['fr'];
                         $totalizer->rate += $totalizerRatesOfItem[$k]['fb'] + (($amount + $extraAmountBefore) * (0 + $totalizerRatesOfItem[$k]['fp']) + $totalizerRatesOfItem[$k]['fa']);
                     }
                     $totalizer->original_rate = round($totalizer->original_rate, 2);
                     $totalizer->rate = round($totalizer->rate, 2);
                     if ($totalizer->first_rate == null && $anyAutoRecurringInBasket) {
                         $totalizer->first_rate = '0.0';
                         $totalizer->first_item_days = $totalizer->item_days;
                         $totalizer->first_totalizer_days = $totalizer->totalizer_days;
                         $totalizer->first_original_rate = '0.0';
                     }
                 }
             }
         }
         unset($totalizerRatesOfItem);
         // Now can add to items the taxes, before we look at next tax rates priorities:
         foreach ($itemsTaxesToAdd as $taxToAdd) {
             $taxToAdd[0]->{$taxToAdd[1]} += $taxToAdd[2];
         }
     }
     // Now that all items and totalizers have been computed, adjust basket:
     foreach ($taxableTotalizers as $k => $totalizer) {
         if ($totalizer->totalizer_type == $totalizerType && $totalizer->hasItems()) {
             if (!$totalizer->applyThisTotalizerToBasket($paymentBasket, $anyAutoRecurringInBasket)) {
                 unset($taxableTotalizers[$k]);
             }
         }
     }
 }
	/**
	 * 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;
 }
 /**
  * Gets html for slip page
  * 
  * @param  cbpaidPaymentBasket  $paymentBasket
  * @return string
  */
 private function _outputSlip($paymentBasket)
 {
     global $_CB_framework;
     $slip_html_title = CBPTXT::Th($this->getAccountParam('slip_html_title', "Payment Slip No. "));
     $slip_html_for_site = CBPTXT::Th($this->getAccountParam('slip_html_for_site', "For website:"));
     $slip_html_for_url = $this->getAccountParam('slip_site_url', 1);
     $slip_html_for_item = CBPTXT::Th($this->getAccountParam('slip_html_for_item', "For item:"));
     $slip_html_for_member = CBPTXT::Th($this->getAccountParam('slip_html_for_member', "For member:"));
     $slip_html_reference = CBPTXT::Th($this->getAccountParam('slip_html_reference', "Important: include our reference with your payment:"));
     $slip_reference_site = $this->getAccountParam('slip_reference_site', 1);
     $slip_html_conclusion = CBPTXT::Th($this->getAccountParam('slip_html_conclusion', "If you pay by check, please print and enclose this page with your check."));
     $slip_html_pure = $this->getAccountParam('slip_html_pure', 0);
     $slip_popup_window = $this->getAccountParam('slip_popup_window', 1);
     $slip_print_button = $this->getAccountParam('slip_print_button', 1);
     outputCbTemplate();
     $this->_outputRegTemplate();
     $ret = '<div class="cbpaidPaymentSlip">';
     if ($slip_html_pure) {
         $vars = array('[order_id]' => $paymentBasket->id, '[item_number]' => $paymentBasket->item_number, '[item_description]' => $paymentBasket->item_name, '[user_id]' => $paymentBasket->user_id, '[username]' => $paymentBasket->username, '[address_name]' => $paymentBasket->address_name, '[address_street]' => $paymentBasket->address_street, '[address_city]' => $paymentBasket->address_city, '[address_state]' => $paymentBasket->address_state, '[address_zip]' => $paymentBasket->address_zip, '[address_country]' => $paymentBasket->address_country, '[address_country_code]' => $paymentBasket->address_country_code, '[first_name]' => $paymentBasket->first_name, '[last_name]' => $paymentBasket->last_name, '[order_table]' => $paymentBasket->displayBasket(), '[sitename]' => $_CB_framework->getCfg('sitename'), '[live_site]' => preg_replace("/^(https?:\\/\\/)/i", '', $_CB_framework->getCfg('live_site')));
         $default_html = '<h2>Payment Slip No. [order_id]</h2>' . '<h3 id="cbpaidWebsite">For website: [sitename]</h3>' . '<p id="cbpaidAddress"><address>[live_site]</address></p>' . '<h3 id="cbpaidItem">For item: [item_number]</h3>' . '<h3 id="cbpaidUser">For member: [first_name] [last_name]</h3>' . '<div>[order_table]</div>' . '<p id="cbpaidReference"><strong>Important: include our reference with your payment: &nbsp;<u style=\\"font-size:125%\\">Number [order_id] / [live_site]</u></strong></p>' . '<p id=\\"cbpaidCheck\\">If you pay by check, please print and enclose this page with your check.</p>';
         $slip_html_custom = CBPTXT::Th($this->getAccountParam('slip_html_custom', $default_html));
         $ret .= strtr($slip_html_custom, $vars);
     } else {
         if ($slip_html_title) {
             $ret .= '<h2>' . $slip_html_title . ' ' . $paymentBasket->id . "</h2>\n";
         }
         if ($slip_html_for_site) {
             $ret .= "<h3 id=\"cbpaidWebsite\">" . $slip_html_for_site . ' ' . $_CB_framework->getCfg('sitename') . "</h3>\n";
         }
         if ($slip_html_for_url) {
             $ret .= "<p id=\"cbpaidAddress\"><address>" . $_CB_framework->getCfg('live_site') . "</address></p>\n";
         }
         if ($slip_html_for_item) {
             $ret .= "<h3 id=\"cbpaidItem\">" . $slip_html_for_item . ' ' . $paymentBasket->item_number . "</h3>\n";
         }
         if ($slip_html_for_member) {
             $ret .= "<h3 id=\"cbpaidUser\">" . $slip_html_for_member . ' ' . $paymentBasket->first_name . ' ' . $paymentBasket->last_name . "</h3>\n";
         }
         $ret .= $paymentBasket->displayBasket();
         if ($slip_html_reference) {
             $ret .= "<p id=\"cbpaidReference\"><strong>" . $slip_html_reference . " &nbsp;<u style=\"font-size:125%\">" . CBPTXT::T("Number") . ' ' . $paymentBasket->id;
             if ($slip_reference_site) {
                 $ret .= " / " . preg_replace("/^(https?:\\/\\/)/i", '', $_CB_framework->getCfg('live_site'));
             }
             $ret .= "</u></strong></p>\n";
         }
         if ($slip_html_conclusion) {
             $ret .= "<p id=\"cbpaidCheck\">" . $slip_html_conclusion . "</p>\n";
         }
     }
     if ($slip_popup_window) {
         $ret .= "<div id=\"cbpaidPrint\" style=\"width:100%;text-align:center;\"><p><a href=\"javascript:void(window.print())\">" . CBPTXT::Th("PRINT") . "</a></p></div>\n";
         $ret .= "<div id=\"cbpaidClose\" style=\"width:100%;text-align:center;\"><p><a href=\"javascript:void(window.close())\">" . CBPTXT::Th("CLOSE") . "</a></p></div>\n";
     } else {
         if ($slip_print_button) {
             $slip_urlHtmlSpecialchared = $this->_getSlipUrlHtmlSpecialchared($paymentBasket, true);
             $ret .= "<div id=\"cbpaidPrint\" style=\"width:100%;text-align:center;\"><p><a href=\"" . $slip_urlHtmlSpecialchared . "\" " . 'onclick="window.open(\'' . $slip_urlHtmlSpecialchared . '\', \'payslip\', \'status=no,toolbar=no,scrollbars=yes,titlebar=no,menubar=no,resizable=yes,width=640,height=480,directories=no,location=no\'); return false;" ' . 'target="_blank" ' . '>' . CBPTXT::Th("PRINT") . "</a></p></div>\n";
         }
     }
     $recordPaymentUrl = cbpaidApp::getBaseClass()->getRecordPaymentUrl($paymentBasket);
     if ($recordPaymentUrl) {
         $ret .= '<div id="cbpaidRecordPayment"><a href="' . $recordPaymentUrl . '" title="' . htmlspecialchars(CBPTXT::T("Record the offline payment now")) . '">' . CBPTXT::Th("RECORD OFFLINE PAYMENT") . '</a></div>';
     }
     $ret .= "</div>";
     return $ret;
 }
 /**
  * Loads matured baskets scheduled by CBSubs for timed action
  *
  * @param  int  $limit
  * @return cbpaidPaymentBasket[]
  */
 public function loadMaturedBaskets($limit)
 {
     global $_CB_database;
     $loaderBasket = new cbpaidPaymentBasket();
     $baskets = $loaderBasket->loadThisMatchingList(array('scheduler_next_maturity' => array('<=', $_CB_database->getUtcDateTime()), 'scheduler_next_maturity ' => array('>', $loaderBasket->getDbo()->getNullDate())), array('scheduler_next_maturity' => 'ASC'), 0, $limit);
     return $baskets;
 }
	/**
	 * 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;
	}
Example #15
0
	/**
	 * Extends the XML invoice address in params
	 *
	 * @param  SimpleXMLElement     $param
	 * @param  PluginTable          $pluginObject
	 * @param  cbpaidPaymentBasket  $paymentBasket  (the data being displayed)
	 * @param  boolean              $isSaving
	 * @return SimpleXMLElement
	 */
	public function onxmlBeforeCbSubsDisplayOrSaveInvoice( /** @noinspection PhpUnusedParameterInspection */ $param, $pluginObject, $paymentBasket, $isSaving ) {
		global $_CB_framework, $_PLUGINS;

		$paymentItems			=	$paymentBasket->loadPaymentItems();
		$taxableTotalizers		=	$paymentBasket->loadPaymentTotalizers();

		$_PLUGINS->loadPluginGroup( 'user/plug_cbpaidsubscriptions/plugin/cbsubstax/validations', null, ( $_CB_framework->getUi() == 2 ? 0 : 1 ) );

		$taxRulesRates				=	cbpaidPaymentTotalizer_salestax::getApplicableRatesWithoutBusinessCheck( $paymentBasket, $paymentItems, $taxableTotalizers );
		$fromXml					=	array();
		foreach ( $taxRulesRates as $AllTaxRates ) {
			foreach ( $AllTaxRates as $taxRate ) {
				//$taxRate	= NEW cbpaidsalestaxTotalizertype();

				$business_check		=	$taxRate->business_check;
				if ( $business_check ) {
					$absoluteValidationsPath	=	$_CB_framework->getCfg('absolute_path') . '/'. $_PLUGINS->getPluginRelPath( $pluginObject ) . '/plugin/cbsubstax/validations/' . $business_check;
					$valphp		=	$absoluteValidationsPath . '/validation.php';
					if ( is_readable( $valphp ) ) {
						/** @noinspection PhpIncludeInspection */
						include_once $valphp;
						// $className	=	'cbpaidValidate_' . $tax->business_check;
					}
					$fromFile		=	$absoluteValidationsPath . '/xml/edit.invoice.xml';
					if ( is_readable( $fromFile ) ) {
						$fromRoot	=	new SimpleXMLElement( $fromFile, LIBXML_NONET | ( defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0 ), true );
						$fromXml	=	array_merge( $fromXml, $fromRoot->xpath( '/*/editinvoicevalidationintegration/*' ) );
					}
				}
			}
		}
		return $fromXml;
	}
	/**
	 * Returns the URL and hidden params, and name of currency param for a form to change currency
	 *
	 * @param  cbpaidPaymentBasket  $paymentBasket
	 * @return array                ( url unsefed/unhtmlspecialchared, array( hidden form params ), name of currency input )
	 */
	public static function getCurrencyChangeFormParams( $paymentBasket ) {
		$getParams							=	$paymentBasket->getSetBasketPaymentMethodUrl( null, 'html', 'setbsktcurrency' );
		$ajaxGetParams						=	cbUnHtmlspecialchars( $paymentBasket->getSetBasketPaymentMethodUrl( null, 'raw', 'setbsktcurrency' ) );
		$formHiddens						=	array(	cbpaidApp::getBaseClass()->_getPagingParamName('act') => 'setbsktcurrency',
			'ajaxurl' => bin2hex( $ajaxGetParams ) );
		return array( $getParams, $formHiddens, 'currency' );
	}
 /**
  * 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;
 }
 /**
  * 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;
 }
	/**
	 * Loads matured baskets scheduled by CBSubs for timed action
	 *
	 * @param  int  $limit
	 * @return cbpaidPaymentBasket[]
	 */
	public function loadMaturedBaskets( $limit ) {
		global $_CB_framework;

		$loaderBasket	=	new cbpaidPaymentBasket();
		$baskets		=	$loaderBasket->loadThisMatchingList( array(	'scheduler_next_maturity' => array( '<=', date( 'Y-m-d H:i:s', $_CB_framework->now()) ),
				'scheduler_next_maturity ' => array( '>',  $loaderBasket->getDbo()->getNullDate() )			// the space in KEY is ON PURPOSE to avoid same array entry !!!
			),
			array( 'scheduler_next_maturity' => 'ASC' ),
			0,
			$limit );
		return $baskets;
	}
	/**
	 * Internal method to handles a gateway notification:
	 *
	 * @param  array                $postdata         'x_response_code', 'x_response_subcode', 'x_response_reason_code', 'x_response_reason_text', 'x_auth_code', 'x_avs_code', 'x_trans_id', 'x_invoice_num', 'x_description', 'x_amount', 'x_method', 'x_type', 'x_cust_id', 'x_first_name', 'x_last_name', 'x_company', 'x_address', 'x_city', 'x_state', 'x_zip', 'x_country', 'x_phone', 'x_fax', 'x_email', 'x_ship_to_first_name', 'x_ship_to_last_name', 'x_ship_to_company', 'x_ship_to_address', 'x_ship_to_city', 'x_ship_to_state', 'x_ship_to_zip', 'x_ship_to_country', 'x_tax', 'x_duty', 'x_freight', 'x_tax_exempt', 'x_po_num', 'x_MD5_Hash', 'x_cavv_response', 'x_test_request', 'x_subscription_id', 'x_subscription_paynum'
	 * @return boolean
	 */
	private function _handleNotifyInternal( $postdata )
	{
		global $_CB_framework, $_CB_database;

		// Only ARB notifications have: x_subscription_id (Subscription ID) and the x_subscription_paynum (Subscription Payment Number)

		// check that required params are here:
		static $mandatory	=	array( 'x_cust_id', 'x_response_code', 'x_trans_id', 'x_amount', 'x_po_num', 'x_MD5_Hash' );
		foreach ( $mandatory as $v ) {
			if ( ! isset( $postdata[$v] ) ) {
				$ipn						=&	$this->_prepareIpn( 'D', '', '', 'ARB-Silent: mandatory param missing', null, 'utf-8' );
				$ipn->raw_data				=	'$message_type="' . addslashes( cbGetParam( $postdata, 'x_type' ) ) . "\";\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"
											;
				$ipn->store();

				return false;
			}
		}

		// check MD5 hash:
		if ( ! $this->_checkHashARBsilent( $postdata ) ) {
			$ipn							=&	$this->_prepareIpn( 'O', '', '', 'ARB-Silent: ARB silent post Hash check failed', null, 'utf-8' );
			$ipn->raw_data					=	'$message_type="' . addslashes( cbGetParam( $postdata, 'x_type' ) ) . "\";\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"
											;
			$ipn->store();

			return false;
		}

/*
x_response_code			=	1
x_response_subcode		=	1
x_response_reason_code	=	1
x_response_reason_text	=	This+transaction+has+been+approved%2E
x_auth_code				=	QbJHm4
x_avs_code				=	Y
x_trans_id				=	2147490176
x_invoice_num			=	INV12345					<--------
x_description			=	My+test+description
x_amount				=	0%2E44
x_method				=	CC
x_type					=	auth%5Fcapture
x_cust_id				=	CustId                      <--------
x_first_name			=	Firstname
x_last_name				=	LastNamenardkkwhczdp
x_company				=	
x_address				=	
x_city					=	
x_state					=	
x_zip					=	
x_country				=	
x_phone					=	
x_fax					=	
x_email					=	
x_ship_to_first_name	=	
x_ship_to_last_name		=	
x_ship_to_company		=	
x_ship_to_address		=	
x_ship_to_city			=	
x_ship_to_state			=	
x_ship_to_zip			=	
x_ship_to_country		=	
x_tax					=	0%2E0000
x_duty					=	0%2E0000
x_freight				=	0%2E0000
x_tax_exempt			=	FALSE
x_po_num				=	                           <-------- not in ARB
x_MD5_Hash				=	B9B3D19AEFD7BECC86C5FB3DB717D565
x_cavv_response			=	2
x_test_request			=	false
x_subscription_id		=	101635
x_subscription_paynum	=	1
*/
		// For ARB we have:
		$mandatory							=	array( 'x_test_request', 'x_subscription_id', 'x_subscription_paynum' );
		// For AIM we have: 'cb_custom' (paymentbasket id)

		// For now ignore non-ARB silent posts:
		foreach ( $mandatory as $v ) {
			if ( ! isset( $postdata[$v] ) ) {
				// This is not an ARB silent post: silently ignore it:
				return true;
			}
		}

		// get the mandatory params, such as x_cust_id:
		$x_cust_id							=	cbGetParam( $postdata, 'x_cust_id' );
		$x_subscription_id					=	cbGetParam( $postdata, 'x_subscription_id', 0 );
		$x_type								=	strtoupper( cbGetParam( $postdata, 'x_type' ) );
		$x_response_code					=	cbGetParam( $postdata, 'x_response_code' );
		// $x_response_subcode					=	cbGetParam( $postdata, 'x_response_subcode' );
		$x_response_reason_code				=	cbGetParam( $postdata, 'x_response_reason_code' );
		$x_response_reason_text				=	cbGetParam( $postdata, 'x_response_reason_text' );
		$x_invoice_num						=	cbGetParam( $postdata, 'x_invoice_num' );
		$x_trans_id							=	cbGetParam( $postdata, 'x_trans_id' );
		$x_amount							=	cbGetParam( $postdata, 'x_amount' );
		$x_method							=	cbGetParam( $postdata, 'x_method' );
		if ( ( ! $x_trans_id ) || ( ! $x_invoice_num ) || ( ! $x_cust_id ) || ( ! $x_subscription_id ) || ( ! $x_type ) || ( ! $x_response_code ) || ( ! $x_response_reason_code ) ) {
			return false;
		}

		// Log the INS:
		$log_type							=	'I';
		$paymentStatus						=	$this->_paymentStatus( $x_type, $x_response_code );
		$reasonCode							=	null;
		$paymentType						=	( $x_method == 'CC' ? 'Credit Card' : ( $x_method == 'ECHECK' ? 'E-Check' : $x_method ) );
		$reasonText							=	( $x_response_code != 1 ? $x_response_code . ': ' . $x_response_reason_text : null );		//TODO do it in normal AIM and ARB too
		$ipn								=&	$this->_prepareIpn( $log_type, $paymentStatus, $paymentType, $reasonText, $_CB_framework->now(), 'utf-8' );
		$ipn->raw_data						=	'$message_type="' . $x_type . "\";\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"
											;
		$ipn->user_id						=	(int) $x_cust_id;
		$ipn->payment_status				=	$paymentStatus;
		if ( ( $paymentStatus == 'Pending' ) && ( $x_response_code == 4 ) ) {
			$ipn->pending_reason			=	'paymentreview';		// held for payment review
		}
				
		// handle the message:
		switch ( $x_type ) {				// The type of credit card transaction: AUTH_CAPTURE, AUTH_ONLY, CAPTURE_ONLY, CREDIT, PRIOR_AUTH_CAPTURE, VOID
			case 'AUTH_CAPTURE':			// The amount is sent for authorization, and if approved, is automatically submitted for settlement.
			case 'CAPTURE_ONLY':			// This transaction type is used to complete a previously authorized transaction that was not originally submitted through the payment gateway or that requires voice authorization.
			case 'PRIOR_AUTH_CAPTURE':		// An Authorization Only and a Prior Authorization and Capture together are considered one complete transaction.
			case 'VOID':					// This transaction type is used to cancel an original transaction that is not yet settled and prevents it from being sent for settlement.
			case 'CREDIT':					// This transaction type is used to refund a customer for a transaction that was originally processed and successfully settled through the payment gateway.
			case 'AUTH_ONLY':				// This transaction type is sent for authorization only. The transaction will not be sent for settlement until the credit card transaction type Prior Authorization and Capture (see definition below) is submitted, or the transaction is submitted for capture manually in the Merchant Interface.
				$paymentBasket				=	new cbpaidPaymentBasket( $_CB_database );
				$basketLoaded				=	$paymentBasket->loadThisMatching( array( 'user_id' => (int) $x_cust_id, 'subscr_id' => (string) $x_subscription_id ) );
				if ( $basketLoaded ) {
					$ipn->bindBasket( $paymentBasket );
					$ipn->test_ipn			=	$paymentBasket->test_ipn;
				} else {
					$ipn->mc_gross			=	$x_amount;
					$ipn->mc_currency		=	'USD';
				}
				if ( in_array( $x_type, array( 'CREDIT', 'VOID' ) ) ) {
					$ipn->mc_gross			=	- $ipn->mc_gross;
				}
				$ipn->txn_id				=	$x_trans_id;
				$ipn->payment_date			=	date( 'H:i:s M d, Y T', $_CB_framework->now() );		// Paypal-style
				if ( $x_subscription_id ) {
					$ipn->txn_type			=	'subscr_payment';
					$ipn->subscr_id			=	$x_subscription_id;
				} else {
					$ipn->txn_type			=	'web_accept';
				}

				$ipn->raw_result			=	'VERIFIED';
				if ( $basketLoaded ) {
					// Account for the payment: if it is a subscription, then it is of type "auto-renewing with processor notifications" anyway:
					$autorecurring_type		=	( $x_subscription_id ? 2 : 0 );		// 0: not auto-recurring, 1: auto-recurring without payment processor notifications, 2: auto-renewing with processor notifications updating $expiry_date
					$autorenew_type			=	( $x_subscription_id ? 2 : 0 );		// 0: not auto-renewing (manual renewals), 1: asked for by user, 2: mandatory by configuration
					$this->updatePaymentStatus( $paymentBasket, $ipn->txn_type, $paymentStatus, $ipn, 1, $autorecurring_type, $autorenew_type, false );
					$ret					=	true;
				} else {
					$ret					=	false;
				}
				break;
			default:
				$ipn->log_type				=	'T';
				$ipn->reason_code			=	'Unexpected x_type';
				$ret						=	false;
		}
		$ipn->store();

		return $ret;
	}
	/** *** From PAY and onDuringLogin For Now !
	 * Shows a payment form corresponding to the latest payment basket (otpionally of a given subscription) and gives its status
	 *
	 * @param  UserTable             $user
	 * @param  cbpaidProduct[]|null  $chosenPlans      array of cbpaidProduct : Chosen plans to pay
	 * @param  string                $introText
	 * @param  array                 $subscriptionIds  array of int: Subscription ids to pay
	 * @param  string                $paymentStatus    returns one of following: 'noBasketFound', and all cpayPaymentBasket statuses: treated here: null (payment not made), 'Pending', 'Completed'
	 * @return string|null
	 */
	public static function showPaymentForm( &$user, $chosenPlans, $introText, $subscriptionIds, &$paymentStatus ) {
		// get the most recent payment basket for that user and plan, and with that subscription if $subscriptionId != null:
		$paymentBasket		=	new cbpaidPaymentBasket();
		if ( is_array( $chosenPlans ) ) {
			/** @var $lastPlan cbpaidProduct|boolean */
			$lastPlan		=	end( $chosenPlans );
			reset( $chosenPlans );
			if ( $lastPlan === false ) {
				$lastPlanId	=	null;
			} else {
				$lastPlanId	=	$lastPlan->get( 'id' );
			}
		} else {
			$lastPlanId		=	(int) $chosenPlans;
			if ( ! $lastPlanId ) {
				$lastPlanId	=	null;
			}
		}
		if ( is_array( $subscriptionIds ) && ( count( $subscriptionIds ) > 0 ) ) {
			$lastPlanAndSubId	=	end( $subscriptionIds );
			reset( $subscriptionIds );
			if ( count( $lastPlanAndSubId ) == 2 ) {
				list( $lastPlanId, $lastSubId )	=	$lastPlanAndSubId;
			} else {
				$lastSubId	=	null;
			}
		} else {
			$lastSubId		=	null;
		}
		$basketLoaded		=	$paymentBasket->loadLatestBasketOfUserPlanSubscription( $user->id, $lastPlanId, $lastSubId );
		if ( $basketLoaded ) {
			$paymentStatus	=	$paymentBasket->payment_status;
			// display basket and payment buttons or redirect for payment depending if multiple payment choices or intro text present:
			$result			=	self::showBasketForPayment( $user, $paymentBasket, $introText );
		} else {
			$basketLoaded			=	$paymentBasket->loadLatestBasketOfUserPlanSubscription( $user->id, $lastPlanId, $lastSubId, 'Pending' );
			if ( ! $basketLoaded ) {
				// This is an error condition, subscription has been created, and is called for payment but no basket is found in database, so create a new one:
				// $paymentStatus = 'noBasketFound';
				// cbpaidApp::getBaseClass()->_setErrorMSG("No payment basket found, creating new one.");
				// $result = null;
				cbpaidApp::getBaseClass()->_setErrorMSG(CBPTXT::T("No payment basket found, creating new one."));
			}
			if ( $chosenPlans && ( count( $chosenPlans ) > 0 ) ) {
				$paymentBasket		=	cbpaidControllerOrder::createSubscriptionsAndPayment( $user, $chosenPlans, array(), null, $subscriptionIds, null, null );
				if ( is_object( $paymentBasket ) ) {
					if ( $basketLoaded ) {
						cbpaidApp::getBaseClass()->_setErrorMSG( CBPTXT::T("A payment basket is pending in progress for payment for this. Are you sure that you didn't already pay for this ?. Here you can pay again:") );
					}
					$paymentStatus	=	$paymentBasket->payment_status;
					$result			=	self::showBasketForPayment( $user, $paymentBasket, $introText );
				} else {
					$result			=	$paymentBasket;		// display messages as nothing has to be paid.
				}
			} else {
				// can be called with chosenPlans null to try loading a valid payment basket from the user:
				// trigger_error( 'cbpaid:_showPaymentForm: no chosen plans.', E_USER_NOTICE );
				$result				=	null;
			}
		}
		return $result;
	}
	/**
	* 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;
	}
	/**
	 * Returns a protected user-specific invoice display address URL
	 *
	 * @param  cbpaidPaymentBasket  $paymentBasket
	 * @param  string               $task           'invoice' or 'recordpayment'
	 * @param  string               $format         'html', 'component'
	 * @return string
	 */
	protected function getSecuredBasketShowUrl( $paymentBasket, $task, $format ) {
		global $_CB_framework;

		$basegetarray			=	array( 'user' => $paymentBasket->user_id, 'Itemid' => 0, 'act' => 'show' . $task, $task => $paymentBasket->id );
		if ( ! $_CB_framework->MyId() ) {
			$basegetarray['invoicecheck']	=	$paymentBasket->checkHashInvoice();
		}
		return $this->getHttpsAbsURLwithParam( $basegetarray, 'pluginclass', true, null, $format );
	}
 /**
  * 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;
 }
	/**
	 * Handles changes of payment basket $paymentBasket payment statuses events
	 * This function may be called more than one time for events different than the Completed or Processed state if there are multiple notifications
	 *
	 * $unifiedStatus status mappings with e.g. Paypal status:
	 * CB Unified status				Paypal status
	 * Completed				<--		Completed
	 * Processed				<--		Processed, Canceled_Reversal
	 * Denied					<--		Denied, Expired, Failed, Voided
	 * Refunded					<--		Reversed, Refunded, Partially-Refunded
	 * Pending					<--		Pending, In-Progress
	 * RegistrationCancelled	<--		A new cb registration got cancelled by user (e.g. paypal cancel payment button)
	 *
	 * @param  UserTable                      $user                    User paying
	 * @param  cbpaidPaymentBasket            $paymentBasket           CBPaid Payment basket being paid (corresponding to PayPal variable names)
	 * @param  cbpaidUsersubscriptionRecord[] $subscriptions           CBPay Subscriptions being paid
	 * @param  string                         $unifiedStatus           new unified status: see above
	 * @param  string                         $previousUnifiedStatus   previous unified status: see above
	 * @param  string                         $eventType               type of event (paypal type): 'web_accept', 'subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed'
	 * @param  cbpaidPaymentNotification      $notification            notification object of the payment
	 * @return void
	 */
 	public function onCPayAfterPaymentStatusUpdateEvent( $user, $paymentBasket, /** @noinspection PhpUnusedParameterInspection */ $subscriptions, $unifiedStatus, /** @noinspection PhpUnusedParameterInspection */ $previousUnifiedStatus, /** @noinspection PhpUnusedParameterInspection */ $eventType, /** @noinspection PhpUnusedParameterInspection */ $notification ) {
		global $_CB_framework, $_SERVER;

		if ( ! is_object( $user ) ) {
			return;
		}

		if ( ! in_array( $unifiedStatus, array( 'Completed', 'Processed' ) ) ) {
			return;
		}

		$params				=	cbpaidApp::settingsParams();
		$trackingCode		=	trim( $params->get( 'googleanalytics_trackingcode', null ) );

		if ( ! $trackingCode ) {
			return;
		}

		$isHttps			=	( isset( $_SERVER['HTTPS'] ) && ( ! empty( $_SERVER['HTTPS'] ) ) && ( $_SERVER['HTTPS'] != 'off' ) );

		$_CB_framework->document->addHeadScriptUrl( ( $isHttps ? 'https://ssl.' : 'http://www.' ) . 'google-analytics.com/ga.js' );

		$domainName			=	trim( $params->get( 'googleanalytics_domainname', null ) );

		$js					= 	"var _gaq = _gaq || [];"
							.	"_gaq.push(['_setAccount', '" . addslashes( $trackingCode ) . "']);";

		if ( $domainName ) {
			$js				.=	"_gaq.push(['_setDomainName', '" . addslashes( $domainName ) . "']);"
							.	( $domainName != 'none' ? "_gaq.push(['_setAllowHash', false]);" : null );
		}

		$js					.=	"_gaq.push(['_trackPageview']);"
							.	"_gaq.push(['_addTrans',"
							.		"'" . addslashes( $paymentBasket->item_number ) . "',"			// Order ID
							.		"'Community Builder',"											// Affiliation
							.		"'" . addslashes( $paymentBasket->mc_gross ) . "',"				// Total
							.		"'" . addslashes( $paymentBasket->tax ) . "',"					// Tax
							.		"'" . addslashes( $paymentBasket->mc_shipping ) . "',"			// Shipping
							.		"'" . addslashes( $paymentBasket->address_city ) . "',"			// City
							.		"'" . addslashes( $paymentBasket->address_state ) . "',"		// State
							.		"'" . addslashes( $paymentBasket->address_country ) . "'"		// Country
							.	"]);";

		$paymentItems		=	$paymentBasket->loadPaymentItems();

		if ( $paymentItems ) foreach ( $paymentItems as $item ) {
			$subscription	=	$item->loadSubscription();
			$plan			=	$subscription->getPlan();

			$js				.=	"_gaq.push(['_addItem',"
							.		"'" . addslashes( $paymentBasket->item_number ) . "',"		// Order ID
							.		"'" . addslashes( $item->id ) . "',"						// SKU
							.		"'" . addslashes( $plan->name ) . "',"						// Product Name
							.		"'" . addslashes( $plan->item_type ) . "',"					// Category
							.		"'" . addslashes( $item->getPrice() ) . "',"				// Price
							.		"'" . addslashes( $item->quantity ) . "'"					// Quantity
							.	"]);";
		}

		$js					.=	"_gaq.push(['_trackTrans']);";

		$_CB_framework->outputCbJQuery( $js );
	}