2012-02-10 2 views
1

У меня есть страница asp.net, которая позволяет ввести информацию о кредитной карте и сумму платежа для авторизации платежа. Внезапно около двух недель назад мы начали получать отчеты о двойных обвинениях, но мы не внесли никаких изменений на страницу. Страница была настроена для отключения кнопки отправки при нажатии. При попытке решить проблему, с тех пор я также установил флаг на странице, когда нажимается кнопка, так что, если флаг установлен, он не позволит кнопке postback (это метод, который мы используем на другой странице это не имеет проблем), но это продолжается.ASP.NET с отключенной кнопкой с дублирующимися postbacks

Есть несколько причин, по которым я рассматриваю пользователя, обновляющего страницу, как крайне маловероятный источник проблемы. Во-первых, мы показываем страницу в элементе управления веб-браузером WPF, он соответствует окну, и единственным признаком того, что это даже веб-страница, является щелчок шума обратной передачи, если вы должны щелкнуть правой кнопкой мыши по телу или ошибка страницы. Только кнопки обновления или возврата находятся в контекстном меню браузера. Затем я не думаю о том, что пользователи не хотят обновлять или возвращаться, если они не получат ошибку страницы, но они сообщают о получении ошибок в процессе. Наконец, я принял меры, чтобы избежать дублирования обратной передачи на стороне сервера, помещая токен в сеанс и проверяя его перед обработкой карты. Таким образом, пользователь должен будет обновить и нажать кнопку «Повторить» быстрее, чем первый запрос может записать токен в состояние сеанса. Самый быстрый способ добиться этого - нажать submit, F5, Ввести все подряд. Я ненавижу игнорировать единственный способ, которым я знаю, что это может произойти, но, похоже, безопасно сказать, что это не то, что происходит. Наконец, после публикации страницы, приложение WPF, через объект сценария, сигнализирует о том, что он может закрыться, чтобы пользователь не смог ничего сделать на странице после обратной передачи до исчезновения браузера.

Проблема только в том, что я не знаю, что происходит. Каким-то образом представление просто прошло через безопасный защитник javascript и безопасную защиту маркера на стороне сервера и получил двойное обвинение, и я понятия не имею, как это сделать. Они регистрировались как происходящие в течение 2 секунд друг от друга. Я проверял, что наш код приложения WPF не вызывает Refresh или иным образом контролирует навигацию браузера. У кого-нибудь есть идеи?

UPDATE Вот некоторые из соответствующего кода:

<style type="text/css"> 
     ... 
    </style> 

    <script type="text/javascript" language="javascript"> 
     function OnProcessing(button) // 
     { 
      //Check if client side validation passes before disabling 

      // if postback - return false. If it's 1, then it's a postback. 
      if (document.getElementById("<%=HFSubmitForm.ClientID %>").value == '1') { 
       return false; 
      } 
      else { 
       // mark that submit is to be done and return true 
       document.getElementById("<%=HFSubmitForm.ClientID %>").value = '1'; 
       button.disabled = true; 
       window.external.OnPaymentProcessing(); 
       return true; 
      } 
     } 

    </script> 
