PayPal IPN Delivery status set to “Retrying” with HTTP response code 404

I am using the WordPress Plugin Donate Plus, which hasn’t been updated in a while and which isn’t working properly for most users. So I have made some changes to the code in an attempt to get it working again. I have everything working fine except that in the IPN History of my PayPal account all transactions have the Delivery status “Retrying” with an HTTP response code of 404.

There is an IPN debugging feature in the script which is returning “Verified IPN Transaction [Completed]”, suggesting that everything has gone OK. However, I am getting multiple debugging messages, multiple thank you messages sent out, and multiple entries into my MySQL database, all of which I presume are related to the “Retrying” issue.

Read More

I have set the notification URL manually in PayPal, as I’ve heard that this can resolve some issues. However I am still experiencing the same issue.

I understand that the script needs to send the complete unaltered IPN message back to PayPal; i.e the message must contain the same fields in the same order and be encoded in the same way as the original message. I am not sure how to check what is returned by $postipn however, in order to check whether this is the issue.

I am including the full paypal.php script below in case anyone can spot what the issue is:

<?php
    /*
    //************************************************************
    //************************************************************
    //**  Bugs fixed by...                                      **
    //**                                                        **
    //**  Copyright Encentra 2011                               **
    //**  www.encentra.se                                       **
    //**  consultant: Johan Rufus Lagerström                    **
    //************************************************************
    //************************************************************
    */

#########################################################
#                                                       #
#  File            : PayPal.php                         #
#  Version         : 1.9                                #
#  Last Modified   : 12/15/2005                         #
#  Copyright       : This program is free software; you #
# can redistribute it and/or #modify it under the terms #
# of the GNU General Public License as published by the #
# Free Software Foundation                              #
# See the #GNU General Public License for more details. #
#               DO NOT REMOVE LINK                      #
# Visit: http://www.belahost.com for updates/scripts    #
#########################################################
#    THIS SCRIPT IS FREEWARE AND IS NOT FOR RE-SALE     #
#########################################################

require("../../../wp-blog-header.php");
global $wpdb;
$dplus = get_option('DonatePlus');
$email_IPN_results = $dplus['IPN_email'];
$tmp_nl = "rn";

if(class_exists('DonatePlus'))$donateplus = new DonatePlus();

#1 = Live on PayPal Network 
#2 = Testing with www.BelaHost.com/pp
#3 = Testing with the PayPal Sandbox
$verifymode     = $dplus['testing_mode']; # be sure to change value for testing/live!
# Send notifications to here
$send_mail_to   = $dplus['paypal_email'];
# subject of messages
$sysname        = "Donate Plus - Paypal IPN Transaction";
# Your primary PayPal e-mail address
//$paypal_email     = $dplus['paypal_email'];
# Your sendmail path
//$mailpath         = "/usr/sbin/sendmail -t";
#the name you wish to see the messages from
//$from_name        = $dplus['ty_name'];
#the emails will be coming from
//$from_email   = $dplus['ty_email'];


# Convert Super globals For backward compatibility
if(phpversion() <= "4.0.6") {$_POST=($HTTP_POST_VARS);}

# Check for IPN post if none then return 404 error.
if (!$_POST['txn_type']){
    if( $email_IPN_results ) send_mail($send_mail_to,$sysname." [ERROR - 404]","IPN Fail: 404 error!","",__LINE__);
    header("Status: 404 Not Found");
    die();
}else{
    header("Status: 200 OK");
}

# Now we Read the Posted IPN
$postvars = array();

//print_r($_POST);
foreach ($_POST as $ipnvars => $ipnvalue){
    $postvars[] = $ipnvars; $postvals[] = $ipnvalue;
}
# Now we ADD "cmd=_notify-validate" for Post back Validation
$postipn    = 'cmd=_notify-validate'; 
$orgipn     = '<b>Posted IPN variables in order received:</b><br><br>';
# Prepare for validation
for($x=0; $x < count($postvars); $x++){ 
    $y          = $x+1; 
    $postkey    = $postvars[$x]; 
    $postval    = $postvals[$x]; 
    $postipn    .= "&".$postkey."=".urlencode($postval);  
    $orgipn     .= "<b>#".$y."</b> Key: ".$postkey." <b>=</b> ".$postval."<br>";
}


