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
Related
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();
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
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.
I implemented a dynamic button "buy now" (not saved in my PayPal account) with IPN and it works fine (yeah!).
Now I have a doubt about his security, because if someone change with firebug (for example) the amount value, the transaction is valid for paypal also if my IPN listener says there is a problem with amount.
My question is "Can I encrypt the form with a php / codeigniter library?"
Because I tried to check amount in the IPN listener, but the transaction on paypal continue correctly and It isn't blocked from IPN.
Here, you find a part of my listener code:
private function isVerifiedIPN(){
$req = 'cmd=_notify-validate';
$posts = $this->input->post();
foreach ($posts as $key => $value){
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
if($this->config->item('SIMULATION'))
$url = $this->config->item('SIMULATION_URL');
else
$url = $this->config->item('PRODUCTION_URL');
if(!$this->isVerifiedAmmount() ||
!$this->isPrimaryPayPalEmail() ||
!$this->isNotProcessed()){
$req = '';
}
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: $url\r\n"; //443
$header .= "Content-type: application/x-www-form-urlencoded\r\n";
$header .= "Content-length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ("ssl://$url", 443, $errno, $errstr, 30);
if (!$fp)
{
$this->sendReport("Errore connessione socket");
return FALSE;
}
else
{
fputs ($fp, $header . $req);
while (!feof($fp))
{
$res = fgets ($fp, 1024);
if (strcmp($res, "VERIFIED") == 0)
{
// transizione valida
fclose ($fp);
return TRUE;
}
else if (strcmp ($res, "INVALID") == 0)
{
$this->sendReport('Transizione non valida');
fclose ($fp);
return FALSE;
}
}
}
}
You can dynamically encrypt buttons so that people with Firebug (or similar software) can't edit them. The PayPal API library has an example of this you can use, but I can't find it again right now.
This PayPal help file explains how to get the various keys you need using your server command line.
I also found a tutorial and a certificate builder (I didn't use, so can't confirm how secure it is...)
Once you've generated your key and certificate, you need to put them on your server and set DEFAULT_EWP_PRIVATE_KEY_PATH and DEFAULT_EWP_CERT_PATH to the relevant files.
Upload the public certificate to PayPal (instructions in linked tutorials), and set DEFAULT_CERT_ID to the Cert ID it gives you for that file. It'll also give you a file you can download - add that to your server and set PAYPAL_CERT_PATH to the path for that file.
For those who find it too hard to use a library to get the encryption going, or have hosting requirement issues with getting that working, the other trick is to not encrypt, but create a hash that you pass so that you can detect tampering, and then validate this hash when the IPN comes in for processing. I explain this here:
How do I make a PayPal encrypted buy now button with custom fields?
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;