PPExt - PayPal extension

Hello

Latest version can be downloaded from:

http://www.yiiframework.com/extension/ppext/

EDIT: The first version (ppext.tar.gz / ppext.zip in this post) has faulty IPN-code,

please download from the url above.

I am developing an event based PayPal extension for Yii, and would like to know it there is any interest in this? Feedback and feature requests are very welcome.

Features so far:

  • Button manager (you specify settings using an array of NVP-values)

  • PDT handler

  • IPN handler

  • Logging via Yii::log()

EXAMPLE USAGE

PDT- and IPN listeners (see src-code for a better example, including some payment processing)




class MinimalController extends Controller

{

	public function actionPdt() {

		$pdt = new PPPdtAction($this, "pdt");


                // Process payment

		$pdt->onRequest = function($event) {

			$this->onSuccess($event);

		};


                // Notify user on success

		$pdt->onSuccess = function($event) {

			$event->sender->controller->renderText("Success");

		};


                // Notify user on failure

		$pdt->onFailure = function($event) {

			$event->sender->controller->renderText("Failure");

		};


		$pdt->run();

	}


	public function actionIpn() {

		$ipn = new PPIpnAction($this,"ipn");


                // Process payment

		$ipn->onRequest = function($event) {

			$event->setMsg("Received payment.");

			$ipn->onSuccess($event);

		};


                // Log success

		$ipn->onSuccess = function($event) {

			Yii::log($event->msg, "info", "payPal.controllers.DefaultController");

		};


                // Log failure

		$ipn->onFailure = function($event) {

			Yii::log($event->msg, "error", "payPal.controller.DefaultController");

		};


		$ipn->run();

	}

}



Button Manager - Creating a buy now button




$this->bm = Yii::app()->getModule('payPal')->buttonManager;

$this->bm->createButton($name, array('BUTTONTYPE'=>'BUYNOW'));



Installation instructions:

  1. Extract the file into WebRoot/modules/payPal

  2. Open WebRoot/modules/payPal/PayPalModule.php for further instructions

Discovered a bug in the previous attachment, this should work better.

I have done some refactoring and cleaned up the IPN and PDT functionality.

  • All IPN and PDT events are logged (success and failure)

  • Moved ipnRequest and pdtRequest from PPUtils into IPN- and PDT actions

  • Details about events (payments usually) is now stored in PPEvent::details

The new event class:




class PPEvent extends CEvent {

	public $responseAr = array();	// HTTP Response from PayPal

	public $requestAr = array();	// HTTP Request sent to PayPal

	public $details = array();	// Verified payment details (assoc array)

	public $msg = "";		// Description of event

}



Version 0.2 will be uploaded as soon as I have finished a couple of additional improvements, and testing.

This looks really neat… thanks for sharing and inspiring piece of code :)

Thanks for the positive reply :)

Version 0.2 is ready:

  • PPIpnAction and PPPdtAction cleaned up and tested

  • DefaultController (IPN and PDT example) cleaned up and tested

  • Updated documentation

Todo:

  • PPDbButtonManager - Database implementation of the button manager

  • Helper methods for common buttons

  • Standard payment processing methods (validating payment status, saving transactions in DB, …)?

Thank you very much for sharing. I keep my self ‘connected’ to this post to see how it evolves.

Uploaded module into:

http://www.yiiframework.com/extension/ppext/

Thank you stianlik

Bug in components/PPUtils.php




Index: application/modules/payPal/components/PPUtils.php

===================================================================

--- application/modules/payPal/components/PPUtils.php	(revision )

+++ application/modules/payPal/components/PPUtils.php	(revision )

@@ -205,7 +205,7 @@

 

 		// Sent HTTP GET request

 		$getStr = self::urlencode($getVars);

-		$getStr = self::implode("&",$getStr);

+		$getStr .= self::implode("&",$getStr);

 		$response = PPUtils::httpGet(self::getUrl(self::NVP), $getStr, true);

 

 		// Return false on HTTP error



Please ignore my last post. My mistake.

The run method in PPIpnAction class check that txn_id exist otherwise it is consider the notification as failure without even doing the validation with Paypal.

  • I think it should validation the notification with Paypal no matter what. And log any invalidate notification as hack attempt.

  • Also there are notification that does not contain txn_id. For example subscr_cancel or the subscription update. Maybe we should check both subscr_id and txn_id before marking the notification as failure.

I think it’s sensible to remove the txn_id check. Then all requests will be validated (failures are also logged) and txn_id / subscr_id can be checked by a simple isset() in onRequest.

EDIT: I have made the changes, check out version 0.4.

Excellent work by the way. A big time saver for me.

Consider putting this on github?

Thank you. I will consider github (or probably launchpad since I’m using bzr at the moment) and post a link if I decide to upload.

Moved from how to use ppext.

I’m working on a simple ecommerce example, it will be posted on the extension page after completion.

I have attached an example to illustrate how you:

  • Create buy now buttons

  • Remove buttons

  • Display buttons

Everything is included in a controller to make it simple.

1071

PPStoreExampleController.php

Example: Creating a buy now button using the button manager




    $buttonManager = Yii::app()->getModule('payPal')->buttonManager;

    $nvp = array(

        'BUTTONTYPE'=>'BUYNOW',

        'L_BUTTONVAR0'=>'currency_code=USD',

        'L_BUTTONVAR1'=>'item_name=My button',

        'L_BUTTONVAR2'=>'amount=200.00',

    );

    $buttonManager->createButton('My button', $nvp);



