2012-04-24 192 views
5

我使用Paypal的自适应付款系统。使用沙箱帐户,我可以创建PayRequest并转发到PayPal进行付款。 这则看起来像:如何通过IPN使用Paypal自适应付款?

请求 =

2012年4月24日下午10点35分46秒com.paypal.adaptive.api.requests.PayRequest执行 信息:发送PayRequest有:requestEnvelope。 errorLanguage = EN_US & 操作类型 = PAY & receiverList.receiver(0).email = seller_1334320690_biz%40email.org & receiverList.receiver(0).amount = 5.0 & CURRENCYCODE = EUR & feesPayer = SENDER & cancelUrl = HTTPS%3A%2F%2Flocalhost%3A8443 & RETURNURL = HTTP%3A%2F%2Flocalhost%2F & ipnNotificationUrl = HTTP%3A %2F%2Flocalhostu%2Ffinishdeposit &

响应 =

04月24 2012下午10时35分48秒com.paypal.adaptive.api.requests.PayPalBaseRequest makeRequest的 INFO:收到的响应:responseEnvelope.timestamp = 2012-04-24T13%3A35%3A48.587-07%3A00 & responseEnvelope。 ACK =成功& responseEnvelope.correlationId = c8dee8023cca6 & responseEnvelope.build = 2756816 & payKey = AP-1UF57245CJ360523K & paymentExecStatus =创建

现在我想弄清楚,我怎么可以检查, Ť他付款顺利完成。 所以我试图实现使用沙盒工具工作的ipn系统。 但是,我不知道如何将2连接在一起。即付款时,我假设我需要在数据库中创建一个该用户已付款的记录,可能是因为挂起/创建? 然后等待ipn返回以通知我付款已完成,并更新数据库表以表示完成? 我如何将PayRequest与IPN通知相关联,我将从PayPal获取?PayPal是只发送少量的信息与像IPN-通知:

  • ITEM_NUMBER = AK-1234
  • residence_country = US
  • verify_sign = ArcmaOINNZx08uC3iQY0zhEQN3IZAz70ynRk93Or8ixRi23bb4rGNIrd
  • ADDRESS_COUNTRY =美国
  • ADDRESS_CITY =圣Jose
  • address_status =未确认
  • payment_status =已完成
  • [email protected]
  • payer_id = TESTBUYERID01
  • 如first_name =约翰
  • 航运= 3.04
  • [email protected]
  • mc_fee = 0.44
  • txn_id = 484221854
  • 数量= 1
  • [email protected]
  • notify_version = 2.1
  • txn_type = web_accept
  • 的test_ipn = 1
  • payer_status =验证
  • mc_currency = USD
  • mc_gross = 12.34
  • 定制= XYZ123
  • mc_gross_1 = 9.34
  • PAYMENT_DATE = 11:54:48 2012年4月22日PDT
  • charset = windows-1252
  • address_country_code = US
  • address_zip = 95131
  • ADDRESS_STATE = CA
  • 税= 2.02
  • ITEM_NAME =东西
  • ADDRESS_NAME =约翰·史密斯
  • 姓氏=史密斯
  • payment_type =即时
  • address_street = 123,任何街道
  • receiver_i d = TESTSELLERID1

我找不到可用的IPN通知。最好的办法是,如果我可以得到与支付响应已经得到的IPN通知相同的相关ID。因此,我可以将response-correlation-id保存在我的数据库中,然后检查是否收到具有相同相关标识的IPN通知。

+0

因此,为了澄清你想使用自适应付款(PAY)方法来支付,然后IPN消息来验证成功? – swade1987 2012-04-27 13:31:54

+0

是的,我想将支付关联到支付的用户。 – lazydaemon 2012-05-01 12:16:20

回答

2

这应该会帮助你大量。