</head> 
<body id="body" runat="server" style="font-family: arial, Helvetica, sans-serif; font-size: 11px;" scroll="no" onkeydown="return CancelEnterKey(event)"> 
    <form id="form1" runat="server"> 
     <asp:scriptmanager ID="Scriptmanager1" runat="server" EnablePageMethods="True"></asp:scriptmanager> 
     <script src="Resources/Scripts/CardInput.js?<%= DateTime.Now.Ticks %>" type="text/javascript" language="javascript"></script> 

     <div id="divCardSwiper" style="text-align:center;" runat="server"> 
      <input id="txtSwipeTarget" type="text" onblur="FocusOnSwipeTarget()" onkeydown="return SwipeTargetCharAdded(event)" 
        style="position: absolute; left: -1000px" /> 
      <table style="margin-left:auto; margin-right:auto"> 
       <tr> 
        <td style="text-align:center"> 
         <span style="font-size: 20pt; font-weight: bold; color: #808080">Please Swipe Credit Card</span> 
        </td> 
       </tr> 
       <tr><td style="text-align:center"><img alt="Card Swiper Image" src="Resources/scra-magnesafe-mini-3.png"/></td></tr> 
       <tr><td style="text-align:center"><span style="font-size: 12pt; font-weight: bold; color: #808080">Or <a href="#" onclick="ManualEntry();return false;">click here</a> to enter manually.</span></td></tr> 
      </table> 
     </div> 
     <div id="divCcForm" runat="server"> 
      <table> 
       <!-- Input Fields --> 
      </table> 
      <asp:Label ID="lblError" runat="server" Font-Bold="True" ForeColor="Red"></asp:Label> 
      <div style="text-align:center;"> 
       <asp:Button ID="btnProcess" runat="server" 
       Text="Process" OnClick="btnProcess_Click" OnClientClick="if (OnProcessing(this)==false){return false;}" UseSubmitBehavior="False"/> 
       <p><strong>Processing may take a moment.<br><font color="red">PLEASE ONLY CLICK PROCESS ONCE</font></strong></p> 
      </div> 

     </div> 
     <asp:Label ID="label1" runat="server" Visible="False"></asp:Label> 
     <asp:HiddenField ID="HFRequestToken" runat="server"/> 
     <asp:HiddenField ID="HFSubmitForm" runat="server"/> 
    </form> 
</body> 

