I am implementing a PayPal IPN Controller (currently in Java) using Play Framework 2.0, which receives a form-encoded POST request from the PayPal server. I am adapting a Play 1.x controller from blog post by Guillame Leone to work with Play 2.0. Since "params" are now gone, this tasks seems a little tricky.
There are two steps to the PayPal IPN process:
1) Parse the request body as text, so I can submit the exact response to PayPal that they want. It must be the same string in the same order as they submit to me. This currently works:
@BodyParser.Of(BodyParser.TolerantText.class)
public static Result validation() throws Exception {
Logger.info("Received IPN request");
String str = "cmd=_notify-validate&" + request().body().asText();
Logger.info("Validation string: " + str);
2) Send validation request to PayPal, and when they respond with "VERIFIED" response I want to decode the form-encoded request body and access it as a map. But since I have already parsed the request body as text using the annotation, the following returns null instead of a map:
Map<String,String[]> data = request().body().asFormUrlEncoded() //returns null
What is the best way to parse this request twice, once as text (ignoring the form encoded MIME type) and a second time as form-encoded data?
(It seems like I might want to get rid of the annotation and directly call the TolerantText and FormEncoded parsers, but I have no idea how to do that!)
It appears that there is no direct way to do this using Play Java APIs, due to to interoperability problems between Java and Scala collections.
To access the form data using Java collections, I had to write a Scala helper:
object BodyParserUtils {
def parseTextAsFormUrlEncodedForJava(textBody: String) = {
import collection.JavaConverters._
val scalaMap = FormUrlEncodedParser.parse(textBody)
scalaMap.mapValues(_.asJava).asJava
}
}
And then I am able to set the data variable:
Map<String, List<String>> data = BodyParserUtils.
parseTextAsFormUrlEncodedForJava(request().body().asText());
And in case you want a bare (yet complete) Play 2.0 Java controller to start receiving PayPal IPN requests here is the whole thing:
public class PaypalController extends Controller {
@BodyParser.Of(BodyParser.TolerantText.class)
public static Result validation() throws Exception {
Logger.debug("Received IPN request");
String str = "cmd=_notify-validate&" + request().body().asText();
Logger.debug("Validation string: " + str);
URL url = new URL("https://www.sandbox.paypal.com/cgi-bin/webscr");
URLConnection connection = url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
PrintWriter out = new PrintWriter(connection.getOutputStream());
out.println(str);
out.close();
BufferedReader in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String result = in.readLine();
in.close();
Logger.debug("IPN result: " + result);
Map<String, List<String>> data = BodyParserUtils
.parseTextAsFormUrlEncodedForJava(request().body().asText());
Logger.debug("Form data: " + data);
if (StringUtils.equals(result, "VERIFIED")) {
return ok();
} else {
return badRequest();
}
}
}