Code - PayPal IPN Notification Listener for MVC/C#

PayPal IPN Notification Listener for MVC

I recently had to wire up some PayPal stuff and I am still amazed at the lack of documentation PayPal provides for .Net code. I was trying to wire up the IPN Listener feature for MVC. I finally got it working, so I thought I would share the basics.

First, create your controller action. I have a class to bind the information to (PayPalCheckoutInfo). It's very important that PayPal gets a success message (EmptyResult works) or it will keep retrying for up to 4 days. You'll then grab the parameters that' passed in and use it.


    public EmptyResult PayPalPaymentNotification(PayPalCheckoutInfo payPalCheckoutInfo)         
    { 
      PayPalListenerModel model = new PayPalListenerModel();             
      model._PayPalCheckoutInfo = payPalCheckoutInfo;               
      byte[] parameters = Request.BinaryRead(Request.ContentLength);
      
      if (parameters != null)             
      {                 
        model.GetStatus(parameters);             
      }

      return new EmptyResult();           
    } 
Next, your model. You need to verify the message to make sure it's authentic, and you do that by calling the Verify method. If you do not get "VERIFIED", it needs investigated. Once you set up the IPN notifications on your PayPal account, you will get notifications for everything. You need to add your code to handle the messages. If they are refunds or chargebacks, maybe add an alert of some sort.

You will want to read through the PDFs PayPal provides to get more details.

NOTE: to test this locally, you will need to make your localhost available to the outside (open port in firewall, etc).


        public class PayPalListenerModel : BaseModel     
        { 
          public PayPalCheckoutInfo _PayPalCheckoutInfo { get; set; }
          
          public void GetStatus(byte[] parameters)         
          {
          
            //verify the transaction             
            string status = Verify(true, parameters);
            
            if (status == "VERIFIED")            
            {                
         
              //check that the payment_status is Completed                 
              if (_PayPalCheckoutInfo.payment_status.ToLower() == "completed")                 
              { 
              
                //check that txn_id has not been previously processed to prevent duplicates                      
         
                //check that receiver_email is your Primary PayPal email                                          
         
                //check that payment_amount/payment_currency are correct                       
         
                 //process payment/refund/etc                     
         
              }                 
              else if (status == "INVALID")             
              {                 
          
                //log for manual investigation             
              }             
              else             
              {                 
                //log response/ipn data for manual investigation             
              }

            }
            
          }           
          
          private string Verify(bool isSandbox, byte[] parameters)         
          {               
          
            string response = "";               
            try             
            {                   
          
              string url = isSandbox ? 
                "https://www.sandbox.paypal.com/cgi-bin/webscr" : "https://www.paypal.com/cgi-bin/webscr";  
                           
              HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);                 
              webRequest.Method = "POST";                 
              webRequest.ContentType = "application/x-www-form-urlencoded";
                             
              //must keep the original intact and pass back to PayPal with a _notify-validate command
              string data = Encoding.ASCII.GetString(parameters);                 
              data += "&cmd=_notify-validate";   
                                  
              webRequest.ContentLength = data.Length;                   
          
              //Send the request to PayPal and get the response                 
              using (StreamWriter streamOut = new StreamWriter(webRequest.GetRequestStream(),System.Text.Encoding.ASCII))                 
              {                     
                streamOut.Write(data);                     
                streamOut.Close();                 
              }                   
          
              using (StreamReader streamIn = new StreamReader(webRequest.GetResponse().GetResponseStream()))                 
              {                     
                response = streamIn.ReadToEnd();                     
                streamIn.Close();                 
              }             
          
            }             
            catch {}               
          
            return response;    
                 
          }       
        } 
        
      
Here is the class for holding the parameters.