if($verifymode == 1){       //1 = Live on PayPal Network
    ## Verify Mode 1: This will post the IPN variables to the Paypal Network for Validation
    $port = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
    //$port = fsockopen ("paypal.com", 80, $errno, $errstr, 30);
    $header = "POST /cgi-bin/webscr HTTP/1.0rn".
    "Host: www.paypal.comrn".
    "Content-Type: application/x-www-form-urlencodedrn".
    "Content-Length: ".strlen($postipn)."rnrn";
}elseif ($verifymode == 2){ //2 = Testing with www.BelaHost.com/pp
    ## Verify Mode 2: This will post the IPN variables to Belahost Test Script for validation
    ## Located at www.belahost.com/pp/index.php
    $port = fsockopen ("www.belahost.com", 80, $errno, $errstr, 30);
    $header = "POST /pp/ HTTP/1.0rn".
    "Host: www.belahost.comrn".
    "Content-Type: application/x-www-form-urlencodedrn".
    "Content-Length: ".strlen($postipn)."rnrn";        
}elseif ($verifymode == 3){ //3 = Testing with the PayPal Sandbox
    $port = fsockopen ("ssl://www.sandbox.paypal.com", 443, $errno, $errstr, 30);
    $header = "POST /cgi-bin/webscr HTTP/1.0rn".
    "Host: www.sandbox.paypal.comrn".
    "Content-Type: application/x-www-form-urlencodedrn".
    "Content-Length: ".strlen($postipn)."rnrn";
}else{ 
    $error=1; 
    //echo "CheckMode: ".$verifymode." is invalid!";
    if( $email_IPN_results ) send_mail($send_mail_to,$sysname." [ERROR - Misc]","Fail: CheckMode: ".$verifymode." is invalid!","",__LINE__);
    die(); 
}