protected void btnProcess_Click(object sender, EventArgs e) 
    { 
     if (IsProcessing()) 
     { 
      //Payment was already processing 
      btnProcess.Enabled = false; //Make sure button doesn't become available again 
      logger.Warn(String.Format("PaymentCollection.aspx was submitted multiple times. Only processing the initial request (Session Token: {0}). FacilityID: {1}, FamilyID: {2}, Amount: {3}", 
               Session[_postBackTokenKey], ViewState[_facilityIDKey], ViewState[_familyIDKey], txtAmount.Text)); 
      return; 
     } 

     lblError.Text = String.Empty; 
     string script = "window.external.OnPaymentProcessingCancelled()"; 
     bool isRefund = (bool)ViewState[_isRefundKey]; 
     bool processed = false; 

     if (ValidateForm(isRefund)) 
     { 
      ProcessingInput pi = new ProcessingInput(); 

      try 
      { 
       CreditCardType cardType = (CreditCardType)Int32.Parse(ddlCardType.SelectedValue); 

       pi.CreditCardNumber = txtCardNum.Text.Trim(); 
       pi.ExpirationMonth = Int32.Parse(ddlExpMo.SelectedValue); 
       pi.ExpirationYear = Int32.Parse(ddlExpYr.SelectedValue); 
       pi.FacilityID = new Guid(ViewState[_facilityIDKey].ToString()); 
       pi.FamilyID = new Guid(ViewState[_familyIDKey].ToString()); 
       pi.NameOnCard = txtName.Text.Trim(); 
       pi.OrderID = Guid.NewGuid(); 
       pi.PaymentType = cardType.ToMpsPaymentType(); 
       pi.PurchaseAmount = Math.Abs(Decimal.Parse(txtAmount.Text)); 
       pi.Cvc = txtCvc.Text.Trim(); 
       pi.IsCardPresent = cbCardPresent.Checked; 


       if (pi.PurchaseAmount >= 0.01m) 
       { 
        MerchantProcessingClient svc = new MerchantProcessingClient(); 

        try 
        { 
         ProcessingResult result; 

         logger.Debug("Processing transaction (Session Token: {0}) for Facility: {1}, Family: {2}, Purchase Amount{3}", 
              Session[_postBackTokenKey], pi.FacilityID, pi.FamilyID, pi.PurchaseAmount); 

         if (!isRefund) 
          result = svc.AuthorizePayment(pi); 
         else 
          result = svc.RefundTransaction(pi); 

         if (result.Approved) 
         { 
          //Signal Oasis that it can continue 
          StringBuilder scriptFormat = new StringBuilder(); 
          scriptFormat.AppendLine("window.external.OrderID = '{0}';"); 
          scriptFormat.AppendLine("window.external.AuthCode = '{1}';"); 
          scriptFormat.AppendLine("window.external.AmountCharged = {2};"); 
          scriptFormat.AppendLine("window.external.SetPaymentDateFromBinary('{3}');"); //Had to script Int64 as string or it caused an overflow exception for some reason 
          scriptFormat.AppendLine("window.external.CcLast4 = '{4}';"); 
          scriptFormat.AppendLine("window.external.SetCreditCardType({5});"); 
          scriptFormat.AppendLine("window.external.CardPresent = {6};"); 
          scriptFormat.AppendLine("window.external.OnPaymentProcessed();"); 

          script = String.Format(scriptFormat.ToString(), result.OrderID, result.AuthCode, result.TransAmount, result.TransDate.ToBinary(), 
                 (result.MaskedCardNum == null ? String.Empty : result.MaskedCardNum.Replace("*", "")), (int)cardType, 
                 pi.IsCardPresent.ToString().ToLower()); 

          processed = true; //Don't allow processing again 
         } 
         else 
         { 
          //log and display errors 
         } 
        } 
        catch (Exception ex) 
        { 
         //log, email, and display errors 
        } 
       } 
       else 
        lblError.Text = "Transaction Amount is zero or too small to process."; 
      } 
      catch (Exception ex) 
      { 
       //log, e-mail, and display errors 
      } 
     } 

     this.ClientScript.RegisterStartupScript(this.GetType(), "PaymentApprovedScript", script, true); 

     //Session[_isProcessingKey] = processed; //Set is processing back to false if there was an error 
     if (!processed) 
      Session[_postBackTokenKey] = null; //Clear postback token if there was an error to allow re-submission 
    } 

    private bool IsProcessing() 
    { 
     bool isProcessing = false; 
     Guid postbackToken = new Guid(HFRequestToken.Value); 

     // This won't prevent simultaneous POSTs because the second could read the value from 
     // session before the first writes it to session. It will help eliminate duplicate posts 
     // if the user is messing with the back button or refreshing. 
     if (Session[_postBackTokenKey] != null && (Guid)Session[_postBackTokenKey] == postbackToken) 
      isProcessing = true; 
     else 
      Session[_postBackTokenKey] = postbackToken; 

     return isProcessing; 
    } 
+0

Пользователь нажимает кнопку «Назад» в браузере и повторно возвращает ответный вызов? –

+0

Спасибо, Роман. Я забыл упомянуть, что javascript уведомляет WPF-приложение о том, что он может закрыть окно при отправке назад, чтобы пользователь мог время от времени его отлично. Нажатие клавиши backspace сделает немного более вероятным, что опция обновления, но в сочетании с внезапной волной появления и частоты (почти один раз в день, иногда больше) также делает это маловероятным, не так ли? – xr280xr

+1

Можем ли мы увидеть некоторые из соответствующих кода? ASPX-разметка и любой Javascript, VB/C# .. –

ответ

1

Я помню, что-то подобное произойдет, когда (хотя и не с кредитной картой). К сожалению, я не помню, что вызвало это, но я чувствую, что это связано с браузером и не под моим контролем, например. что-то в некоторых браузерах вызывало двойные представления, даже если пользователь не понимал этого.