public class PayPalCheckoutInfo
    {
        //ipnguide.pdf - page 43
        #region "Transaction and Notification-Related Variables"
        /// use this to verify its not spoofed, this is our info
        public string receiver_email { get; set; } //127
        public string receiver_id { get; set; } //13
        /// Keep this ID to avoid processing the transaction twice
        /// The merchant’s original transaction identification number for the payment from the buyer, against which the case was registered.
        public string txn_id { get; set; }
        /// The kind of transaction for which the IPN message was sent.
        public string txn_type { get; set; }
        /// Encrypted string used to validate the authenticity of the transaction
        public string verify_sign { get; set; }

        #endregion

        #region "Buyer Information Variables"

        public string address_country { get; set; } //64
        public string address_city { get; set; } //40
        public string address_country_code { get; set; }  //2
        public string address_name { get; set; } //128 - prob don't need
        public string address_state { get; set; } //40
        public string address_status { get; set; }  //confirmed/unconfirmed
        public string address_street { get; set; } //200
        public string address_zip { get; set; } //20
        public string contact_phone { get; set; } //20
        public string first_name { get; set; } //64
        public string last_name { get; set; } //64
        public string payer_email { get; set; } //127
        public string payer_id { get; set; }  //13

        public int? Zip
        {
            get
            {
                int temp;
                if (int.TryParse(address_zip, out temp))
                {
                    return temp;
                }
                else
                {
                    return null;
                }
            }
        }

        #endregion
        #region "Payment Information Variables"

        /*
        public string auth_amount { get; set; }
        public string auth_exp { get; set; } //28
        public string auth_id { get; set; } //19
        public string auth_status { get; set; }
         */

        /// Token passed back from PayPal for cross ref
        public string token { get; set; }
        //public string checkoutstatus { get; set; }
        /// Passthrough variable you can use to identify your Invoice Number for this purchase. If omitted, no variable is passed back.
        public string invoice { get; set; } //127
        /// Item name as passed by you, the merchant. Or, if not passed by you, as
        /// entered by your customer. If this is a shopping cart transaction, PayPal
        /// will append the number of the item (e.g., item_name1, item_name2,
        /// and so forth).
        public string item_name1 { get; set; } //127
        public string item_number1 { get; set; } //127
        public string mc_currency { get; set; } //currency of the payment.
        public string mc_fee { get; set; } //Transaction fee associated with the payment
        public string mc_gross { get; set; }    //Full amount of the customer's payment 
        /// Whether the customer has a verified PayPal account.
        /// verified – Customer has a verified PayPal account
        /// unverified – Customer has an unverified PayPal account.
        public string payer_status { get; set; }
        /// HH:MM:SS Mmm DD, YYYY PDT (28chars)
        public string payment_date { get; set; }

        public DateTime TrxnDate
        {
            get
            {
                DateTime dt = DateTime.Now;
                if (DateTime.TryParse(payment_date, out dt))
                {
                    return dt;
                }
                else
                {
                    return DateTime.Now;
                }
            }
        }
        
        /// The status of the payment:
        /// Canceled_Reversal: A reversal has been canceled. For example, you
        /// won a dispute with the customer, and the funds for the transaction that was
        /// reversed have been returned to you.
        /// Completed: The payment has been completed, and the funds have been added successfully to your account balance
        ///Created: A German ELV payment is made using Express Checkout. Denied: You denied the payment. This happens only if the payment was
        /// previously pending because of possible reasons described for the pending_reason variable or the Fraud_Management_Filters_x variable.
        /// Expired: This authorization has expired and cannot be captured.
        ///Failed: The payment has failed. This happens only if the payment was made from your customer’s bank account.
        ///Pending: The payment is pending. See pending_reason for more information.
        ///Refunded: You refunded the payment.
        ///Reversed: A payment was reversed due to a chargeback or other type of reversal. The funds have been removed from your account balance and
        ///returned to the buyer. The reason for the reversal is specified in the ReasonCode element.
        ///Processed: A payment has been accepted.
        ///Voided: This authorization has been voided.
        public string payment_status { get; set; }
        /// echeck: This payment was funded with an eCheck.
        /// instant: This payment was funded with PayPal balance, credit card, or Instant Transfer.
        public string payment_type { get; set; }
        /// This variable is set only if payment_status = Pending. - too many reasons (look it up in pdf)
        public string pending_reason { get; set; }
        public string protection_eligibility { get; set; }
        public string quantity { get; set; }
        public string reason_code { get; set; }
        public string correlationID { get; set; }
        public string ack { get; set; }
        public string errmsg { get; set; }
        public int? errcode { get; set; }

        /// should hold the clientid passed in from setexpresscheckout
        public string custom { get; set; }

        public decimal Total
        {
            get
            {
                decimal amount = 0;
                if (decimal.TryParse(mc_gross, out amount))
                {
                    return amount;
                }
                else
                {
                    return 0;
                }
            }
        }
        public decimal Fee
        {
            get
            {
                decimal amount = 0;
                if (decimal.TryParse(mc_fee, out amount))
                {
                    return amount;
                }
                else
                {
                    return 0;
                }
            }
        }
        #endregion
    }