# Error at this point: If at this point you need to check your Firewall or your Port restrictions?
if (!$port && !$error){
    //echo "Problem: Error Number: ".$errno." Error String: ".$errstr;
    #Here is a small email notification so you know if your system ever fails           
    if( $email_IPN_results ) send_mail($send_mail_to,$sysname." [ERROR - Misc]","Your Paypal System failed due to $errno and string $errstr","",__LINE__);          
    die();
}else{
    # If No Errors to this point then we proceed with the processing.
    # Open port to paypal or test site and post Varibles.
    fputs ($port, $header.$postipn);
    while (!feof($port)){
        $reply = fgets ($port, 1024);
        $reply = trim ($reply); 
    }

    # Prepare a Debug Report
    $ipnreport = $orgipn."<br><b>"."IPN Reply: ".$reply."</b>";

    # Buyer Information
    $address_city           = $_POST['address_city'];
    $address_country        = $_POST['address_country'];
    $address_country_code   = $_POST['address_country_code'];
    $address_name           = $_POST ['address_name'];
    $address_state          = $_POST['address_state'];
    $address_status         = $_POST['address_status'];
    $address_street         = $_POST['address_street'];
    $address_zip            = $_POST['address_zip'];
    $first_name             = $_POST['first_name'];
    $last_name              = $_POST['last_name'];
    $payer_business_name    = $_POST['payer_business_name'];
    $payer_email            = $_POST['payer_email'];
    $payer_id               = $_POST['payer_id'];
    $payer_status           = $_POST['payer_status'];
    $residence_country      = $_POST['residence_country'];
    # Below Instant BASIC Payment Notifiction Variables
    $business               = $_POST['business'];
    $item_name              = $_POST['item_name'];
    $item_number            = $_POST['item_number'];
    $quantity               = $_POST['quantity'];
    $receiver_email         = $_POST['receiver_email'];
    $receiver_id            = $_POST['receiver_id'];
    #Advanced and Customer information
    $custom                 = $_POST['custom'];
    $invoice                = $_POST['invoice'];
    $memo                   = $_POST['memo'];
    $option_name1           = $_POST['option_name1'];       //name
    $option_name2           = $_POST['option_name2'];
    $option_selection1      = $_POST['option_selection1'];  //email
    $option_selection2      = $_POST['option_selection2'];  //comment
    $tax                    = $_POST['tax'];
    #Website Payment Pro and Other IPN Variables
    $auth_id                = $_POST['auth_id'];
    $auth_exp               = $_POST['auth_exp'];
    $auth_amount            = $_POST['auth_amount'];
    $auth_status            = $_POST['auth_status'];
    # Shopping Cart Information
    $mc_gross               = $_POST['mc_gross'];
    $mc_handling            = $_POST['mc_handling'];
    $mc_shipping            = $_POST['mc_shipping'];
    $num_cart_items         = $_POST['num_cart_items'];
    # Other Transaction Information
    $parent_txn_id          = $_POST['parent_txn_id'];
    $payment_date           = $_POST['payment_date'];
    $payment_status         = $_POST['payment_status'];
    $payment_type           = $_POST['payment_type'];
    $pending_reason         = $_POST['pending_reason'];
    $reason_code            = $_POST['reason_code'];
    $remaining_settle       = $_POST['remaining_settle'];
    $transaction_entity     = $_POST['transaction_entity'];
    $txn_id                 = $_POST['txn_id'];
    $txn_type               = $_POST['txn_type'];
    # Currency and Exchange Information
    $exchange_rate          = $_POST['exchange_rate'];
    $mc_currency            = $_POST['mc_currency'];
    $mc_fee                 = $_POST['mc_fee'];
    $payment_fee            = $_POST['payment_fee'];
    $payment_gross          = $_POST['payment_gross'];
    $settle_amount          = $_POST['settle_amount'];
    $settle_currency        = $_POST['settle_currency'];
    # Auction Information 
    $for_auction            = $_POST['for_auction'];
    $auction_buyer_id       = $_POST['auction_buyer_id'];
    $auction_closing_date   = $_POST['auction_closing_date'];
    $auction_multi_item     = $_POST['auction_multi_item'];
    # Below are Subscription - Instant Payment Notifiction Variables
    $subscr_date            = $_POST['subscr_date'];
    $subscr_effective       = $_POST['subscr_effective'];
    $period1                = $_POST['period1'];
    $period2                = $_POST['period2'];
    $period3                = $_POST['period3'];
    $amount1                = $_POST['amount1'];
    $amount2                = $_POST['amount2'];
    $amount3                = $_POST['amount3'];
    $mc_amount1             = $_POST['mc_amount1'];
    $mc_amount2             = $_POST['mc_amount2'];
    $mc_amount3             = $_POST['mc_amount3'];
    $recurring              = $_POST['recurring'];
    $reattempt              = $_POST['reattempt'];
    $retry_at               = $_POST['retry_at'];
    $recur_times            = $_POST['recur_times'];
    $username               = $_POST['username'];
    $password               = $_POST['password'];
    $subscr_id              = $_POST['subscr_id'];
    # Complaint Variables Used when paypal logs a complaint
    $case_id                = $_POST['case_id'];
    $case_type              = $_POST['case_type'];
    $case_creation_date     = $_POST['case_creation_date'];
    #Last but not least
    $notify_version         = $_POST['notify_version'];
    $verify_sign            = $_POST['verify_sign'];

    #There are certain variables that we will not store for cart since they are dynamic                    
    #such as mc_gross_x as they will be forever changing/increasing your script must check these

    #IPN was Confirmed as both Genuine and VERIFIED
    if(!strcmp($reply, "VERIFIED")){
        /* Now that IPN was VERIFIED below are a few things which you may want to do at this point.
         1. Check that the "payment_status" variable is: "Completed"
         2. If it is Pending you may want to wait or inform your customer?
         3. You should Check your datebase to ensure this "txn_id" or "subscr_id" is not a duplicate. txn_id is not sent with subscriptions!
         4. Check "payment_gross" or "mc_gross" matches match your prices!
         5. You definately want to check the "receiver_email" or "business" is yours. 
         6. We have included an insert to database for table paypal_ipn
         */
        $split = explode(':', $item_number);
        $display = $split[0];
        $uID = $split[1];
        $url = 'http://'.str_replace('http://','',$option_name2);

        if(!$mc_gross)$mc_gross = $payment_gross;
        $table_name = $wpdb->prefix."donations";

        //kontrollera om transaktionen redan finns sparad
        $tmp_txn_id     = $wpdb->escape($txn_id);
        $tmp_count      = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM $table_name WHERE txn_id='$tmp_txn_id';")); 


        //if($payment_status    == 'Completed'){
        if($tmp_count == 0){
            //all pin:s som kommer in tolkar vi som complete
            $tmp_payment_status = "Completed";
            $mc_net = $mc_gross - $mc_fee;
            //USE SECURE INSERT!
            $wpdb->query(
                $wpdb->prepare("INSERT INTO $table_name
                ( name, email, url, comment, display, amount, charge, net, currency, date, user_id, status, txn_id )
                VALUES ( %s, %s, %s, %s, %d, %s, %s, %s, %s, %s, %d, %s, %s )", 
                $option_name1, $option_selection1, $url, strip_tags($option_selection2), $display, $mc_gross, $mc_fee, $mc_net, $mc_currency, date('Y-m-d H:i:s'), $uID, $tmp_payment_status, $txn_id )
            );
            //send payer thank you email about where to download
            global $currency;
            $subject    = stripslashes($dplus['ty_subject']);
            $prefix     = $currency[$mc_currency]['symbol'];
            $amount     = $prefix.$mc_gross.' '.$mc_currency;
            $payer_msg  = nl2br($donateplus->TagReplace(stripslashes($dplus['ty_emailmsg']), $option_name1, $amount));
            //$payer_msg = utf8_encode($payer_msg);
            //echo '<br />'.$payer_msg;
            $headers  = 'MIME-Version: 1.0'."rn";
            //$headers .= 'Content-type: text/html; charset=iso-8859-1'."rn";
            $headers .= 'Content-type: text/html; charset=utf-8'."rn";
            $headers .= 'From: '.$dplus['ty_name'].' <'.$dplus['ty_email'].'>'."rn";
            wp_mail($option_selection1, $subject, $payer_msg, $headers);
            //wp_mail($notify_email, 'New Donation Recieved!', "Donation from $option_name1 for $payment_amount");
            //echo $postquery;
            if( $email_IPN_results ) send_mail($send_mail_to,$sysname." [PIN - Completed]","n Verified IPN Transaction [Completed] n n$ipnreportn","",__LINE__);
        }else{
            //not "Completed"
            //tar bort nedan, annars trilalr det in 10 mail per donation
            //send_mail($send_mail_to,$sysname." [PIN - $payment_status]","n Verified IPN Transaction [$payment_status] n n$ipnreportn","",__LINE__);
        }


    }elseif(!strcmp($reply, "INVALID")){    # IPN was Not Validated as Genuine and is INVALID
        /* Now that IPN was INVALID below are a few things which you may want to do at this point.
         1. Check your code for any post back Validation problems!
         2. Investigate the Fact that this Could be an attack on your script IPN!
         3. If updating your DB, Ensure this "txn_id" is Not a Duplicate!
        */

        # Remove Echo line below when live
        //echo $ipnreport;
        if( $email_IPN_results ) send_mail($send_mail_to,$sysname." [ERROR - Invalid]","Invalid IPN Transaction".$tmp_nl.$tmp_nl.$ipnreport,"",__LINE__);

    }else{  #ERROR
        # If your script reaches this point there is a problem. Communication from your script to test/paypal pages could be 1 reason.
        echo $ipnreport;
        if( $email_IPN_results ) send_mail($send_mail_to,$sysname." [ERROR - Misc]","FATAL ERROR No Reply at all".$tmp_nl.$tmp_nl.$ipnreport,"",__LINE__);
    }


    #Terminate the Socket connection and Exit
    fclose ($port); 
    die();
}







