Showing posts with label IPN. Show all posts
Showing posts with label IPN. Show all posts

Friday, July 23, 2010

Setting the notifyURL for the PayPal IPN

In the previous post we saw how to implement a PayPal IPN listener. This is a quick post to show you how to set this listener using PayPal Website Payments Standard.

First you create your "Buy Now" (or "Add to Cart", "Subscribe"...) button. You can do this at https://www.paypal.com/cgi-bin/webscr?cmd=_wp-standard-overview-outside. This wizard creates a form with a couple of hidden parameters. It doesn't set the notify url however, which is the url PayPal IPN needs to call to notify you the user made a purchase. You can add this url to the form as follows:

<input type="hidden" name="notify_url" value="<h:outputText value="#{paypalBean.notifyURL}"/>" />

This sample uses JSF to create the URL, but you can use any tag here (or use a hardcoded URL).

Here is the code that generates the URL:

public String getNotifyURL() {
HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
String nUrl = getPath("PaypalListenerServlet");
return response.encodeURL(nUrl);
}

public final static String getPath(String path) {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
StringBuffer url = new StringBuffer();
String s = request.getProtocol();
url.append(s.substring(0, s.indexOf('/')).toLowerCase());
url.append("://");
url.append(request.getServerName());
url.append(":");
url.append(request.getServerPort());
url.append(request.getContextPath());
if (path.charAt(0) != '/') {
url.append("/");
}
url.append(path);
return url.toString();
}

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.