Science And Technology

During my upgrade I had to upgrade my file downloader as well. I had to go through the atrocious task of testing paypals ipn functions and make sure they're working correctly. This turned out to be a huge headache. Apparently paypal sandbox doesn't send back a VERIFIED string anymore even if you're IPN listener is correctly configured and responding correctly. I spent hours last night stepping through the IPN procedure trying to figure out why the IPN was always failing.

After several hours of googling and coming across several official paypal instruction pages that contradict one another. Here's one that even contradicts itself. You'll notice that in the instructions of the page they say to post back the ipn data to www.paypal.com or www.sandbox.paypal.com, depending on whether you are going live or testing your listener in the Sandbox. But then lower on the page they provide a sample script and in that script they're posting to ssl://www.paypal.com WTF!!!

  • https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNImplementation
At least now I know that ssl://www.paypal.com is incorrect and www.paypal.com or www.sandbox.paypal.com is the correct url to post paypals ipn data back to , not like it matters because the sandbox mode never returns a "VERIFIED" string anyway.

Something else that may trip you up in sandbox ipn testing is, if you choose web accept in the ipn simulator it defaults to payment_status=Completed. Now here is a quote from paypal's instructions.

Lets look at web_accept it's the most common used and easiest to work with... web_accept performs 5 data checks before it processes anything. These checks are the minimum recommended by PayPal that should be performed in your as follows:

  1. Is status = Completed If its a new transaction then the status should not be Complete, if 'payment_status' => 'Completed', then its not new and we've already processed this, so the script will return false, trigger a fail message and NOT process.
  2. We have not already processed the subcribe or unsubscribe for this subscr_id When 'payment_status' => '', then we'll do another check to really make sure we haven't processed this transaction before. If we have then the value returned by 'txn_id' will be found in the database table #_ajp_orders_subscription_payments and the script will return false, trigger a fail message and NOT process.
  3. The reciever e-mail account is ours. This checks that the Admin Email address recorded in PaymentsPlus > General Settings > Mosets Tree > PayPal Admin Email matches the email returned by 'receiver_email' if it doesn't then the script will return a fail message for this item but still process.
  4. That payment amount is correct A clever script kiddie can manipulate the values in a payment button and try to get the item for less that its real value, so this will check the value being returned by 'mc_gross' and compare it with the value stored in the database, if it doesn't match then the script will return a fail message for this item showing both the stored and returned value but still process. If there's a discrepancy here then you need to discuss it with your user and take appropriate action, one of which could be reporting the violation to PayPal and suspending the listing.
  5. That the payment currency is correct Currency, is a bit fickle to get right, PayPal returns the currency from its own records in 'mc_currency', so this means that you must have this configured in your PayPal account at PayPal and then ensure that the currency matches your PayPal Account setting to that found in PaymentsPlus > Payment Gateway Settings > Currency code (under the PayPal Settings Configuration heading) if it dosen't match then the script will return a fail message for this item but still process.
So that basically says if you don't change the web accept payment_status variable from completed to something else, the ipn will not send back a "VERIFIED" string, not that it matters anyway because paypal's sandbox doesn't send a VERIFIED string anyway!!!

So what DOES paypal sandbox send back? Well the answer is literally nothing. Here, I'll show you.

HTTP/1.0 302 Found
Location: https://www.sandbox.paypal.com
Server: BigIP
Connection: close
Content-Length: 0
That's it. That's all they sand back in the sandbox. Screw BigIP. Should have named it BigTimeWaster.

So in closing here is a paypal ipn script that is adapted to the fact that the paypal sandbox ipn sucks donkey balls. In live mode of course paypal DOES send back the VERIFIED string that we want.

 
 
function validate_ipn() {
//live mode
$url = "www.paypal.com";
 
//if test mode
$url = "www.sandbox.paypal.com";
$path = "cgi-bin/webscr";
 
 
// generate the post string from the _POST vars aswell as load the
// _POST vars into an arry so we can play with them from the calling
// script.
$post_string = '';    
foreach ($_POST as $field=>$value) { 
       $this->ipn_data["$field"] = $value;
       $post_string .= $field.'='.urlencode(stripslashes($value)).'&'; 
}
$post_string.="cmd=_notify-validate"; // append ipn command
 
// open the connection to paypal
$fp = fsockopen($url,"80",$err_num,$err_str,30); 
if(!$fp) {
 
       // could not open the connection.  If loggin is on, the error message
       // will be in the log.
       $this->last_error = "fsockopen error no. $errnum: $errstr";
       $this->log_ipn_results(false);       
       return false;
 
} else { 
 
       // Post the data back to paypal
       fputs($fp, "POST " . $path . " HTTP/1.1\r\n"); 
       fputs($fp, "Host: " . $url . "\r\n"); 
       fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n"); 
       fputs($fp, "Content-length: ".strlen($post_string)."\r\n"); 
       fputs($fp, "Connection: close\r\n\r\n"); 
       fputs($fp, $post_string . "\r\n\r\n"); 
 
       // loop through the response from the server and append to variable
       while(!feof($fp)) { 
              $this->ipn_response .= fgets($fp, 1024); 
       } 
 
       fclose($fp); // close connection
 
}
 
 
if (eregi("VERIFIED",$this->ipn_response) || $url == 'www.sandbox.paypal.com') {
//if(true){  
       // Valid IPN transaction.
       $this->logThis('SUCCESS!!');
       //$this->log_ipn_results(true);
       return true;       
 
} else {  
       // Invalid IPN transaction.  Check the log for details.
       $this->logThis('IPN Validation Failed.');
       //$this->log_ipn_results(false);   
       return false;
 
}
}
 
function logThis($string)
{
    ob_start();
    print_r($string);
    $message = ob_get_clean();
 
    //put the file in the root for testing purposes. 
    $f = fopen(JPATH_ROOT . DS . 'test.txt','a');
    fwrite($f,$message);
    fclose($f);    
}