PPExt - PayPal extension


(Stianlik) #1

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


(Stianlik) #2

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


(Stianlik) #3

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.


(Spamec) #4

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


(Stianlik) #5

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, …)?


(Antonio) #6

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


(Stianlik) #7

Uploaded module into:

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


(Antonio) #8

Thank you stianlik


(Jamesmoey) #9

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




(Jamesmoey) #10

Please ignore my last post. My mistake.


(Jamesmoey) #11

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.


(Stianlik) #12

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.


(Jamesmoey) #13

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

Consider putting this on github?


(Stianlik) #14

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.


(Stianlik) #15

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.


(Kostya Molchanov) #16

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?


(Stianlik) #17

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


(Kostya Molchanov) #18

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.


(Stianlik) #19

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.


(Kostya Molchanov) #20

Everything works perfect. Thanks a lot!