/* =================================
         Below are functions
   ================================= */

# Email function
function send_mail($to, $subj, $body, $headers="",$tmp_line=0){
    //global
    global $tmp_nl; 

    //var_dump till en sträng
    $posts = var_export($_POST, true);

    //body
    $tmp_body   = "===================================".$tmp_nl.
                $subj." [line: $tmp_line]".$tmp_nl.
                "===================================".$tmp_nl.
                $body.$tmp_nl.
                $tmp_nl.
                "===================================".$tmp_nl.
                $posts;

    //skickar mail
    wp_mail($to, $subj, $tmp_body, $headers);

    /*
    global $from_name, $from_email, $mailpath;

# E-mail Configuration
    $announce_subject = "$subj";
    $announce_from_email = "$from_email";
    $announce_from_name = "$from_name";
    $announce_to_email = "$to";
    $MP = "$mailpath"; 
    $spec_envelope = 1;
    # End email config
# Access Sendmail
# Conditionally match envelope address
    if(isset($spec_envelope))
    {
    $MP .= " -f $announce_from_email";
    }
    $fd = popen($MP,"w"); 
    fputs($fd, "To: $announce_to_emailn"); 
    fputs($fd, "From: $announce_from_name <$announce_from_email>n");
    fputs($fd, "Subject: $announce_subjectn"); 
    fputs($fd, "X-Mailer: MyPayPal_Mailern");
    fputs($fd, "Content-Type: text/htmln");
    fputs($fd, $body); # $body will be sent when the function is used
    pclose($fd);
    */
}

