17

I am implementing ipnlistner inside my project. I set the ipn url inside my paypal account. But i am not getting all the transaction ipn responses to that url. But when i am checking ipn history in my account it displays that all the ipn has been sent. For example yesterday it is showing all 112 ipn had sent. but i am getting only 7 in my db. Here is my code for ipn listner. I am inserting all the data i am getting in the db at the first line only.

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Response;

class PaypalIPN extends Controller {

private $use_sandbox = null;

const VALID = 'VERIFIED';

const INVALID = 'INVALID';

public function useSandbox() {
    $this->use_sandbox = env( 'USE_SANDBOX' );
}

public function getPaypalUri() {
    if ( $this->use_sandbox ) {
        return env( 'SANDBOX_VERIFY_URI' );
    } else {
        return env( 'VERIFY_URI' );
    }
}

public function verifyIPN() {
    try {
        DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( $_POST, true ) ] );
        if ( ! count( $_POST ) ) {
            throw new \Exception( "Missing POST Data" );
        }
        $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 ) {
                // Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
                if ( $keyval[0] === 'payment_date' ) {
                    if ( substr_count( $keyval[1], '+' ) === 1 ) {
                        $keyval[1] = str_replace( '+', '%2B', $keyval[1] );
                    }
                }
                $myPost[ $keyval[0] ] = urldecode( $keyval[1] );
            }
        }
        // Build the body of the verification post request, adding the _notify-validate command.
        $req                     = 'cmd=_notify-validate';
        $get_magic_quotes_exists = false;
        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";
        }

        // Use the sandbox endpoint during testing.
        $this->useSandbox();

        // Post the data back to PayPal, using curl. Throw exceptions if errors occur.
        $ch = curl_init( $this->getPaypalUri() );
        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_SSLVERSION, 6 );
        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_CONNECTTIMEOUT, 30 );
        curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Connection: Close' ) );
        $res = curl_exec( $ch );
        if ( ! ( $res ) ) {
            $errno  = curl_errno( $ch );
            $errstr = curl_error( $ch );
            curl_close( $ch );
            throw new \Exception( "cURL error: [$errno] $errstr" );
        }
        $info      = curl_getinfo( $ch );
        $http_code = $info['http_code'];
        if ( $http_code != 200 ) {
            throw new \Exception( "PayPal responded with http code $http_code" );
        }
        curl_close( $ch );

        // Check if PayPal verifies the IPN data, and if so, return true.
        if ( $res == self::VALID ) {
            DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( $res, true ) ] );
        } else {
            DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( $res, true ) ] );
        }

        // Reply with an empty 200 response to indicate to paypal the IPN was received correctly.
        header( "HTTP/1.1 200 OK" );
    }catch (\Exception $e){
        DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( ["Exception"=>$e->getMessage()]) ] );
    }
}
}

I am veryfying IPN on this url

https://ipnpb.paypal.com/cgi-bin/webscr

and my ipn url is

https://www.myproject.com/api/verify-ipn

Note: previously i created some paypal buttons on this account, i am not getting the ipn responses for that button payments

Please help or guide what to do for this..

Community
  • 1
  • 1
RAUSHAN KUMAR
  • 5,846
  • 4
  • 34
  • 70
  • At what point do you write to the database? -- show that code – Martin Dec 28 '17 at 15:48
  • 1
    What does your PHP Error Log say? – Martin Dec 28 '17 at 15:49
  • Database writes are done through the `DB` class. Not certain what library it is but probably the source of the issue regardless. – smcjones Dec 28 '17 at 16:07
  • @smcjones as we can't see it, we can't know it's not the issue. – Martin Dec 28 '17 at 23:05
  • @Martin nor can we know it isn't not the issue. I follow you. However, a quick glance tells me it's Laravel's `DB` class (so tagged). The syntax is familiar to me. Probably not a Laravel bug. – smcjones Dec 29 '17 at 03:52
  • @Martin i am not getting any error in my laravel.log file, so it's not easy for me to debug the code. Some responses are getting stored in db and for some responses i am not getting data in db – RAUSHAN KUMAR Dec 29 '17 at 05:31
  • @smcjones in laravel we have `DB` facade to write the query. So that is not an issue – RAUSHAN KUMAR Dec 29 '17 at 05:32
  • @Martin i think i got the issue, so i edited my question here. thanks for your responses. – RAUSHAN KUMAR Dec 29 '17 at 05:48

3 Answers3

4

Please create below function in your controller and at your Paypal account add into IPN URL and check every hit of IPN.