namespace Gateway 
{ 
    public class MerchantSellerIPNService : IMerchantSellerIPNService 
    { 
     /// <summary> 
     /// This is the method which is hit when using the URL in the PAY request to PayPal. 
     /// </summary> 
     /// <param name="stream"></param> 
     /// <returns></returns> 
     public string ProcessIPN(Stream stream) 
     { 
      // Declare locally used variables. 
      byte[] requestArray = null; 
      string requestString = null; 
      string responseString = null; 
      StreamReader IPNReturnReader; 
      StreamWriter streamWriter; 
      MemoryStream responseStream = new MemoryStream(); 
      HttpWebRequest payPalRequest; 
      System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding(); 

      // Get the URL to send the IPN received back to PayPal (use either of the two below depending on the environment.) 
      <add key="PAYPAL_IPN_URL" value="https://www.sandbox.paypal.com/cgi-bin/webscr" /> 
      <add key="PAYPAL_IPN_URL" value="https://www.paypal.com/cgi-bin/webscr"/> 

      string IPNReturnURL = ConfigurationManager.AppSettings["PAYPAL_IPN_URL"]; 

      // Read in the data provided from PayPal 
      StreamReader streamReader = new StreamReader(stream); 

      // Obtain the email address and pre-approval key passed to use via PayPal for later use. 
      string strPayPalMessage = streamReader.ReadToEnd(); 

      // Initalize the POST web request we are going to send to PayPal to valid the IPN we received from them. 
      payPalRequest = (HttpWebRequest)WebRequest.Create(IPNReturnURL); 
      payPalRequest.Method = "POST"; 
      payPalRequest.ContentType = "application/x-www-form-urlencoded"; 

      // Create an array containing the IPN message PayPal sent to us. 
      requestArray = encoding.GetBytes(strPayPalMessage); 

      // Then add the necessary string to the back to use for verfication. 
      requestString = Encoding.ASCII.GetString(requestArray); 
      requestString += "&cmd=_notify-validate"; 
      payPalRequest.ContentLength = requestString.Length; 

      // Now write the updated IPN message back to PayPal for verification. 
      streamWriter = new StreamWriter(payPalRequest.GetRequestStream(), System.Text.Encoding.ASCII); 
      streamWriter.Write(requestString); 
      streamWriter.Close(); 

      // Read the response from PayPal and process it. 
      IPNReturnReader = new StreamReader(payPalRequest.GetResponse().GetResponseStream()); 
      responseString = IPNReturnReader.ReadToEnd(); 
      IPNReturnReader.Close(); 

      if (responseString == "VERIFIED") 
      { 
       try 
       { 
        if (strPayPalMessage.Contains("payment_status=Completed")) 
        { 
         if (ProcessPaymentIPNMessage(strPayPalMessage)) 
          PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessPaymentIPNMessage - Able to create new payment Transaction Detail Record"), "DEBUG"); 
         else 
          PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessPaymentIPNMessage - Unable to create new payment Transaction Detail Record"), "DEBUG"); 
        } 
        else if (strPayPalMessage.Contains("payment_status=Refunded")) 
        { 
         if (ProcessRefundIPNMessage(strPayPalMessage)) 
          PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessRefundIPNMessage - Able to create new refund Transaction Detail Record"), "DEBUG"); 
         else 
          PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessRefundIPNMessage - Unable to create new refund Transaction Detail Record"), "DEBUG"); 
        } 
        else 
        { 
         PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - ProcessIPN - Unknown message type"), "DEBUG"); 
        } 
       } 
       catch (Exception ex) 
       { 
        PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - ProcessIPN failed"), "DEBUG"); 
       } 
      } 
      else if (responseString == "INVALID") 
      { 
       PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - Invalid IPN Message Received: "), "DEBUG"); 
      } 
      else 
      { 
       PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - Fatal IPN Message Received: "), "DEBUG"); 
      } 

      return "MerchantSellerIPNService Completed"; 
     } 

     /// <summary> 
     /// Method used to process the Payment IPN notification message and update the database as required. 
     /// </summary> 
     /// <returns></returns> 
     private bool ProcessPaymentIPNMessage(string PayPalIPNMessage) 
     { 
      // Firstly, we need to split the IPN message into sections based on the & sign. 
      string[] PayPalMessageElemetsArray = PayPalIPNMessage.Split('&'); 

      // Now obtain the list of information (from the message) we require to make the TransactionDetail record. 
      string merchantTransactionId = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("txn_id=", StringComparison.Ordinal)); 
      string feeAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_fee=", StringComparison.Ordinal)); 
      string grossAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_gross=", StringComparison.Ordinal)); 

