Web Analytics Made Easy -
StatCounter Need help understanding Double-Submittal Code - CodingForum

Announcement

Collapse
No announcement yet.

Need help understanding Double-Submittal Code

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Need help understanding Double-Submittal Code

    I have some code that I got that is supposed to prevent a form from being double-submitted and billing a customer twice.

    Looking at the code, I'm not certain I understand how it works?!

    Here it is...
    PHP Code:
    <?php    session_start();    ?>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
        <html xmlns="http://www.w3.org/1999/xhtml">

    <head>
    </head>

    <body>
        <div id="wrapper" class="clearfix">
            <div id="inner">
                <!-- Include BODY HEADER -->
          <?php    require_once(ROOT 'components/body_header.inc.php');    ?>

                <!-- PAYMENT FORM -->
                <div id="paymentForm">
                    <?php
                        
    // Initialize variables.
                        
    $form_value '';

                        
    // Check if Form value was set.
                        
    if (isset($_POST['form_value'])){
                            
    $form_value $_POST['form_value'];
                        }

                        
    // Check if Form value was set in $_SESSION.
                        
    if (!isset($_SESSION['form_value'])){
                            
    $_SESSION['form_value'] = '';
                        }


                        
    // *********************************************************************
                        // HANDLE FORM.
                        // *********************************************************************
                        
    if (isset($_POST['submitted'])){
                            
    // Form was Submitted.

                            // Check for Double-Submittal.
                            
    if ($form_value == $_SESSION['form_value']){
                                
    // Initial Payment was Submitted.

                                // Check for Errors.
                                
    if (empty($errors)){
                                    
    // PROCESS PAYMENT.

                                    // Force new Unique ID to be assigned on Form Re-submit!!!
    //                                unset($_SESSION['form_value']);

                                    
    switch($response_array[0]){
                                        case 
    "1":
                                            
    // Approved.
                                            
    $responseMsg1 "<p>Congratulations!  Your transaction was successful.</p>
                                                                                <p>Your Order Number is: '" 
    $invoiceNumber "'</p>";

                                    
    // Do not return to Payment Form!!!
                                    
    exit();
                                }
    // End of CHECK FOR ERRORS.
                            
    }else{
                                
    // Form Double-Submitted!!
                                
    $responseMsg1 "<p>Sorry!  You have already submitted a payment.</p>";
                                
    $responseMsg2 "<p>For your protection, this Payment Form has been disabled.</p>";

                                
    // Do not return to Payment Form!!!
                                
    exit();
                            }
    // End of CHECK FOR DOUBLE-SUBMITTAL.
                        
    }else{
                            
    // Drop through to Payment Form.

                        
    }// End of HANDLE FORM.
                    
    ?>

                    <!-- NEW -->
                    <?php
                        
    // Create a Unique ID to be assigned to Form.
                        
    $_SESSION['form_value'] = md5(uniqid(rand(), true));
                    
    ?>


                    <!-- @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -->
                    <!-- HTML PAYMENT FORM -->
                    <form id="payment" action="" method="post">

                        <!-- NEW -->
                        <!-- Hidden field used to store Form's Unique ID. -->
                        <input type="hidden" name="form_value" id="form_value"
                                         value="<?php echo $_SESSION['form_value']; ?>" />

                        <!-- Submit Form -->
                        <fieldset id="submit">
                            <!-- Place Order button -->
                            <input name="submit" type="image" src="../buttons/PlaceOrder_black.png" value="Place Order" />
                            <input name="submitted" type="hidden" value="true" />
                        </fieldset>
                    </form>
                </div><!-- End of PAYMENT FORM -->

            </div><!-- End of #INNER -->
        </div><!-- End of #WRAPPER -->
    </body>
    </html>

    Questions:

    1.) How is it that a Form can be re-submitted and create a double-posting in the first place?

    2.) If you submit a form like this, and then the form is re-displayed with a Pass/Fail message, and then you hit the "Back" button on your browser and then the "Forward" button, what values appear in the $_POST on this second time displaying the form?

    3.) I've read this code several times, and even though it works, I'm not seeing where the $form_value gets changed so that it does not match $_SESSION['form_value']?!

    It seems like you never get back to this code...

    PHP Code:
    <!-- NEW -->
    <?php
            
    // Create a Unique ID to be assigned to Form.
            
    $_SESSION['form_value'] = md5(uniqid(rand(), true));
    ?>
    ...even if you hit the "Back" and then "Forward" browser buttons?!

    Thanks,



    Debbie

  • #2
    When you go to a landing page from a method="post" form and hit your refresh button your browser will ask if you want to re-send the data (unless there was a redirect somewhere in the chain, in which case you won't get the option). That would be one way in which a user could "double-submit" the form without really meaning to.

    Anyway, this script generates a random hash string, stores a copy in the $_SESSION array, and assigns the hash value to a hidden input named "form_value." When the form is submitted the script checks for $_POST['form_value'] having been set to a value equal to the copy stored in $_SESSION. If they match, then the submission is new and the script goes on to check for errors and attempt to process the payment.

    If the values don't match, then a double-submit condition exists and the data is ignored and the form is rejected.

    Then, whether rejected or accepted, the script generates a new random hash value and stores it in $_SESSION and in the form input named "form_value" and sets the form back up.

    At this point, if the user was to hit "refresh" they would be asked if they want to re-send the data. If they say "yes" and the page gets the same data re-sent, they will be re-sending the OLD value of form_value and it will no longer match the copy stored in $_SESSION, hence the form will be rejected.
    The object of opening the mind, as of opening the mouth, is to shut it again on something solid. –G.K. Chesterton
    See Mediocrity in its Infancy
    It's usually a good idea to start out with this at the VERY TOP of your CSS: * {border:0;margin:0;padding:0;}
    Seek and you shall find... basically:
    validate your markup | view your page cross-browser/cross-platform | free web tutorials | free hosting

    Comment


    • #3
      Originally posted by Rowsdower! View Post
      When you go to a landing page from a method="post" form and hit your refresh button your browser will ask if you want to re-send the data (unless there was a redirect somewhere in the chain, in which case you won't get the option). That would be one way in which a user could "double-submit" the form without really meaning to.
      But most people who go "Back-Forward" will say "Yes, resubmit data" so they can go forward again.

      Seems like a major issue on every web page in your website.


      At this point, if the user was to hit "refresh" they would be asked if they want to re-send the data. If they say "yes" and the page gets the same data re-sent, they will be re-sending the OLD value of form_value and it will no longer match the copy stored in $_SESSION, hence the form will be rejected.
      That is the part I don't get.

      ** Hard to explain here **

      I stepped through my code in NetBeans a million times last night.

      Let me try and describe where I am confused...

      1.) Form loaded
      - $_SESSION['form_value'] gets 123
      - Hidden Field gets same value (123)
      - Form displayed

      2.) Form submitted
      - $form_value gets 123 from Hidden Field
      - $form_value (123) compared to SESSION (123)
      - Transaction successful message displayed

      3.) Hit "Back" - Form Re-Loaded
      - $_SESSION['form_value'] gets 456
      - Hidden Field should get same value (456)
      - Form Re-displayed

      4.) Hit "Forward" - Form Re-submitted (??)
      - $form_value should get NEW 456 from Hidden Field
      PHP Code:
              // Check if Form value was set.
              
      if (isset($_POST['form_value'])){
                  
      $form_value $_POST['form_value'];
              } 
      - Would expect $form_value (to be 456 and not 123) compared to SESSION (456)
      - Making Transaction successful message displayed


      I see what is happening in NetBeans but not understanding the logic of why old $_POST values that I cannot see in NetBeans suddenly appear and are used?!


      Debbie

      Comment


      • #4
        But most people who go "Back-Forward" will say "Yes, resubmit data" so they can go forward again.
        Yes, and this is why this script exists. This is to prevent people from accidentally double/tripe/quadrupal/+++ paying if and when they do say "yes" to that question.

        That is the part I don't get....
        I will start off by saying that I have no idea what NetBeans is (sorry). But fortunately, that's not really important for us to talk about this.

        Your BROWSER has cached the post data from your last submission in its memory (not the "temporary internet files" folder, but actually in the computer's RAM). So when you re-send the data to the server (as a result of refresh or back/forward navigation) your browser asks if you want to re-send the previous data. When you say "yes" your browser is sending the post data that it originally did despite the fact that your server's session data is now different for the form_value variable. Your browser doesn't know or care that the server's form_value has changed because it is just repeating its previous action. Think of it as copy/paste. Using the form's submit button is like copying. Then using the browser's re-send data thingy is like pasting. Even if you go back and see new text, you're still only going to paste again until you make another copy action. (does that analogy help?)

        It doesn't matter if you use the "back" button on your browser and can see the exact same hash key there in the hidden input field as you saw the first time around or if it's an all-new key. Unless you click the submit button in the form you aren't sending any NEW data to the server. You are only going to be re-sending the exact same data as the last time and the key present in that data is only LOCAL TO YOU now. The server has already moved on with the $_SESSION variable to a new hash key. So when you click the browser's "forward" button, the hash key you send will not match the session's stored value on the server and the form submission will be blocked.

        For further illustration, try changing the input type of "form_value" from "hidden" to "text." Then add print_r($_POST); just after session_start(); and go all through the steps you listed in your last post (steps 1-4). See which key it is that is still being passed in the post data. For more fun, edit the text field key to anything you like before hitting the browser's "forward" button. Did your change get submitted? No. Why? Because you didn't re-submit the form, you merely re-sent the OLD form data from the browser's memory.
        The object of opening the mind, as of opening the mouth, is to shut it again on something solid. –G.K. Chesterton
        See Mediocrity in its Infancy
        It's usually a good idea to start out with this at the VERY TOP of your CSS: * {border:0;margin:0;padding:0;}
        Seek and you shall find... basically:
        validate your markup | view your page cross-browser/cross-platform | free web tutorials | free hosting

        Comment


        • #5
          Rowsdower,

          Thanks for the thoughtful response!!


          Originally posted by Rowsdower! View Post
          Yes, and this is why this script exists. This is to prevent people from accidentally double/tripe/quadrupal/+++ paying if and when they do say "yes" to that question.
          It seems like you should incorporate this on every web page that has a form? Or maybe even more than that?


          I will start off by saying that I have no idea what NetBeans is (sorry).
          It is an IDE.


          Your BROWSER has cached the post data from your last submission in its memory (not the "temporary internet files" folder, but actually in the computer's RAM). So when you re-send the data to the server (as a result of refresh or back/forward navigation) your browser asks if you want to re-send the previous data. When you say "yes" your browser is sending the post data that it originally did despite the fact that your server's session data is now different for the form_value variable.
          This seems to be the key point here.


          Your browser doesn't know or care that the server's form_value has changed because it is just repeating its previous action. Think of it as copy/paste. Using the form's submit button is like copying. Then using the browser's re-send data thingy is like pasting. Even if you go back and see new text, you're still only going to paste again until you make another copy action. (does that analogy help?)
          Yes, that helps.


          It doesn't matter if you use the "back" button on your browser and can see the exact same hash key there in the hidden input field as you saw the first time around or if it's an all-new key. Unless you click the submit button in the form you aren't sending any NEW data to the server. You are only going to be re-sending the exact same data as the last time and the key present in that data is only LOCAL TO YOU now.
          Another key point!


          The server has already moved on with the $_SESSION variable to a new hash key. So when you click the browser's "forward" button, the hash key you send will not match the session's stored value on the server and the form submission will be blocked.
          What was throwing me off is that NetBeans showed...

          PHP Code:
              // Check if Form value was set.
              
          if (isset($_POST['form_value'])){
                  
          $form_value $_POST['form_value'];
              } 
          ...this code above was evaluated as being TRUE so I just assumed that NEW data was being (or should be) sent?!


          For further illustration, try changing the input type of "form_value" from "hidden" to "text." Then add print_r($_POST); just after session_start(); and go all through the steps you listed in your last post (steps 1-4). See which key it is that is still being passed in the post data.
          Interesting manual technique, but my IDE does the same thing but in a better way.


          For more fun, edit the text field key to anything you like before hitting the browser's "forward" button. Did your change get submitted? No. Why? Because you didn't re-submit the form, you merely re-sent the OLD form data from the browser's memory.
          Okay, I think I get it now.

          THANKS for helping me get this figured out in my head!!

          Now the challenge is to figure out WHEN, WHERE, and WHY I need to use similar code on my website.

          (I'm so fearful of something breaking or blowing up and me not even knowing that I should have written code to handle things... I know I started using this code after I found out that people could resubmit the Credit card Checkout Form a million times if I didn't prevent that!!)


          Debbie

          Comment


          • #6
            Originally posted by doubledee View Post
            It seems like you should incorporate this on every web page that has a form? Or maybe even more than that?
            Well I touched on one other option in an earlier post I made in this thread. If you are using a redirect you won't have to worry about this problem at all.

            In a case like that rather than submitting to a workflow like this:

            Page A -> Page A

            You use a workflow something like one of these:

            Page A -> Processing Page -> Page A
            Page A -> Processing Page -> Page B
            Page A -> Processing Page -> Another Processing Page -> Page A
            Page A -> Processing Page -> Another Processing Page -> Page B

            And you get the idea. You post to an intermediary page which handles the payment (or database, or whatever you need done) but DOES NOT print anything to the browser, but instead uses a header('Location: Page A.html'); function (or wherever you want to send them) to redirect the user to another landing page, so that they land there WITHOUT using a post submission. Then if the user hits the "refresh" button or if they use the "back" button and the "forward" button they go right between Page A and whatever landing page they ultimately hit and never see the intermediary page(s), hence the browser never asks to re-send the data.
            Last edited by Rowsdower!; Sep 1, 2011, 02:21 PM. Reason: typos...
            The object of opening the mind, as of opening the mouth, is to shut it again on something solid. –G.K. Chesterton
            See Mediocrity in its Infancy
            It's usually a good idea to start out with this at the VERY TOP of your CSS: * {border:0;margin:0;padding:0;}
            Seek and you shall find... basically:
            validate your markup | view your page cross-browser/cross-platform | free web tutorials | free hosting

            Comment


            • #7
              Originally posted by Rowsdower! View Post
              Well I touched on one other option in an earlier post I made in this thread. If you are using a redirect you won't have to worry about this problem at all.

              In a case like that rather than submitting to a workflow like this:

              Page A -> Page A

              You use a workflow something like one of these:

              Page A -> Processing Page -> Page A
              Page A -> Processing Page -> Page B
              Page A -> Processing Page -> Another Processing Page -> Page A
              Page A -> Processing Page -> Another Processing Page -> Page B

              And you get the idea. You post to an intermediary page which handles the payment (or database, or whatever you need done) but DOES NOT print anything to the browser, but instead uses a header('Location: Page A.html'); function (or wherever you want to send them) to redirect the user to another landing page, so that they land there WITHOUT using a post submission. Then if the user hits the "refresh" button or if they use the "back" button and the "forward" button they go right between Page A and whatever landing page they ultimately hit and never see the intermediary page(s), hence the browser never asks to re-send the data.
              That sounds like a much more mature way to code a website...

              How hard is it to design/code that way?

              And how do you successfully and securely pass data between all of those pages? (I'm having that issue right now on a related topic.)



              Debbie

              Comment


              • #8
                Coding that way isn't tough at all. I am in the habit of coding all of my processing at the top of the PHP file before any output is generated. The only PHP that occurs after the start of the HTML output is for displaying dynamic text, figures, and calculations that I have already made earlier in the script.

                So for me, the only adjustment to make in a case like that is to cut the file off before the HTML output and use a redirect header to follow on to the next page, which contains everything that would have otherwise been below the first script. If there is any information that I need to pass to the final page - such as a success or failure message - I set it in the intermediary page using a $_SESSION variable (or log it in a database if I need to), then I use it in the landing page and unset that $_SESSION variable (or delete that database entry) right after printing it so that I don't forget to release it.

                Other than that type of mild adjustment there really isn't anything special to worry about for page functionality.

                As for security - I am the wrong source to ask. I have no background in security at all so I don't know what (if any) special things need to be done to secure this process that wouldn't need to be done for an A->A method.
                The object of opening the mind, as of opening the mouth, is to shut it again on something solid. –G.K. Chesterton
                See Mediocrity in its Infancy
                It's usually a good idea to start out with this at the VERY TOP of your CSS: * {border:0;margin:0;padding:0;}
                Seek and you shall find... basically:
                validate your markup | view your page cross-browser/cross-platform | free web tutorials | free hosting

                Comment


                • #9
                  Here I go again breaking something that was basically working...

                  I would like to learn how to break up my MEGA-php pages that get re-submitted to themselves are are really several pages all-in-one including a mess of nested PHP and HTML?!

                  On simple pages like my Log In page, I have done a good job of putting 95% of the PHP first and then the HTML at the end. This is easy to do because things are straight-forward...
                  Code:
                  <?php
                  If form was submitted, check data.
                  If data is okay, look for user.
                  If user found, set Session variables and re-direct.
                  ?>
                  
                  <html>
                  display form here
                  </html>
                  Where things get much trickier is when I have something like my "Add a Comment" page - or even worse on my "Credit Card payment Processing" page - that first has to display the form, and then check the form data, and then if the comment was successfully submitted re-display the page with a message like "You comment was added." or a fail message like "A system error occurred."

                  The approach I've taken - which works, but is a maintenance nightmare - is as follows...

                  - Start off with HTML headers
                  - Create HTML Wrapper DIVs for page layout
                  - Switch to PHP to handle form data
                  - If no errors, echo HTML to display Success/Failure message which would fall in the middle of my HTML Wrapper DIVs above
                  - Close out this inserted HTML to close out HTML page
                  - If form is double-submitted, echo different HTML which would fall in the middle of my HTML Wrapper DIVs above
                  - Close out this inserted HTML to close out HTML page
                  - Have standard HTML Form code here which we get to if the form was not submitted.


                  So basically I have...
                  Code:
                  <html>
                  <head></head>
                  <body>
                  php code that displays Success/Fail HTML messages, then close out html prematurely.
                  
                  php code that displays Double-Submittal Error message, then close out html prematurely.
                  
                  html form if no POST exists
                  </body>
                  </html>
                  My "CC Payment Processing" script is 903 lines long... One big mess to manage!!!

                  If I could break things up, so there is a "add_comment.php" script to display the form and then a second page "add_comment_results.php" to display the outcome of the attempted submittal, that would be much easier to manage.

                  And if there could be a 3rd page in between those two, to do the PHP processing, maybe even better?!

                  How I can dissect these unwieldy pages and break them down remains to be seen.

                  If you can help me get started, I think my website would be much more 1.) Maintainable, 2.) Scalable, and 3.) Secure.

                  Thanks,


                  Debbie
                  Last edited by doubledee; Sep 1, 2011, 03:36 PM.

                  Comment


                  • #10
                    Originally posted by doubledee View Post
                    And how do you successfully and securely pass data between all of those pages? (I'm having that issue right now on a related topic.)
                    There are only 2 ways and we've told you this many times:
                    Sessions
                    Databases.

                    Sessions for short term use where users are almost certain to be following a logic flow (EG page1 leads to page2)

                    Databases for storage of long term data - EG where a user may go away and come back.

                    There is no other way plain and simple. What part of this continues to trouble you?
                    Last edited by tangoforce; Sep 1, 2011, 03:36 PM.
                    "Tango says double quotes with a single ( ' ) quote in the middle"
                    '$Name says single quotes with a double ( " ) quote in the middle'
                    "Tango says double quotes ( \" ) must escape a double quote"
                    '$Name single quotes ( \' ) must escape a single quote'

                    Comment


                    • #11
                      OK, let's play nice everybody... No need to be rude.


                      As for learning "how" to separate your code, well, that's something you more or less have to just learn. It's like learning how to chew your food. It can't really be "taught," you just find out what works for you and go with it. It is going to be influenced by your own coding style so anything any one other coder does might not be right for you. Just find what works and what you can keep track of in your own mind. The important part is that YOU find it manageable/scalable.

                      The best thing I can recommend is to save a backup version of your site (database included) and put it somewhere safe so you can restore it if things get wrecked. Then... smash it up. Take it one page at a time and test with each new page to make sure it works after being realigned.

                      First I would take all "processing" blocks of code out of the body and move them up to the top of the page. You'll probably need to pay special attention to your if/else statements and any for/while loops to make sure that things are being processed correctly.

                      Then, for any files that get submitted, I would grab the relevant processing script from your existing page and paste it in to the new intermediary file so that ALL it does is process the form. Then, at the bottom of the intermediary file, use the header() function to redirect the user either back to the original page or else to your new landing page - whatever the case (and your preference) may be.

                      Really, the main thing is to try, try again. And if you break it, remember that there isn't anything that can't be fixed since you made a backup.

                      And try not to think of having more intermediary pages or more redirects as being better. Generally, you want as few stops as possible. The number of files is not important to anyone but the developer. It is the number of HTTP requests that will impact users, so I generally try not to redirect more than once.

                      I can also say that when I wanted a 1-file system for a form submission I have been known in the past to have a page submit a post form to itself, but at the very top of the script I have a form-handling section in an "if" statement while the rest of the page is in an "else" statement. Then the form-handling portion redirects the user back to the same page again when the processing is done and when redirected the user runs in the "else" statement and loads the page as normal. So you see, you can still do a redirect in an all-in-one file - if that is what you want to do. This could have easily been done with two files and the user wouldn't really know the difference. File count is irrelevant in that sense.

                      This is one place that you get to do it however you like.
                      The object of opening the mind, as of opening the mouth, is to shut it again on something solid. –G.K. Chesterton
                      See Mediocrity in its Infancy
                      It's usually a good idea to start out with this at the VERY TOP of your CSS: * {border:0;margin:0;padding:0;}
                      Seek and you shall find... basically:
                      validate your markup | view your page cross-browser/cross-platform | free web tutorials | free hosting

                      Comment


                      • #12
                        Originally posted by Rowsdower! View Post
                        OK, let's play nice everybody... No need to be rude.
                        I'm not being rude, I'm being plain honest and saying it as it is. There are two ways to realistically pass data from one page to another.
                        "Tango says double quotes with a single ( ' ) quote in the middle"
                        '$Name says single quotes with a double ( " ) quote in the middle'
                        "Tango says double quotes ( \" ) must escape a double quote"
                        '$Name single quotes ( \' ) must escape a single quote'

                        Comment


                        • #13
                          Originally posted by doubledee View Post
                          My "CC Payment Processing" script is 903 lines long... One big mess to manage!!!
                          That's not totally out of line IMO though if it is a "mess" as you say then it would likely be worth it to "un mess" it I have a page for payments that is just over 400 lines long and does everything needed.
                          Dave .... HostMonster for all of your hosting needs

                          Comment


                          • #14
                            Originally posted by djm0219 View Post
                            That's not totally out of line IMO though if it is a "mess" as you say then it would likely be worth it to "un mess" it
                            That's what you guys are here for - to help Debbie "un-mess stuff"?!


                            I have a page for payments that is just over 400 lines long and does everything needed.
                            Then you must be twice as efficient as me?!

                            Working on "add_comment2.php" as we speak...


                            Debbie

                            Comment


                            • #15
                              Originally posted by doubledee View Post
                              Then you must be twice as efficient as me?!
                              Naw, just been doing this programming stuff for WAY too long
                              Dave .... HostMonster for all of your hosting needs

                              Comment

                              Working...
                              X
                              😀
                              🥰
                              🤢
                              😎
                              😡
                              👍
                              👎