Home > database >  How to ensure an online payment is executed only once?
How to ensure an online payment is executed only once?

Time:08-26

I developed and I maintain a website for a client of mine that has online payments.

For the most part it uses the payment gateway provided by the client's bank Banorte in Mexico. The issue I am having is that on some occasions the payments get executed twice. I am not sure if the user double clicks or there's some other issue, currently still trying to find out what happens, but the last case that this happened the space between the payments was around 2 minutes. The other issue regarding this is that the product is limited to 1 per person since it's an event access.

I have a form that gets the card information and on click I do the following (using Foundation 6):

var cardSubmit = false;
$(document).on("submit", "#card-form", function(e) {
    e.preventDefault();
    if(cardSubmit) {
        $("#process").addClass("loading");
        $("#card-form button[type='submit']").prop("disabled", true).html("Processing...");
        setTimeout(function() {
            e.currentTarget.submit();
        }, 2500);
    }
})

$(document).on("formvalid.zf.abide", "#card-form", function() {
    cardSubmit = true;
})

$(document).on("forminvalid.zf.abide", "#card-form", function() {
    cardSubmit = false;
})

The .loading class adds an ::after element that goes on top of everything and besides I disable the button. This is to ensure that the user can't click on the button again. I am not sure if under this scenario a double click is possible.

After submitting (code reduced for obvious reasons), I do the following:

$client = new \GuzzleHttp\Client(['base_uri' => 'https://via.banorte.com:443']);

$parametros = [...]; // information required for the payment

$postQuery = http_build_query($parametros);
$contentLength = strlen($postQuery);

$resultadoPago = $client->post('/payw2', [
    'query' => $parametros,
]);
$headersPago = $resultadoPago->getHeaders();

if ($headersPago['RESULTADO_PAYW'][0] == 'A') {
    // This runs if the response is Authorized
    // The first thing I do here is create the new transaction and mark it as paid
    // Then I do some more stuff and finally redirect to a thank you page
}

Then if the user goes to the product I check if there are already transactions that have this same product so that I don't allow the user to try and buy it again. So that leads me to believe that the double payment occurs in the same window and something fails in the process.

Is there a way to guarantee that I execute this only once? Or am I always dependent on the response of the payment gateway? I was thinking of adding the same check as mentioned in the previous paragraph, but not sure if the page is refreshing or the user is refreshing the page.

CodePudding user response:

I would consider adding the true or false to a data attribute and make sure the target was the actual form. I would not trust e.currentTarget to still be the same after 2.5 seconds

$(document).on("submit", "#card-form", function(e) {
  e.preventDefault();
  if (!$(this).data("cardSubmit")) return ;
  let DOMForm = this;
  $("#process").addClass("loading");
  $("button[type='submit']",this).prop("disabled", true).html("Processing...");
  setTimeout(function() {
    DOMForm.submit();
    $(DOMForm).data("cardSubmit",false); 
  }, 2500);
})

$(document).on("formvalid.zf.abide", "#card-form", function() {
  $(this).data("cardSubmit",true);
})

$(document).on("forminvalid.zf.abide", "#card-form", function() {
  $(this).data("cardSubmit",false);
})

CodePudding user response:

I think the deeper issue is your validation of whether a product has been purchased. I dont see a reason why double-clicking would make a product be purchased twice if you keep track of the purchases and products in a database.

If you do manage a database, you could keep track of the purchases yourself and validate against the database on each call if the product is available. If available, then process the payment and update your database accordingly with the payment service response. Finally, return the status of the purchase to your frontend.

  • Related