2
votes

I have been at this for 3 days now and still can't get it to work.

What I want to do is to get PayPal response from the IPN listener so that I can modify my database accordingly, but no matter what I do, it just won't work. I have already done the following in my PayPal Sandbox account:

  1. Enabled Auto Return

  2. Set Auto Return URL ('paypal/success')

  3. Enabled Payment Data Transfer (PDT)
  4. Enabled IPN message reception
  5. Set IPN URL ('paypal/ipn')

The redirect to Auto Return URL works fine and I receive the payment data in success page, but the IPN won't process for reasons beyond me. A quick look at the IPN history on my PayPal profile shows that the messages are being sent, but I don't receive them at my end.

Here is my current IPN listener: Paypal/ipn

public function ipn() { 
        //Build the data to post back to Paypal
        $postback = 'cmd=_notify-validate'; 
        // go through each of the posted vars and add them to the postback variable
        foreach ($_POST as $key => $value) {
            $value = urlencode(stripslashes($value));
            $postback .= "&$key=$value";
        }

        // build the header string to post back to PayPal system to validate
        $header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
        $header .= "Host: www.sandbox.paypal.com\r\n";//or www.sandbox.paypal.com
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($postback) . "\r\n\r\n";

        // Send to paypal or the sandbox depending on whether you're live or developing
        // comment out one of the following lines
        $fp = fsockopen ('www.sandbox.paypal.com', 443, $errno, $errstr, 30);//open the connection
        //$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);
        // or use port 443 for an SSL connection
        //$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);

        if ( ! $fp ) {
            // HTTP ERROR Failed to connect
            $message = 'HTTP ERROR Failed to connect!'; 
            $this->email_me($message);
        } else { // if we've connected OK

            fputs ($fp, $header . $postback); //post the data back
            while ( ! feof($fp) ) {
                $response = fgets ($fp, 1024);

                if (strcmp (trim($response), "VERIFIED") == 0) { //It's verified

                    //read the payment details and the account holder
                    $payment_status = $_POST['payment_status'];
                    $receiver_email = urldecode($_POST['receiver_email']);

                    // further checks
                    if( ($payment_status == 'Completed') && ($receiver_email == $this->business_email) ) {

                        $message = 'IPN verified successfully!';
                        $this->email_me($message);

                        // Insert the transaction data in the database
                        $this->product_model->insert_transaction_details($_POST);

                    } else {

                        $message = 'Payment could not be verified!';
                        $this->email_me($message);  

                    }

                } else {

                    $message = 'IPN invalid!';
                    $this->email_me($message);  

                }
            }
        }
    }

Can someone point me in the right direction please? Also, is there anyway I can check the IPN response ("VERIFIED" or "INVALID") on chrome debugger or in my PayPal Sandbox dashboard? I can see delivery status in my dashboard but it doesn't say 'Verified' or 'Invalid' anywhere.

3
Is your IPN end point accessible from the internet? If it is on a local host PayPal won't be able to connect to it.Mark_1
It's on a live server.Valkay
Is it firewalled? See paypal.com/us/smarthelp/article/…Mark_1
I don't think I understand what you mean @Mark_1Valkay
If your server is behind a firewall you will need to add a rule to allow PayPal's IPN process to connect to your server. If your server is already accessible to everyone this won't be required.Mark_1

3 Answers

1
votes

I found the solution! I wrote the IPN handler inside a controller that allows access to users who are logged in as admin. Apparently, the IPN method was denying access to PayPal to verify the transaction. I figured this out and wrote the IPN method in a different controller and everything worked perfectly.

I also changed my IPN handler to this code (although the original might still work... i didn't try it):

class Paypal_ipn extends MY_Controller {
    public function __construct() {
        parent::__construct();
        $this->load->model('product_model');
        $this->sandbox = $this->config->item('sandbox'); 
        $this->paypal_host = $this->config->item('paypal_host'); 
        $this->paypal_url = $this->config->item('paypal_url'); 
        $this->business_email = $this->config->item('business');
    }


    public function ipn() {
        // STEP 1: Read POST data

        // reading posted data from directly from $_POST causes serialization 
        // issues with array data in POST
        // reading raw POST data from input stream instead. 
        $raw_post_data = file_get_contents('php://input');
        $raw_post_array = explode('&', $raw_post_data);
        $myPost = array();
        foreach ($raw_post_array as $keyval) {
          $keyval = explode ('=', $keyval);
          if (count($keyval) == 2)
             $myPost[$keyval[0]] = urldecode($keyval[1]);
        }
        // read the post from PayPal system and add 'cmd'
        $req = 'cmd=_notify-validate';
        if(function_exists('get_magic_quotes_gpc')) {
           $get_magic_quotes_exists = true;
        } 
        foreach ($myPost 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";
        }

        // STEP 2: Post IPN data back to paypal to validate

        $ch = curl_init($this->paypal_url);

        $headers = array(
            'POST /cgi-bin/webscr HTTP/1.1',
            'Host: ' . $this->paypal_host,
            'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
            'Content-Length: ' . strlen($req),
            'User-Agent: PayPal-IPN-VerificationScript',
            'Connection: Close'
        );

        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        if( !($res = curl_exec($ch)) ) {
            curl_close($ch);
            exit;
        }
        curl_close($ch);

        // STEP 3: Inspect IPN validation result and act accordingly

        if (strcmp ($res, "VERIFIED") == 0) {
            // check whether the payment_status is Completed
            // check that txn_id has not been previously processed
            // check that receiver_email is your Primary PayPal email
            // check that payment_amount/payment_currency are correct
            // process payment

            // assign posted variables to local variables
            $item_name = $_POST['item_name'];
            $item_number = $_POST['item_number'];
            $payment_status = $_POST['payment_status'];
            $payment_amount = $_POST['mc_gross'];
            $payment_currency = $_POST['mc_currency'];
            $txn_id = $_POST['txn_id'];
            $receiver_email = urldecode($_POST['receiver_email']); 
            $payer_email = $_POST['payer_email'];
            $school_id = $_POST['custom'];

            // further checks
            if($payment_status == 'Completed') {

                $message = 'IPN verified successfully!';
                $this->email_developer($message);

                // Insert the transaction data in the database
                $this->product_model->insert_transaction_details($_POST);

            } else {

                $message = 'Payment could not be verified!';
                $this->email_developer($message);  

            }

        } else if (strcmp ($res, "INVALID") == 0) {
            // log for manual investigation
            $message = 'IPN Invalid!';
            $this->email_developer($message);

        }
    }

}

For those that might experience my predicament, ensure you also do the following:

  1. If you enabled Cross Site Request Forgery (CSRF), ensure the IPN listener/handler is whitelisted, else IPN message will fail (Error 403 in PayPal IPN history).
  2. To be sure your IPN listener is working well, run it as a URL and see the response. If there is any error, it won't work. For response, trying echoing "Verified" or "Invalid".
  3. Use the PayPal IPN Simulator to test the process. Include a procedure that will submit information to the database upon success.

I hope it helps someone.

0
votes

use php://input instead of $_POST

reson described here in details : PHP "php://input" vs $_POST

also paypal has documentation for implementing IPN Listener and its in php as well Paypal tutorial

0
votes

One of the reason didn't get IPN is because there's a problem when connecting to paypal , fixing this issue all you have to do is to change the port.

So for this code from

$fp = fsockopen($url_parsed['host'],'80',$err_num,$err_str,30);  

To

$fp = fsockopen($url_parsed['host'],'443',$err_num,$err_str,30);

You can also refer from here