PayPal IPN runs repeatedly - php

I am writing a WordPress plugin that processes payments through PayPal. I have a PayPal IPN script that sends an email notification (in addition to PayPal's email notification) when a payment is successful. Some of the users of my plugin are reporting that they receive multiple copies of this email notification over several days.
I discovered this problem early on when I was developing the plugin, and the solution I found was to immediately send PayPal a 200 response. (Here is some discussion of the issue: https://www.paypal-community.com/t5/About-Settings-Archive/Paypal-repeats-identical-IPN-posts/td-p/465559 ). This seems to be working fine on my test site, but obviously isn't working for all of my users.
When I use the PayPal IPN simulator, it doesn't give me any error messages.
Aside from sending the 200 response right away, is there anything I can do to stop PayPal from repeating the IPN request over and over?
Here is my code:
<?php
// Create a query var so PayPal has somewhere to go
// https://willnorris.com/2009/06/wordpress-plugin-pet-peeve-2-direct-calls-to-plugin-files
function cdashmm_register_query_var($vars) {
$vars[] = 'cdash-member-manager';
return $vars;
}
add_filter('query_vars', 'cdashmm_register_query_var');
// If PayPal has gone to our query var, check that it is correct and process the payment
function cdashmm_parse_paypal_ipn_request($wp) {
// only process requests with "cdash-member-manager=paypal-ipn"
if (array_key_exists('cdash-member-manager', $wp->query_vars) && $wp->query_vars['cdash-member-manager'] == 'paypal-ipn') {
if( !isset( $_POST['txn_id'] ) ) {
// send a 200 message to PayPal IPN so it knows this happened
header('HTTP/1.1 200 OK');
// POST data isn't there, so we aren't going to do anything else
} else {
// we have valid POST, so we're going to do stuff with it
// send a 200 message to PayPal IPN so it knows this happened
header('HTTP/1.1 200 OK');
// process the request.
$req = 'cmd=_notify-validate';
foreach($_POST as $key => $value) :
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
endforeach;
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Host: www.paypal.com\r\n";
$header .= "Connection: close\r\n\r\n";
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
if(!$fp) {
// HTTP ERROR
} else {
fputs ($fp, $header . $req);
while(!feof($fp)) {
$res = fgets ($fp, 1024);
$fh = fopen('result.txt', 'w');
fwrite($fh, $res);
fclose($fh);
if (strcmp (trim($res), "VERIFIED") == 0) {
/* Do a bunch of WordPress stuff - create some posts, send some emails */
}
elseif(strcmp (trim($res), "INVALID") == 0) {
// probably ought to do something here
}
}
fclose ($fp);
}
}
}
}
add_action('parse_request', 'cdashmm_parse_paypal_ipn_request');
?>

You cannot stop Paypal from repeating the request. This is part of the IPN system to make sure that the transactions clear even if the site goes down. Therefore, you should store this transaction ID in the database and check to be sure you have not encountered it in the past. If you have encountered it previously, you can log that you are seeing a repeat. Otherwise, process it.
Simple idea of this using a Transactions class:
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
$value = urldecode($value);
foreach ($pp_vars as $search) {
if ($key == $search)
$$key = $value;
}
if (preg_match("/txn_id/", $key)) {
$txn_id = $value;
}
if (preg_match("/item_number/", $key)) {
$item_number = $value;
}
}
$model = new Transactions();
if ($model->exists('txid', $txn_id)) {
$res = "REPEAT";
}
$model->action[0] = $res;
$model->txid[0] = $txn_id;
$model->description[0] = $req;
$model->price[0] = $payment_gross;
$model->reviewed[0] = 0;
$model->user_id[0] = $user->id;
$model->created_at[0] = date("Y-m-d H:i:s");
$model->updated_at[0] = $model->created_at;
$model->save();

Related

PayPal IPN php always INVALID

I've made an attempt at writing a small PayPal IPN handler however I'm always getting 'INVALID' when using the IPN simulator https://developer.paypal.com/webapps/developer/applications/ipn_simulator . I'm not sure what about the URL I'm sending back isn't right. I have looked at some Gists for this topic but they all seem a little long winded. Their docs on this are here https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNIntro/#protocol_and_arch and I think that I have followed all the steps for the protocol.
Any help is appreciated
<?php
/**
* Validates a PayPal IPN notification
* #param array $req The $_POST variable for the request
* #return String Returns the validity: 'VALID' or 'INVALID'
*/
function verifyPayPalIPN($req) {
// Base URL for the php validation
$baseURL = 'https://www.paypal.com/cgi-bin/webscr?cmd=_notify-validate';
// Loop through the POST parameters to
// create the validaton URL
$postVars = '';
foreach ($req as $key => $value) {
$postVars .= $key . '=' . urlencode($value) . '&';
}
// Strip the last & off of the URL
$postVars = substr($postVars, 0, -1);
// Send the request to PayPal to confirm
// whether the request to the script is
// from PayPal
$requestValidity = system("curl --data '$postVars' $baseURL");
return $requestValidity;
}
file_put_contents('/tmp/result.txt', verifyPayPalIPN($_POST));
?>
cmd=_notify-validate should be part of your postVars string
$baseURL = 'https://www.paypal.com/cgi-bin/webscr';
// Loop through the POST parameters to
// create the validaton URL
$postVars = 'cmd=_notify-validate';
foreach ($req as $key => $value) {
$postVars .= $key . '=' . urlencode($value) . '&';
}
The code that I have used in the past is this:
$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";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ("ssl://www.paypal.com", 443, $errno, $errstr, 30);
if (!$fp) {
// HTTP ERROR
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0) {
} else if (strcmp ($res, "INVALID") == 0) {
}
}
fclose($fp);
}
Note the port no. 443 also
firstly, check if your paying enviroment and validating enviroment is the same (both sandbox or both production), if they are not, say your customer paid their order in an production enviroment, but then your PHP code trying to request a sandbox enviroment end point to verify the IPN request, then it will always end as INVALIDE

