I am trying to implement Paypal IPN but it never reaches the url I've set. I've written a script to log visits to this url and all I get are my visits.
How long does it take for Paypal to sent the notification?
EDIT
IPNs suddenly started to come but now I can't verify...Here is the code:
$url = 'https://www.paypal.com/cgi-bin/webscr';
$postdata = '';
foreach ($_POST as $i => $v) {
$postdata .= $i . '=' . urlencode($v) . '&';
}
$postdata .= 'cmd=_notify-validate';
$web = parse_url($url);
if ($web['scheme'] == 'https') {
$web['port'] = 443;
$ssl = 'ssl://';
} else {
$web['port'] = 80;
$ssl = '';
}
$fp = #fsockopen($ssl . $web['host'], $web['port'], $errnum, $errstr, 30);
if (!$fp) {
echo $errnum . ': ' . $errstr;
} else {
fputs($fp, "POST " . $web['path'] . " HTTP/1.1\r\n");
fputs($fp, "Host: " . $web['host'] . "\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: " . strlen($postdata) . "\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $postdata . "\r\n\r\n");
while (!feof($fp)) {
$info[] = #fgets($fp, 1024);
}
fclose($fp);
$info = implode(',', $info);
if (eregi('VERIFIED', $info)) {
} else {
}
}
I already commented above. But I'm pretty sure the html encoded & is messing up your callback.
There's big difference between URL encoding and HTML encoding.
Change this '&' to this '&'. & is a url/post character used to separate different sets of key/value pairs. By changing it to &, you made your whole callback a single value.
Also, just some advice, but I would ditch this
if (eregi('VERIFIED', $info)) {} else {}
and replace it with this
if (preg_match('/VERIFIED/', $info)) {} else {}
eregi is depreciated.
http://php.net/manual/en/function.eregi.php
Related
We are integrating the travel API of www.transhotel-dev.com.
The code is like this:
<?php
$servletHOST = "www.transhotel-dev.com";
$servletPATH = "/interfaces/SController";
$pXML = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?><Login><Username>Username</Username><Password>Password</Password></Login>";
$pCall = "Login";
$postdata = "pXML=" . urlencode($pXML) . "&pCall=" . urlencode($pCall);
$fp = pfsockopen($servletHOST, 1184);
if ($fp) {
fputs($fp, "POST $servletPATH HTTP/1.0\n");
fputs($fp, "Accept: */*\n");
$strlength = strlen( $postdata );
fputs($fp, "Content-length: " . $strlength . "\n\n");
fputs($fp, $postdata . "\n" );
$output = "";
while (!feof($fp)) {
$output .= fgets($fp, 1024);
}
fclose($fp);
echo $output;
}
?>
HTTP compression and POST method are required to go beyond this point. Can anybody help?
The following calls require the use of https secure protocol
(https://www.transhotel-dev.com:1449/interfaces/SController):
Login
AddAmountCardHPlus
GetNifInvoices
NifAgencyReservations
NifHotelReservations
ConfirmReservation (When contain the data of a credit card)
BuildSearchForm
LoginRQ
LoginB2B
CreateAgency
NifActivitiesReservations
GetActivitiesProvider
NifTransfersReservations
GetTransfersProvider
LoginHPlus
UserLogInHPlus
so you should use ssl protocol for login action:
$fp = pfsockopen("ssl://www.transhotel-dev.com", 1449);
I'm trying to create a fire and forget method in PHP so that I can POST data to a web server and not have wait for a response. I read that this could be achieved by using CURL like in the following code:
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_exec($ch);
curl_close($ch);
However I don't think it works as I expect. For example if the URL I send the request to has an error it causes my script to throw an error as well. If it was fire and forget I would expect that not to happen.
Can anyone tell me whether I'm doing something wrong or offer an alternative suggestion. I'm using Windows locally and Linux for dev, staging and production environments.
UPDATE
I have found an alternative solution here: http://blog.markturansky.com/archives/205
I've cleaned it up into the code below:
function curl_post_async($url, $params = array())
{
// create POST string
$post_params = array();
foreach ($params as $key => &$val)
{
$post_params[] = $key . '=' . urlencode($val);
}
$post_string = implode('&', $post_params);
// get URL segments
$parts = parse_url($url);
// workout port and open socket
$port = isset($parts['port']) ? $parts['port'] : 80;
$fp = fsockopen($parts['host'], $port, $errno, $errstr, 30);
// create output string
$output = "POST " . $parts['path'] . " HTTP/1.1\r\n";
$output .= "Host: " . $parts['host'] . "\r\n";
$output .= "Content-Type: application/x-www-form-urlencoded\r\n";
$output .= "Content-Length: " . strlen($post_string) . "\r\n";
$output .= "Connection: Close\r\n\r\n";
$output .= isset($post_string) ? $post_string : '';
// send output to $url handle
fwrite($fp, $output);
fclose($fp);
}
This one seems to work better for me.
Is it a valid solution?
Yes, using sockets is the way to go if you don't care about the response from the URL you're calling. This is because socket connection can be terminated straight after sending the request without waiting and this is exactly what you're after - Fire and Forget.
Two notes though:
It's no longer a cURL request, so it's worth renaming the function. :)
It's definitely worth checking whether the socket could've been opened to prevent the script from complaining later when if fails:
$fp = fsockopen($parts['host'], $port, $errno, $errstr, 30);
if ( ! $fp)
{
return FALSE;
}
It's worth linking to the original source of the fsocket() script you're now using:
http://w-shadow.com/blog/2007/10/16/how-to-run-a-php-script-in-the-background/
Here is a cleaned up version of diggersworld's code that also handles other HTTP methods then POST and throws meaningful exceptions if the function fails.
/**
* Send a HTTP request, but do not wait for the response
*
* #param string $method The HTTP method
* #param string $url The url (including query string)
* #param array $params Added to the URL or request body depending on method
*/
public function sendRequest(string $method, string $url, array $params = []): void
{
$parts = parse_url($url);
if ($parts === false)
throw new Exception('Unable to parse URL');
$host = $parts['host'] ?? null;
$port = $parts['port'] ?? 80;
$path = $parts['path'] ?? '/';
$query = $parts['query'] ?? '';
parse_str($query, $queryParts);
if ($host === null)
throw new Exception('Unknown host');
$connection = fsockopen($host, $port, $errno, $errstr, 30);
if ($connection === false)
throw new Exception('Unable to connect to ' . $host);
$method = strtoupper($method);
if (!in_array($method, ['POST', 'PUT', 'PATCH'], true)) {
$queryParts = $params + $queryParts;
$params = [];
}
// Build request
$request = $method . ' ' . $path;
if ($queryParts) {
$request .= '?' . http_build_query($queryParts);
}
$request .= ' HTTP/1.1' . "\r\n";
$request .= 'Host: ' . $host . "\r\n";
$body = http_build_query($params);
if ($body) {
$request .= 'Content-Type: application/x-www-form-urlencoded' . "\r\n";
$request .= 'Content-Length: ' . strlen($body) . "\r\n";
}
$request .= 'Connection: Close' . "\r\n\r\n";
$request .= $body;
// Send request to server
fwrite($connection, $request);
fclose($connection);
}
I'm trying to figure out how to get this working with PHP. I have a working IPN for paypal that is less than 20 lines of code to get the data I need. I have tried reading the Google docs but they are either way too specific or way too general. There is some sample code, which is about 1300 lines in 5 files and I can't make sense of it. I just need a handful of vars back from a completed transaction, nothing more. Is it possible to do this with a few lines of code (and I mean without 1300 lines worth of "include" files) or is Google Checkout's process really that bulky?
Here's a bit of code I started. Not yet finished.
It works perfectly.
All you need to do is take the data Google sends back and this code writes to a file and use it to insert into your sales table send notification of payment received to customer and so on.
The trick is that when Google sends you a post you must call back with and Authorization header or it will not take it in consideration.
function post2google($url, $timeout = 30, $port = 80, $buffer = 128) {
$mid = "123456789";
$mky = "qwertyuiop";
$aut = base64_encode($mid . ":" . $mky);
$arr = parse_url($url);
$ssl = "";
if($arr['scheme'] == "https") $ssl = "ssl://";
$post = "POST " . $arr['path'] . " HTTP/1.1\r\n";
$post .= "Host: " . $arr['host'] . "\r\n";
$post .= "Authorization: Basic " . $aut . "\r\n";
$post .= "Content-Type: application/xml; charset=UTF-8\r\n";
$post .= "Accept: application/xml; charset=UTF-8\r\n";
$post .= "Content-Length: " . strlen($arr['query']) . "\r\n";
$post .= "Connection: Close\r\n";
$post .= "\r\n";
$post .= $arr['query'];
$f = fsockopen($ssl . $arr['host'], $port, $errno, $errstr, $timeout);
if(!$f)
return $errstr . " (" . $errno . ")";
else{
fputs($f, $post);
while(!feof($f)) { $echo .= #fgets($f, $buffer); }
fclose($f);
return $echo;
}
}
$re = post2google("https://checkout.google.com/api/checkout/v2/reportsForm/Merchant/123456789?_type=notification-history-request&serial-number=" . $_REQUEST['serial-number'], 3, 443);
$re = str_replace("&", "\n", $re) . "\n\n--\n\n";
file_put_contents("gpn.txt", $re, FILE_APPEND);
I've gotten it to work, and here's the skeleton of my code that can be used to handle HTTP notifications/responses. This was obviously derived from tntu's example above. (Thanks!)
//incoming data is in the var $_POST['serial-number']
//"send" the response to acknowledge the serial number that google talks about all over but never explains how
echo "_type=notification-acknowledgment&serial-number=".$_POST['serial-number'];
//now we need to call google's server and ask for this transaction's data:
//you'll need to change your merchant id in the $url and $mid vars, and your merchant key in the $mky var
$url = "https://sandbox.google.com/checkout/api/checkout/v2/reportsForm/Merchant/1234567890?_type=notification-history-request&serial-number=" . $_REQUEST['serial-number'];
$mid = "1234567890";
$mky = "ABCDEFGHIJK";
$aut = base64_encode($mid . ":" . $mky);
$arr = parse_url($url);
$ssl = "";
if($arr['scheme'] == "https") $ssl = "ssl://";
$post = "POST " . $arr['path'] . " HTTP/1.1\r\n";
$post .= "Host: " . $arr['host'] . "\r\n";
$post .= "Authorization: Basic " . $aut . "\r\n";
$post .= "Content-Type: application/xml; charset=UTF-8\r\n";
$post .= "Accept: application/xml; charset=UTF-8\r\n";
$post .= "Content-Length: " . strlen($arr['query']) . "\r\n";
$post .= "Connection: Close\r\n";
$post .= "\r\n";
$post .= $arr['query'];
//now we actually make the request by opening a socket and calling Google's server
$f = fsockopen($ssl . $arr['host'], 443, $errno, $errstr, 30);
if(!$f){
//something failed in the opening of the socket, we didn't contact google at all, you can do whatever you want here such as emailing yourself about it and what you were trying to send, etc
#mail("troubleshooting#yourdomain.com","Google IPN - HTTP ERROR ",$errstr . " (" . $errno . ")\n\n\n".$arr['query']);
}else{
//the socket was opened, send the request for the order data:
fputs($f, $post); // you're sending
while(!feof($f)) { $response .= #fgets($f, 128); } //google replies and you store it in $response
fclose($f); //close the socket, we're done talking to google's server
$spl=strpos($response,"_type="); //parse the type because parse_str won't catch it
if ($spl!==false){
$spl2=strpos($response,"&",$spl);
$ordertype=substr($response,($spl+6),($spl2-$spl)-6);
}//$ordertype will tell you which type of notification is being sent, new-order-notification, risk-information-notification, etc
$subresponse=substr($response,$spl2+1); //put the rest of it into an array for easy access
parse_str($subresponse,$order);//you can now access google's response in $order[] vars
//IMPORTANT: dots in Google's field names are replaced by underscore, for example:
// $order['google-order-number'] and $order['buyer-billing-address_address1'] NOT $order['buyer-billing-address.address1']
//order field names are shown here:
//https://developers.google.com/checkout/developer/Google_Checkout_HTML_API_Notification_API#order_summary
//this is the point where you will want to use the data contained in $order[] to create a new record in your database or whatever.
//NOTE: be sure to store and check for duplicates using the google-order-number because you will get multiple notifications from google regarding the same order
if (strtoupper($order['order-summary_financial-order-state']) == "CHARGEABLE"){
//CHARGEABLE is what indicates it is safe to create a login for the user (if you are delivering digital goods)
// insert into db, and/or email user with key or download url
}
}
How do I debug the ipn.php file when using the paypal sandbox ipn simulator tool?
The code looks like this:
// read the post from PayPal system and add 'cmd'
$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.sandbox.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) {
$DBH = new PDO("mysql:host=localhost;dbname=db", "user", "pass");
$DBH - > setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$STH = $DBH - > prepare("update table2 set status = :status where tracking_id = :tracking_id");
$status = 1;
parse_str($req, $data);
$STH - > bindParam(':status', $status, PDO::PARAM_INT, 1);
$STH - > bindParam(':tracking_id', 'id_goes_here', PDO::PARAM_STR, 50);
$STH - > execute();
$DBH = null;
} else if (strcmp($res, "INVALID") == 0) {
// do something else
}
}
fclose($fp);
}
I normally use netbeans debug facility to debug, but how do I debug using the sandbox simulator? When I click send ipn from the sandbox ipn simulator, I get a message in the sandbox saying IPN successfully sent., but when I then go into my database to check the status, it's still 0.
Maybe problem is in parameter binding
$STH - > bindParam(':tracking_id', 'id_goes_here', PDO::PARAM_STR, 50);
if table 'table2' do not contain row with tracking_id = 'id_goes_here' update action will fail.
Try this
<?php
$testMode = false;
$url = 'https://www.paypal.com/cgi-bin/webscr';
if ($testMode === true)
$url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
$ipnResponse = ''; // holds the IPN response from paypal
$ipnData = array(); // array will contain the POST values for IPN
$urlParsed = parse_url($url);
$req = 'cmd=_notify-validate'; // Add 'cmd' to req (ipn command)
// Read the post from PayPal system and add them to req
foreach ($_POST as $key => $value) {
$ipnData["$key"] = $value;
$value = urlencode(stripslashes($value));
$req .= "&" . $key . "=" . $value;
}
// Open the connection to paypal
$fp = fsockopen($urlParsed['host'], "80", $errno, $errstr, 30);
// If could open the connection and check response
if ($fp) {
fputs($fp, "POST " . $urlParsed['path'] . " HTTP/1.1\r\n");
fputs($fp, "Host: " . $urlParsed['host'] . "\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: " . strlen($req) . "\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $req . "\r\n\r\n");
// Loop through the response from the server and append to variable
while (!feof($fp)) {
$ipnResponse .= fgets($fp, 1024);
}
fclose($fp);
// Valid IPN transaction.
if (preg_match('/^VERIFIED/', $ipnResponse)) {
// Some action on IPN validation - update payment status etc
die("OK. IPN Validation: Success");
}
// Invalid IPN transaction
else {
// Some action on IPN validation - update payment status etc
die("ERROR. IPN Validation: Failed");
}
}
// Else no connection, so maybe wrong url or other reasons, you can do another call later
else {
die("ERROR. IPN Connection: fsockopen error");
}
?>
In general when I want to debug a script that doesn't provide direct output in the browser, I call a simple logging function throughout the script to output variable values or help determine where errors are occurring.
When I have no idea where the script is dying, I write a line to a log file after each significant line of code. After running the script and then checking the log file I can see exactly which line caused the script to die, a very simplified example is below. Of course after everything is running smoothly all of the logging should be removed.
public function logToFile($msg){
$file = 'log.txt';
$current = file_get_contents($file);
$current .= $msg . "\n";
file_put_contents($file, $current);
}
public function doOtherStuff(){
foreach ($_POST as $key => $value) {
$ipnData["$key"] = $value;
$value = urlencode(stripslashes($value));
$req .= "&" . $key . "=" . $value;
}
logToFile("1");
$fp = fsockopen($urlParsed['host'], "80", $errno, $errstr, 30);
logToFile("2");
if ($fp) {
logToFile("3");
fputs($fp, "POST " . $urlParsed['path'] . " HTTP/1.1\r\n");
logToFile("4");
fputs($fp, "Host: " . $urlParsed['host'] . "\r\n");
logToFile("5");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
logToFile("6");
fputs($fp, "Content-length: " . strlen($req) . "\r\n");
logToFile("7");
fputs($fp, "Connection: close\r\n\r\n");
logToFile("8");
fputs($fp, $req . "\r\n\r\n");
logToFile("9");
while (!feof($fp)) {
logToFile("10");
$ipnResponse .= fgets($fp, 1024);
}
logToFile("11");
fclose($fp);
}
}
I'm testing Salesforce's WebToLead form using this simple example http://wiki.developerforce.com/index.php/Simple_Web2Lead_Implementation.
When I changed the double quotes (used to concatenate the $header values) into single quotes, I keep getting the following error:
Fatal error: Maximum execution time of 60 seconds exceeded in C:\wamp\www\test.php on line 23
When I change them back to double quotes, everything works fine. What am I missing?
Here is a simplified version you can use if you have a Salesforce developer account:
<?php
//do quality checks on the incoming data here.
//then bundle the request and send it to Salesforce.com
$req = "&lead_source=". urlencode("test");
$req .= "&first_name=" . urlencode("first name test");
$req .= "&debug=" . urlencode("1");
$req .= "&oid=" . urlencode("<your oid>");
$req .= "&retURL=" . "";
$req .= "&debugEmail=" . urlencode("<your email>");
$header = 'POST /servlet/servlet.WebToLead?encoding=UTF-8 HTTP/1.0\r\n';
$header .= 'Content-Type: application/x-www-form-urlencoded\r\n';
$header .= 'Host: www.salesforce.com\r\n';
$header .= 'Content-Length: ' . strlen($req) . '\r\n\r\n';
$fp = fsockopen ('www.salesforce.com', 80, $errno, $errstr, 30);
if (!$fp) {
echo "No connection made";
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024); // error is thrown here
echo $res;
}
}
fclose($fp);
?>
Well, '\r\n\r\n' won't be evaluated into the actual characters, while "\r\n\r\n" will.
Since they aren't being evaluated properly, the header will be improperly formed, which would explain your timeout.