PPExt - PayPal extension

The code above will create a hosted button stored at PayPal, it is also stored locally in your database (or a php file, if you’re using PPPhpButtonManager). To view the button you need to fetch the HTML code using


 $buttonManager->getButton('My button');

.

The button should be created only once, if you do it multiple times, you will create a bunch of new buttons stored at PayPal.

Is there any step by step guide for using this ext?

Not that I know of. There are some examples in the extension page. You can also find some information in this thread (a very simple store sample, etc.).

I would recommend looking at PayPal’s documentation. The extension will take care of sending/receiving data, but you need to validate and process events (e.g. payments) as documented by PayPal.

  1. Read about instant payment notificatoin

  2. Read about the button manager if you’d like to use hosted buttons (take not of the NVP examples, that’s what you will be using in PPButtonManager)

Hi!

thank you for your valuable work. I made it running thanks to this thread :) I’ve a site where the button is used only once, since it is used for pay per use services, so amount will change each time.

So I create a random ID button:




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


      $nvp = array(

          'BUTTONTYPE'=>'BUYNOW',

          'L_BUTTONVAR0'=>'currency_code=USD',

          'L_BUTTONVAR1'=>"item_name=Service description",

          'L_BUTTONVAR2'=>"amount=". $_POST['amount'],

          'L_BUTTONVAR3'=>'image_url=...logo.png',

          'L_BUTTONVAR4'=>'return=...thankyoupage'

      );

      $btnId = genRandomString();

      $buttonManager->createButton( $btnId, $nvp);


      $b = $buttonManager->getButton( $btnId);

      echo $b->webSiteCode;



However my buttons in the "My saved buttons" page in PayPal sandbox account are all in the form




My button (ID: NLJ23RALHGAW2) Created 7/28/2011



The ID is not the one I’ve created before.

How can I force the ID of the button, so I can later on delete the button (after receiving the IPN)?

Does the extension provide a quick way to delete a button?

Finally, since I also have products with fixed prices, I would like to implement "add cart" and "view cart" buttons. Is it possible to do that with PPExt?

thank you in advance! :)

Ivan

Hi!

I’m glad you like the extension.

No, the id you specify is only used locally on your server. PayPal generates their own id.

I don’t think that is possible (check PayPal’s button manager documentation to be sure),

but you can retrieve the hosted button id (NLJ23RALHGAW2 in your example) using:


$b->hostedButtonId

Yes:


$buttonManager->manageButtonStatus($btnId,array('BUTTONSTATUS'=>'DELETE'))

Yes, PPExt let’s you use all possible options from PayPal’s button manager API. The following link includes an “Add to cart” example: Button manager examples.

I get an error like this


include(PPIpnAction.php) [<a href='function.include'>function.include</a>]: failed to open stream: No such file or directory 

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

in public function actionIpn()

What’s wrong?

Thanks!

Looks like Yii is unable to find the PPIpnAction class. Have you placed your controller

in WebRoot/modules/payPal/controllers? If not, you need to import the required classes:




//Yii::import('payPal.models.*'); // Models used by the module (account details, etc.)

Yii::import('payPal.components.*'); // Misc. utility classes

//Yii::import('payPal.controllers.pdt.*'); // PDT action(s)

Yii::import('payPal.controllers.ipn.*'); // IPN actoin(s)



they are in controllers/ipn or /pdt folders

After I have imported them, I started receiving ipn response.

But now I’m getting this error in the log




2011/08/07 14:49:12 [info] [payPal.controllers.ipn.PPIpnAction] Successfull IPN request

Request:

cmd=_notify-validate&mc_gross=39.91&protection_eligibility=Eligible&address_status=confirmed&payer_id=T4YK2TRA2MAXY&tax=0.00&address_street=1+Main+St&payment_date=04%3A48%3A29+Aug+07%2C+2011+PDT&payment_status=Completed&charset=windows-1252&address_zip=95131&first_name=Test&mc_fee=1.46&address_country_code=US&address_name=Test+User&notify_version=3.2&custom=872&payer_status=verified&business=o.test_1312236115_biz%40gmail.com&address_country=United+States&address_city=San+Jose&quantity=1&verify_sign=AfZa-X9xQQDI-EDvbMaXq9gs045VASgw5o3D.KNIoPoLauJRvn.mzNfp&payer_email=o.tura_1312717469_per%40gmail.com&txn_id=9GU259941J0014206&payment_type=instant&btn_id=1829010&last_name=User&address_state=CA&receiver_email=o.test_1312236115_biz%40gmail.com&payment_fee=1.46&shipping_discount=0.00&insurance_amount=0.00&receiver_id=X2AFDH9NN7H6Y&txn_type=web_accept&item_name=My+button&discount=0.00&mc_currency=USD&item_number=&residence_country=US&test_ipn=1&shipping_method=Default&handling_amount=0.00&transaction_subject=872&payment_gross=39.91&shipping=0.00&ipn_track_id=C3zzS7evgtWpzw-zCNLpVA