Но решение заключается в том, чтобы справиться с этой ситуацией безопасным для гонки способом. Даже если нет причин, почему (например) автоматический процесс должен или должен работать против вашей страницы, предположите, что это может быть. Может быть, кто-то использует наполнитель формы плагина, который автоматически отменяет? Или, может быть, у них просто есть некорректная надстройка или мышь с плохим контактом на левой кнопке. Кажется странным, но кто знает, что может сделать конечный пользователь, который может неосознанно обходить любые защищенные на стороне клиента защиты.

Предположим, что кто-то может ударить ваш почтовый URL дважды подряд (или 100 раз подряд). Потому что, на самом деле, независимо от того, какая защита на стороне клиента у вас есть, они могли бы. Не беспокойтесь о клиенте. Вместо этого на сервере перед началом транзакции получите безопасную блокировку потока, установите флаг, связанный с их сеансом, который указывает, что транзакция уже выполняется, и выйдите, если этот флаг найден.

Если вы почему-то не доверяете сеансу, просто убедитесь, что данные уникальны перед запуском.

(изменение за комментарий) Если вы переходите в ситуацию, когда у вас более одного SQL-сервера, ответственного за управление сеансом (или, как правило, у вас нет абсолютного способа получить гарантированный замок с помощью обычных средств), тогда вы должны прыгать от радости, что вы делаете так много денег, и нанять специалиста для решения этой проблемы для вас :) Тем временем, не беспокойтесь об этом, если это действительно проблема, с которой вы столкнетесь в ближайшее время.

На простом уровне, вот как бы я это сделал (с одним веб-сервером). Похоже, вы уже знаете, как это сделать, но все равно ...

public class MakeMoney() { 

    private static object locker=new Object(); 

    public void DoTransaction(SaleData data) { 
     lock(locker) { 
      if (SessionLocked) { 
       throw new Exception("Already in progress"); 
       /// or just exit however you want 
      } 
      LockSession(); 
     }  

     Profit(); 

     UnlockSession(); 
    } 
} 

Реализация, LockSessionUnlockSession и SessionLocked только надо делать с окружающей средой. С одним сервером Session или HttpContext.Cache, вероятно, прекрасны. Даже если задействовано несколько серверов, вы можете создать один нераспределенный сервер, который отвечает только за блокировки - даже веб-сайт с большим объемом (если вы не делаете миллионы продаж в минуту!) Должен иметь возможность с только что на одном сервере.

Масштабируемость - это проблема, но если вы ее реализуете любым способом, она должна быть куском пирога, чтобы заменить контроллер на управление замками, если вы когда-нибудь окажетесь в этой славной ситуации.

+0

Это имеет смысл в корне. На практике, однако, я не уверен, как это сделать. Я написал некоторый код, чтобы разместить Семафор в сеансе (на самом деле у меня был другой поток об этом в последнее время) и использование этого для предотвращения повторных обратных передач. Затем я начал думать о масштабируемости; если мы должны были перейти на сеанс SQL или использовать несколько серверов для балансировки нагрузки, он больше не будет работать. Я бы заинтересовался, чтобы услышать больше конкретных конкретных для реализации идей. Включены ли подключаемые модули/надстройки в WPX-браузере IE с активным X-браузером? – xr280xr

+0

Я не уверен в завершенном комментарии WPF (то есть, я понятия не имею, о чем вы говорите :), но см. Правки для получения более подробной информации о реализации. –

+0

Вы упомянули багги [браузер] плагины/надстройки, которые заставляли меня любопытно, если [WPF WebBrowser Control] (http://msdn.microsoft.com/en-us/library/system.windows.controls.webbrowser. aspx), который обертывает элемент управления IE ActiveX, имеет в нем надстройки и плагины. Спасибо за пример. Я не был уверен, что блокировка/монитор будут работать из-за безгражданности asp.net.Я имею в виду, что первый postback создаст экземпляр MakeMoney, а второй postback создаст отдельный экземпляр MakeMoney. Я думал, что заблокированный код был заблокирован только в том экземпляре, в котором он был заблокирован. Разве это не так? – xr280xr

Смежные вопросы