For payment processing, you should look at PPDefaultController and PPPhpTransaction.

My steps:

  1. Move module to /protected/modules/payPal

  2. Create account on paypal with e-mail kostya.molchanov@gmail.com

  3. In sandbox create 2 test-accounts - seller_1292519600_biz@gmail.com and buyer_1292577660_per@gmail.com

  4. In config.php


'modules'=>array(

		'user',

		'rights',

		'payPal'=>array(

			'env'=>'sandbox',

			'account'=>array(

					'username'=>'seller_1292519600_biz@gmail.com',

					'password'=>'sellerseller',

					'signature'=>'Your PayPal API signature',

					'email'=>'seller_1292519600_biz@gmail.com',

					'identityToken'=>'Your PayPal identity token',

				),

			'components'=>array(

				'buttonManager'=>array(

//					'class'=>'payPal.components.PPDbButtonManager',

					'class'=>'payPal.components.PPPhpButtonManager',

				),

			),

		),

	),

  1. In view.php file

		<?php

			$buttonManager = Yii::app()->getModule('payPal')->buttonManager;

			$nvp = array(

				'BUTTONTYPE'=>'BUYNOW',

				'L_BUTTONVAR0'=>'currency_code=USD',

				'L_BUTTONVAR1'=>'item_name=My button',

				'L_BUTTONVAR2'=>'amount=200.00',

			);

			$buttonManager->createButton('My button', $nvp);

		?>

  1. Log

2010/12/18 13:47:30 [error] [payPal.helpers.PPUtils] NVP request failed

Request:BUTTONTYPE=BUYNOW&L_BUTTONVAR0=currency_code%3DUSD&L_BUTTONVAR1=item_name%3DMy+button&L_BUTTONVAR2=amount%3D200.00&METHOD=BMCreateButton&VERSION=63.0&USER=seller_1292519600_biz%40gmail.com&PWD=sellerseller&SIGNATURE=Your+PayPal+API+signature

Response:TIMESTAMP=2010-12-18T07:47:30Z&CORRELATIONID=8059d1cb27f4a&ACK=Failure&VERSION=63.0&BUILD=1603674&L_ERRORCODE0=10002&L_SHORTMESSAGE0=Security error&L_LONGMESSAGE0=Security header is not valid&L_SEVERITYCODE0=Error

in /home/gluck/src/thesloganchefs/protected/modules/payPal/components/PPUtils.php (222)

in /home/gluck/src/thesloganchefs/protected/modules/payPal/components/PPButtonManager.php (59)

in /home/gluck/src/thesloganchefs/protected/views/projects/view.php (21)

2010/12/18 13:47:30 [error] [payPal.components.PPButtonManager] Failed create button

Request: BUTTONTYPE=BUYNOW&L_BUTTONVAR0=currency_code=USD&L_BUTTONVAR1=item_name=My button&L_BUTTONVAR2=amount=200.00&METHOD=BMCreateButton

in /home/gluck/src/thesloganchefs/protected/modules/payPal/components/PPButtonManager.php (240)

in /home/gluck/src/thesloganchefs/protected/modules/payPal/components/PPButtonManager.php (60)

in /home/gluck/src/thesloganchefs/protected/views/projects/view.php (21)

This is my first problem. And where paypal will send payment results?

You have to provide those values.

  1. Login to paypal using the seller account

  2. Find your signature at "My account" > "Profile" > "API Acess" > "View API Signature" (or "Request API Access")

  3. Find your identityToken at "My account" > "Profile" and look for "Secure Merchant Account ID"

You will be notified of payments by IPN (see PPDefaultController). Configure the IPN handler

in your PayPal account at "My account" > "Profile" > "Instant Payment Notification Preferences".

Before processing payments using IPN, you should read and understand this: Introducing IPN

Thank you very much. I copy PPDefaultLegacyController to protected/controllers/PayController.php Instant Payment Notification Preferences i set http://mydomain.com/pay/ipn/ and everything become good, my site receive payments results. but there is another question, how to send for example user_id to paypal and then receive this user_id to make some actions on my site. I read about “custom” but can’t create button with “custom” parametr and don’t know how to receive this parametr from paypal.

To create buttons with the "custom" parameter set to $userId, you can include the following code in your controller (use getButtonForm($id,$userId) instead of $buttonManager->getButton($id)->webSiteCode in your actions):




/**

 * Get button form (HTML code) for button $id including

 * custom field with $userId as value.

 * 

 * @param string $id Button id

 * @param string $userId User id

 * @return string HTML Form

 */

public function getButtonForm($id,$userId) {

    if ( ($button = $this->buttonManager->getButton($id)) !== false)

        return self::addHiddenField('custom',$userId,$button->webSiteCode);

    else return false;

}

	

/**

 * Add hidden field to form.

 *

 * @param string $name Field name (e.g. 'custom')

 * @param string $value Field value (e.g. 'My custom value')

 * @param string $form Form (e.g. $button->webSiteCode)

 * @return string Form with hidden an extra hidden field

 */

public static function addHiddenField($name,$value,$form) {

	return preg_replace(

		'/< *\/form *>/i',

		'<input type="hidden" name="'.$name.'" value="'.$value.'" />'."\n</form>\n",

		$form

	);

}



When you receive the IPN- and PDT notifications from PayPal, the user id can be found in $event->details[‘custom’].

I will include a better mechanism for this sort of thing in the next version.

Everything works perfect. Thanks a lot!