Okay, here is the code I'm using. The original author hasn't been seen in three years and I haven't been able to get a response for a month so I'm asking here. The people here seem to be abel to help me more than anyone else.

This is the ipn.php:

<?php

// === Include the scripts to make this IPN works === //
include('core/class.config.php');
include('core/class.database.php');
include('core/lib/class.paypal.php');
// =====================================//

// Initiate the classes, and establish a DB conection
$Config = new Config;
$Paypal = new Paypal;
$DB = new Database(
    $Config->getDbInfo('db_host'), 
    $Config->getDbInfo('db_port'), 
    $Config->getDbInfo('db_username'), 
    $Config->getDbInfo('db_password'), 
    $Config->getDbInfo('db_name')
);

// Set test mode features (TRUE or FALSE)
$Paypal->testMode(FALSE);

// Lets check to see if we are valid or not
$Paypal->setLogFile('core/logs/ipn_log.txt');

// Check the payment status
$check = $Paypal->checkPayment($_POST);
if($check == TRUE)
{
    // We must break down all the fancy stuff to get the account ID
    // Format: Item info --- Account: (Account name) (# (account number))
    // ex: Item: 5 Shortswords --- Account: wilson212(#6)
    $account = explode(" --- ", $_POST['item_name']);
    $pre_accountid = $account['1'];
    $pre_accountid = str_replace("Account: ", "", $pre_accountid);
    $pre_accountid = explode("(#", $pre_accountid);
    $accountid = str_replace(")", "", $pre_accountid['1']);

    if(isset($_POST['pending_reason']))
    {
        $pending_reason = $_POST['pending_reason'];
    }
    else
    {
        $pending_reason = NULL;
    }

    if(isset($_POST['reason_code']))
    {
        $reason_code = $_POST['reason_code'];
    }
    else
    {
        $reason_code = NULL;
    }


    // Do the DB injection here
    $DB->query("INSERT INTO `mw_donate_transactions`(
        `trans_id`,
        `account`,
        `item_number`,
        `buyer_email`,
        `payment_type`,
        `payment_status`,
        `pending_reason`,
        `reason_code`,
        `amount`,
        `item_given`)
       VALUES(
        '".$_POST['txn_id']."',
        '".$accountid."',
        '".$_POST['item_number']."',
        '".$_POST['payer_email']."',
        '".$_POST['payment_type']."',
        '".$_POST['payment_status']."',
        '".$pending_reason."',
        '".$reason_code."',
        '".$_POST['mc_gross']."',
        '0'
        )
    ");
}
?>

The line $Paypal->testMode(FALSE); doesn't seem to care one way or the other if it's TRUE or FALSE for writing to the log file.

And here is the class.paypal.php

<?php
class Paypal 
{

    var $isTest = FALSE;

//  ************************************************************    
// Adds variables to the form

    function addVar($var,$value)
    {
        $this->vars[$var][0] = $var;
        $this->vars[$var][1] = $value;
    }

//  ************************************************************
// Sets the button image. 1 = Donate, 2 = Buy now, 3 = custom

    function setButtonType($type, $button_image = "")
    {
        switch($type)
        {
            // Donate   
            case 1:
                $this->button = '<input type="image" src="images/donate.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">';
                break;
            // Buy now
            case 2:
                $this->button = '<input type="image" src="images/paynow.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">';
                break;
            // Custom
            case 3:
                $this->button = '<input type="image" src="'.$button_image.'" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">';
        }
        $this->button .= "\n";
    }

//  ************************************************************    
// Prints the form with all the hidden posts, and displays the button

    function showForm()
    {
        $url = $this->getAddress();
        $form  = '<form action="https://'.$url.'/cgi-bin/webscr" method="post" target="_blank" style="display:inline;">'."\n";
        foreach($this->vars as $key => $value)
        {
            $form .= '<input type="hidden" name="'.$value[0].'" value="'.$value[1].'">'."\n";
        }               
        $form .= $this->button;    
        $form .= '</form>';
        echo $form;
    }

//  ************************************************************    
// Setup the log file

    function setLogFile($logFile)
    {
        $this->logFile = $logFile;
    }

//  ************************************************************    
// Writes into the log file, a message

    private function writeLog($msg)
    {
        $outmsg = date('Y-m-d H:i:s')." : ".$msg."<br />\n";

        $file = fopen($this->logFile,'a');
        fwrite($file,$outmsg);
        fclose($file);
    }

//  ************************************************************    
// For the IPN. use to check if payment is valid, and the status