Response:

VERIFIED

in /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/controllers/ipn/PPIpnAction.php (128)

in /home/amirades/amiradesign.com.ua/test/protected/controllers/PayController.php (93)

in /home/amirades/amiradesign.com.ua/test/index.php (15)

2011/08/07 14:49:12 [error] [php] Trying to get property of non-object (/home/amirades/amiradesign.com.ua/test/protected/modules/payPal/models/PPPhpTransaction.php:103)

Stack trace:

#0 /home/amirades/amiradesign.com.ua/test/yii/framework/base/CModel.php(152): CInlineValidator->validate()

#1 /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/models/PPPhpTransaction.php(129): PPPhpTransaction->validate()

#2 /home/amirades/amiradesign.com.ua/test/protected/controllers/PayController.php(129): PPPhpTransaction->save()

#3 /home/amirades/amiradesign.com.ua/test/yii/framework/base/CComponent.php(568): PayController->ipnRequest()

#4 /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/controllers/ipn/PPIpnAction.php(61): PPIpnAction->raiseEvent()

#5 /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/controllers/ipn/PPIpnAction.php(129): PPIpnAction->onRequest()

#6 /home/amirades/amiradesign.com.ua/test/protected/controllers/PayController.php(93): PPIpnAction->run()

#7 /home/amirades/amiradesign.com.ua/test/yii/framework/web/actions/CInlineAction.php(50): PayController->actionIpn()

#8 /home/amirades/amiradesign.com.ua/test/yii/framework/web/CController.php(300): CInlineAction->runWithParams()

#9 /home/amirades/amiradesign.com.ua/test/yii/framework/web/CController.php(278): PayController->runAction()

#10 /home/amirades/amiradesign.com.ua/test/yii/framework/web/CController.php(257): PayController->runActionWithFilters()

#11 /home/amirades/amiradesign.com.ua/test/yii/framework/web/CWebApplication.php(328): PayController->run()

#12 /home/amirades/amiradesign.com.ua/test/yii/framework/web/CWebApplication.php(121): CWebApplication->runController()

#13 /home/amirades/amiradesign.com.ua/test/yii/framework/base/CApplication.php(155): CWebApplication->processRequest()

#14 /home/amirades/amiradesign.com.ua/test/index.php(15): CWebApplication->run()

REQUEST_URI=/pay/ipn

in /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/models/PPPhpTransaction.php (103)

in /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/models/PPPhpTransaction.php (129)

in /home/amirades/amiradesign.com.ua/test/protected/controllers/PayController.php (129)

2011/08/07 14:49:12 [trace] [system.CModule] Loading "errorHandler" application component

in /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/models/PPPhpTransaction.php (103)

in /home/amirades/amiradesign.com.ua/test/protected/modules/payPal/models/PPPhpTransaction.php (129)

in /home/amirades/amiradesign.com.ua/test/protected/controllers/PayController.php (129)




what’s is wrong? And how exactly should my listener action look like? Thanks!!!

Hm… not sure exactly. controllers/PPDefaultController.php is an example listener for

both IPN and PDT. It’s using PPPhpTransaction.php for validation and storage. The

easiest way to start using PPExt is to modify PPDefaultController.php.

When you’re ready you should also create your own model (PPPhpTransaction is an example,

I strongly recommend you use a database model instead).

I find it most convenient to use one controller for both IPN and PDT actions, placed

under payPal/controllers (as PPDefaultController.php).

I am having similar issues. I can’t seem to get the IPN response to trigger anything in the application.

I am using a listener in /Webroot/controllers/PaymentController with the following method:




public function actionIpn() {

    Yii::import('payPal.models.*');

    Yii::import('payPal.controllers.ipn.*');

    Yii::import('payPal.components.*');

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


    $ipn->onRequest = array($this, "ipnRequest");

    $ipn->onFailure = array($this, "ipnFailure");

    $ipn->onSuccess = array($this, "ipnSuccess");


    $ipn->run();

	}

My IPN Notification URL is [site]/payment/ipn/

PayPal IPN status is: Sent

I don’t see anything in the log at all…

