Friday, July 23, 2010

PayPal Instant Payment Notification (IPN) Servlet with HttpClient 4

Instant Payment Notification (IPN) is PayPal's message service that sends a notification when a transaction is affected. Basically it's a url that gets called by PayPal once a transaction has been successfully completed. This allows your code to do something (send a mail, write to a log...) once the user has made a purchase to PayPal. See https://www.paypal.com/ipn for more information.

Here's how it works: once the user has completed his transaction, PayPal will call this URL for you. A number of parameters (including transaction id, amount, currency...) are passed. What you have to do is create a new HTTP POST request to PayPal with the exact same parameters that PayPal passed to you. If the request originated from the PayPal servers, PayPal will reply with "VERIFIED", else it will send "INVALID". This mechanism is put into place to prevent anyone else than PayPal itself calling your IPN URL and faking a transaction. See https://www.paypal.com/us/cgi-bin/webscr?cmd=p/acc/ipn-info-outside for more information.

There are different ways this can be implemented. PayPal has some java samples available for download (https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/library_download_sdks). In these samples, they implement the IPN as a JSP page. Not only is this ugly (they put plain java code in JSP's), it's also inefficient: there is no output for the user (since this IPN isn't called by a browser), so there is no page to show. It's much better to implement this as a servlet. I used the latest HttpClient (4.0.1 at the time of this writing, http://hc.apache.org/httpcomponents-client/index.html) to implement the callback to PayPal.

Here's the code:

public class PaypalListenerServlet extends HttpServlet {

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(Constants.PAYPAL_URL);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("cmd", "_notify-validate")); //You need to add this parameter to tell PayPal to verify
for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements();) {
String name = e.nextElement();
String value = request.getParameter(name);
params.add(new BasicNameValuePair(name, value));
}
post.setEntity(new UrlEncodedFormEntity(params));
String rc = getRC(client.execute(post)).trim();
if ("VERIFIED".equals(rc)) {
//Your business code comes here
}
}

private String getRC(HttpResponse response) throws IOException, IllegalStateException {
InputStream is = response.getEntity().getContent();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String result = "";
String line = null;
while ((line = br.readLine()) != null) {
result += line;
}
return result;
}

}

I create a new HttpClient for each request. Do NOT use a single HttpClient per servlet: although HttpClient IS thread-safe, it only uses one thread, meaning it can only process one request at a time. If you're worried about the overhead of creating a new HttpClient for each request (I wouldn't be) you can still consider binding one in the session context.

8 comments:

  1. Thanks a lot - very useful post!

    ReplyDelete
  2. nice, but "Constants.PAYPAL_URL" what should be here?

    ReplyDelete
  3. disc: you replace this by the paypal URL. This is https://www.sandbox.paypal.com/cgi-bin/webscr for the sandbox environment and https://www.paypal.com/cgi-bin/webscr for the live environment.

    ReplyDelete
  4. One question:
    If i make a donation to xxx@x.xx in the sandbox Paypal site.
    with HttpResponse I can get the status of VERIFIED if I make a notification ipn knowing receiver_email, payer_email and payment_gross?

    ReplyDelete
  5. Why I get "INVALID" response every time ? The sandbox IPN tool which I'm using to send the messages says that everything is ok but in the code the rc variable is always "INVALID"? Thanks in andvance.

    ReplyDelete
  6. I am glad to find your impressive way of writing the post.Thanks for sharing the post.i'm sharing your information to all friends.If you
    Want more details kindly click cash on credit card

    ReplyDelete
  7. I create a new HttpClient for each request. Do NOT use a single HttpClient per servlet: alkaram lawn silk dupatta , 2 piece lawn suit online pakistan although HttpClient IS thread-safe, it only uses one thread, meaning it can only process one request at a time. If you're worried about the overhead of creating a new HttpClient for each request (I wouldn't be) you can still consider binding one in the session context.

    ReplyDelete