Paypal ipn warnings

I have the same issue like PayPal Instant Payment Notification Warning. I have two sites. I have the ipn url set in paypal account to site1. But I'm sending notify_url in paypal form for current site. My verification code on both sites:
public function paymentCheck()
{
$request = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$request .= "&$key=$value";
}
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($request) . "\r\n\r\n";
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
if (!$fp)
return false;
else {
fputs ($fp, $header . $request);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strcmp ($res, "INVALID") == 0) {
return false;
}
}
fclose ($fp);
}
return true;
}
My sites handle ipn. All working fine. But some of paypal messages is failing and they turned it off. Do you have any ideas, what can cause warnings?
It means that for some reason PayPal is getting something other than a 200 OK response back from your script. If it looks like your script is working it could mean that all of the tasks are completing, but then an issue at the very end causes a failure that you don't even see other than the fact that PayPal isn't getting a good response back.
You need to check your web server logs for the times when your IPN script was hit and look at the results. You'll probably find a bunch of them that show 500 or something other than 200. You should also be able to see the error details there, too, and get it resolved.

Paypal sandbox notify_url file not working

Can someone please tell me whats wrong with this code.
I am trying for payment integration with Paypal with html form. I have specified the notify_url, payment goes all right but i am not able to enter in this block if (strcmp($res, "VERIFIED") == 0){}
// Response from Paypl
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$value = preg_replace('/(.*[^%^0^D])(%0A)(.*)/i', '${1}%0D%0A${3}', $value); // IPN fix
$req .= "&$key=$value";
}
// assign posted variables to local variables
$data['item_name'] = $_POST['item_name'];
// 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
echo 'HTTP ERROR';
} else {
file_put_contents('test1.txt', 'test');
fputs($fp, $header . $req);
while (!feof($fp)) {
$res = fgets($fp, 1024);
if (strcmp($res, "VERIFIED") == 0) {
echo 'SUCCESS';
} else if (strcmp($res, "INVALID") == 0) {
echo 'INVALID';
}
}
fclose($fp);
}
Use CuRl method to POST data back instead of fsock here is link:https://developer.paypal.com/webapps/developer/docs/classic/ipn/ht_ipn/ or My suggestion is Use AngellaEye library which is more effective and useful for Paypal integration here is link:http://www.angelleye.com/how-to-integrate-paypal-with-php-class-library/
Download it from this link and go throgh it.

PayPal: pass the email address to the return/"thank you" page

I've successfully created a small "pay now" button with PayPal IPN and a listener. The button itself is wizard-generated.
After the payment, the user is redirected to a return/"thank you" page on my host.
Everything works as expected, but I need to receive the customer e-mail on the "thank you" page too: how can I do that?
You can get the user email using the Payment Data Transfer (PDT), which sends a GET variable named tx to your redirect url.
The tx variable contains a transaction number which you can use to send to a post request to Paypal's server and retrieve the transaction information.
The last time I used PDT was a year ago, but I believe there is a setting in your Paypal account that you need to enable and set a redirect url for this to work.
Here are some links that describes PDT in further detail:
https://www.paypal.com/us/cgi-bin/webscr?cmd=p/xcl/rec/pdt-techview-outside
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/howto_html_paymentdatatransfer
Here is an example of how to parse send a post request to Paypal and parse the data. I just dug this up from an old file. So no guarantees that it works. This is based off a script that Paypal uses as an example for php. You can use curl instead, and that's probably the better choice. I think there is some kind of security issue with using fsockopen.
//Paypal will give you a token to use once you enable PDT
$auth_token = 'token';
//Transaction number
$tx_token = $_GET['tx'];
$payPalUrl = ( $dev === true ) ? 'ssl://www.sandbox.paypal.com' : 'ssl://www.paypal.com';
$req = 'cmd=_notify-synch';
$req .= "&tx=$tx_token&at=$auth_token";
$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 ($payPalUrl, 443, $errno, $errstr, 30);
$keyarray = false;
if ( $fp ) {
fputs ($fp, $header . $req);
$res = '';
$headerdone = false;
while (!feof($fp)) {
$line = fgets ($fp, 1024);
if (strcmp($line, "\r\n") == 0) {
$headerdone = true;
}
else if ($headerdone) {
$res .= $line;
}
}
$lines = explode("\n", $res);
if (strcmp ($lines[0], "SUCCESS") == 0) {
//If successful we can now get the data returned in an associative array
$keyarray = array();
for ($i=1; $i<count($lines);$i++){
list($key,$val) = explode("=", $lines[$i]);
$keyarray[urldecode($key)] = urldecode($val);
}
}
}
fclose ($fp);
return $keyarray;

