Thursday, 2 February 2012

Salesforce Paypal IPN integration

In my previous post Salesforce Paypal Integration I went through sending payment details to the Paypal Mass Payments API. By the end of that post, your application should be receiving "Success" messages from Paypal, and seeing payments move between two accounts. This is really only half the story though.

The "Success" message is Paypal confirming it has received the SOAP request, and it was correctly structured. You don't actually know what has happened to each payment row in your request. What, for example, happens to payments issued to e-mail addresses not registered to a Paypal account? Or if a transaction is cancelled or reversed? These are things you need to know, and that is where the Paypal IPN system comes into play.

The IPN messages are documented quite well here on the PayPal website, and indeed if you were developing in ASP or Java, that's probably all you'd need to get going with them, but this is Salesforce.com, and so things are a little different.

IPN messages are the second half of our payment process diagram:

They originate from PayPal, and are delivered to a URL in your application.

Your first action is to send the whole message back to PayPal, which will verify it originated there, and that it is intact.

You can then examine the message, extract the referenced payments, and execute update actions on their status/pay dates and so forth.

IPN messages for a transaction can appear any time up to 30 days after it was requested, and IPN messages can update a single transaction many times, depending on how complicated it proves to be.


So how do we deal with IPN messages in Salesforce? In short, we are going to do the following:
  • Establish a publically visible end point, using a visualforce page, and Salesforce sites
  • Implement a custom controller to execute the behaviour described above
We'll execute these points in reverse order though, because if we try to create the page before the controller, it will complain that it can't find the referenced files and functions.

So go to Your name -> Setup -> Develop -> Apex classes and select to make a new class. Then copy the file PaypalIPNController.cls from the GitHub repository:

 https://github.com/srlawr/PaypalSFDC/blob/master/PaypalIPNController.cls



Have a read through it so you know what's going on, and you'll probably notice that because this controller has to send the message back to PayPal we will need to add the verification URL to our Remote Site Settings so your application can communicate with the outside world.

Go to Your name -> Setup -> Security Controls -> Remote Site Settings and add a new Paypal URL for:

PayPal Sandbox IPN: https://www.sandbox.paypal.com/cgi-bin/webscr

With the Apex controller in place, we need to create the page to front it. There isn't actually anything on this page, because it's never going to be served out to a browser, it is simply acting as as URL for web-service calls. We do need to point it at this controller though,

Navigate your way to  Your name -> Setup  -> Develop -> Pages and create a new page. Call it Paypal_IPN, for clarity. This Visualforce page only has one real line to it, which you can see in paypal_ipn.page on GitHub:

https://github.com/srlawr/PaypalSFDC/blob/master/paypal_ipn.page


This is all the code you really need. When the page is loaded, the controller is constructed, and you can see the Apex page "Action" attribute to call the function "digestMessage", this is because you cannot make DML calls in a controller's constructor, or any method calls direct from it.

The final action on the Salesforce side of things is to expose this page via Salesforce sites. Click Your name -> Setup -> Develop -> Sites and click to create a new Site. I simply set the label, name and default web address all to "Paypal" and then in the Active Site Home Page selected the new page we just created.

The sites detail page should list a number of attributes of this new object, one of which is "Secure Web Address" - copy the value of this to your clipboard, because we're going to need it in a minute.


Next then, back in your Paypal sandbox, we need to tell it what URL to send the messages too, so log into your Payments Pro account and click on "Profile" -> "Instant Payment Notification preferences". On this page, you will need to enter the Secure Web Address of the Salesforce.com page, and Enable IPN messages. From the confirmation page, head over to the "IPN History Page", this page could become your best friend during development/testing.



Ready to roll!

It feels like we've done enough work now. Lets see if it's all come together.

If you return to your Payments List page, and make sure you have a handful of payment records with a status of "Unpaid", you should find yourself looking at something like this:


Just like in the last stage, if you click the "Pay out" button, the page should reload, with a success message and, having updated all the PayPal Status values to "Sent".



What has happened now is PayPal has received the request and is whipping through each of your transfers. Typically, (typically!!) this takes a few seconds in the first instance, and after each row has been considered, your first IPN message will be sent over by PayPal.

Twiddle your thumbs for just a few seconds (or if you wish to use this time constructively, check your PayPal sandbox, and make sure your transaction has been recorded on their side) and then reload the Payment List page in Salesforce.

You should see the status column has been updated, to something similar to this:


In this case, three of my transactions (those associated with my PayPal Sandbox email addresses) have completed entirely, and instantly, but one transaction is currently sitting unclaimed, because that e-mail address isn't associated with a PayPal account. You can read up more on IPN status codes over on the PayPal website.

In Paypal you can view your IPN messages, like the one below, from your IPN history page, which you'll find linked from the configuration page you put your IPN endpoint in above.



That really is it though, for a basic demonstration of Integrating the PayPal Mass Payments API with Force.com.

It should be absolutely noted that this is a purely educational blog post, and no-one should ever attempt to implement payment gateways or infrastructures without the appropriate knowledge and testing.

If you would like to talk to Desynit about applying this knowledge to your business or organisation, please get in touch via the details on our website. Otherwise, any questions or discussions are always more than welcome in the comments section below.