I tried dropping this in /modules/PayPal/controllers, but my app can’t find the controller with /payPal/payment/ipn url.

You should move the import statements to the top of your file, before the class declaration.

It seems like the ipn action hasn’t been run. Can you try accessing [site]/payment/ipn using your

browser? $ipn->run() will first try to connect to PayPal, and if that fails you should get an error message in the log.

That’s peculiar. Can your app find PPDefaultController?

Thanks for the quick response!

I moved the import prior to class declaration.

I have been directly requesting the controller via a browser and it does trigger the actions, I see the built-in log entries and others that I’ve added to trace it. Every time I send a test IPN or resend an actual one, no action is called, I don’t even trigger log entries at the top of the ActionIpn method. It seems like the URL is not resolving correctly actually since nothing in the actionIpn method works. The IPN URL I have at PayPal is … [mysite]/payment/ipn/. I see the POST requests “POST /reservation/payment/ipn/ HTTP/1.0” 200 181 “-” “-”

Unable to resolve the request "payPal/PPDefaultController".

Just FYI… this is a controller based on Legacy (PHP <5.3) code.

Good, then the module is working :)

I think you’re right. Not sure what could cause the problem though.

Probably need to use payPal/pPdefaultController to resolve the request (yes I know, it’s a horrible controller name…), but since you were able to resolve payments/ipn there is no need to test PPDefaultController (or PPLegacyController).

I think I have some POST conflict somewhere as I have this as part of a subsystem underneath an ExpressionEngine site. I really don’t know. I’ll keep trying some things.

Actually I am breaking down my controller and see that the POST is failing due to various issues in the functions. I now have a simple controller that has two methods. The IPN path calls actionIpn successfully now and the log entries fire. The ipnRequest() function fails in the Failure($event) call that I’ve commented out. For some reason if this method fails, so does actionIpn. In other words, I get no logging if the ipnRequest method fails.

I do notice that the !isset($event->details["txn_id"] evaluates as TRUE even though the POST does have that parameter. That be the root of the issue here. Not sure.




<?php


Yii::import('payPal.models.*');

Yii::import('payPal.controllers.ipn.*');

Yii::import('payPal.components.*');


class ApiController extends Controller {

	

public function actionIpn() {

    Yii::log('API:IPN:PAYPALREQUEST','info','application.controllers.api');

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

    $ipn->onRequest = array($this, "ipnRequest");

    $ipn->onFailure = array($this, "ipnFailure");

    $ipn->onSuccess = array($this, "ipnSuccess"); 

    $ipn->run();

}

	

public function ipnRequest() {


    Yii::log("API:IPN:READTOPROCESS","info","application.controllers.api");

    if (!isset($event->details["txn_id"])) {

	$event->msg = "Missing txn_id";

	Yii::log($event->msg,"warning","application.controllers.PaymentController");

	//$event->sender->onFailure($event);

	return;

      }				

}

}

I seemed to have solved this by passing $event through to the ipnRequest method. It wasn’t there in the DefaultLegacyController. Just need to get the transaction saving to my DB now…

can you show the code, please?

I’m glad you figured it out.

If I understand trenchard correctly (correct me if I’m wrong), this is how he fixed the problem:

The following method (1) is missing an argument:




public function ipnRequest() { ... }



It should look like this (2):




public function ipnRequest($event) { ... }



PPDefaultController and PPDefaultLegacyController use the correct method signature (2).

I’m mentioning this so that other users don’t start troubleshooting an issue that doesn’t exist.

Yes, I simply was missing the $event parameter. The sample controller is right. I was wrong.





public function ipnRequest($event) {

    Yii::log("API:IPN:READY_TO_PROCESS","info","application.controllers.api");

    //Check transaction ID

    if (!isset($event->details["txn_id"])) {

        $event->msg = "API:IPN:HAS_NO_TX";

        Yii::log($event->msg,"warning","application.controllers.api");

        $event->sender->onFailure($event);

     	return;

    } else {

        $event->msg = "API:IPN:CONFIRMED_TXN_ID";

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

    } 


..

}.

I’ll definitely post the full code I’m using in the next few days. I created a custom controller for API listeners based on the legacy default controller that I’ll use for all inbound 3rd party services. ppext is a great extension that everyone should check out if needed. It certainly takes full advantage of the Yii conventions and doesn’t just place the IPN sample procedural code in a controller.

Please, tell what settings should look like for real PayPal account?

Do I need this?

‘env’=>‘sandbox’,

I set all settings like for sandbox but with real account data, env is commented out. But it won’t work.