      // TODO: REMOVE THIS ITS FOR DEBUGGING PURPOSES 
      string errorMessage2 = String.Format("ProcessPaymentIPNMessage - merchantTransactionId: {0}, feeAmount: {1}, grossAmount: {2}", merchantTransactionId, feeAmount, grossAmount); 
      PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage2), "DEBUG"); 

      try 
      { 
       // We now need to remove the variable name and '=' from the elements so we only have the necessary information. 
       merchantTransactionId = merchantTransactionId.Replace("txn_id=", ""); 
       feeAmount = feeAmount.Replace("mc_fee=", ""); 
       grossAmount = grossAmount.Replace("mc_gross=", ""); 

       // Now convert the values obtained from the IPN message and calculate the net amount. 
       decimal dFeeAmount = Convert.ToDecimal(feeAmount); 
       decimal dGrossAmount = Convert.ToDecimal(grossAmount); 
       decimal dNetAmount = Math.Round((dGrossAmount - dFeeAmount), 2); 

       try 
       { 
        // Finally create the new transaction fee record. 
        TransactionDetail transactionDetail = new TransactionDetail(); 
        transactionDetail.MerchantTransactionId = merchantTransactionId; 
        transactionDetail.Gross = dGrossAmount; 
        transactionDetail.Fee = Decimal.Negate(dFeeAmount); 
        transactionDetail.Net = dNetAmount; 
        transactionDetail.TransactionType = (int)TransactionDetailTransactionType.InStorePayment; 
        transactionDetail.Save(); 
       } 
       catch (Exception ex) 
       { 
        string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId); 
        PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG"); 
        return false; 
       } 

       return true; 
      } 
      catch (Exception ex) 
      { 
       string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId); 
       PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG"); 
       return false; 
      } 
     } 

     /// <summary> 
     /// Method used to process the Refund IPN notification message and update the database as required. 
     /// </summary> 
     /// <returns></returns> 
     private bool ProcessRefundIPNMessage(string PayPalIPNMessage) 
     { 
      // Firstly, we need to split the IPN message into sections based on the & sign. 
      string[] PayPalMessageElemetsArray = PayPalIPNMessage.Split('&'); 

      // Now obtain the list of information (from the message) we require to make the TransactionDetail record. 
      string merchantTransactionId = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("txn_id=", StringComparison.Ordinal)); 
      string parentTransactionId = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("parent_txn_id=", StringComparison.Ordinal)); 
      string feeAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_fee=", StringComparison.Ordinal)); 
      string grossAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_gross=", StringComparison.Ordinal)); 

      try 
      { 
       // We now need to remove the variable name and '=' from the elements so we only have the necessary information. 
       merchantTransactionId = merchantTransactionId.Replace("txn_id=", ""); 
       parentTransactionId = parentTransactionId.Replace("parent_txn_id=", ""); 
       feeAmount = feeAmount.Replace("mc_fee=", "").Replace("-", ""); 
       grossAmount = grossAmount.Replace("mc_gross=", "").Replace("-", ""); 

       // Now convert the values obtained from the IPN message and calculate the net amount. 
       decimal dFeeAmount = Convert.ToDecimal(feeAmount); 
       decimal dGrossAmount = Convert.ToDecimal(grossAmount); 
       decimal dNetAmount = Math.Round((dGrossAmount - dFeeAmount), 2); 

       // Now create the new transaction fee record. 
       try 
       { 
        // Finally create the new transaction fee record. 
        TransactionDetail transactionDetail = new TransactionDetail(); 
        transactionDetail.MerchantTransactionId = merchantTransactionId; 
        transactionDetail.Gross = dGrossAmount; 
        transactionDetail.Fee = Decimal.Negate(dFeeAmount); 
        transactionDetail.Net = dNetAmount; 
        transactionDetail.TransactionType = (int)TransactionDetailTransactionType.InStoreRefund; 
        transactionDetail.Save(); 
       } 
       catch (Exception ex) 
       { 
        string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId); 
        PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG"); 
        return false; 
       } 

       // Finally update the PurchaseRefund record with the Parent Transaction Id (used as a backup incase the API IPN message for the payment wasn't received). 
       try 
       { 
        PurchaseRefund refund = PurchaseRefund.SingleOrDefault(x => x.RefundTransactionId == merchantTransactionId); 
        if (refund != null) 
        { 
         refund.ParentTransactionId = parentTransactionId; 
         refund.Save(); 
        } 
       } 
       catch (Exception ex) 
       { 
        string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to update PurchaseRefund record (Transaction ID: {0}) with Parent Transaction Id: {1}", merchantTransactionId, parentTransactionId); 
        PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG"); 
        return false; 
       } 

       // If all is succesful we can return true. 
       return true; 
      } 
      catch (Exception ex) 
      { 
       string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId); 
       PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG"); 
       return false; 
      } 
     } 
    } 
} 
13