    function checkPayment($_POST)
    {
        $req = 'cmd=_notify-validate';
        foreach($_POST as $key => $value) 
        {
            $value = urlencode(stripslashes($value));
            $req .= "&$key=$value";
        }       
        $url = $this->getAddress();

        // Headers to post back to paypal
        $header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
        $fp = fsockopen('ssl://'.$url, 443, $errno, $errstr, 30);

        // $fp = fsockopen ($url, 80, $errno, $errstr, 30);

        if (!$fp) 
        {
            return FALSE;
        } 
        else 
        {
            fputs($fp, $header . $req);

            // Common mistake by people is ending the loop on the first run
            // around, which only gives the header line. There for we are going
            // to wait until the loop is ended before returning anything.
            $loop = FALSE; # Start by saying the loop is false on a verified return
            while(!feof($fp)) 
            {
                $res = fgets($fp, 1024);
                if(strcmp($res, "VERIFIED") == 0) # If line result length matches VERIFIED
                {               
                    $loop = TRUE; # Define the loop contained VERIFIED
                } 
            }
            if($loop == TRUE)
            {
                return TRUE;
            }
            else 
            {
                if($this->logFile != NULL) # If user defined a log file
                {
                    $err = array();
                    $err[] = '--- Start Transaction ---';
                    foreach($_POST as $var)
                    {
                        $err[] = $var;
                    }
                    $err[] = '--- End Transaction ---';
                    $err[] = '';
                    foreach($err as $logerror)
                    {
                        $this->writeLog($logerror); # Log for error checking
                    }
                }
                return FALSE;
            }
            fclose ($fp);
        }
        return false;
    }

//  ************************************************************    
// For use of sandbox

    function testMode($value)
    {
        $this->isTest = $value;
    }

//  ************************************************************
// Return the address based on if its 

    function getAddress()
    {
        if($this->isTest == TRUE)
        {
            return 'www.sandbox.paypal.com';
        } 
        else 
        {
            return 'www.paypal.com';
        }
    }
}
?>

I have even tried forcing the logfile to automatically return as TRUE to the ipn.php. I have placed $Paypal->setLogFile('core/logs/ipn_log.txt'); within the check that is supposed to write to the database just to make sure it returns as TRUE, this is even after returning the class.paypal.php to its original state. It does return as true, so why won't it write to the database? I've checked the fields, the database access is the same code I've been using.

PHP Version: 5.3.28
MySQL Version: 5.1.73-community

Recommended Answers

All 13 Replies

I'll have to look closely at the code later. Exactly what IS the problem? Why should logging care whether test mode is true or false? You definitely want to log errors/warnings/etc no matter whether or not you are in test mode.

At least the author wrote clean code, and commented it appropriately.

At first, I thought $check was not returning TRUE. So I used the logfile of the transaction to test if it was returning true. It was the quickest way I had of testing that return.

The problem is, it is not writing the transaction to my database. My mw_donate_transactions has empty fields. I did check the field names to confirm they are correct without misspellings. And I did write an INSERT to see if they could be written to, which showed they can.

Sadly, my coding classes only brushed over php and sql. All I've learned of the two has come from breaking down the site kit this author wrote. Most of it is amazingly straight forward.

Ok. It may be some other thing. Is the code talking to the database, but you are just getting empty columns there, or is it the php code that has empty fields and can't send them to the database?

FWIW, and not particularly relevant here, is that this is an old version of php 5. I'm not recommending an upgrade as yet, but that is something to consider, especially for performance and security concerns.

All other aspects of the site talk to the database. It's just this one part that doesn't.

Ok. That may help narrow the possibilities somewhat. It is getting late here now. I will look at this again tomorrow.

Any progress? I've asked a few others who know php and mysql, but they're fellow students. And none of the teachers I know are well versed in php enough to have me look at anything specific.

