Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get raw post request in an ApiController

I'm trying to implement a Paypal Instant Payment Notification (IPN)

The protocol is

  1. PayPal HTTP POSTs your listener an IPN message that notifies you of an event.
  2. Your listener returns an empty HTTP 200 response to PayPal.
  3. Your listener HTTP POSTs the complete, unaltered message back to PayPal; the message must contain the same fields (in the same order) as the original message and be encoded in the same way as the original message.
  4. PayPal sends a single word back - either VERIFIED (if the message matches the original) or INVALID (if the message does not match the original).

So far I have

        [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/cgi-bin/webscr";

            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.PostAsync("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;
        }

My problem is I can't figure out how to send the original request to Paypal with the parameters in the same order. I could build a HttpContent with my PaypalIPNBindingModel but I can't guarantee the order.

Is there any way I could achieve this?

Thank you

like image 276
Marc Avatar asked Oct 30 '14 19:10

Marc


1 Answers

I believe you should not use parameter binding and just read the raw request yourself. Subsequently, you can deserialize into the model yourself. Alternatively, if you want to leverage Web API's model binding and at the same time, access the raw request body, here is one way I could think of.

When Web API binds the request body into the parameter, the request body stream is emptied. Subsequently, you cannot read it again.

[HttpPost]
public async Task IPN(PaypalIPNBindingModel model)
{
    var body = await Request.Content.ReadAsStringAsync(); // body will be "".
}

So, you have to read the body before model binding runs in Web API pipeline. If you create a message handler, you can ready the body there and store it in the properties dictionary of the request object.

public class MyHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
                                           HttpRequestMessage request, 
                                             CancellationToken cancellationToken)
    {
        if (request.Content != null)
        {
            string body = await request.Content.ReadAsStringAsync();
            request.Properties["body"] = body;
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

Then, from controller you can retrieve the body string, like this. At this point, you have the raw request body as well as the parameter-bound model.

[HttpPost]
public void IPN(PaypalIPNBindingModel model)
{
    var body = (string)(Request.Properties["body"]);
}
like image 176
Badri Avatar answered Oct 06 '22 01:10

Badri