I'm trying to have the PAYPAL IPN work with 2 different sites, I can't get the php script to know which database to work with. Here's my script.
// 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";
// If testing on Sandbox use:
//$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
// assign posted variables to local variables
$custom = $_POST['item_name'];
//Config file for 1st database
if($custom=="1"){
require_once("../config.php");
//Script to execute
}
//Config file for 2nd database (different site)
if($custom=="2"){
require_once("../config.php");
//Script to execute
}
Your code is making a lot of assumptions and isn't very defensive. It's assuming the form was posted, that it was successfully populated, that $_POST['item_name'] will contain only the strings '1' or '2' and that your script is running in a path which is one folder deep to a directory containing config.php...and that regardless of the condition, the same config.php will be loaded and miraculously know what it needs to do.
What would make more sense is something like this:
try
{
if(isset($_POST['site_name']))
{
require_once('../configs/' . $_POST['site_name'] . '/config.php');
if(!isset($some_var_declared_inside_config_file))
{
throw new Exception('Config not loaded');
}
}
else
{
throw new Exception('Site name not provided');
}
}
catch(Exception $e)
{
echo $e->getMessage();
}
Then you could have your configs setup like so:
/www/paypal.php
/configs/sitea/config.php
/configs/siteb/config.php
/configs/sitec/config.php
I found a great script for this that worked great. It automatically send the paypal response to the correct site based on the email.
http://www.scriptomart.com/view-item/15-Paypal-IPN-Multiple-Email-Accounts.html is the script but the one I bought wasn't free so you may have to look for that one. I can't find the exact link.
Related
So, no matter what I try to do, I seem to always get INVALID from my PayPal IPN process. I have found other pages that have similar issues, however either the solution has not worked for me or it was not solved.
Here is my current php code:
include_once($_SERVER['DOCUMENT_ROOT']."/api/static.php");
include_once($_SERVER['DOCUMENT_ROOT']."/api/api.php");
$req = 'cmd=_notify-validate';
foreach($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$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.paypal.com', 443, $errno, $errstr, 30);
if(!$fp) {
//There was an error.
$mccubedConnection->query("replace into testing (result) values (-2);");
} else {
fputs($fp, $header . $req);
while(!feof($fp)) {
$res = fgets($fp, 1024);
if(strcmp($res, "VERIFIED") == 0) {
//Valid
$mccubedConnection->query("replace into testing (result) values (1);");
} else if(strcmp($res, "INVALID") == 0) {
//Invalid
$mccubedConnection->query("replace into testing (result) values (-1);");
}
} fclose($fp);
}
$mccubedConnection->query("replace into testing (result) values (0);");
$mccubedConnection->close();
The MySQL queries are working fine, and in the database it's posting a -1 and a 0. Where my desire is for it to be posting a 1 and a 0 (This way is just temporary for my testing, I will be handling the transaction differently).
I am using the INP simulator here: https://developer.paypal.com/developer/ipnSimulator/
And supplying the correct url to where my php file is, as it is executing, but posting a -1 and 0 to the DB.
Thanks for your time!
So it appears to me that this is an issue with PayPal's simulator specifically. I have done even more research and found this post, where they mention that it works on live servers, but not within sandbox. I really hate to make assumptions and to just assume it works and to push it, but if anyone else can confirm this is true, please let me know! (btw this has been an issue on that github for almost 2 months now... quite incredible)
Having made a newbe error let me try again.
The website is for a non-profit and I added a PayPal "donation" page couple of years ago; done as a shopping cart (3 types of donations and/or membership). PP returns to a php script that uses the PDT data to build a thank-you page and set cookies for a double-opt-in mailing list. IPN sends the thank-you email, opt-in email, database, etc. works fine.
Now adding a PayPal option to ticket reservation pages. Again, a shopping cart that calls PayPal and seems to work fine in the sandbox; PayPal screen is as expected, and the two foo emails look correct. I pass a different return URL which gets called but bombs on the hand shake. The code is cut&past from the previous effort and based on the PayPal PDT example.
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-synch';
$header = "";
$tx_token = $_GET['tx'];
$req .= "&tx=$tx_token&at=$auth_token"; // see header for def of auth_token
// 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";
// If possible, securely post back to paypal using HTTPS
// Your PHP server will need to be SSL enabled
$fp = fsockopen ($socket, $port, $errno, $errstr, 30); // see header
if (!$fp)
{
// HTTP ERROR
mail($email, "PDT Error", "fsockopen error " . $errno . ": " . $errstr);
}
else
{
fputs ($fp, $header . $req);
// read the body data
$res = '';
$headerdone = false;
while (!feof($fp))
{
$line = fgets ($fp, 1024);
echo $line . "<br>";
if (strcmp($line, "\r\n") == 0)
{
// read the header
$headerdone = true;
}
else if ($headerdone)
{
// header has been read. now read the contents
$res .= $line;
}
}
The results from the added echo are:
HTTP/1.0 302 Found
Location: https://www.sandbox.paypal.com
Server: BigIP
Connection: close
Content-Length: 0
The var $auth_token, $socket and $port are set up with a "if ($test)" to switch between the sandbox and live. Obviously with 0 length payload, nothing else works.
I read here that about the new Auth_token. There are some other nits about testing for SUCCESS that I haven't gotten to, with no data to play with..
I can't remember, does the sandbox trigger the IPN? I'm getting nothing there either - set for the sandbox. Thanks for any suggestions about where to look.
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'm trying to set up the PayPal IPN on my web application, I copied from PayPal's documentation on an example PHP snippet which is found here.
However, when I'm testing with the PayPal's sandbox, sending an IPN with the simulator which is found here.
Now, when PayPal sends the IPN, I log the actions and data of the IPN, when trying to open an connection with fsockopen, it is NULL when I do var_export on it.
I don't understand why it's not going any further with the code when the fsockopen connection is NULL.
I'm using Codeigniter for my application, and this is the part of the code that fails:
if($this->uri->segment(3) == 'ipn')
{
$error_msg = '';
$error_msg .= " initiated ";
$req = 'cmd=_notify-validate';
$error_msg .= " \n\n req: " . var_export($req, true);
foreach($this->input->post() as $key => $value)
{
$value = urlencode(stripslashes($value));
$req .= "&" . $key . "=" . $value;
}
$error_msg .= " \n\n req: " . var_export($req, true);
$header = '';
$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";
$error_msg .= " \n\n headers: " . var_export($header, true);
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
$error_msg .= " \n\n fp: " . var_export($fp, true);
I use $error_msg to log the data, this is an example what is logged:
initiated
req: 'cmd=_notify-validate'
req: 'cmd=_notify-validate&test_ipn=1&payment_type=echeck&payment_date=17%3A30%3A40+Jan+03%2C+2012+PST&payment_status=Completed&address_status=confirmed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John+Smith&address_country=United+States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San+Jose&address_street=123%2C+any+street&business=seller%40paypalsandbox.com&receiver_email=seller%40paypalsandbox.com&receiver_id=TESTSELLERID1&residence_country=US&item_name=something&item_number=DX4WYSur44CQICgO2lC%2FB10NmdaiPNH3xPZXQNAlfrEqpse0xnime22zaNXDFgbRrOL4Xsz4emkhqFw4JhOSHzCtaHt9%2B0p9p8xW6R71PVbFXNyEVjkPeHNdQm32PJg&quantity=1&shipping=3.04&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=12.34&txn_type=web_accept&txn_id=4014130¬ify_version=2.1&custom=xyz123&invoice=abc1234&charset=windows-1252&verify_sign=An5ns1Kso7MWUdW4ErQKJJJ4qi4-AN8d2a.xggmx9Dn4AgHpvPHJHTAp'
headers: 'POST /cgi-bin/webscr HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 969
'
fp: NULL
As you can see $fp is returning NULL on the last line of the logged data. Is there any idea why this is happening?
I can confirm I have OpenSSL enabled and installed on my server:
EDIT: Just tested fsockopen on port 80 to google.com, I still get NULL with no error number or message. So this problems occurs to every URL.
EDIT #2: Tested on my server by doing this:
fsockopen('ssl://www.paypal.com/cgi-bin/webscr', 443, $errno, $errstr, 30)
A PHP Error was encountered
Severity: Warning
Message: fsockopen(): unable to connect to
ssl://www.paypal.com/cgi-bin/webscr:443 (php_network_getaddresses:
getaddrinfo failed: nodename nor servname provided, or not known)
If anyone else is having the same problem, try using HTTPS instead of SSL
$fp = fsockopen ('https://www.paypal.com', 443, $errno, $errstr, 30);
And if your testing on Paypals Sandbox use:
$fp = fsockopen ('https://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
Be careful of $config['csrf_protection'] = TRUE; this will block all external POSTS as they will not come with a CSRF token, I've had this with my paypal IPN before, I needed to enable a crude but effective way to get the callback (in config.php):
if(stripos($_SERVER["REQUEST_URI"],'/paypal') === FALSE) {
// disable CSRF for the /paypal
$config['csrf_protection'] = TRUE;
} else {
$config['csrf_protection'] = FALSE;
}
I'm guessing this could be an issue, you would get null as no data would be captured as CI reviews your $_POST/$_GET vars for security reasons.
If I misunderstood your question and am way off track, just let me know via a comment.
Check List
sandbox url: "https://www.sandbox.paypal.com/cgi-bin/webscr";
like #jakub says CSRF must be disabled for your paypal controller
IPN wont validate on localhost, however you should still get vars back.
var_dump(fsockopen ('https://www.sandbox.paypal.com/', 443, $errno, $errstr, 30));
var_dump($_POST);
The variables $errno and $errstr probably hold the reason for the failure. Echo them out with your error message.
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