Not sure if this will help, but here is a ipn I use for diffrent projects. I modified the code to insert your database query. if you run this it should insert your records.. or give you a log file that will show you what is going on. This is the simplest ipn I have been using and works good.

    define('DEBUG', 1); // 0 = no debugging
    define('USER_SANBOX', 1); // 0 = sandbox mode 1 = live mode
    define('LOG_FILE', 1); // 0 no log file

    $post_data = file_get_contents('php://input');
    $post_array = explode('&', $post_data);
    $post = array();

    foreach($post_array as $keyval){
        $keyval = explode('=', $keyval);
        if(count($keyval) == 2){
            $post[$keyval[0]] = urldecode($keyval[1]);
        }
    }

    $req = 'cmd=_notify-validate';

    if(function_exists('get_magic_quotes_gpc')){
        $get_magic_quotes_exists = true;
    }

    foreach($post 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";
    }

    if(USE_SANDBOX == true) {
            $paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
    } else {
            $paypal_url = "https://www.paypal.com/cgi-bin/webscr";
    }

    $ch = curl_init($paypal_url);
    if ($ch == FALSE) {
            return FALSE;
    }

    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);

    if(DEBUG == true) {
            curl_setopt($ch, CURLOPT_HEADER, 1);
            curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
    }

    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));

    $res = curl_exec($ch);
    if (curl_errno($ch) != 0) // cURL error
    {
        if(DEBUG == true) {        
        error_log(date('[Y-m-d H:i e] '). "Can't connect to PayPal to validate IPN message: " . curl_error($ch) . PHP_EOL, 3, LOG_FILE);
    }
        curl_close($ch);
        exit;

    } else {
        // Log the entire HTTP response if debug is switched on.
        if(DEBUG == true) {
            error_log(date('[Y-m-d H:i e] '). "HTTP request of validation request:". curl_getinfo($ch, CURLINFO_HEADER_OUT) ." for IPN payload: $req" . PHP_EOL, 3, LOG_FILE);
            error_log(date('[Y-m-d H:i e] '). "HTTP response of validation request: $res" . PHP_EOL, 3, LOG_FILE);

            // Split response headers and payload
            list($headers, $res) = explode("\r\n\r\n", $res, 2);
        }
        curl_close($ch);
    }


    if (strcmp ($res, "VERIFIED") == 0) {


         $DB->query("INSERT INTO `mw_donate_transactions`(
                    `trans_id`,
                    `account`,
                    `item_number`,
                    `buyer_email`,
                    `payment_type`,
                    `payment_status`,
                    `pending_reason`,
                    `reason_code`,
                    `amount`,
                    `item_given`)
                    VALUES(
                    '".$_POST['txn_id']."',
                    '".$accountid."',
                    '".$_POST['item_number']."',
                    '".$_POST['payer_email']."',
                    '".$_POST['payment_type']."',
                    '".$_POST['payment_status']."',
                    '".$pending_reason."',
                    '".$reason_code."',
                    '".$_POST['mc_gross']."',
                    '0'
                    )
                    ");


        if(DEBUG == true) 
        {
            error_log(date('[Y-m-d H:i e] '). "Verified IPN: $req ". PHP_EOL, 3, LOG_FILE);
        }

    } else if (strcmp ($res, "INVALID") == 0) {
        // log for manual investigation
        // Add business logic here which deals with invalid IPN messages
        if(DEBUG == true) {
            error_log(date('[Y-m-d H:i e] '). "Invalid IPN: $req" . PHP_EOL, 3, LOG_FILE);
        }
    }

Thanks, I'll give it a try and see what happens.

Sorry I haven't had time to review your code yet - too much stuff going on at work. Hopefully Gabriel's code will help you sort this out. I still intend to get to it. Anyway, my boss has me working full time on major projects with both our analytics/operations and QA teams. I need a clone! :-)

After running the code from gabrielcastillo, I get the following error.

[2014-02-04 19:38 America/Los_Angeles] Can't connect to PayPal to validate IPN message: SSL certificate problem: unable to get local issuer certificate

I just spent two hours tracking down, and then reinstalling the Apache with openssl, configuring it, and the ipn gives the error again. However, paypal tells me it was validated Click Here

And through it all, it still doesn't write anything to the db. And yes, I added the code so it could access the db info.

here is a section I missed from the code.

// CONFIG: Please download 'cacert.pem' from "[http://curl.haxx.se/docs/caextract.html](http://curl.haxx.se/docs/caextract.html)" and set the directory path
// of the certificate as shown below. Ensure the file is readable by the webserver.
// This is mandatory for some environments.

/*$cert = __DIR__ . "/cacert.pem";
curl_setopt($ch, CURLOPT_CAINFO, $cert);*/

This goes between:
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));

insert above code here

$res = curl_exec($ch);

If this doesn't work, set curl option for ssl verify to false or 0

Going to give it a shot. I'll let you know how it turns out.

I had to turn verify to false. The cert couldn't be found, though I put it's location in environmental variables.

Here is the log file:

