I came across this thread yesterday when I was searching for a solution to this problem. I have since gotten it working, so here's my code in case anyone else needs it. It's important to note that the PayPal IPN Simulator in the sandbox reports the url being invalid if it does not respond properly, even if the server does handle the POST request.
The following code requires the Apache HttpClient package from the HttpComponents project (I used version 4.1.2)
Code:
@Controller
@RequestMapping("/confirm")
public class IPNController {
private final static String CONTENT_TYPE = "Content-Type";
private final static String MIME_APP_URLENC = "application/x-www-form-urlencoded";
private final static String PAY_PAL_DEBUG = "https://www.sandbox.paypal.com/cgi-bin/webscr";
private final static String PAY_PAL_PROD = "https://www.paypal.com/cgi-bin/webscr";
private final static String PARAM_NAME_CMD = "cmd";
private final static String PARAM_VAL_CMD = "_notify-validate";
private final static String RESP_VERIFIED = "VERIFIED";
private final IPNProcessingService ipnService;
private final String paypalUrl;
@Autowired
public IPNController ( IPNProcessingService ipnService, @Value( "${paypal.debug:true}") boolean debug ) {
this.ipnService = ipnService;
paypalUrl = debug ? PAY_PAL_DEBUG : PAY_PAL_PROD;
}
@RequestMapping( method=RequestMethod.POST )
public void processIPN( HttpServletRequest request ) {
logger.debug( "POST Confirm" );
//Create client for Http communication
HttpClient httpClient = new DefaultHttpClient();
HttpParams clientParams = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(clientParams ,40000 );
HttpConnectionParams.setSoTimeout(clientParams ,40000 );
HttpPost httppost = new HttpPost( paypalUrl );
httppost.setHeader( CONTENT_TYPE, MIME_APP_URLENC );
try {
//Store Payment info for passing to processing service
Map<String, String> params = new HashMap<String, String>();
//Use name/value pair for building the encoded response string
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
//Append the required command
nameValuePairs.add( new BasicNameValuePair( PARAM_NAME_CMD, PARAM_VAL_CMD ) );
//Process the parameters
Enumeration<String> names = request.getParameterNames();
while ( names.hasMoreElements() ) {
String param = names.nextElement();
String value = request.getParameter( param );
nameValuePairs.add( new BasicNameValuePair( param, value ) );
params.put( param, value );
logger.debug( param + "=" + value );
}
httppost.setEntity( new UrlEncodedFormEntity( nameValuePairs ) ) );
if ( verifyResponse( httpClient.execute( httppost ) ) ) {
//Implement your processing logic here, I used an @Asyn annotation
//Remember to track completed transactions and don't process duplicates
ipnService.processPaymentNotification( params );
}
} catch ( UnsupportedEncodingException e ) {
e.printStackTrace();
} catch ( ClientProtocolException e ) {
e.printStackTrace();
} catch ( IOException e ) {
e.printStackTrace();
}
}
private boolean verifyResponse( HttpResponse response ) throws IllegalStateException, IOException {
InputStream is = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String responseText = reader.readLine();
is.close();
logger.debug( "RESPONSE : " + responseText );
return responseText.equals( RESP_VERIFIED );
}
}
The same pattern can be followed for using other Paypal back end services. I hope this helps others who may be struggling with this.