0
votes

I'm creating an IPN for a custom digital ecommerce but i have a problem: everything works file,i create a "pending payment" in my database with an ID that i call PID (payment id),the user go to the paypal page and when the payment is completed paypal contact me on the IPN listener that checks if the payment is completed and enable all the media that the user bought.

I successfully created an IPN using the micah carrick php class ( http://www.micahcarrick.com/php-paypal-ipn-integration-class.html ) and everything is working exept i ALWAYS get a pendign payment status and i can't get a confirmed one.

I'm currently testing it in the paypal sandbox,i created 2 buyers and one seller and i have enabled the "payment review" for everybody.

I tryed also different approach but i always get the same result.

Code: file_put_contents('ipn.log',"\n>IPN\n",FILE_APPEND);

//Check the Payment ID,i pass it to the IPN by GET
if(!isset($_GET['pid'])|| !is_numeric($_GET['pid'])){
     file_put_contents('ipn.log',"\n!!!IPN:INVALID PID(".$_GET['pid'].")!!!\n",FILE_APPEND);
     exit('PID INVALIDO!');

}


//Logging errors
ini_set('log_errors', true);
ini_set('error_log', dirname(__FILE__).'/ipn_errors.log');

// instantiate the IpnListener class  
require('ipnlistener.php');
$listener = new IpnListener();      


//Use the sandbox instead of going "live"
$listener->use_sandbox = true;


//validate the request

try {
   $listener->requirePostMethod();
   $verified = $listener->processIpn();
} 
catch (Exception $e) {
   error_log($e->getMessage());
   exit(0);
}


//Just for debug
file_put_contents('ipn.log',"\n###IPN:verifying...###\n",FILE_APPEND); 


if($verified){//the payment is verified                                                                           
        file_put_contents('ipn.log',"\n###IPN:transaction verified(confirmed=".$_POST['payment_status'].")###\n".$listener->getTextReport(),FILE_APPEND); 
        /*
        Once you have a verified IPN you need to do a few more checks on the POST
        fields--typically against data you stored in your database during when the
        end user made a purchase (such as in the "success" page on a web payments
        standard button). The fields PayPal recommends checking are:
        1. Check the $_POST['payment_status'] is "Completed"
        2. Check that $_POST['txn_id'] has not been previously processed
        3. Check that $_POST['receiver_email'] is your Primary PayPal email
        4. Check that $_POST['payment_amount'] and $_POST['payment_currency']
        are correct
        Since implementations on this varies, I will leave these checks out of this
        example and just send an email using the getTextReport() method to get all
        of the details about the IPN.
        */  
        if($_POST['payment_status']=="Completed"){
                //--check if the price is right and enable the user media--
                confirm_payment($_GET['pid'],$_POST['payment_amount']);
                file_put_contents('ipn.log',"\n###IPN:Transaction completed###\n".$listener->getTextReport(),FILE_APPEND);    
        }                                                                        

} 

else {
/*
An Invalid IPN *may* be caused by a fraudulent transaction attempt. It's
a good idea to have a developer or sys admin manually investigate any
invalid IPN.
*/

   file_put_contents('ipn.log',"\n###IPN:ERROR###\n".$listener->getTextReport(),FILE_APPEND);    

}

The debug log i created is always like this

> IPN <--it states that the ipn was correctly called
##IPN:verifying...### <--the IPN is verifying the transaction
##IPN:transaction verified(confirmed=Pending)<--the transaction is verified but it's NOT confirmed because it's pending,i can't enable the download!

2

2 Answers

3
votes

Disable Payment Review. Payment Review will always place them in a Pending state.
That's actually the whole point of it; to be able to use negative testing and payment review in order to test 'negative' scenario's to verify your error handling.

-1
votes

I am not familiar with the class you are using, but this is what i have been using for PP IPN in all my work and it works like a charm, maybe one day i'll make my own Object Oriented way but for now this seems to be doing the trick and i hope it helps you. (Just to get you on the right track, i am using the same file for incoming and outcoming messages to/from PP)

$sandbox="sandbox.";
$paypal_email="[email protected]";

$item_id = "1XN12PJ";
$cost = "22.30";
$item_name = 'My Item';
$return_url = "http://www.example.com/return";
$cancel_url = "http://www.example.com/cancel";
$notify_url = "http://www.example.com/notify";

function check_txnid($tnxid){
    global $link;
    $sql = mysql_query("SELECT * FROM `payments_pending` WHERE `txnid` = '$tnxid'", $link);     
    return mysql_num_rows($sql)==0;
}

function check_price($price, $id){
    $sql = mysql_query("SELECT `cost` FROM `orders` WHERE `id` = '$id'");
    if (mysql_numrows($sql) != 0) {
        $row = mysql_fetch_array($sql);
        $num = (float) $row['cost'];
        if($num - $price == 0){
            return true;
        }
    }
    return false;
}

if (!isset($_POST["txn_id"]) && !isset($_POST["txn_type"])){    // Request TO Paypal
    // Firstly Append paypal account to querystring
    $querystring .= "?business=".urlencode($paypal_email)."&";  

    // Append amount& currency (£) to quersytring so it cannot be edited in html
    $querystring .= "lc=CA&";
    $querystring .= "currency_code=CAD&";
    $querystring .= "item_number=".$item_id."&";

    //The item name and amount can be brought in dynamically by querying the $_POST['item_number'] variable.
    $querystring .= "item_name=".urlencode($item_name)."&";
    $querystring .= "amount=".$cost."&";

    //loop for posted values and append to querystring
    foreach($_POST as $key => $value){
        $value = urlencode(stripslashes($value));
        $querystring .= "$key=$value&";
    }

    // Append paypal configs
    $querystring .= "return=".urlencode(stripslashes($return_url))."&";
    $querystring .= "cancel_return=".urlencode(stripslashes($cancel_url))."&";
    $querystring .= "notify_url=".urlencode($notify_url);

    // Append querystring with custom field
    //$querystring .= "&custom=".USERID;

    // Redirect to paypal IPN
    header('location:https://www.'.$sandbox.'paypal.com/cgi-bin/webscr'.$querystring);
    exit();
}else{      // Response FROM Paypal
    // read the post from PayPal system and add 'cmd'
    $req = 'cmd=_notify-validate';
    foreach ($_POST as $key => $value) {
        $req .= "&$key=$value";
    }

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

    // post back to PayPal system to validate
    $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://www.'.$sandbox.'paypal.com', 443, $errno, $errstr, 30);
    if(!$fp){
        // HTTP ERROR : Do something to notify you
    }
    else {  
        fputs($fp, $header.$req);

        $res = "";
        while (!feof($fp)){
            $res .= fgets($fp, 1024);
        }

        if(strpos($res, "VERIFIED")!==false){
            // Validate payment (Check unique txnid & correct price)
            $valid_txnid = check_txnid($data['txn_id']);
            // $valid_price = check_price($data['payment_amount'], $data['item_number']);

            $valid_price = check_price($data['payment_amount'], $_POST['item_number']);
            // PAYMENT VALIDATED & VERIFIED!
            if($valid_txnid && $valid_price){
                $orderid = updatePayments($data);
                if($orderid){
                    // Payment has been made & successfully inserted into the Database

                }else{
                    // Error inserting into DB
                }
            }
            else{                   
                // Payment made but data has been changed  : Do something to notify you
            }                       
        }
        else{
            if(strpos($res, "VERIFIED")!==false){
                // PAYMENT INVALID & INVESTIGATE MANUALY!  : Do something to notify you
            }
        }
        fclose($fp);
    }   
}