I'm setting up a simple paypal button. When clicked, I need to write the order into my db and then redirect it via POST to paypal. The database part works just fine, however the POST to paypal doesn't.
I'm using the code by wez furlong to send a post request to the paypal server and need to process the response in order to redirect the client correctly (I need to get the location from the response).
In the end, this should behave just like a normal paypal "buy now" button, redirecting the user to paypal when clicked.
I just can't seem to get at the response and the paypal documentation is extremely vague on this. How do I extract the proper url for redirecting the client?
Any help appreciated!
Tobias
Here is how I implemented this functionality on my website. It is working fine:
Code to display the Paypal button:
Content += "<br/><a href='javascript:Pay();'><img src='http://www.example.com/images/paypal_checkout_button.jpg' alt='Secure payment through PayPal' border='0' /></a><br/><br/>";
Pay() is the javascript function called when the client clicks the button.
Code of function Pay()
function Pay() {
var MessageParam = "msgjson=" + JSON.encode(msgJSON) + "&invoicejson=" + JSON.encode(invoiceJSON);
var myRequest = new Request({url:"http://www.example.com/savetransaction.php", onSuccess: function(InvoiceId) {CallPayPal(InvoiceId);}}).post(MessageParam);
}
Function Pay does two things:
1 - submits the details of the transaction to a PHP script (savetransaction.php) running on the server. This script saves the transaction in a database. Note that the transaction is saved as Pending in the database. It will change to Paid once I receive confirmation from Paypal that it has been paid. Script savetransaction.php returns an invoice id number, which is the id from the database after the insert. This is the unique identifier that identifies this transaction throughout the payment process. I will send this number to paypal and it will send it back to me in the payment confirmation.
2 - onSuccess it calls function CallPayPal(InvoiceId), which will send the client to Paypal (see below).
Code of function CallPayPal(InvoideId)
function CallPayPal(InvoiceId) {
var PayPalUrl = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_xclick-subscriptions&business=YOURCODEHERE&lc=US&item_name=YOURITEMNAMEHERE&item_number=YOURITEMNUMBER&no_note=1&no_shipping=2&rm=1&return=URL_OF_SCRIPT_THAT_WILL_HANDLE_PAYPAL_RESPONSE&cancel_return=URL_OF_SCRIPT_THAT_WILL_BE_CALLED_IF_CLIENT_CANCEL_PAYMENT_AT_PAYPAL&src=1&a3=" + invoiceJSON.AmtSubs + "&p3=1&t3=M¤cy_code=USD&bn=PP%2dSubscriptionsBF%3abtn_subscribeCC_LG%2egif%3aNonHosted";
window.open(PayPalUrl + "&invoice=" + InvoiceId,"_self");
}
You will need to change the paypal url to fit your needs. Mine is for a subscribe button. Note that I include the InvoiceId at the end. This will be received by PayPal and sent back in the response.
When the client finishes paying, Paypal will send him back to the address I included in the variable return. Paypal will append a transaction code to the url. See below the php script where I handle the return from Paypal:
Code for PayPalPDT.php
$req = 'cmd=_notify-synch';
$tx_token = $_GET['tx'];
$auth_token = "enter your own authorization token";
$req .= "&tx=$tx_token&at=$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";
//$fp = fsockopen ('http://www.sandbox.paypal.com', 80, $errno, $errstr, 30);
// If possible, securely post back to paypal using HTTPS
// Your PHP server will need to be SSL enabled
// replace www.sandbox.paypal.com with www.paypal.com when you go live
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
if (!$fp)
{
// HTTP ERROR
}
else
{
fputs ($fp, $header . $req);
// read the body data
$res = '';
$headerdone = false;
while (!feof($fp))
{
$line = fgets ($fp, 1024);
if (strcmp($line, "\r\n") == 0)
{
// read the header
$headerdone = true;
}
else if ($headerdone)
{
// header has been read. now read the contents
$res .= $line;
}
}
// parse the data
$lines = explode("\n", $res);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0)
{
for ($i=1; $i<count($lines);$i++)
{
list($key,$val) = explode("=", $lines[$i]);
$keyarray[urldecode($key)] = urldecode($val);
}
$firstname = $keyarray['first_name'];
$lastname = $keyarray['last_name'];
$itemname = $keyarray['item_name'];
$amount = $keyarray['payment_gross'];
$invoiceid = $keyarray['invoice'];
$profileid = $keyarray['subscr_id'];
// check the payment_status is Completed
// check that txn_id has not been previously processed
// check that receiver_email is your Primary PayPal email
// check that payment_amount/payment_currency are correct
// process payment
// show receipt to client
}
}
I hope this helps. Please feel free to ask follow up questions.
Good luck!
Use GET variables if you're using a header redirect.
For example: https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=yourpaypalemail&amount=theamount&item_name=the%20item%20name
See also 'HTML variables for Website Payments Standard' at https://merchant.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_html_Appx_websitestandard_htmlvariables for additional variables you could use.
Since you need to redirect the user to PayPal, you can't do a POST from the server to make that happen.
Instead, I'd suggest you use some AJAX and intercept the button click. When the button gets clicked, your JavaScript function can call a URL on your server to write out the data, and then also send the user off to PayPal.
The code on wezfurlong is for having the server do a PHP post to paypal, not for redirecting the client. I think I implemented this by creating a html form that points to paypal and a javascript command to submit it, something like:
<html><body>
<form method='post' action='paypalurl'>
<input name='some required field' blah blah...>
</form>
<script>
document.forms[0].submit();
</script>
</body></html>
Related
I know that there have been a few changes to the PayPal IPN system as of May 15th, 2018. I happen to be in the middle of implementing my first IPN listener; and I'm not sure if I'm lost because of my resources (including SO posts, some dating back to 2009 on this subject) have been obsolesced by PayPal's changes, or merely because I am inexperienced in this field.
My suspicion is that I am pointing to the incorrect PayPal address:
$fh = fsockopen('ssl://www.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://www.sandbox.paypal.com',80,$errno,$errstr,30);
//$fh = fsockopen('https://ipnpb.sandbox.paypal.com/cgi-bin/webscr',80,$errno,$errstr,30);
The first address completes in a successful handshake with PayPal, but returns INVALID as the IPN response. The second two handshake, but don't pass the if (!$fh) test.
I have tried both the code below, and the CURL code found in Chris Muench's answer here: PayPal IPN returns invalid in sandbox
<?php
// Paypal IPN code
header('HTTP/1.1 200 OK'); // send header
$resp = 'cmd=_notify-validate';
foreach ($_POST as $parm => $var){
$var = urlencode(stripslashes($var));
$resp .= "&$parm=$var";
}
$httphead = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$httphead .= "Content-Type: application/x-www-form-urlencoded\r\n";
$httphead .= "Content-Length: " . strlen($resp) . "\r\n\r\n";
// create a ="file handle" for writing to a URL to paypal.com on Port 443 (the IPN port)
$errno ='';
$errstr='';
$fh = fsockopen('ssl://www.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://www.sandbox.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://ipnpb.sandbox.paypal.com/cgi-bin/webscr',443,$errno,$errstr,30);
if (!$fh) {
// if-fh does not work - cnxn FAILED
}
else{
// Connection opened, so spit back the response and get PayPal's view whether it was an authentic notification
fputs ($fh,$httphead.$resp);
while (!feof($fh)){
$readresp = fgets ($fh, 1024);
if (strcmp(trim($readresp),"VERIFIED") == 0){
// if-fh works - cnxn OPEN';
// WE ALL WIN!!
}
else if(strcmp(trim($readresp),"INVALID") == 0){
// A possible hacking attempt, or
// In my case, a possible hacking false-positive
}
}
fclose ($fh);
}
?>
I'm testing using the IPN simulator in my sandbox account.
None of the SO recommended solutions have worked.
Please help!
For the record, this url seems to be working fine: https://www.paypal.com/cgi-bin/webscr
I'm using the Paypal IPN to update the status of an order to 2. Once we deliver the order, we manually change it to 3, but then sometimes it goes back to 2 on it's own (note that until it's 2, we can't even see it in our order panel).
Does the Paypal IPN update it again? What's causing this?
<?php
include('includes/config.php');
// 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.paypal.com', 443, $errno, $errstr, 30);
if (!$fp) {
// HTTP ERROR
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strpos($res, "VERIFIED") == 0) {
...
It sounds like IPN is updating it again, but there are a number of a reasons that could be happening.
If your IPN script has a problem with it that keeps from fully completing then PayPal will send the IPN again until it receives a 200 response back from your server. It's possible you have some sort of a syntax error or something that happens after the majority of the code runs. So the code runs, updates your system to a 2, but then fails after that, so PayPal sends the IPN again. Log into your PayPal account and check your IPN History to see if that could be what's going on.
Another reason could be if the original payment was pending for some reason. When IPN hit with the pending status it updates to 2, then it hits again once the payment status updates and updates it to a 2 again.
These are just a couple of ideas of what could possibly be happening. I would recommend adding some logging to your IPN script of some sort. A log file, database log, or even an email to yourself so you can see if it's running more than once for the same transaction. From there you could figure out exactly why it's running more than once.
You know those times where you feel really dumb after asking a question? This is one of those times.
I added AND 'status' = '0' to the query so even if it does check it later, it won't do anything.
this is the first time I am using Paypal to process a payment. I have set up a developer account, created some test merchant/buyer accounts and successfully created a cart that I sent to paypal sandbox. Here, using one of my buyers accounts, I can, again, successfully complete the purchase. In the account history of paypal, it shows that the transaction was completed. All good till now.
The problem lays in the return url, where I get the IPN. Here is the code I copied from paypal website.
<?php
include($_SERVER['WROOT'].'core/init.php');
//Put together postback info
$postback = 'cmd=_notify-validate';
foreach($_POST as $key =>$value){
$postback .= "&$key=$value";
}
// EMAIL $postback
// build the header string to post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Host: www.sandbox.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($postback) . "\r\n\r\n";
// also tried with $fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
$fp = fsockopen ('www.sandbox.paypal.com', 80, $errno, $errstr, 30);
if(!$fp){ //no conn
die();
}
//post data back
fputs($fp, $header . $postback);
while(!feof($fp)){
$res=stream_get_contents($fp, 1024);
if((strcmp($res, "VERIFIED")) == 0){
// EMAIL with a success notification
// update the database - THIS IS ONLY A SMALL TEST TO SEE IF THE TRANSACTION IS SUCCESSFUL -
$new = $dbh->prepare(" ISNERT INTO orders(txn_id) VALUES(:txn_id) ");
$new->execute(array( 'txn_id' => $_POST['txn_id'] ));
} else if ( strcmp ($res, "INVALID") == 0 ) {
// EMAIL with a error notification
// LOG THE ERROR TO A FILE
}
}
?>
Ok, first of all I don't do any check to see if the email, gross amount and other parameters are valid, that is something I will do after I can solve this problem.
Anyway, after a buyer pays, my database should update, but it does not.
What I did ?
First of all, I email me the $postback variable as soon as it's created and it worked. I got the email with a huge string of response and all the data was correct.
But the $res variable is not either VERIFIED or INVALID so anything past the fsockopen() does not work.
As I said, the payment on the paypal website is successfull. The documentation is fairly poor and I can't get an answer.
The one thing I want to add is that my website does NOT have a SSL certificate, but I do not store any of the customer data, everything is processed on the secure paypal website. Do I need a SSL certificate ?
One last thing. I tried using this class https://github.com/Quixotix/PHP-PayPal-IPN and the error log message is Invalid response status: 302
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.
Is there a way to validate a payment before paypal proceed the order ?
I Use my own shopping cart.
When the customer click on Submit Order, I redirect the user on an other page call, PayPalRedirect.php
PayPalRedirect.php
<form name="paypalform" action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_cart">
<input type="hidden" name="upload" value="1">
<input type="hidden" name="invoice" value="<? echo $idInvoice; ?>">
<input type="hidden" name="business" value="business_email#example.com">
<input type="hidden" name="notify_url" value="http://mydomain.com/catalog/IPNReceip">
<?
$cpt = 1;
foreach($ordering as $k => $v)
{
?>
<input type="hidden" name="item_name_<? echo $cpt?>" value="<? echo$v->Product->ProductNumber; ?>">
<input type="hidden" name="quantity_<? echo $cpt?>" value="<? echo $v->Qty; ?>">
<input type="hidden" name="amount_<? echo $cpt?>" value="<? echo $v->Price ?>">
<?
$cpt++;
}
?>
<input type="hidden" name="currency_code" value="CAD">
<input type="image" src="http://www.paypal.com/en_US/i/btn/x-click-but01.gif" name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
</form>
I use this JavaScript to submit the form when the page is loaded:
<script>
document.forms.paypalform.submit();
</script>
At this time all is alright. The user is Redirect to PayPal page and can login to PayPal and then pay the order.
My question at this point is. Is there a way with PayPal to call a web service in my side (for example: http://mydomain.com/ValidatePayment.php) and pass all the items of the shoping cart that paypal receive to confirm that the price is correct. If the price is not correct, I would like to response to paypal that the payment is invalid and cancel the transaction. All that before the customer click on PayNow of paypal page.
How can I do this if it's possible?
Thanks a lot
I don't think it is possible to do what you want. Once you send the client over to Paypal, they will handle the payment and send you a confirmation at the end.
If you want to make sure the client paid the correct amount, you should check the confirmation that Paypal sends you.
As you probably know Paypal has two payment confirmation mechanisms - PDT and IPN.
PDT relies on the client coming back to your website after the payment. Once the client makes the payment, Paypal will send you a PDT message with the details of the transaction. This information is what you can use to show a receipt to the client. The issue is if the client closes his browser immediately after making a payment. If that happens, you may never receive the PDT message.
IPN does not rely on the client coming back to your website. Every time a client makes a payment, Paypal will send you an IPN message with the details of the transaction.
You can use either one or both to confirm that payment was completed. And you can also check that the payment was made for the amount you were expecting.
See below the flow I use on my website:
1 - client presses button to make payment with Paypal
2 - my website sends client to Paypal with the details of the transaction
3 - Paypal handles the payment
4 - once payment is completed, Paypal will send the client back to my website with a PDT message.
5 - my website sends a confirmation message to Paypal to check that the PDT message is legit and came from Paypal.
6 - Paypal sends back a message with all the details of the transaction, including price paid.
7 - my website checks the confirmation message from Paypal and shows a receipt on screen to my client
8 - Paypal will also send an IPN message once a payment is made to my seller account.
9 - when my website receives an IPN message, it sends back a message to Paypal to confirm that the message is legit and was originated from Paypal.
10 - my website then checks the confirmation message sent back from Paypal and confirms the payment was correct.
Note that in most cases I will receive two messages from Paypal (one PDT and one IPN). It is ok because I keep track of each transaction and if I receive an IPN for a transaction where I have already marked as paid, I simply discard the IPN message.
Note that if you receive the PDT message you don't really need the IPN message. However I recommend you implement both just in case the client closes his browser before Paypal has a chance to send him back to your website.
In both messages (PDT and IPN) Paypal tells you if the payment cleared or not. As you probably know, some payment types do not clear until a few days after they are submitted to Paypal. Paypal recommends that you don't ship the product until the payment has cleared. Paypal will send you another IPN message once the payment is cleared.
I have two scripts on my website - one to handle the PDT messages and another one to handle the IPN messages. They are very similar when handling payment confirmation. See below an example:
$req = 'cmd=_notify-synch';
$tx_token = $_GET['tx'];
$auth_token = "enter your own authorization token";
$req .= "&tx=$tx_token&at=$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";
//$fp = fsockopen ('http://www.sandbox.paypal.com', 80, $errno, $errstr, 30);
// If possible, securely post back to paypal using HTTPS
// Your PHP server will need to be SSL enabled
// replace www.sandbox.paypal.com with www.paypal.com when you go live
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
if (!$fp)
{
// HTTP ERROR
}
else
{
fputs ($fp, $header . $req);
// read the body data
$res = '';
$headerdone = false;
while (!feof($fp))
{
$line = fgets ($fp, 1024);
if (strcmp($line, "\r\n") == 0)
{
// read the header
$headerdone = true;
}
else if ($headerdone)
{
// header has been read. now read the contents
$res .= $line;
}
}
// parse the data
$lines = explode("\n", $res);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0)
{
for ($i=1; $i<count($lines);$i++)
{
list($key,$val) = explode("=", $lines[$i]);
$keyarray[urldecode($key)] = urldecode($val);
}
$firstname = $keyarray['first_name'];
$lastname = $keyarray['last_name'];
$itemname = $keyarray['item_name'];
$amount = $keyarray['payment_gross'];
$invoiceid = $keyarray['invoice'];
$profileid = $keyarray['subscr_id'];
// check the payment_status is Completed
// check that txn_id has not been previously processed
// check that receiver_email is your Primary PayPal email
// check that payment_amount/payment_currency are correct
// process payment
// show receipt to client
}
}
I hope this helps. Please feel free to ask follow up questions.
Good luck!