Hack62.Add a Buy Now Button


Hack 62. Add a Buy Now Button

Use PayPal to add a Buy Now button to your PHP web application.

PayPal's Buy Now buttons are ideal for e-commerce impulse sales. They are easy for the customer to use, and there's no lengthy checkout procedure. One click and you're on PayPal's secured site.

These buttons are also very easy to install; because of their simplicity, though, web applications often don't put in the extra work required to track and secure sales. This hack shows how to track and secure purchases made using Buy Now buttons. I will take you through the steps of creating a Buy Now button, modifying it to create a database record of the purchase, and I'll show how to secure the purchase using Instant Payment Notification (IPN).

This hack requires PHP 5 and MySQL Version 4.1.3 or higher, along with the mysqli extension.


6.13.1. Creating a Buy Now Button

You don't need to worry about manually creating the HTML form for a Buy Now button. Just go to your PayPal account, choose Merchant Tools, and click on the Buy Now Buttons link. Within seconds, you'll have the HTML form ready to embed into your web site. The Buy Now button looks like Figure 6-31.

Figure 6-31. The Buy Now button


As it stands right now, this form has a couple of shortcomings. It's not geared toward tracking purchases in a database, and like any form on the Internet, it is subject to hijacking. A local copy of the form can be made, and values can be changed, all in an effort to sabotage your site (and your customers); even worse, if there is no way of crosschecking the values from this form against a correct set of values, an order might easily be processed at discounted prices.

The buynow.html file in Example 6-37 (shown shortly) contains the HTML for the Buy Now button. It differs in one major respect from the code generated by PayPal: the action attribute of the form tag doesn't point to the PayPal site, but to another script, presubmit.php (shown in Example 6-39). This intermediary script allows information to be added to a database before submitting purchases to PayPal.

6.13.2. Tracking the Sale

Briefly stated, the code in presubmit.php inserts a record into a tblorders table and into a related tblorderitems table. The order ID is then retrieved and added to the query string constructed from the values posted to this page. Finally, this query string is forwarded to the PayPal site, as shown in Figure 6-32.

Figure 6-32. The PayPal checkout page


Creating a database entry and passing the order number along to PayPal will assist in tracking and securing the purchase.

Since you're accessing the database using the relatively new object-oriented (OO) interface to the mysqli extension, a few comments are in order. Even if you have no experience in OO programming, it is easy to understand the code that inserts a record into the orders table. Since this table has an auto_increment field, it's necessary to retrieve the order ID after insertion, so as to identify the order later.

The way you insert a record into the tblorderitems table is not as straightforward. To create a record in this table, the code uses a prepared statement; the mysqli extension supports this MySQL 4.1 capability. Prepared statements are commonly used to insert multiple records into a database and are much more efficient than a series of individual SQL statements. However, the code here uses a prepared statement because data passed to a prepared statement does not have to be escaped first. Prepared statements automatically escape data (a nice convenience feature taken advantage of here).

6.13.3. Verifying the Sale

Securing a payment means ensuring that the payment is made to the correct account in the correct amount and is not a duplicate of an earlier transaction. IPN allows us to do this programmatically by identifying a URL that will receive notification of payment.

When a purchase is made at the site, the sequence of events is as follows:

  1. Clicking the Buy Now button invokes the script, creating a database record, and then forwards the buyer to the PayPal site.

  2. After PayPal receives payment, a hidden post is made to the IPN URL. This post contains encrypted code and information about the payment.

  3. To ensure that this post is not specious and did in fact originate at PayPal, we must return this post to PayPal.

  4. PayPal will then respond, verifying that the post originated with them.

verifypurchase.php (in Example 6-40) contains the code that confirms the source of the post and validates the data. It can be summarized as follows:

  1. The PayPal post is captured and resubmitted, adding the name/value pair cmd=_notify-validate as required by PayPal.

  2. If this resubmitted post is verified as having originated from PayPal, the code confirms the details of the purchase.

  3. The price for the specific item is retrieved from the database and is compared to the posted value.

  4. The code then makes sure that the transaction ID is not a duplicate of an earlier purchase and, by verifying the receiver's email, ensures that the payment has been made into the correct account.

A few comments on less-obvious features of the code are in order. Using the CURL package is not the only way to handle the resubmission of posted values to PayPal, but it certainly does make it easy. When using IPN, the names of some of the values retrieved are different from the ones originally posted. receiver_email is synonymous with the business email posted from the form. mc_gross holds the payment amount and, in this example, its value should be equal to the price of the purchased item. On the other hand, item_ number and custom have not changed at all. Again, access to the database is through the OO interface of mysqli.

Depending on server settings, the port and socket settings might or might not be required when creating a connection object. Notice also that you can specify the database when creating the connection. Also, since this code uses (and depends on) PHP 5, objects are automatically passed by reference, and there is no need to adjust syntax when objects are function parameters.


6.13.4. The Code

Save the code in Example 6-37 as buynow.html.

