У меня есть страница 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;
}
Пользователь нажимает кнопку «Назад» в браузере и повторно возвращает ответный вызов? –
Спасибо, Роман. Я забыл упомянуть, что javascript уведомляет WPF-приложение о том, что он может закрыть окно при отправке назад, чтобы пользователь мог время от времени его отлично. Нажатие клавиши backspace сделает немного более вероятным, что опция обновления, но в сочетании с внезапной волной появления и частоты (почти один раз в день, иногда больше) также делает это маловероятным, не так ли? – xr280xr
Можем ли мы увидеть некоторые из соответствующих кода? ASPX-разметка и любой Javascript, VB/C# .. –