/**
  * Convert contry string (e.g. United States of America) into 2 or 3 letter (e.g. US or USA)
  * Or country 2 or 3 letters code to country string in English
  * Or betweeen country codes: Values for $nbLetters:
  * 3  : country   -> 3 letters
  * 2  : country   -> 2 letters
  * -2 : 2 letters -> country
  * -3 : 3 letters -> country
  * 23 : 2 letters -> 3 letters
  * 32 : 3 letters -> 2 letters
  *
  * @param  string $country    Full text country name (if $nbLetters = 2 or 3) or country code (if $nbLetters = -2 or -3 or 23 or 32)
  * @param  int    $nbLetters  Number of letters code (2 or 3 for full name to code and -2 or -3 for code to name)
  * @return string             2/3/full-letters country name
  */
 protected function countryToLetters($country, $nbLetters)
 {
     $countries = new cbpaidCountries();
     switch ($nbLetters) {
         case 3:
             $ret = $countries->countryToThreeLetters($country);
             break;
         case 2:
             $ret = $countries->countryToTwoLetters($country);
             break;
         case -2:
             $ret = $countries->twoLettersToCountry($country);
             break;
         case -3:
             $ret = $countries->threeLettersToCountry($country);
             break;
         case 23:
             $ret = $countries->twoToThreeLettersCountry($country);
             break;
         case 32:
             $ret = $countries->threeToTwoLettersCountry($country);
             break;
         default:
             trigger_error('Unknown nbLetters in countryToLetters', E_USER_WARNING);
             $ret = null;
             break;
     }
     if ($ret === null) {
         $n = $nbLetters < 0 ? 255 : $nbLetters % 10;
         $ret = substr($country, 0, $n);
     }
     return $ret;
 }
	/**
	 * Computes country from the $this->address_country_code 2-letters country code and stores it into $this->address_country
	 */
	protected function _computeCountryFromTwoLettersCountry( ) {
		$countries						=	new cbpaidCountries();
		$country						=	$countries->twoLettersToCountry( $this->address_country_code );
		if ( $country ) {
			$this->address_country		=	$country;
		}
	}
	/**
	 * Prepares and signs payflow payment $requestParams
	 *
	 * @param cbpaidPaymentBasket $paymentBasket
	 * @param bool $subscription
	 * @return array $requestParams
	 */
	private function _payflowPayment( $paymentBasket, $subscription = false )
	{
		$requestParams									=	array();

		if ( $this->hasPaypalPayflow() ) {
			$countries									=	new cbpaidCountries();

			if ( $paymentBasket->period3 ) {
				if ( $paymentBasket->period1 ) {
					$amount								=	sprintf( '%.2f', $paymentBasket->mc_amount1 );
				} else {
					$amount								=	sprintf( '%.2f', $paymentBasket->mc_amount3 );
				}
			} else {
				$amount									=	sprintf( '%.2f', $paymentBasket->mc_gross );
			}

			if ( $this->getAccountParam( 'normal_gateway' ) == '0' ) {
				$requestParams['MODE']					=	'TEST';
			}

			$request									=	array(	'PARTNER' => 'PayPal',
																	'VENDOR' => $this->getAccountParam( 'paypal_payflow_vendor' ),
																	'USER' => $this->getAccountParam( 'paypal_payflow_user' ),
																	'PWD' => $this->getAccountParam( 'paypal_payflow_password' ),
																	'TRXTYPE' => 'S',
																	'AMT' => $amount,
																	'CREATESECURETOKEN' => 'Y',
																	'SECURETOKENID' => uniqid(),
																	'TEMPLATE' => $this->getAccountParam( 'template_layout', 'MINLAYOUT' ),
																	'ORDERDESC' => $paymentBasket->item_name,
																	'INVNUM' => $paymentBasket->invoice,
																	'CURRENCY' => $paymentBasket->mc_currency,
																	'USER1' => $paymentBasket->id,
																	'USER2' => $paymentBasket->user_id,
																	'USER3' => $paymentBasket->item_number,
																	'USER4' => ( $subscription ? 'R' : 'S' )
																);

			if ( $subscription ) {
				$request['RECURRING']					=	'Y';
			}

			if ( $this->getAccountParam( 'givehiddenbillemail' ) && ( strlen( $paymentBasket->payer_email ) <= 127 ) ) {
				$request['EMAIL']						=	$paymentBasket->payer_email;
			}

			if ( $this->getAccountParam( 'givehiddenbilladdress' ) ) {
				cbimport( 'cb.tabs' );

				$addressFields							=	array(	'BILLTOFIRSTNAME' => array( $paymentBasket->first_name, 30 ),
																	'BILLTOLASTNAME' => array( $paymentBasket->last_name, 30 ),
																	'BILLTOSTREET' => array( $paymentBasket->address_street, 150 ),
																	'BILLTOZIP' => array( $paymentBasket->address_zip, 9 ),
																	'BILLTOCITY' => array( $paymentBasket->address_city, 45 ),
																	'BILLTOCOUNTRY' => array( $countries->countryToTwoLetters( $paymentBasket->address_country ), 2 )
																);

				if ( $paymentBasket->address_state != 'other' ) {
					$addressFields['BILLTOSTATE']		=	array( substr( $paymentBasket->address_state, -2 ), 2 );
				}

				foreach ( $addressFields as $k => $valueMaxlength ) {
					$adrField							=	cbIsoUtf_substr( $valueMaxlength[0], 0, $valueMaxlength[1] );

					if ( $adrField ) {
						$request[$k]					=	$adrField;
					}
				}
			}

			if ( $this->getAccountParam( 'givehiddenbilltelno' ) && ( strlen( $paymentBasket->contact_phone ) <= 50 ) ) {
				$request['BILLTOPHONENUM']				=	$paymentBasket->contact_phone;
			}

			if ( $this->getAccountParam( 'givehiddenshipemail' ) && ( strlen( $paymentBasket->payer_email ) <= 127 ) ) {
				$request['SHIPTOEMAIL']					=	$paymentBasket->payer_email;
			}

			if ( $this->getAccountParam( 'givehiddenshipaddress' ) ) {
				cbimport( 'cb.tabs' );

				$addressFields							=	array(	'SHIPTOFIRSTNAME' => array( $paymentBasket->first_name, 30 ),
																	'SHIPTOLASTNAME' => array( $paymentBasket->last_name, 30 ),
																	'SHIPTOSTREET' => array( $paymentBasket->address_street, 150 ),
																	'SHIPTOZIP' => array( $paymentBasket->address_zip, 9 ),
																	'SHIPTOCITY' => array( $paymentBasket->address_city, 45 ),
																	'SHIPTOCOUNTRY' => array( $countries->countryToThreeLetters( $paymentBasket->address_country ), 3 )
																);

				if ( $paymentBasket->address_state != 'other' ) {
					$addressFields['SHIPTOSTATE']		=	array( substr( $paymentBasket->address_state, -2 ), 2 );
				}

				foreach ( $addressFields as $k => $valueMaxlength ) {
					$adrField							=	cbIsoUtf_substr( $valueMaxlength[0], 0, $valueMaxlength[1] );

					if ( $adrField ) {
						$request[$k]					=	$adrField;
					}
				}
			}

			if ( $this->getAccountParam( 'givehiddenshiptelno' ) && ( strlen( $paymentBasket->contact_phone ) <= 50 ) ) {
				$request['SHIPTOPHONENUM']				=	$paymentBasket->contact_phone;
			}

			$formUrl									=	array();

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

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

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

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

			if ( $error || ( $status != 200 ) || ( ! $response ) ) {
				$this->_setLogErrorMSG( 3, null, $this->getPayName() . ' HTTPS POST request to payment gateway server failed.', CBPTXT::T( "Submitted subscription payment didn't return an error but didn't complete." ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
			} else {
				if ( cbGetParam( $results, 'RESULT' ) == '0' ) {
					$requestParams['SECURETOKEN']		=	cbGetParam( $results, 'SECURETOKEN' );
					$requestParams['SECURETOKENID']		=	cbGetParam( $results, 'SECURETOKENID' );
				} else{
					$this->_setLogErrorMSG( 3, null, $this->getPayName() . ' Paypal Payflow error returned. ERROR: ' . cbGetParam( $results, 'RESPMSG' ), CBPTXT::T( 'Please contact site administrator to check error log.' ) );
				}
			}
		}

		return $requestParams;
	}
 /**
  * Gets an invoice address field
  *
  * @param  string  $fieldName  'cb_subs_inv_address_first_name' or last_name, payer_business_name, address_street, address_city, address_zip, address_state, address_country, contact_phone, vat_number
  * @return string          Value (including default value for country if tax plugin is installed)
  */
 public function getInvoiceAddressField($fieldName)
 {
     $cbUser = CBuser::getInstance((int) $this->id);
     $value = isset($cbUser->{$fieldName}) ? $cbUser->{$fieldName} : null;
     if ($value) {
         return $value;
     }
     if ($fieldName != 'cb_subs_inv_address_country') {
         return $value;
     }
     $countries = new cbpaidCountries();
     return $countries->twoLettersToCountry($this->getDefaultInvoiceCountryCode());
 }
	/**
	 * Attempts to subscribe a credit card for recurring subscription of a payment basket.
	 *
	 * @param array $card                           contains type, number, firstname, lastname, expmonth, expyear, and optionally: address, zip, country
	 * @param cbpaidPaymentBasket $paymentBasket
	 * @param int $now                              unix timestamp of now
	 * @param cbpaidsubscriptionsNotification $ipn  returns the stored notification
	 * @param int $occurrences                      returns the number of occurences pay-subscribed firmly
	 * @param int $autorecurring_type               returns:  0: not auto-recurring, 1: auto-recurring without payment processor notifications, 2: auto-renewing with processor notifications updating $expiry_date
	 * @param int $autorenew_type                   returns:  0: not auto-renewing (manual renewals), 1: asked for by user, 2: mandatory by configuration
	 * @return mixed                                subscriptionId if subscription request succeeded, otherwise ARRAY( 'level' => 'inform', 'spurious' or 'fatal', 'errorText', 'errorCode' => string ) of error to display
	 */
	protected function processSubscriptionPayment( $card, $paymentBasket, $now, &$ipn, &$occurrences, &$autorecurring_type, &$autorenew_type )
	{
		$return											=	false;

		if ( $this->hasPaypalApi() ) {
			$countries									=	new cbpaidCountries();

			list( $p3, $t3, $start )					=	$this->_paypalPeriodsLimits( explode( ' ', $paymentBasket->period3 ), $now );

			if ( $paymentBasket->period1 ) {
				list( /* $p1 */, /* $t1 */, $start )	=	$this->_paypalPeriodsLimits( explode( ' ', $paymentBasket->period1 ), $now );

				$initialAmount							=	$paymentBasket->mc_amount1;
			} else {
				$initialAmount							=	$paymentBasket->mc_amount3;
			}

			$requestParams								=	array(	'METHOD' => 'CreateRecurringPaymentsProfile',
																	'SUBSCRIBERNAME' => cbIsoUtf_substr( $card['firstname'] . ' ' . $card['lastname'], 0, 32 ),
																	'PROFILESTARTDATE' => substr( date( 'c', $start ), 0, 19 ),
																	'PROFILEREFERENCE' => $paymentBasket->invoice,
																	'DESC' => cbIsoUtf_substr( $paymentBasket->item_name, 0, 127 ),
																	'BILLINGPERIOD' => $t3,
																	'BILLINGFREQUENCY' => $p3,
																	'INITAMT' => sprintf( '%.2f', $initialAmount ),
																	'AMT' => sprintf( '%.2f', $paymentBasket->mc_amount3 ),
																	'CURRENCYCODE' => $paymentBasket->mc_currency,
																	'CREDITCARDTYPE' => cbIsoUtf_substr( $card['type'], 0, 10 ),
																	'ACCT' => substr( preg_replace ( '/[^0-9]+/', '', strval( $card['number'] ) ), 0, 22 ),
																	'EXPDATE' => substr( sprintf( '%02d', intval( $card['expmonth'] ) ), 0, 2 ) . substr( strval( intval( $card['expyear'] ) ), 0, 4 ),
																	'CVV2' => substr( preg_replace ( '/[^0-9]+/', '', strval( $card['cvv'] ) ), 0, 4 ),
																	'EMAIL' => cbIsoUtf_substr( $paymentBasket->payer_email, 0, 127 ),
																	'PAYERID' => $paymentBasket->user_id,
																	'FIRSTNAME' => cbIsoUtf_substr( $card['firstname'], 0, 25 ),
																	'LASTNAME' => cbIsoUtf_substr( $card['lastname'], 0, 25 ),
																	'STREET' => cbIsoUtf_substr( $paymentBasket->address_street, 0, 100 ),
																	'CITY' => cbIsoUtf_substr( $paymentBasket->address_city, 0, 40 ),
																	'STATE' => cbIsoUtf_substr( substr( $paymentBasket->address_state, -2 ), 0, 2 ),
																	'COUNTRYCODE' => $countries->countryToTwoLetters( $paymentBasket->address_country ),
																	'ZIP' => cbIsoUtf_substr( $paymentBasket->address_zip, 0, 20 )
																);

			if ( $paymentBasket->recur_times ) {
				$requestParams['TOTALBILLINGCYCLES']	=	$paymentBasket->recur_times;
			}

			$this->_signRequestParams( $requestParams );

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

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

			if ( $error || ( $status != 200 ) || ( ! $response ) ) {
				$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' HTTPS POST request to payment gateway server failed.', CBPTXT::T( "Submitted subscription payment didn't return an error but didn't complete." ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );

				$logType								=	'C';
			} else {
				if ( cbGetParam( $results, 'ACK' ) == 'Success' ) {
					$autorecurring_type					=	2;
					$autorenew_type						=	( $autorecurring_type ? ( ( $this->getAccountParam( 'enabled', 0 ) == 3 ) && ( $paymentBasket->isAnyAutoRecurring() == 2 ) ? 1 : 2 ) : 0 );

					$return								=	cbGetParam( $results, 'PROFILEID' );

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

					$logType							=	'W';
				}
			}

			$ipn										=	$this->_logNotification( $logType, $now, $paymentBasket, $card, $requestParams, $response, $results, $return );
		} else {
			$this->_setLogErrorMSG( 3, $ipn, $this->getPayName() . ' Needed Paypal API username, password and signature not set.', CBPTXT::T( "Submitted subscription payment didn't return an error but didn't complete." ) . ' ' . CBPTXT::T( 'Please contact site administrator to check error log.' ) );
		}

		return $return;
	}
	/**
	 * Draws the credit-card form
	 *
	 * @param  UserTable            $user                 User
	 * @param  cbpaidPaymentBasket  $paymentBasket        paymentBasket object
	 * @param  string               $cardType             CC-brand if no choice
	 * @param  string               $postUrl              URL for the <form>
	 * @param  string               $payButtonText        Text for payment text (if basket allows single-payments)
	 * @param  string               $subscribeButtonText  Text for subscribe button (if basket allows auto-recurring subscriptions)
	 * @param  string|null          $chosenCard
	 * @return string
	 */
	private function _drawCCform( &$user, &$paymentBasket, $cardType, $postUrl, $payButtonText, $subscribeButtonText, $chosenCard = null ) {
		global $_CB_framework, $ueConfig;

		$params					=&	cbpaidApp::settingsParams();

		$sealCode				=	$params->get( 'security_logos_and_seals' );		// keep $param, it's a global setting !
		$drawCCV				=	$params->get( 'show_cc_ccv', 1 );				// keep $param, it's a global setting !
		$drawAVS				=	$this->getAccountParam( 'show_cc_avs', 0 );		// keep $param, it's a global setting !

		if ( in_array( $ueConfig['name_style'], array( 2, 3 ) ) ) {
			$oFirstName	= htmlspecialchars( $user->firstname );
			$oLastName	= htmlspecialchars( $user->lastname );
		} else {
			$posLname	= strrpos( $user->name, ' ' );
			if ( $posLname !== false ) {
				$oFirstName	= htmlspecialchars( substr( $user->name, 0, $posLname ) );
				$oLastName	= htmlspecialchars( substr( $user->name, $posLname + 1 ) );
			} else {
				$oFirstName = '';
				$oLastName	= htmlspecialchars( $user->name );
			}
		}

		$txtHiddenInputs =
			'<input type="hidden" name="' . $this->_getPagingParamName( 'basket' )   . '" value="'	. $paymentBasket->id . "\" />\n"
				.'<input type="hidden" name="' . $this->_getPagingParamName( 'shopuser' ) . '" value="'	.  $this->shopuserParam( $paymentBasket ) . "\" />\n"
				.'<input type="hidden" name="' . $this->_getPagingParamName( 'paymenttype' ) . "\" value=\"0\" />\n";

		$txtVisibleInputs = array(
			'number'	 => '<input class="inputbox" size="20" maxlength="20" type="text" autocomplete="off" name="' . $this->_getPagingParamName( 'number' )    . '" value="" />',
			'firstname'	 => '<input class="inputbox" size="20" maxlength="50" type="text" autocomplete="off" name="' . $this->_getPagingParamName( 'firstname' ) . '" value="' . $oFirstName . '" />',
			'lastname'	 => '<input class="inputbox" size="20" maxlength="50" type="text" autocomplete="off" name="' . $this->_getPagingParamName( 'lastname' )  . '" value="' . $oLastName  . '" />'
		);
		if ( $drawCCV ) {
			$txtVisibleInputs['cvv'] = '<input class="inputbox" size="6" maxlength="4" type="text" autocomplete="off" name="' . $this->_getPagingParamName( 'cvv' ) . '" value="" />';
		}

		if ( ! $cardType ) {
			$cardSelector = $this->_drawCCSelector( $user, $paymentBasket, $chosenCard );
			$txtVisibleInputs['cardtype'] = $cardSelector;
		} else {
			$txtVisibleInputs['cardtype'] = $this->_renderCCimg( $cardType, 'big' );
			$txtHiddenInputs .= '<input type="hidden" name="' . $this->_getPagingParamName( 'cardtype' ) . '" value="'	. $cardType . "\" />\n";
		}
		$months = array();
		$months[] = moscomprofilerHTML::makeOption( '', 'MM' );
		for ( $i=1; $i <= 12; $i++ ) {
			$months[] = moscomprofilerHTML::makeOption( $i, sprintf( '%00d', $i ) );
		}
		$txtVisibleInputs['expmonth'] = moscomprofilerHTML::selectList( $months, $this->_getPagingParamName( 'expmonth' ), 'class="inputbox" size="1"', 'value', 'text', '' );

		$years = array();
		$years[] = moscomprofilerHTML::makeOption( '', 'YYYY' );
		$yearNow	= date('Y');
		$monthNow	= date('m');
		for ( $i = ( ( $monthNow == 1 ) ? -1 : 0 ) ; $i < $this->ccYearsInAdvance; $i++ ) {
			$years[] = moscomprofilerHTML::makeOption( $yearNow + $i, sprintf( '%0000d', $yearNow + $i ) );
		}
		$txtVisibleInputs['expyear'] = moscomprofilerHTML::selectList( $years, $this->_getPagingParamName( 'expyear' ), 'class="inputbox" size="1"', 'value', 'text', '' );

		if ( $drawAVS ) {
			if ( $drawAVS >= 2 ) {
				/** @var $user cbpaidUserWithSubsFields */
				$txtVisibleInputs['address'] =	'<input class="inputbox" size="40" maxlength="60" type="text" autocomplete="off" name="' . $this->_getPagingParamName( 'address' )    . '" value="' . htmlspecialchars( $user->cb_subs_inv_address_street ) . '" />';
			}
			$txtVisibleInputs['zip']		=	'<input class="inputbox" size="10" maxlength="20" type="text" autocomplete="off" name="' . $this->_getPagingParamName( 'zip' )    . '" value="' . htmlspecialchars( $user->cb_subs_inv_address_zip ) . '" />';
			$allCountriesSelect				=	array();
			$countries						=	new cbpaidCountries();
			foreach ( $countries->twoLetterIsoCountries() as $countryName) {
				$allCountriesSelect[]		=	moscomprofilerHTML::makeOption( $countryName, CBPTXT::T( $countryName ) );
			}
			$txtVisibleInputs['country']	=	moscomprofilerHTML::selectList( $allCountriesSelect, $this->_getPagingParamName( 'country' ), 'class="inputbox" size="1"', 'value', 'text', $user->cb_subs_inv_address_country );
		}
		$txtButton		= '<div class="cbregCCbutton" style="min-height:38px;vertical-align:middle;">';
		if ( $payButtonText ) {
			$txtButton .= '<button type="submit" name="cbPayNow" id="cbPayNow" value="' . htmlspecialchars( $payButtonText ) . '" title="' .  htmlspecialchars( CBPTXT::T("Pay now") ) . '">'
				.	$payButtonText
				.	'</button>';
			$js			=	'$("#cbPayNow").click( function() {'
				.		'if(cbCCformSubmitbutton(this.form)) {'
				.			'$("#cbsubsCCform input[name=\'' . $this->_getPagingParamName( 'paymenttype' ) . '\']").val("1");'
				.			'$(this).parent().fadeOut("slow", function() { $("#cbpayWheel").fadeIn("slow"); } );'
				.			'$(this.form).submit();'
				.		'}'
				.	' } );'
			;
			$_CB_framework->outputCbJQuery( $js );
		}
		if ( $payButtonText && $subscribeButtonText ) {
			$txtButton .= '<br /> ' .CBPTXT::T("or") . ' <br /> ';
		}
		if ( $subscribeButtonText ) {
			$txtButton .= '<button type="submit" name="cbSubscribeNow" id="cbSubscribeNow" value="' . htmlspecialchars( $subscribeButtonText ) . '" title="' . htmlspecialchars( CBPTXT::T("Subscribe to payments now") ) . '">'
				.	$subscribeButtonText
				.	'</button>';
			$js			=	'$("#cbSubscribeNow").click( function() {'
				.		'if(cbCCformSubmitbutton(this.form)) {'
				.			'$("#cbsubsCCform input[name=\'' . $this->_getPagingParamName( 'paymenttype' ) . '\']").val("2");'
				.			'$(this).parent().fadeOut("slow", function() { $("#cbpayWheel").fadeIn("slow"); } );'
				.			'$(this.form).submit();'
				.		'}'
				.	' } );'
			;
			$_CB_framework->outputCbJQuery( $js );
		}
		$txtButton		.= '</div>';
		$txtButton		.= '<div id="cbpayWheel" style="display:none;margin:4px 25px;"><img src="' . $this->baseClass->getPluginLIvePath() . '/icons/hot/wheel_pay.gif" alt="spinning wheel" /></div>'
			.  "\n";
		$ret	=	'';
		$this->_renderCCvalidation( '#cbsubsCCform' );

		$ret .= '<form action="' . $postUrl . '" method="post" autocomplete="off" id="cbsubsCCform" name="cbsubsCCform" class="cb_form">' . "\n";
		ob_start();
		$this->_renderCCform( $cardType, $txtVisibleInputs, $txtButton );
		$ret .= ob_get_contents();
		ob_end_clean();
		$ret .= $txtHiddenInputs;
		$ret .= "</form>\n";
		ob_start();
		$this->_renderCCsealCode( $sealCode );
		$ret .= ob_get_contents();
		ob_end_clean();
		return $ret;
	}