Example 6-37. An example Buy Now page
 <html> <head> <title>Buy Now Button</title> </head> <body> <!-- alter action of form--> <form action="presubmit.php" method="post"> <label>Purchase: Object-Oriented PHP for 24.95</label> <input type="hidden" name="cmd" value="_xclick" /> <input type="hidden" name="business" value="seller@myisp.com" /> <input type="hidden" name="item_name" value="Object-Oriented PHP" /> <input type="hidden" name="item_number" value="673498" /> <input type="hidden" name="amount" value="24.95" /> <input type="hidden" name="no_note" value="1" /> <input type="hidden" name="currency_code" value="USD" /> <input type="image" src="/books/1/201/1/html/2/https://www.paypal.com/en_US/i/btn/x-click-but23.gif" border="0" name="submit" alt="" /> </form> </body> </html> 

Save the code in Example 6-38 as connection.php.

Example 6-38. The connection information
 <?php  $hostname = "localhost";  $databasename = "books";  $username = "username";  $password = "password"; ?> 

Save the code in Example 6-39 as presubmit.php.

Example 6-39. The script that stores the order on the way to PayPal
 <?php  include "connection.php";  //create a connection using mysqli $con = new mysqli($hostname, $username, $password, $databasename, 3306, "/var/lib/mysql/mysql.sock"); //create an order id $strsql = "INSERT INTO tblorders SET orderdate = CURDATE( )"; $con->query($strsql); //retrieve insertid - property not method $id = $con->insert_id; $item_number = $_POST['item_number']; //now add order item to db $strsql = "INSERT INTO tblorderitems SET orderid = ?, ". "inventorynumber = ?"; //use statement even though only one insert $stmt = $con->stmt_init( ); $stmt->prepare($strsql); //bind integer and string values $stmt->bind_param('ii', $id, $item_number); $stmt->execute( ); //resubmit $querystring = "?"; //loop for posted values foreach($_POST as $key => $value) { $value = urlencode(stripslashes($value)); $querystring .= "$key=$value&"; } //update querystring with order id //use "custom" not "on0" for order id value $querystring .= "custom=$id"; header('location:https://www.paypal.com/cgi-bin/webscr'.$querystring); exit( ); ?> 

Save the code in Example 6-40 as verifypurchase.php.

Example 6-40. The purchase verification script
 <?php include "connection.php"; ////////////////////////////////////////////////////////////// function check_txnid($con, $txnid) { $valid_txnid = false; //get result set $strsql = "SELECT * FROM tblorders ". " WHERE txnid = '$txnid'"; $rs = $con->query($strsql); if($rs->num_rows == 0) { $valid_txnid = true; } return $valid_txnid; } ////////////////////////////////////////////////////////////// function check_price($con, $price, $inventoryid) { $valid_price = false; //get result set $strsql = "SELECT listprice FROM tblbooks ". " WHERE inventorynumber = '$inventoryid'"; $rs = $con->query($strsql); $row = $rs->fetch_array(); $num = (float)$row[0]; if($num == $price) { $valid_price = true; } return $valid_price; } ////////////////////////////////////////////////////////////// function check_email($email) { $valid_email = false; //compare to paypal merchant email if($email == "seller@myisp.com" ) { $valid_email = true; } return $valid_email; } ////////////////////////////////////////////////////////////// function do_post($data) { //now send back to paypal $c = curl_init('https://www.paypal.com/cgi-bin/webscr'); curl_setopt($c, CURLOPT_POST,1); curl_setopt($c, CURLOPT_POSTFIELDS, $data); curl_setopt($c, CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); $status = curl_exec($c); curl_close($c); return $status; } ////////////////////////////////////////////////////////////// //loop for posted values $data = ""; foreach($_POST as $key => $value) { $value = urlencode(stripslashes($value)); $data .= "$key=$value&";  }  //must add this before returning to paypal  $data .= "cmd=_notify-validate";  $status = do_post($data); //strip CR  $status = rtrim($status);  $payment_status = $_POST['payment_status'];  //get transaction id $txn_id = $_POST['txn_id'];  if ($status == "VERIFIED" && $payment_status == "Completed")  { //need these variables  $price = $_POST['mc_gross'];  //get order number $orderid = $_POST['custom'];  $inventoryid = $_POST['item_number'];  //merchant's email i.e. paypal account  //equals business in paynow.html $receiver_email = $_POST['receiver_email'];  //create a mysqli connection  $con = new mysqli($hostname, $username, $password, $databasename, 3306, "/var/lib/mysql/mysql.sock"); //check merchant email, price & not recycled txn id //no need to change syntax to pass object by reference $valid_txnid = check_txnid($con, $txn_id); $valid_price = check_price($con, $price, $inventoryid); $valid_email = check_email($receiver_email); //if all checks write record if($valid_price && $valid_email && $valid_txnid) { //update database with txn id $strsql = "UPDATE tblorders SET txnid = '$txn_id' ". "WHERE orderid = $orderid"; $con->query($strsql); $message ="Successful, transaction id: $txn_id\n"; } else { //unsuccessful transaction $message ="Unsuccessful, transaction id: $txn_id\n"; }  }  else if($status == "INVALID")  { //notify suspicious transaction $message ="Suspicious IPN with transaction id: $txn_id";  }  else  { //deal with other types $message ="Incomplete purchase with transaction id: $txn_id"; } mail ("notify@myisp.com", "PayPal", $message); ?> 

