When an order is processed, PayPal generates a notification that is then posted to the URL specified in the form as the hidden field notify_url. This is an example of a web service that relies on ordinary HTTP?no fancy SOAP or other RPC underpinnings.
|
Example 6-2 shows the JSP used to receive notifications from the PayPal server.
<%@ page contentType="text/html; charset=iso-8859-1" language="java" import="com.cascadetg.ch06.*" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <% new PayPalReciept( ).handleRequest(request, response); response.setStatus(200); %>
The line response.setStatus(200) notifies PayPal that the notification has successfully been handled. By default, PayPal resends the notification until it gets a status code of 200 back, indicating that the transmission was successful. Disabling this line or substituting a different response code is an easy way to generate additional test load for your server without having to manually enter a lot of transactions.
The bulk of the work for this application is handled by supporting Java classes, as shown in Figure 6-5.
The first class, shown in Example 6-3, exposes one main method, handleRequest(), to the JSP page. In the handleRequest( ) method, the first thing to be done is verify that the data sent by PayPal is correct with the verifyRequest( ) method. The verifyRequest( ) method retrieves the parameters sent by PayPal and then posts them back to PayPal via an HTTPS connection, with an additional parameter added (cmd=_notify-validate).
package com.cascadetg.ch06; import java.util.*; import java.net.*; import java.io.*; import javax.servlet.http.*; import javax.mail.*; import javax.mail.internet.*; public class PayPalReciept { // Used to store retrived values from the PayPal submission. String paymentStatus; String txnId; String receiverEmail; String payerEmail; private static final boolean debug = false; // System.getProperty line to get the proper new line character[s]. String newLine = System.getProperty("line.separator", "."); // Keep track of sent transactions. Note that this is in-memory // storage only - for a "real" system you would want to persist // this information, as well as the rest of fields of this object. // (mostly likely to a database of some sort). private static Hashtable processedTxnId = new Hashtable( ); // This method takes an incoming request and validates it both // against the PayPal server and some internal logic. public boolean validateRequest(HttpServletRequest request) { try { // Read the post from PayPal system. Enumeration parameters = request.getParameterNames( ); // We then add a "cmd" attribute to send back to PayPal // to indicate that we want to validate the request. StringBuffer send = new StringBuffer("cmd=_notify-validate"); // Here, we put all of the parameters passed in from the // PayPal notification POST. while (parameters.hasMoreElements( )) { String paramName = (String)parameters.nextElement( ); String paramValue = request.getParameter(paramName); send.append("&"); send.append(paramName); send.append("="); send.append(URLEncoder.encode(paramValue)); } if (debug) System.out.println(send.toString( )); // This next sequence opens a connection to the PayPal // server, sets up the connection, and writes the sent // parameters back to the PayPal server. URL paypalServer = new URL("https://www.paypal.com/cgi-bin/webscr"); URLConnection paypalConnection = paypalServer.openConnection( ); paypalConnection.setDoOutput(true); paypalConnection.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded"); PrintWriter paypalServerWriter = new PrintWriter(paypalConnection.getOutputStream( )); paypalServerWriter.println(send); paypalServerWriter.close( ); if (debug) System.out.println("Sent to PayPal server."); // We then need to read the response from the PayPal // server. BufferedReader in = new BufferedReader( new InputStreamReader( paypalConnection.getInputStream( ))); String paypalResponse = in.readLine( ); in.close( ); if (debug) System.out.println( "Read PayPal server response = " + paypalResponse); // Set the values of this object from the values sent // by the initial request. If these values are verified, // we'll want them for later. If the values aren't // verified, or something else is wrong, we'll want // to track them for logging purposes. setValues(request); // If everything is ok, the response back should be // VERIFIED. Otherwise, something went wrong. if (paypalResponse.equals("VERIFIED")) { // If it isn't completed, it's a status message of // some sort. We're only interested in Completed // payments. if (!paymentStatus.equals("Completed")) return false; // Make sure that we are the actual recipients of // the money. if (receiverEmail .compareToIgnoreCase(PayPalTokens.paypalEmail) != 0) return false; // Check the in-memory cache to verify that we // haven't already handled this transaction. if (processedTxnId.get(this.getTxnId( )) != null) return false; // Everything looks good, so let's add this to the // transaction cache. processedTxnId.put(this.getTxnId( ), this.getTxnId( )); return true; } else { System.out.println("Invalid PayPal transaction!"); System.out.println(this.toString( )); return false; } } catch (Exception e) { System.out.println("Unable to connect to PayPal server."); e.printStackTrace( ); return false; } } // "Flatten" the object to a String. public String toString( ) { StringBuffer output = new StringBuffer( ); Enumeration outEnum = this.getAttributes( ).keys( ); while (outEnum.hasMoreElements( )) { String outputStr = (String)outEnum.nextElement( ); output.append(outputStr); output.append(" : "); output.append(paypalAttributes.get(outputStr).toString( )); output.append(newLine); } return output.toString( ); } public String toHTMLString( ) { StringBuffer htmlString = new StringBuffer( ); htmlString.append("<HTML><BODY>"); htmlString.append("<TABLE HEIGHT='100%' WIDTH='100%'>"); htmlString.append("<TR><TD>"); Enumeration myValues = this.getAttributes( ).keys( ); while (myValues.hasMoreElements( )) { String next = (String)myValues.nextElement( ); htmlString.append(next); htmlString.append(" : "); htmlString.append(this.getAttribute(next).toString( )); htmlString.append("<BR>"); htmlString.append(newLine); } htmlString.append("</TD></TR></TABLE></BODY></HTML>"); return htmlString.toString( ); } // PayPal can send a variety of attributes back as part of a // transaction. We're interested in all of them, so we'll note // them all. Hashtable paypalAttributes = new Hashtable( ); public String getAttribute(String attribute) { return (String)paypalAttributes.get((String)attribute); } public Hashtable getAttributes( ) { return paypalAttributes; } /** * Reads the incoming values and fills out the object. Notice that * we are also recording the incoming IP address - if someone is * sending fake requests, the IP address can be an important bit of * information. * * @param request */ private void setValues(HttpServletRequest request) { paypalAttributes = new Hashtable(request.getParameterMap( )); Enumeration attributes = request.getParameterNames( ); while (attributes.hasMoreElements( )) { String temp = (String)attributes.nextElement( ); paypalAttributes.put(temp, request.getParameter(temp)); } paypalAttributes.put( "incoming_ip", request.getRemoteAddr( ).toString( )); paymentStatus = request.getParameter("payment_status"); txnId = request.getParameter("txn_id"); receiverEmail = request.getParameter("receiver_email"); payerEmail = request.getParameter("payer_email"); } /** * The main entry point from the JSP page request. We'll look at * the request and validate it, and depending on the results, we'll * either send a notification email OR a fax and a notification * email. */ public void handleRequest( HttpServletRequest request, HttpServletResponse response) { if (validateRequest(request)) { if (debug) System.out.print("Sending fax... " + this.toString( )); FaxSender myFaxSender = new FaxSender( ); myFaxSender.setText(this.toHTMLString( )); myFaxSender.setTextType(FaxSender.HTML); if (debug) System.out.print("fax prepped..."); myFaxSender.sendFax( ); this.paypalAttributes.put( "fax_id", myFaxSender.getResult( ).toString( )); if (debug) System.out.println("Fax sent."); } sendEmail(this.toString( )); } // These are the usual retrieval methods a'la the JavaBean // patterns. Notice that there are only retrieval methods - no // setters are provided. public String getPayerEmail( ) { return payerEmail; } public String getPaymentStatus( ) { return paymentStatus; } public String getReceiverEmail( ) { return receiverEmail; } /** Returns the transaction ID (aka TxnId) */ public String getTxnId( ) { return txnId; } public void sendEmail(String text) { try { java.util.Properties myProperties = new Properties( ); myProperties.put("mail.smtp.host", PayPalTokens.mailhost); myProperties.put("mail.smtp.auth", "true"); Session mySession = Session.getInstance(myProperties); //mySession.setDebug(true); Transport myTransport = mySession.getTransport("smtp"); myTransport.connect( PayPalTokens.mailhost, PayPalTokens.mailhost_username, PayPalTokens.mailhost_password); Message myMessage = new javax.mail.internet.MimeMessage(mySession); myMessage.setFrom( new InternetAddress( PayPalTokens.paypalEmail, "PayPal Listener")); myMessage.addRecipient( Message.RecipientType.TO, new InternetAddress(PayPalTokens.paypalEmail)); myMessage.setSubject("PayPal Notification"); myMessage.setContent(this.toHTMLString( ), "text/html"); Address[] temp = { new InternetAddress(PayPalTokens.paypalEmail)}; myMessage.setReplyTo(temp); myMessage.saveChanges( ); myTransport.sendMessage( myMessage, myMessage.getAllRecipients( )); myTransport.close( ); } catch (Exception e) { e.printStackTrace( ); } } public static void main(String[] args) { (new PayPalReciept( )).sendEmail("test"); } }
If PayPal doesn't respond with a VERIFIED token, it's possible that someone is attempting to forge a transaction. If you see this occur in a real world environment, you should take immediate, aggressive steps to deal with this; it may be an attempt by a hacker to steal funds or even your identity.