14 comments:

  1. This is a very good tutorial however on the last code it doesnt register Map and won't compile as Map is an unexpected token, then when i put the after Map it says that Map is doing an illegal assignment. So any help would be appreciated.

    Overall though this is quite the complex tutorial but than again it is very useful.

    ReplyDelete
    Replies
    1. Ah, thank you Scottatea, you have made a very good point. Blogspot has stripped all of the angle brackets out of that snippet of code, which means all the Map and List definitions have lost their type.

      The Map definition on line 11 should read:

      Map<String, String> queryList = ApexPages.currentPage().getParameters();

      and there are some other cases where the types have been stripped, I have fixed those now, and that problem (at least) should be resolved!

      Thanks for your feedback, please don't hesitate to get in touch if you have any other problems.

      Simon

      Delete
    2. Thank you very much for your reply! it seems that I was missing the last angle bracket. I am curious but where is Paypal_payment__c supposed to be called?

      I believe that is the only problem I am having now as the editor tells me it is an invalid type.

      Thank you for the clarifications!

      Delete
    3. Thank you! Another error on my part, when I repaired the post this morning I copied some of my production code in to patch it. Paypal_payment__c is a custom Salesforce object in my organisation, which is what I actually use to manage payments.

      In this tutorial though, I have now fixed the paymentObjects list to be defined as:

      List<Contact> paymentObjects = new List<Contact>{};

      because as you can see lower down in the function we call

      paymentObjects.add(contact);

      to build up our list of updates to save, and in this case, we are just updating the standard contact object. (so make sure you have the custom field "Payment_status__c" in Contact and it should work)

      Cheers again, and please let me know how you get on!

      Delete
    4. Awesome thank you! That fixed everything except the editor says on line 48 payment.Id is not a valid variable so I just surrounded it with ' ' and it seems to be compiling fine!

      It is a really neat tutorial and i havent found a good answer for having the IPN to integrate with salesforce, so this helps a lot!

      Delete
  2. Hello, how about using your code with my desktop PayPal IPN device? see http://www.etherfeed.com

    ReplyDelete
    Replies
    1. Hi Bakalski, Etherfeed is a very interesting project! With a nice website to boot.

      The code and scenarios covered by this blog post won't be relevant though unfortunately, as this is for integrating Paypal with the CRM platform Salesforce.. and therefore (unfortunately) completely disconnected from the idea of integrating with a desktop application, or indeed even PHP!

      If you consult the Paypal IPN documentation though, I'm sure they cover PHP integration, and I can promise you, it's not too hard to get this rolling.

      I think one problem you will have using the route you describe on the website could be that you can only have one paypal IPN target URL, and surely the client-sites are going to want to direct that to their payment engine to actually reliably validate and process payments? If the IPN is consumed by Etherfeed, how can the originating site sign off a payment? I would suggest maybe using a different mechanism to accumulate paypal sales data, but quite what, I don't know!!

      Good luck :)

      Delete
  3. Does anybody faced any force security issues while calling doing DML using page action.

    ReplyDelete
    Replies
    1. Hi Farhat. Do you mean security restrictions, or security vulnerabilities?!

      I would be eager to investigate/debate either!

      Delete
  4. Simon,
    Thank you for writing up such a detailed guide. Could you share how you solved building the verification response?

    ReplyDelete
    Replies
    1. Hi Darek. Thank you for prompting me on this! I will endeavour to document this as soon as possible. I will aim to have a detailed post up within a week.

      In short though, as the Paypal API version is fixed in the first request message, amd the list of parameters is specified in the API, we literally built a hardcoded list for the correct order of parameters, and then iterated over the Salesforce getParameters() response, adding the NVPs to a string, in the right order. There is a complication (which I will cover in the post) when you hit the reciever_email_1, reciever_email_2 and status_1, status_2 fields, because you don't know how many of them there will be in advance, but we found a solution to this (again, which I will cover in the blog) and I'm confident (if you can't wait) you can deduce a solution too!!

      Watch this space..

      Delete
  5. Simon, thanks for the pointers. I built the hard-coded list of correctly ordered parameters and then iterated over the getParameters() response as you suggested. I'm fairly certain that I am now sending the correct order of parameters back to PayPal. I am comparing the IPN Message available in the IPN History in PayPal to the HTTPRequest that I log in Apex. After I run my HTTPRequest through a URL Decoder, the two strings match (using WinMerge). I am still getting an INVALID response back from PayPal. I think the problem has something to do with the encoding steps I am taking. I have set PayPal to use UTF-8 and I have done the same in Apex. I've placed a snippet of what I am trying below. I greatly appreciate any advice you could offer.

    finalQueryString = EncodingUtil.urlEncode(finalQueryString, 'UTF-8');

    String url = 'https://www.sandbox.paypal.com/cgi-bin/webscr&cmd=_notify-validate&' + finalQueryString;

    req.setEndpoint(url);
    req.setMethod('POST');
    req.setHeader('Content-Type', 'text/xml;charset=UTF-8');

    HttpResponse res = new HttpResponse();

    system.debug(req);
    res = http.send(req);

    verification = res.getBody();
    System.debug('Paypal full response ' + res);
    System.debug('PayPal returned ' + verification);

    ReplyDelete
  6. Where can I find the documentation on the API which give me the correct order of the NVP's? Thanks

    ReplyDelete
  7. You don't go other site. Really this blog is Awesome. I as if it very much. you made nice post to receive knowledge. I am seriously impressed. I want to read simple things your post regularly. Hopefully you will make extra post. Thank you a great deal of.
    email processing system

    ReplyDelete