6.13.5. Running the Hack

First, you will need a PayPal account. Create one by going to the PayPal home page and signing up for a business account.

Then you need to alter the sample scripts and pages to match your application specifications. Your buynow.html file will of course reflect the product you are selling. You will also need to change the email addresses in both the buynow.html file and the verifypurchase.php script. Replace seller@myisp.com with the email address associated with your PayPal account. This is important because it identifies the account that will receive payment. Change notify@myisp.com to the appropriate address for receiving confirmation of payment.

You might not need a payment confirmation at all, or you might want to replace it with code to write a logfile, especially in the case of a failed payment.


Change the connection.php script to reflect values appropriate to your MySQL server. No changes are required for the presubmit.php script unless you change the database structure.

You will doubtless create a database suited to your specific business needs, but if you want to test this code as is, here are the SQL statements that will create the minimum required database structure:

 CREATE TABLE `tblbooks` (  `inventorynumber` int(11) NOT NULL auto_increment,  `title` varchar(150) NOT NULL default '',  `author` varchar(100) NOT NULL default '',  `cost` float(6,2) NOT NULL default '0.00',  `listprice` float(7,2) NOT NULL default '0.00',  `publicationdate` varchar(4) default NULL,  `publisher` varchar(4) NOT NULL default '',  PRIMARY KEY (`inventorynumber`),  KEY `authidx` (`author`),  KEY `titleidx` (`title`),    ) ENGINE=MyISAM DEFAULT CHARSET=latin1    CREATE TABLE `tblorders` (  `orderid` int(11) NOT NULL auto_increment,  `customerid` int(11) default NULL,  `orderdate` date default NULL,  `txnid` varchar(17) default NULL,  PRIMARY KEY (`orderid`)    ) ENGINE=MyISAM DEFAULT CHARSET=latin1    CREATE TABLE `tblorderitems` (  `orderid` int(11) NOT NULL default '0',  `inventorynumber` int(11) NOT NULL default '0',  PRIMARY KEY (`orderid`,`inventorynumber`)    ) ENGINE=MyISAM DEFAULT CHARSET=latin1 

Next, upload the files to your server, ensuring that the connection.php, buynow.html, and presubmit.php files are in the same directory. You can put the verifypurchase.php script in the same directory as well, but it's probably better off in its own directory. If you do put this script in a separate directory, be sure to change the include path for the connection.php file.

Go to your PayPal account, turn on IPN, and enter the fully qualified URL for the verifypurchase.php script. To make a purchase, point your browser at buynow.php. You will know that everything is working when you click on the Buy Now button and are taken to the PayPal site, and when payment is complete you receive an email containing the transaction ID.

6.13.6. Hacking the Hack

One size never fits all. In this particular case, the price of the individual item purchased is identical to the total price. However, in many cases, shipping charges might need to be added and different currencies taken into account. You can easily accommodate such changes by adjusting the check_price( ) function.

Use of mysqli is not a requirement, although it is apparent that prepared statements can be a real advantage, especially when processing a shopping cart rather than a single item.

Signing up for a PayPal developer account makes sense for someone who regularly develops PayPal applications. It certainly is an advantage to use the PayPal sandbox to test applications before going live with them. This is especially important for an application like IPN, where there is only server-to-server interaction and no user input.

Debugging an automated server response also presents challenges, as there's no browser to display errors. One option is to email the variable that holds the data reposted to PayPal. Email this value; you should see something like the following:

 mc_gross=24.95&address_status=confirmed&payer_id=TYWM55XFZCN8S &tax=0.00&address_street=36+Main+Street &payment_date=16%3A00%3A32+Aug+11%2C+2005+PDT &payment_status=Completed&charset=windows-1252&address_zip=12345 &first_name=Peter&mc_fee=0.82&address_country_code=US  &address_name=Peter+Buyer&notify_version=1.9&custom=20 &payer_status=unverified&business=seller%40isp.com &address_country=United+States&address_city=Toledo&quantity=1 &verify_sign=ACUe-E7Hjxmeel8FjYAtjnx-yjHAAVhtx75Yq6UdimmRaeJhnewr0ugZ &payer_email=buyer%40myisp.com&txn_id=1E044782YK461110T &payment_type=instant&last_name=Buyer&address_state=OH &receiver_email=seller%40isp.com&payment_fee=0.82 &receiver_id=JEFVKNSSDLTBL&txn_type=web_accept &item_name=Object+Oriented+PHP&mc_currency=USD &item_number=06734980548&test_ipn=1&payment_gross=24.95 &shipping=0.00&cmd=_notify-validate 

Do this, and you can confirm all the name/value pairs posted.

Peter Lavin

6.13.7. See Also

  • "Create a Shopping Cart" [Hack #66]



PHP Hacks
PHP Hacks: Tips & Tools For Creating Dynamic Websites
ISBN: 0596101392
EAN: 2147483647
Year: 2006
Pages: 163

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net