?>

.htaccess file

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>

# END WordPress

<Files wp-cron.php>
allow from all
Satisfy Any
</Files>

AddType text/x-component .htc

Related posts

Leave a Reply

2 comments

  1. To me, it seems there are only a few possibilities.

    The code

    if (!$_POST['txn_type']){
        if( $email_IPN_results ) send_mail($send_mail_to,$sysname." [ERROR - 404]","IPN Fail: 404 error!","",__LINE__);
        header("Status: 404 Not Found");
        die();
    }else{
        header("Status: 200 OK");
    }
    

    is obviously a section worth investigating, since it outputs the 404. In my IPN handler, I use empty($_POST) where you’ve used !$_POST['txn_type']. You could also try !isset($_POST['txn_type']). !$_POST['txn_type'] is literally evaluating whether the txn_type equals false, which isn’t as reliable as specifically checking to see if the value just exists in general. I doubt this is the actual problem, but it’s something to look at.

    Some other possible causes of the 404 could be:

    • Somewhere in your paypal button code, it sets an IPN URL that doesn’t exist.
    • The IPN URL you manually entered has a typo.
    • Your htaccess is blocking direct access to the IPN URL. If htaccess does any redirecting (from domain.com to http://www.domain.com for instance) and you are using domain.com as your IPN domain, the redirection will cause all post data to be dropped, and your code will then accurately output the 404 error.

    My money would be on it being an htaccess redirection issue. I’m all too familiar with redirections breaking my form posting.

    EDIT

    You could try making a simple test form that skips paypal altogether just to test the IPN. Something as simple as

    <form method="post" action="IPN_URL">
    <input type='hidden' name='txn_type' value='subscr_payment'>
    <input type='submit' value='Test IPN'></form>
    

    Obviously, replace IPN_URL with the appropriate url. If submitting this doesn’t 404 on you, then there’s probably something wrong with the way that script is setting the IPN URL.

    You could also check the server logs for 404 errors and compare the missing URL in the logs with the URL you’d expect to see.

    EDIT 2

    Just FYI, I did a test with this plugin, same version you’re using and it worked perfectly right away. I didn’t set the IPN URL in paypal. I have “testing mode” set to “Live with Paypal” and filled in the PayPal Email Address field, and left everything else set to their defaults. I made a payment and the IPN and everything else worked fine. I’m using a normal WP setup (not multi site) with version 3.4.1.

    Are you using WP multisite? I know the URL shown in the settings page can be wrong in those situations.

    After reading http://wordpress.org/support/topic/plugin-donate-plus-ipn-not-working, it looks like there are actually a few problems with this plugin. I took a quick look around, and http://wordpress.org/extend/plugins/paypal-donations/ seems to have been updated a bit more recently, and it doesn’t seem to have the same quirks as the other one. You can read about it here. Worst case scenario, you could switch to that one.

  2. Today I had this problem … paypal IPN history shown the notifications as retrying even though the notify_url I specified was valid.

    Problem was that PayPal sends the data to the https:// (secured) version of that url;
    And if you paste the https:// url in the browser you might see that your host is not showing the site over https://, that is what Paypal sees too, usually a 404 error page.