Welcome Guest, Not a member yet? Register   Sign In
PHP cannot interpret all Paypal IPN notifications correctly (tip for all you Paypal coders)
#1

[eluser]Patrick Savalle[/eluser]
I was struggling with the Paypal IPN notification mechanism. It couldn't get it to work.

Whenever a transaction changes state (for instance when the transaction is completed) Paypal calls back the listener URL you set up. With an adaptive payments 'PAY' request you can specify which URL that should be. When the listener gets called, you should reply to paypal with the exact same POST-variables in the exact same order that paypal sended, prepended with 'cmd=_notify-validate&'.

This is what Paypal manual states:

Quote:Your listener software must
1. Wait for an HTTP post from PayPal.
2. Create a request that contains exactly the same IPN variables and values in the same order,
preceded with cmd=_notify-validate.
3. Post the request to www.paypal.com or www.sandbox.paypal.com, depending on
whether you are going live or testing your listener in the Sandbox.
4. Wait for a response from PayPal, which is either VERIFIED or INVALID.
5. Verify that the response status is 200.
6. If the response is VERIFIED, perform the following checks:

I did exactly this. My listener got called and I replied exactly as described, using the same code many of you do to copy request into reply. This code is even in the example of Paypal:

Code:
$req = 'cmd=_notify-validate&'
if (isset($_POST))
{
    foreach ($_POST as $key=>$value)
    {
        $req .= "&".$key."=".urlencode(stripslashes($value));
    }
}

When debugging I noticed that this piece of code does not work for the notifications paypal sends us because PHP interprets some fields in the post to be an array. For instance the paypal HTTP-POST contains (decoded) values like:

Code:
transaction[0].amount=EUR 1.00

The PHP code above to copy the post-variables won't work well on that, the problem are the brackets: some $value vars in the code above are made into arrays by the foreach-statement. So my listener did not reply correctly and Paypal kept answering with 'INVALID'.

The solution was to use this piece of code instead:

Code:
$req = 'cmd=_notify-validate&'.file_get_contents("php://input");

So, just to let you know! On our test-servers using the Paypal sandbox this works well and I think it is the better solution in general. You will probably want to use this piece of code in your Paypal IPN-listeners as well.
#2

[eluser]Patrick Savalle[/eluser]
To make this post complete, this is the IPN-listener I coded:

Code:
function ipn_listener()
{
    error_log("verify_ipn_notication()");
    
    // ----------------------------------------------------------------------------------
    // See: https://cms.paypal.com/cms_content/US/en_US/files/developer/IPNGuide.pdf
    // Code: https://cms.paypal.com/cms_content/US/en_US/files/developer/IPN_PHP_41.txt
    // ----------------------------------------------------------------------------------
    
    // -----------------------------------------------------------------
    // 1. Wait for an HTTP post from PayPal.
    // 2. Create a request that contains exactly the same IPN variables
    // and values in the same order, preceded with cmd=_notify-validate.
    // -----------------------------------------------------------------

    // IMPORTANT!!! See: http://ellislab.com/forums/viewthread/195377/  

    $result = array();
    $res = '';
    $req = 'cmd=_notify-validate&'.file_get_contents("php://input");

    // -----------------------------------------------------------------------------        
    // 3. Post the request to www.paypal.com or www.sandbox.paypal.com, depending on
    // whether you are going live or testing your listener in the Sandbox.
    // -----------------------------------------------------------------------------        

    $fp = fsockopen('www.sandbox.paypal.com', 80, $errno, $errstr, 30);
    if (!$fp)
    {
        error_log($errstr);
        throw new Exception($errstr);
    }
    fputs($fp, "POST /cgi-bin/webscr HTTP/1.0\r\n");
    fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
    fputs($fp, "Content-length: ".strlen($req)."\r\n\r\n");
    fputs($fp, $req );
    while(!feof($fp)) $res .= fgets($fp, 1024);
    fclose($fp);
        
    // ------------------------------------------------------------------------
    // 4. Wait for a response from PayPal, which is either VERIFIED or INVALID.
    // 5. Verify that the response status is 200.
    // ------------------------------------------------------------------------
    
    // little bit of debugging remains
    if (is_numeric(stripos($res, "400 Bad Request")))
    {
        error_log($req);
    }

    if (stripos($res, "200 OK")===FALSE )
    {
        error_log("Paypal IPN callback != 200 OK");
        error_log($res);
        exit;
    }
    
    if (stripos($res, "VERIFIED")===FALSE)
    {
        error_log("Paypal IPN callback != VERIFIED");
        exit;
    }

    $paykey = $_POST['pay_key'];
    $status = $_POST['status'];
    $status = $_POST['status'];
    $transaction_type= = $_POST['transaction_type'];
    $sender_email = $_POST['sender_email'];

    // ---------------------------------------
    // PUT YOUR PAYMENT VERIFICATION CODE HERE
    // ---------------------------------------
}
#3

[eluser]Pschilly[/eluser]
So are you using adaptive payments for the payment processing then? Or the standard "Website Payments Standard"?
#4

[eluser]Patrick Savalle[/eluser]
Adaptive Payments. The paypal documentation is so huge and unclear and obfuscated, times 10 versions, that is wasn't really clear what we needed. Still isn't.

We are building a micropayment system. Just need to allow users to deposit and withdraw money into their accounts with us. Paypal is one of the providers for that.

Adaptive Payments offers parallel payments which we can use.

Still the solution I described is fail-safe, regardless of payment types. And still the POST paypal makes has kind of a stupid format.

I also don't understand the need for a IPN notification, it is much simpler (and more secure) to request a payment-key 'payRequest', than redirect and on return do a request to the Paypal API for the status of the payment 'paymentDetailsRequest'.
#5

[eluser]Pschilly[/eluser]
Have you made a CI library to work with the Adaptive payment structure? I have looked into it and your right their API info is a pain in the ass to look through.

I am currently using the standard payment method... Which works, but is not the best in the world. I'd much rather not have my clients leave the website to do the payment stuff but I am not paying for the Pro version to do so as well its not worth it.

Also, not being able to run Paypal in an iFrame is a pain in the ass too Sad



-- Does adaptive payments allow you to do a payment process via your own web code? (With exception of logging into paypal)? Like for instance, the ability to have an ajax enabled payment processing system?
#6

[eluser]Patrick Savalle[/eluser]
I think it is possible to use the Paypal lightbox for adaptive payments, which is an iframe. At the moment I do a redirect, but the manual states it can be done using the lightbox with only a few modifications.

I will post my Library this weekend. I will make it generic so it can be used by you all.
#7

[eluser]Pschilly[/eluser]
Interesting...

I have never used the Adapative payments, I always use the redirect for the payment then come back... Using the IPN to make the changes -- No manual DB changes (meaning no involvement from the client with the exception of paying)


For the project im working on, I'd much rather have the paypal process in a lightbox iframe... As well it looks a hell of a lot better than redirecting.




Theme © iAndrew 2016 - Forum software by © MyBB