Paypal CSRF

Hello everybody,

I´ve started with Yii and I am having problems with CSRF and Paypal.

I have a simple form with the parameters to send to paypal. In my controller, I have the different urls where I should come after success or cancel in paypal:

In my ResourceController, in the actionPaypalSend is like this:




public function actionPaypalSend (){

    $return_url = 'localhost/myApp/resource/paypalOk';

    $cancel_url = 'localhost/myApp/resource/paypalCancel';

    $notify_url = 'localhost/myApp/concurso/paypalAnswer';

    ....



And in my main.php i have:




'request' => array(

	'class' => 'application.components.HttpRequest',

        'enableCsrfValidation' => true,

),



HttpRequest is like this in the post "How to validate CSRF token with session". It is my first post, so i could no write the url.

The problem I have is that after the payment in paypal, I should be back to localhost/myApp/concurso/paypalAnswer and then i do different things, but the problem is i get "The CSRF token could not be verified."

If i don´t use the CSRF token, it works ok, commentng these lines:




'request' => array(

	'class' => 'application.components.HttpRequest',

        'enableCsrfValidation' => true,

),



I´ve also tried without using ‘application.components.HttpRequest’ and letting Yii handle it, but it doesn´t work either.

I guess i sould send the token to paypal or something like that, and when paypal answers me, I will not have the message “The CSRF token could not be verified.” I tried adding a parameter to the paypal request like this $querystring .= “&YII_CSRF_TOKEN=”.$_POST[‘YII_CSRF_TOKEN’]; but doesn´t work.

Any advice?

Thanks in advance.

hola, desconozco un poco lo que quieres hacer, pero yo he trabajado muy bien con PayPal enviando pagos via POST, yo me fabrique la siguiente clase que te adjunto aqui y que espero te sirva, disculpa si no veo tu caso detalladamente pero no tengo mucho tiempo en este momento y se ve algo enredado de absorber.

into COMPONENTS folder:


<?php

 class PayPal {

 	// estas variables se pueden leer tras una llamada exitosa

 	//

	public $p_txn_type;

	public $p_payer_id;

	public $p_txn_id;

	public $p_payment_date;

	public $p_custom;

	public $p_data;

	public $p_payment_status;

	public $p_verified;

	public $p_mc_gross_1;

	public $p_mc_fee;


	/** limpia las variables de respuesta

     * 

     */

	public function clear()

	{

		$this->p_txn_type="";

		$this->p_payer_id="";

		$this->p_txn_id="";

		$this->p_payment_date="";

		$this->p_custom="";

		$this->p_data="";

		$this->p_payment_status="";

		$this->p_verified="";

		$this->p_mc_gross_1="";

		$this->p_mc_fee="";

	}


	/** crea una instancia del formulario de paypal

     * 

     */

 	public static function createInstantPaymentForm($formid,$custom

 	,$itemname,$itemnumber,$qty

 		,$subtotal,$discount,$total,$onsubmit_jsfunction_name=null)

 	{

 		// parametros a establecer en config

 		//

		$return_url = Yii::app()->params['paypal_returnurl'];

		$cancel_url = Yii::app()->params['paypal_cancelurl'];

		$email = Yii::app()->params['paypal_business'];

		$url = Yii::app()->params['paypal_url'];

		// aqui se le indica a PAYPAL a cual URL debera enviar el IPN

		// esta url debe ser un action visible para paypal, y que al ser invocado

		// llamara a handler().

		$notifyurl = Yii::app()->params['paypal_ipnhandler'];	

		//

		$onsubmit = "onsubmit = 'return {$onsubmit_jsfunction_name}()';";

		if($onsubmit_jsfunction_name == null)

			$onsubmit = "";

		echo "\n\n<!-- paypal form -->\n";

		echo "<form action='$url' method='post' 

			id='$formid' class='paypalform' {$onsubmit}>\n";

		echo "	\t<input type='hidden' name='business' value='$email'>\n";

		echo "	\t<input type='hidden' name='notify_url' value='$notifyurl'>\n";

		echo "	\t<input type='hidden' name='return' value='$return_url'>\n";

		echo "	\t<input type='hidden' name='cancel_url' value='$cancel_url'>\n";

		echo "	\t<input type='hidden' name='currency_code' value='USD'>\n";

		echo "	\t<input type='hidden' name='address_override' value='1'>\n";

		echo "	\t<input type='hidden' name='no_shipping' value='1'>\n";

		//echo "	\t<input type='hidden' name='tax' value='0'>\n";

		echo "	\t<input type='hidden' name='cmd' value='_xclick'>\n";

		echo "	\t<input type='hidden' name='custom' value='$custom' />\n";

		echo "	\t<input type='hidden' name='item_name' value='$itemname'>\n";

		//echo "	\t<input type='hidden' name='item_number' value='$itemnumber'>\n";

		//echo "	\t<input type='hidden' name='quantity' value='$qty'>\n";

		echo "	\t<input type='hidden' name='amount' value='$total'>\n";

		// boton paypal, value es nulo porque se maneja con CSS

		// deberia existir un estilo ".paypalbutton"

		echo "	\t<input type='submit' value='' class='paypalbutton' 

			title='Click to Pay'>\n";

  		echo "</form>\n\n\n";

 	}

	/** este handler debe ir localizado e invocado en un action expuesto 

     * al paypal IPN mechanism.

     * 

     *  tras su invocacion se pueden leer las variables establecidas.

     *

     */

 	public function handler()

 	{

 		Yii::log("inicia handler","info");


 		// url para enviar de vuelta el paquete ipn enviado para ser verificado 

 		//por paypal

 		$verifier = Yii::app()->params['paypal_verifier'];

		error_reporting(E_ALL ^ E_NOTICE);


		$this->p_txn_type = $_POST['txn_type'];

		$this->p_payer_id = $_POST['payer_id'];

		$this->p_txn_id = $_POST['txn_id'];

		$this->p_payment_date = $_POST['payment_date'];

		$this->p_custom = $_POST['custom'];

		$this->p_payment_status = $_POST['payment_status'];

		$this->p_mc_gross_1 = $_POST['mc_gross'];

		$this->p_mc_fee = $_POST['mc_fee'];


		$this->p_data = "";

		foreach($_POST as $k=>$v)

			$this->p_data .= "{$k}={$v}\r\n";


		Yii::log("handler data:\r\n".$this->p_data,"info");


		// Read the post from PayPal and add 'cmd'

		$req = 'cmd=_notify-validate';

		if(function_exists('get_magic_quotes_gpc'))

			$get_magic_quotes_exits = true;


		foreach ($_POST as $key => $value)

		{

			if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1)

			{

				$value = urlencode(stripslashes($value));

			}

			else

			{

				$value = urlencode($value);

			}

			$req .= "&$key=$value";

		}


		// Post back to PayPal to validate

		$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";

		$header .= "Content-Type: application/x-www-form-urlencoded\r\n";

		$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";


		Yii::log("handler verifier:\r\n".$verifier,"info");


		$fp = fsockopen ($verifier, 443, $errno, $errstr, 30);

		if (!$fp)

		{ // HTTP ERROR

			$reception = "HTTP-ERROR";

		}

		else

		{

			// NO HTTP ERROR

			fputs ($fp, $header . $req);

			while (!feof($fp))

			{

				$res = fgets ($fp, 1024);


				if (strcmp ($res, "VERIFIED") == 0)

				{

					$reception = "VERIFIED";

				}

				else

				if (strcmp ($res, "INVALID") == 0)

				{

					$reception = "INVALID";

				}

			}

		}

		fclose ($fp);

		Yii::log("handler verifier result:\r\n".$reception,"info");


		$this->p_verified = $reception;

		return ($reception == "VERIFIED");

 	}

 }

