0

I have the following code:

$user_id = $_GET["user_id"];
$user = get_user_by('id',$user_id); 
$balance = mycred_get_users_balance($user_id);
if ($balance > "0") {
        mycred_subtract( 'Check-in',$user_id, -1, 'Checked in.' );
        echo "You are checked in.";
echo ' ' . $user->first_name;
}
else{ echo "You have a balance of " . $balance . ".";
    echo "<br/>";
    echo "Please purchase more credits at our website freethemind.biz";
}

As part of my custom page, what I want to do is first echo the users first name based on the integer in the variable $user_id which does not seem to echo anything at the moment. Also, I'd like to have a timeout so that lets say if the page is visited and the user_id is 1, the mycred_subtract can only run once every 10 seconds or so. This will avoid the user being debited a point for multiple page requests if there was a loss of connection or something of that sort. I'm not sure on how to implement that but what I did read was you can sleep() but its not recommended server side.

My point is to avoid double debiting the user because of a browser deciding to send multiple requests to the webpage in a very short period of time (2-3 seconds).

EDIT

I see that it is possible to create confirm dialog box so a user can only be debited once it has been manually confirmed, would this be through the follow code: onclick="return confirm('Are you sure?');">My Link</a> If so, could I pass a php variable into that JavaScript, I'd like to contstruct it such that: onclick="return confirm(' 'Check-in' . $user->first_name . '?' ');">My Link</a>

Mjall2
  • 253
  • 1
  • 9
  • 24
  • 1
    Possible duplicate of [Preventing form resubmission](https://stackoverflow.com/questions/3923904/preventing-form-resubmission) – Daniel W. Jul 04 '17 at 14:06
  • `sleep()` simply halts the execution of the script _during that request_ for x amount of seconds. It won't help you with your current issue. Just add a session variable with the current timestamp when a transaction is done. When anyone connects, check if that session variable exists and is within your timeout (10 sec). If it is, don't do another transaction. Although, people can get timeouts after 11 seconds as well. – M. Eriksson Jul 04 '17 at 14:07

1 Answers1

0

You want to be transactionally atomic, transactionally safe.

I'd consider holding some unique value (the primary key is fine, but you might not want to reveal that to the world - UUID() would work fine too) of the table you're editing.

When submitting the request to debit the ledger, submit the last known primary key with the request. When processing the request, start a transaction - confirm the submitted primary key is still the latest - and commit.

This protects the customer from double-debiting. If the request is sent twice, the "latest" primary key value in the debit request will no longer be up-to-date. The database can detect this and refuse to perform the action.

In MySQL + PHP, you can handle the start/commit of the transaction several ways, personally I've found (having worked in the financial sector with PHP) that calling a stored procedure is the most robust approach. Thusly there's practically zero risk of the transaction never completing (if PHP looses contact with the database, for example).

So the flow would look something like:

GET /view-account
  <form method="post" action="debit-account">
     <input type="hidden" name="last_trx_id_for_customer" value="123">
     <input type="numeric" name="amount" value="10.00">
     <button type="submit">Debit me</button>
  </form>

POST /debit-account
  <?php
  $db->execute('CALL sp_debit_account(:last_trx_id_for_customer, :customer_id, :amount)', ...);

  ...

  (Stored proc:)
  START TRANSACTION;
     SELECT MAX(trx_id) FROM ledger WHERE customer_id = p_customer_id INTO p_trx_id;
     IF (p_trx_id != p_supplied_last_trx_id) THEN
         ROLLBACK;
     ELSE
         ... debit account
         COMMIT;
     END IF;
wally
  • 3,492
  • 25
  • 31