How do i integrate PayPal IPN in my Joomla Custom Component?

I am not sure the title of this question covers what I mean.
In this Joomla component I am writing I have built in the ability for customers to buy via PayPal. At first I wrote a seperate view for the IPN, but even though the script worked without a flaw, it kept sending a 503 back to IPN (probably because the ipn-url was something like www.example.com/index.php?option=com_component&view=paypal) so i rewrote part of the script and now the IPN-url is www.example.com/paypal.php. Since this is an actual page it now correctly sends a 200 instead of a 503 back to PayPal.
But...now I don't know how to call the rest of my script which handles all the emailing and database storing of a payment. Since this paypal.php is called directly (and not via index.php) it works completely seperate from Joomla so I cannot call in a Model (or at least I don't know how to do that).
This is my paypal.php file:
<?php
$header = "";
$req = 'cmd=_notify-validate';
$get_magic_quotes_exists = false;
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";
}
}
// Post back to PayPal 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) {
fputs($fp, $header . $req);
while (!feof($fp)) {
$res = fgets($fp, 1024);
if (strcmp($res, "VERIFIED") == 0) {
// Here I must process the payment (emails, database, etc.)
}
else {
// Error
}
}
}
fclose($fp);
Now at the place where it says 'Here I must process payment' I must be able to get data from the database and store data into the database.
So how do I make it so this file acts as part of my component so I can call methods from my Model(s)? Or is there some other way I can integrate IPN into my model while ensuring a 200 instead of a 503.
UPDATE:
Someone mentioned using curl so i tried that. The handler now looks like this:
<?php
$header = "";
$req = 'cmd=_notify-validate';
$postData = 'option=com_component&view=buy&layout=paypal';
$get_magic_quotes_exists = false;
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";
$postData .= "&$key=$value";
}
}
$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) {
fputs($fp, $header . $req);
while (!feof($fp)) {
$res = fgets($fp, 1024);
if (strcmp($res, "VERIFIED") == 0) {
$ch = curl_init("http://www.example.com/index.php");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
$output = curl_exec($ch);
if ($output == FALSE) {
// Error
}
curl_close($ch);
}
else {
// Error
}
}
}
fclose($fp);
The IPN still works fine, but the component is not 'executed'. I never used curl before so maybe it is a fault in the script?
Got it myself finally; so somewhere I found out that to be able to access most of the basic functionality of Joomla from any file you simply need to include 2 files:
/includes/defines.php
/includes/framework.php
Then you simply initialise the framework like so:
$framework = & JFactory::getApplication('site');
$framework->initialise();
And then I import the model which contains all the database/email functionality:
JLoader::import('joomla.application.component.model');
JLoader::import('modelname', 'path_to_my/models');
$model= JModel::getInstance('ModelName', 'ComponentnameModel');
And now I can access the methods from that model (and thus the database) from my IPN-handler.
I just had a similar issue with a paypal component on my website and figured out where the 503 notification originated from.
This issue could have to do with the online/offline status of your website. If your website is offline (meaning you have to log in as admin to have a look at your website) and you're not logged in (PayPal isn't logged in as well) Joomla is generating a standard message displaying a message like"
This site is down for maintenance.
Please check back again soon.
This message is send with a 503 notification.
Depending on how your component is developed, the ipn message from PayPal can be processed by your website, while still sending a 503 error to PayPal.
Hope this helps you out.
For a component com_mycomponent, your mycomponent.php should look like
// Require the com_content helper library
require_once (JPATH_COMPONENT.DS.'controller.php');
// Create the controller
$controller = new MyComponentController();
// Perform the Request task
$controller->execute(JRequest::geCmd('task'));
// Redirect if set by the controller
$controller->redirect();
In controller.php, then use
class MyComponentCOntroller extends JController{
function processPaypalPayment(){
//paste your code here
}
}
In Paypal set your IPN to:
http://mysite.com/?option=com_mycomponent&task=processPaypalPayment

Categories