<?php
 function paymentIpnlistener(){
    $req = 'cmd=_notify-validate';
    foreach ($_POST as $key => $value) {
        $value = urlencode(stripslashes($value));
        $req .= "&$key=$value";
    }
    // post back to PayPal system to validate
    $header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
    // If testing on Sandbox use: 
    $header .= "Host: www.sandbox.paypal.com:443\r\n";
    //$header .= "Host: ipnpb.paypal.com:443\r\n";
    $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
    if (strpos('ssl://www.sandbox.paypal.com', 'sandbox') !== FALSE && function_exists('openssl_open')) {
    $fp = fsockopen('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
  }
    else{
    // The old "normal" way of validating an IPN.
     $fp = fsockopen('ssl://www.sandbox.paypal.com', 80, $errno, $errstr, 30);
    }
    // If testing on Sandbox use:
    //$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
    //$fp = fsockopen ('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30);
    // 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     = $_POST['receiver_email'];
    $payer_email        = $_POST['payer_email'];
    if (!$fp) {
     // HTTP ERROR
    } else {
        fputs ($fp, $header . $req);
        while (!feof($fp)) {
            $res = fgets ($fp, 1024);
            if (strcmp ($res, "VERIFIED") == 0) {
                // check 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

                // Add your live email address    
                $mail_From = "From: me@mybiz.com";
                // Add your live email address 
                $mail_To = "raghbendra.nayak@systematixindia.com";
                $mail_Subject = "VERIFIED IPN";
                $mail_Body = $req;
                foreach ($_POST as $key => $value){
                    $emailtext .= $key . " = " .$value ."\n\n";
                }

                mail($mail_To, $mail_Subject, $emailtext . "\n\n" . $mail_Body, $mail_From);

            }
            else if (strcmp ($res, "INVALID") == 0) {
                // log for manual investigation

                $mail_From = "From: me@mybiz.com";
                $mail_To = "raghbendra.nayak@systematixindia.com";
                $mail_Subject = "INVALID IPN";
                $mail_Body = $req;

                foreach ($_POST as $key => $value){
                    $emailtext .= $key . " = " .$value ."\n\n";
                }

                mail($mail_To, $mail_Subject, $emailtext . "\n\n" . $mail_Body, $mail_From);

            }
        }   // while end
     fclose ($fp);
    }

    }
?>

Above function will send email to you for every time whenever the IPN listener triggered. Simply if its working then you can manage it as per your requirement. Try and let me know.

Raghbendra Nayak
  • 1,606
  • 3
  • 22
  • 47
  • Just checking - what does this have to do with OPs question and problem? It does not look like an answer to me. – Don't Panic Dec 30 '17 at 11:23
  • If he will create function and put this code then he will get IPN response either for failure or success in provided email..... then he can use same as per his requirement ...I faced it before and get the solution for same ...as I shared. – Raghbendra Nayak Dec 30 '17 at 19:07
3

It sounds like there may be an error in your code. Further debugging would be necessary. When I run into something like this I usually check the error logs to see what's going on.

PayPal will keep trying to send requests until it receives a 200 OK HTTP status response with no content in the body. If PayPal is showing successful receipt by the endpoint, then the place where the database is failing to enter data is probably just before your header function is being called.

My next move to debug would be to try to figure out if my database inserts were failing due to some kind of data integrity error/warning.

It might be helpful to catch DB errors and trigger some kind of non-passing response so PayPal resends until you've figured out the scripting problem.

Also:

  • Try to add additional logging using something like Monolog or even just error_log to get to the bottom of where your script is terminating or what is not working as expected.

I should note that I'm implementing PayPal IPN right now too and this library was difficult to use. I spun out my own with a subscriber pattern. It's not ready to go public yet but it was largely to separate out logic from verification for better testing and readability.

smcjones
  • 5,490
  • 1
  • 23
  • 39
  • OP is using the sandbox sdo points about live version are unneeded here – Martin Dec 28 '17 at 15:58
  • Removed the note on sandbox – smcjones Dec 28 '17 at 16:07
  • @smcjones i just get to know that, for the same account we created some paypal buttons previously for recurring payment. Now we changed the ipn url inside the account setting. So now the payments through that button are not sending the ipn responses to my new url. Any way to overcome this situation – RAUSHAN KUMAR Dec 29 '17 at 05:39
  • For new payments i am getting the ipn responses – RAUSHAN KUMAR Dec 29 '17 at 05:39
1

First of all, write a log as the first row of the web service, so you know when you get a request.

Then, somethimes you don't have all request parameters set, so maybe your sql go in error when parameters are not found.

In my paypal form, for example, I send the invoice_number. Then, when I get a request from IPN, I'll check for invoice_number to link payment with invoice. But I get payment on the same paypal account also from other source, so in the other case invoice_number is not set.

You may write a log with all parameter found in the IPN call so you can check what is missing.

Daniele Licitra
  • 1,520
  • 21
  • 45