Reputation: 4028
I am wanting to extend the functionality of a Spring App to include an HTTP endpoint to receive Paypal Instant Payment Notifications.
Paypal sends these in the HTTP body like so:
mc_gross=19.95&protection_eligibility=Eligible&address_status=confirmed&payer_id=LPLWNMTBWMFAY&tax=0.00&address_street=1+Main+St&payment_date=20%3A12%3A59+Jan+13%2C+2009+PST&payment_status=Completed&charset=windows-1252&address_zip=95131&first_name=Test&mc_fee=0.88&address_country_code=US&address_name=Test+User¬ify_version=2.6&custom=&payer_status=verified&address_country=United+States&address_city=San+Jose&quantity=1&verify_sign=AtkOfCXbDm2hu0ZELryHFjY-Vb7PAUvS6nMXgysbElEn9v-1XcmSoGtf&payer_email=gpmac_1231902590_per%40paypal.com&txn_id=61E67681CH3238416&payment_type=instant&last_name=User&address_state=CA&receiver_email=gpmac_1231902686_biz%40paypal.com&payment_fee=0.88&receiver_id=S8XGHLYDW9T3S&txn_type=express_checkout&item_name=&mc_currency=USD&item_number=&residence_country=US&test_ipn=1&handling_amount=0.00&transaction_subject=&payment_gross=19.95&shipping=0.00
https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNIntro/
Do I need to define a concrete class to define the request body e.g.
Request Class
public class PaypalIPNRequest {
private static final long serialVersionUID = 1L;
private String mc_gross;
private String protection_eligibility;
private String address_street;
...
public PaypalIPNRequest() {
}
//getters setters
}
Controller
@Override
@Auditable
@RequestMapping(value = "/ipnRequest.do", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void ipnRequest(@RequestBody final PaypalIPNRequest request) {
}
As stated in this SO answer: @RequestBody and @ResponseBody annotations in Spring
What happens though if Paypal change their IPN request in the future, will this break?
Is there a better way to pass the request body without having a specific class?
Could I use HttpServletRequest?
Upvotes: 2
Views: 704
Reputation: 4392
What I have done in the past is use com.paypal.base.ipn.IPNMessage
to validate and retrieve from the request (like you proposed) just the fields that where important to me instead of mapping the entire request body to a concrete class, i.e.:
private final static String PAYPAL_WEB_IPN_TXN_PARAM = "txn_id";
private final static String PAYPAL_WEB_IPN_AMOUNT_PARAM = "mc_gross";
private final static String PAYPAL_WEB_IPN_PAYMENT_STATUS_PARAM = "payment_status";
private final static String PAYPAL_WEB_IPN_PAYMENT_STATUS = "Completed";
@Resource(name = "payPalConfigurationMap")
private Map<String, String> configurationMap;
private OAuthTokenCredential oAuthTokenCredential;
@PostConstruct
public void init() {
Properties properties = new Properties();
properties.putAll(configurationMap);
PayPalResource.initConfig(properties);
oAuthTokenCredential = new OAuthTokenCredential(
configurationMap.get(Constants.CLIENT_ID),
configurationMap.get(Constants.CLIENT_SECRET),
configurationMap
);
}
public DonationDTO validateWebIPN(HttpServletRequest request) throws Exception {
IPNMessage ipnlistener = new IPNMessage(request, configurationMap);
boolean isIpnVerified = ipnlistener.validate();
String paymentStatus = ipnlistener.getIpnValue(PAYPAL_WEB_IPN_PAYMENT_STATUS_PARAM);
if (isIpnVerified && paymentStatus.equalsIgnoreCase(PAYPAL_WEB_IPN_PAYMENT_STATUS)) {
String amount = ipnlistener.getIpnValue(PAYPAL_WEB_IPN_AMOUNT_PARAM);
String tx = ipnlistener.getIpnValue(PAYPAL_WEB_IPN_TXN_PARAM);
// irrelevant code
return donationDTO;
}else{
String exceptionMessage = "Problem when requesting info from PayPal service";
logger.error(exceptionMessage);
throw new Exception(exceptionMessage);
}
}
This way, unless Paypal changes the name of the fields (which shouldn't happen) you shouldn't have any problem.
Upvotes: 3