他们在沙箱中给你的测试IPN是可怕的。看看真正的回调(即使是测试),你会看到它已经定义了payKey。这是你用来查看它。

请注意,它们需要端口80用于IPN回调(尽管这在任何地方都没有记录)。

这里是一个真正的IPN通知(翻译成JSON,具体到我的应用程序的信息删节):

{"payment_request_date":"Sun Jun 24 06:12:20 PDT 2012", 
"return_url":"http://redacted/paypal/transactions/3?status=completed", 
"fees_payer":"EACHRECEIVER", 
"ipn_notification_url":"http://redacted/paypal/notifications", 
"sender_email":"redacted", 
"verify_sign":"AFcWxVredacted", 
"test_ipn":"1", 
"transaction[0].id_for_sender_txn":"redacted", 
"transaction[0].receiver":"redacted", 
"cancel_url":"http://redacted/paypal/transactions/3?status=canceled", 
"transaction[0].is_primary_receiver":"false", 
"pay_key":"AP-redacted", 
"action_type":"PAY", 
"transaction[0].id":"redacted", 
"transaction[0].status":"Completed", 
"transaction[0].paymentType":"SERVICE", 
"transaction[0].status_for_sender_txn":"Completed", 
"transaction[0].pending_reason":"NONE", 
"transaction_type":"Adaptive Payment PAY", 
"transaction[0].amount":"USD 1.00", 
"status":"COMPLETED", 
"log_default_shipping_address_in_transaction":"false", 
"charset":"windows-1252", 
"notify_version":"UNVERSIONED", 
"reverse_all_parallel_payments_on_error":"true"} 

请注意,您必须手动设置在付款请求reverse_all_parallel_payments_on_error。尽管他们建议这样做(并且它可能会帮助你省心),但默认情况下是错误的。

此外,如果您错过了IPN,则可以使用PaymentDetails直接获取所有相同的信息。

我不知道@ swade1987正在看什么,但我的IPNs不包括关于费用金额的任何信息。 (这实际上我是怎么发现这个帖子;试图找出原因的PP API文档是可怕的)

的其他文档可以在这里https://developer.paypal.com/docs/classic/adaptive-payments/integration-guide/APIPN/

+8

秒,“horrid” – 2012-11-14 16:56:02

+2

很高兴我发现这个职位。我感到困惑为什么沙盒ipn通知与真实付款有很大不同。很高兴我花了很多时间建立表格和类文件来处理测试ipn,而真正的测试并不是这样。感谢paypal你的统治! – sapatos 2014-02-15 00:40:04

+1

就像OP一样,我对paykey没有被传回给我的剧本这个概念感到困惑。感谢分享! – 2014-11-04 06:00:32

3

中发现的有点晚,但无论谁从搜索这里碰到引擎...

我刚刚开始自己​​处理Paypal APIs。 OP引用的IPN消息是在卖家简档中定义的IPN通知URL处传送的消息。相比之下,@sai引用的IPN是Adapative Payments IPN,它被提供给Pay,ExecutePaypement或Preapproval API请求中定义的ipnNotificationUrl。

它们是两种不同类型的IPN消息,它们是documented,查找付款信息变量和付款/预付款消息变量。如果您选择两种IPN,您可以获得两种类型的IPN。

关于由OP引用的IPN消息,您可以使用txn_id字段的值通过transactionId获取PaymentDetails。 transId与PayKey一样好以引用已完成的付款。