[2014-02-08 14:40 America/Los_Angeles] HTTP request of validation request:POST /cgi-bin/webscr HTTP/1.1
Host: www.sandbox.paypal.com
Accept: */*
Connection: Close
Content-Length: 851
Content-Type: application/x-www-form-urlencoded

 for IPN payload: cmd=_notify-validate&residence_country=US&invoice=abc1234&address_city=San+Jose&first_name=John&payer_id=TESTBUYERID01&shipping=3.04&mc_fee=0.44&txn_id=850878048&receiver_email=seller%40paypalsandbox.com&quantity=1&custom=xyz123&payment_date=14%3A37%3A55+8+Feb+2014+PST&address_country_code=US&address_zip=95131&tax=2.02&item_name=something&address_name=John+Smith&last_name=Smith&receiver_id=seller%40paypalsandbox.com&item_number=AK-1234&verify_sign=AFcWxV21C7fd0v3bYYYRCpSSRl31ABv2QQvWpGUwA4-oiIazTqd1mcqo&address_country=United+States&payment_status=Completed&address_status=confirmed&business=seller%40paypalsandbox.com&payer_email=buyer%40paypalsandbox.com&notify_version=2.1&txn_type=web_accept&test_ipn=1&payer_status=verified&mc_currency=USD&mc_gross=12.34&address_state=CA&mc_gross1=12.34&payment_type=echeck&address_street=123%2C+any+street
[2014-02-08 14:40 America/Los_Angeles] HTTP response of validation request: HTTP/1.1 200 OK
Date: Sat, 08 Feb 2014 22:40:27 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Set-Cookie: c9MWDuvPtT9GIMyPc3jwol1VSlO=YLMCwls3k6b6BG4Mlj0pT6ogIDPKYELCYvNpViCi_koR89tTwaxlOheBEutGAp2XTr96J2cWLrYKks2r3lp-5BRz1EUKQtmkOAmOEFq7uSgLuU3Hgvxnen4g03DQ6qMyqnSyo9Mk_nG-5nw1vZ9EP0BG-VVMTBtIcmRSGkiVQ63BCZ-jZLe_Zy9isAoBw2Dwb8NJI3q6EpGUGJdwXvJ3rWwgrgmjgIjz5h-6onywZg8DIfzCAQZswRLeZCZJxz_WCCbmNq01_lOfMq2rcKIHK75eZctkYWHf6NFtRZC3URyMgxM0RY9rLCBqoXR-BV9iTis5KUo3UDQuQRBzdSeillfPwTQMFjQFha7tFAZPHgx5uP8DVUPmbKOBEfaCKjIHtRKyRFUbyzsYnKocX6BaO5IIXScKVycg1XefYMyll9xZgyzKD1EoWYyCzyO; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: cookie_check=yes; expires=Tue, 06-Feb-2024 22:40:27 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navlns=0.0; expires=Mon, 08-Feb-2016 22:40:27 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: Apache=10.72.109.11.1391899227191470; path=/; expires=Mon, 01-Feb-44 22:40:27 GMT
Connection: close
Set-Cookie: X-PP-SILOVER=name%3DSANDBOX3.WEB.1%26silo_version%3D880%26app%3Dslingshot%26TIME%3D1538455122; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: X-PP-SILOVER=; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Set-Cookie: Apache=10.72.128.11.1391899227179527; path=/; expires=Mon, 01-Feb-44 22:40:27 GMT
Vary: Accept-Encoding
Strict-Transport-Security: max-age=14400
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

VERIFIED

It seems to not handle the check at line 81 in your code. And that seems to be where all the IPN's I've tried have been fouled up. However, if I put:

if(DEBUG == true)
{
    error_log(date('[Y-m-d H:i e] '). "Verified IPN: $req ". PHP_EOL, 3, LOG_FILE);
}

before the database, but right after line 81, it shows the message from that Verified IPN, but it still won't write to the DB.

And I have this at the beginning of your code so that the db can be accessed:

include('core/class.config.php');
include('core/class.database.php');
include('core/lib/class.paypal.php');
// =====================================//

// Initiate the classes, and establish a DB conection
$Config = new Config;
$Paypal = new Paypal;
$DB = new Database(
    $Config->getDbInfo('db_host'), 
    $Config->getDbInfo('db_port'), 
    $Config->getDbInfo('db_username'), 
    $Config->getDbInfo('db_password'), 
    $Config->getDbInfo('db_name')
);
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.