Is there any sample for PayPal IPN

Marc picture Marc · Oct 29, 2014 · Viewed 12k times · Source

I have an Asp.Net WEB API 2 project and I would like to implement an Instant Payment Notification (IPN) listener controller.

I can't find any example and nuget package. All I need is to acknowledge that the user paid with the standard html button on Paypal. It's quite simple.

All the nuget packages are to create invoice or custom button. It's not what I need

The samples on paypal are for classic asp.net and not for MVC or WEB API MVC

I'm sure somebody did that already and when I started coding I had a feeling that I was reinventing the wheel.

Is there any IPN listener controller example?

At least a PaypalIPNBindingModel to bind the Paypal query.

    [Route("IPN")]
    [HttpPost]
    public IHttpActionResult IPN(PaypalIPNBindingModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest();
        }

        return Ok();
    }

EDIT

So far I have the following code

        [Route("IPN")]
        [HttpPost]
        public void IPN(PaypalIPNBindingModel model)
        {
            if (!ModelState.IsValid)
            {
                // if you want to use the PayPal sandbox change this from false to true
                string response = GetPayPalResponse(model, true);

                if (response == "VERIFIED")
                {

                }
            }
        }

        string GetPayPalResponse(PaypalIPNBindingModel model, bool useSandbox)
        {
            string responseState = "INVALID";

            // Parse the variables
            // Choose whether to use sandbox or live environment
            string paypalUrl = useSandbox ? "https://www.sandbox.paypal.com/"
            : "https://www.paypal.com/";

            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(paypalUrl);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));

                //STEP 2 in the paypal protocol
                //Send HTTP CODE 200
                HttpResponseMessage response = client.PostAsJsonAsync("cgi-bin/webscr", "").Result;

                if (response.IsSuccessStatusCode)
                {
                    //STEP 3
                    //Send the paypal request back with _notify-validate
                    model.cmd = "_notify-validate";
                    response = client.PostAsync("cgi-bin/webscr", THE RAW PAYPAL REQUEST in THE SAME ORDER ).Result;

                    if(response.IsSuccessStatusCode)
                    {
                        responseState = response.Content.ReadAsStringAsync().Result;
                    }
                }
            }

            return responseState;
        }

but for the step 3 I tried to post my model as json but paypal returns a HTML page instead of VALIDATED or INVALID. I figured out that I have to use application/x-www-form-urlencoded and it the parameters as to be in the same order.

How can I get the request URL?

I would use the query Url and add &cmd=_notify-validate to it

Answer

Michal Hosala picture Michal Hosala · Sep 16, 2015

Based on accepted answer I came up with the following code implementing IPN listener for ASP.NET MVC. The solution has already been deployed and appears to work correctly.

[HttpPost]
public async Task<ActionResult> Ipn()
{
    var ipn = Request.Form.AllKeys.ToDictionary(k => k, k => Request[k]);
    ipn.Add("cmd", "_notify-validate");

    var isIpnValid = await ValidateIpnAsync(ipn);
    if (isIpnValid)
    {
        // process the IPN
    }

    return new EmptyResult();
}

private static async Task<bool> ValidateIpnAsync(IEnumerable<KeyValuePair<string, string>> ipn)
{
    using (var client = new HttpClient())
    {
        const string PayPalUrl = "https://www.paypal.com/cgi-bin/webscr";

        // This is necessary in order for PayPal to not resend the IPN.
        await client.PostAsync(PayPalUrl, new StringContent(string.Empty));

        var response = await client.PostAsync(PayPalUrl, new FormUrlEncodedContent(ipn));

        var responseString = await response.Content.ReadAsStringAsync();
        return (responseString == "VERIFIED");
    }
}

EDIT:

Let me share my experience - the above code was working just fine up until now, but suddenly it failed for one IPN it was processing, i.e. responseString == "INVALID".

The issue turned out to be that my account was set up to use charset == windows-1252 which is PayPal default. However, FormUrlEncodedContent uses UTF-8 for encoding and therefore the validation failed because of national characters like "ř". The solution was to set charset to UTF-8, which can be done in Profile > My selling tools > PayPal button language encoding > More Options, see this SO thread.