0
votes

I'm implementing an IPN listener in ASP.NET, but whenever I try to validate a transaction, the paypal webscr script returns an html document instead of VERIFIED or INVALID. This is the code:

private void Validate(Transaction transaction)
{
    string content = String.Concat(transaction.IpnMessage, "&cmd=_notify-validate");
    byte[] rawContent = Encoding.ASCII.GetBytes(content);

    HttpWebRequest request = (HttpWebRequest) HttpWebRequest.Create(postBackUrl);
    request.Method = "POST";
    request.ProtocolVersion = HttpVersion.Version11;
    request.KeepAlive = false;
    request.ContentType = "application/x-www-form-urlencoded";

    using (StreamWriter writer = new StreamWriter(request.GetRequestStream()))
        writer.Write(rawContent);

    transaction.Save();

    try
    {
        WebResponse response = request.GetResponse();
        StreamReader reader = new StreamReader(response.GetResponseStream());

        string responseContent = reader.ReadToEnd();

        if (responseContent.Equals("VERIFIED"))
            this.Approve(transaction);
        else
            transaction.SetOperation("Rejected", responseContent);
    }
    catch (Exception ex)
    {
        transaction.SetOperation("WebError", ex.Message);
    }
}

And here is how a Transaction object is built from an incoming request:

public static Transaction FromContext(HttpContext context)
{
    StreamReader reader = new StreamReader(context.Request.InputStream);
    Transaction result = new Transaction();
    string ipnMessage;

    reader.BaseStream.Position = 0;
    ipnMessage = reader.ReadToEnd();

    string[] pairs = ipnMessage.Split('&');

    result.data = new Dictionary<string, string>();
    foreach (string pair in pairs)
    {
        if (String.IsNullOrEmpty(pair))
            continue;

        string[] parts = pair.Split('=');
        string field = parts[0];
        string value = (parts.Length > 1) ? (parts[1]) : String.Empty;

        result.data.Add(HttpContext.Current.Server.UrlDecode(field), HttpContext.Current.Server.UrlDecode(value));
    }

    result.ID = result.data["txn_id"];
    result.IpnMessage = ipnMessage;

    if (String.IsNullOrEmpty(result.ID))
        throw new ArgumentException("This is not a valid transaction.");

    return result;
}

If I send the same message via a REST console (Google Chrome extension), it works. Well, kind of: it returns INVALID but should be VERIFIED. I am not using the sandbox, I made a payment to myself of 5 cent.

By the way, I cannot set the Content-Length of the stream. If I Do, an exception is thrown saying that I have not written enough bytes to the stream before sending out the request (while I wrote exactly rawContent.Length bytes).

The postBackUrl is https://www.paypal.com/cgi-bin/webscr. I am testing this by sending a copy of the IPN message manually to the web handler. I copied that from my PayPal account IPN history. I also tried to prepend cmd=_notify-validate instead of appending it, but the result is the same.

I cannot figure out why I receive an HTML document in response.

1

1 Answers

0
votes

I don't code in ASP.NET, but looking at the docs, I noticed that your code concatenates the response in the opposite way:

string content = String.Concat(transaction.IpnMessage, "&cmd=_notify-validate");

It's making it IpnMessage&cmd=_notify-validate, when it should be &cmd=_notify-validateIpnMessage.

So,

string content = String.Concat("&cmd=_notify-validate", transaction.IpnMessage);

Not sure if that's the only reason, but give it a try.

Also, make sure that there's a & in-between the message and "_notify-validate". That was my issue. So if transaction.IpnMessage doesn't start with &, be sure to include it.

string content = String.Concat("&cmd=_notify-validate&", transaction.IpnMessage);