6.2 Getting a Transaction Notification

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.

Using a simple HTTP request/response is perhaps the most basic, universal real world web service. It works with virtually every programming language and requires no special configuration to use. It's a classic case of the simple solution being the best solution.


Example 6-2 shows the JSP used to receive notifications from the PayPal server.

Example 6-2. Notification JSP
<%@ 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.

Figure 6-5. PayPal and Fax classes
figs/rww_0605.gif


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).

Example 6-3. A PayPal receipt
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.