IN YOUR CONTROLLER:

THE ACTION THAT RECEIVE THE IPN CALLBACK FROM PAYPAL,





	public function actionHandler(){

		$pp = new PayPal();

		$pp->clear();

		$verified = $pp->handler();

		$ipn = new Ipn();

		$ipn->customcode = $pp->p_custom;

		$ipn->datecreated = MyDate::now();

		$ipn->data = $pp->p_data;

		$ipn->approved = ($verified && 

			($pp->p_payment_status=='Completed')) ? 1 : 0;

		$ipn->verified = $pp->p_verified;

		$ipn->info = "";

		$ipn->payment_status = $pp->p_payment_status;

		$ipn->txn_id =$pp->p_txn_id;

		$ipn->payment_date =$pp->p_payment_date;

		$ipn->txn_type = $pp->p_txn_type;

		$ipn->payer_id = $pp->p_payer_id;

		$ipn->mc_gross_1 = $pp->p_mc_gross_1;

		$ipn->mc_fee = $pp->p_mc_fee;

		$okaplicado=false;

		if($ipn->save())

		{

			if($ipn->applyIpn()){

				$okaplicado=true;

			}

			$ipn->save(); // guarda la info actualizada

		}

		/* informar mediante un correo

	     *

	     */

		$ipn->sendNotification($okaplicado);

	}



IN A VIEW:

THE PAYPAL PAYMENT BUTTON EXPOSED TO YOUR USER, VIA PAYPAL COMPONENT,

WHEN CLICKED IT SENDS A POST TO PAYPAL USING THE PROVIDED VARIABLES, THEN

PAYPAL RESPOND WITH A POST TO YOUR ACTION:







	PayPal::createInstantPaymentForm("paypalform"

		, Catalog::getOrden()->idorden

		, "Order ".Catalog::getOrdenNumber()

		, 0

		, 1

		, $o->total

		, 0

		, $o->total

		, 'paypalsubmit'

		);



GET PAID…EASY…HOPE IT HELPS YOU.

Gracias bluyell. No tengo mi código delante ahora mismo, pero por lo que puedo recordar se parece bastante al tuyo. Mi problema, explicado en español, es al utilizar CSRF. Si no lo tengo activado, sin cambiar ninguna línea de código, me funciona sin problemas, recibo la respuesta de Paypal correctamente. El problema es que si tengo activado el CSRF, una vez realizado el pago en paypal, cuando paypal me redirige a mi return_url, me salta la excepción de "The CSRF token could not be verified." Entiendo que este funcionamiento de CSRF es correcto, para evitar que alguien haga un post en mi aplicación sin el token adecuado. Lo que no sé es como enviar a Paypal mi token y que cuando Paypal me responda, lo haga con ese mismo token, de forma que entiendo evitaría el problema de "The CSRF token could not be verified."

De nuevo, gracias por tu código, lo comprobaré con el mío por si se me ha escapado algo.

Por si le sirve a alguien lo he solucionado deshabilitando el csrf en el controller que recibe la petición de Paypal como comentan en este link Disable CSRF token validation for certain paths. No sé si es una buena solución, pero por lo menos me evita que aparezca el mensaje de "The CSRF token could not be verified."

¡Muchas gracias Bluyell

Your example helped me a lot!

doodle