Hack 73 Implement Price Checking with IPN

figs/moderate.gif figs/hack73.gif

Prevent fraudulent transactions by comparing the value of the goods purchased with the amount received.

PayPal has taken many steps to ensure that their system is secured against fraudulent transactions. However, just like any online eCommerce system, there are always ways for an unscrupulous person to attempt to cheat you. The PayPal Buy Now and Shopping Cart buttons, for example, are normally displayed as plain text in your web page's source code, which means that anyone can view your HTML code or?more importantly?copy and modify the code, and then submit a spoof payment (presumably with a lower price) to PayPal. And since PayPal doesn't maintain an active database of all the current prices of your products, it's up to you to engage in some proactive price checking.

See [Hack #36] for ways to hide your payment button code from customers and reduce the possibility of spoofed payments.


Obviously, the primary concern is the price, given how easily it can be changed from, say, $18.00 to $.18. While a merchant who is able to view each and every transaction will likely notice when a $100 item was purchased for $.01, but it can, of course, be easy to miss this kind of thing, especially for high-volume merchants. And if you have a fully automated fulfillment system, such as for digital goods [Hack #71], you'll need to employ some sort of price checking.

The following solution employs the trusty IPN system to check whether a customer has paid the correct amount.

7.13.1 Simple Price Checking with Single Item Purchases

The PayPal IPN system posts the variables as they were originally submitted to PayPal, so a spoofed price will be reflected in our IPN postback from PayPal. Because PayPal does not store any of your product information on their servers, you have to query your product information to ensure it matches the price the customer paid.

To use a price-checking system on your site, you need to be able to run a dynamic server page technology (e.g., ASP, as is used in this example) and a simple database (e.g., Microsoft Access). The table in this example, tblProducts, has only two columns: item_number, containing a list of all of the unique product numbers, and item_price, in which the corresponding prices are stored. Naturally, your product database will be more sophisticated, but it will likely have analogous fields.

Here is some skeleton code, written in ASP, that does rudimentary price checking for items purchased with Buy Now buttons:

'Declare and populate our price checking variables

Dim item_number, item_amount

item_number = Request.Form("item_number")

item_amount = "Request.Form("mc_gross")



'Connect to database and create recordset

connStore = "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=

                "C:/InetPub/wwwroot/database/dbPayPal.mdb")

set rsPriceCheck = Server.CreateObject("ADODB.Recordset")

rsPriceCheck.ActiveConnection = connStore

rsPriceCheck.Source = "SELECT item_price FROM tblProducts 

                WHERE item_number = 'item_number'"

rsPriceCheck.Open( )



'Compare the values to see if amount paid is equal to or 

                greater than required



If rsPriceCheck("item_amount") >= item_amount Then

 'Price paid is at least as much as required, process order

 'Order processing code here

Else

 'Price paid is less than required, stop order processing

 'Send alert to purchaser and merchant

End If

See the "Database Coding and Platform Choices" section of the Preface for the additional information needed to put this SQL statement to work with this and the other hacks in this book.


This code relies on the mc_gross variable, which is equal to the purchase price plus any shipping, handling, or tax charges applied to the order (note that mc_gross does not include the deduction of any applicable PayPal fees).

This code merely checks to see if the price paid is equal to or higher than the price in your database. You'll want to account for shipping and sales tax, because these values can also be spoofed by customers.


7.13.2 Price Checking for Shopping Cart Purchases

PayPal does not pass back individual item prices in Shopping Cart transactions. If a customer buys three items worth $1.00, $2.00, and $5.00, respectively (and agrees to pay $3.50 for shipping), you don't get any of those individual values in the IPN data. Rather, the mc_gross variable will have a value of the total amount paid (in this case, $11.50). Thankfully, PayPal does pass back the individual item_number fields, which means that you can still look up the individual prices in your database.

In the long run, however, it might be easier to keep a running total on file for each customer's Shopping Cart so that you can easily cross-check this value with the amount paid.


As described in [Hack #45] and [Hack #50], the PayPal Shopping Cart system returns an item number for each item in the cart. The variables are in the form item_numbern, where n is the cart number for that item, starting with 1. PayPal also provides the num_cart_items variable to indicate the number of items in the cart. To verify the order, add the values of each item as listed in your database and compare the total to the gross amount paid:

Dim item_number, mc_gross, I, num_cart_items, price_check

mc_gross = Request.Form("mc_gross")

num_cart_items = Request.Form("num_cart_items")

price_check = 0



For i=1 to num_cart_items



 'Populate variable with value

 item_number = Request.Form("item_number" & i)



 'Execute SQL query on database with item_number value

 'Connect to database and create recordset

  connStore = "DRIVER={Microsoft Access Driver (*.mdb)};

                DBQ="C:/InetPub/wwwroot/database/dbPayPal.mdb")

  set rsPriceCheck = Server.CreateObject("ADODB.Recordset")

  rsPriceCheck.ActiveConnection = connStore

  rsPriceCheck.Source = "SELECT item_price FROM tblProducts 

                WHERE item_number = 'item_number'"

  rsPriceCheck.Open( )



 'Add value from database to our running count

 price_check = price_check + rsPriceCheck("item_price")



Next



If price_check = mc_gross OR price_check > mc_gross Then

 'Price paid is at least as much as required, process order

 'Order processing code here

Else

 'Price paid is less than required, stop order processing

 'Send alert to purchaser and merchant

End If

This ASP code assumes a quantity of 1 for each cart item. If you offer multiple quantities of items, you will need to take the item_quantity value into account by multiplying the database price value by the price of the item.


7.13.3 Sending a Price Check Alert

Once you see a problem with an order, it's up to you to send an alert. Use any simple mail component available to you and your server technology. In ASP, you can use the Common Data Objects (CDO) mail component with the following code:

Dim PriceErrorCDO

Set PriceErrorCDO = Server.CreateObject("CDOSYS.NewMail")

PriceErrorCDO.From = Request.Form("receiver_email")

PriceErrorCDO.To = Request.Form("receiver_email")

PriceErrorCDO.CC = Request.Form("payer_email")

PriceErrorCDO.Subject = "IPN Price Checking Error"

PriceErrorCDO.Body = "There has been a price-checking error

         on the following transaction: " & Request.Form("txn_id") & ""

PriceErrorCDO.Send( )

Set PriceErrorCDO = Nothing

This email alert code sends an email to the recipient of the payment (you), but you might also want to send an automatic email to the customer to indicate that there will be a delay in processing the order.

If you want a truly automated system, you can simply refund any irregular payments using the PayPal API [Hack #91] .