CRE credit card module, PayFlow Pro, Credit Card Entry window from external app

Yes, I did manage to reverse-engineer the way Epicor Web Access calls the service. No documentation, the source code I decompiled is half broken (from factory!), and there’s a bunch of useless obfuscation to get through, but it does work. Here’s basically how you do it, with two small JS scripts:

// Handler for messages from IFrame
function handleTokenMessage (e) {
    var rsp = e.data;
    if (rsp.operation == 'token' && rsp.params.success) {
        console.log(rsp.params);

        // Got the token! From here we need to save it to DB.
        var token = rsp.params.token;
        var exp = rsp.params.expdate;
        var cardType = rsp.params.cardtype;
        var cvv = rsp.params.cvv;

        var response = 'STATUS=0000:COMPLETE,TOKEN=' + token + ',EXPDATE=' + exp + ',CARDTYPE=' + cardType + ',CVV=' + cvv;
    }

    $('#divLoading').hide();
};


function openHostedTokenPage() {
    $('#divLoading').show();

    // Listen to message from child IFrame...
    window.removeEventListener('message', handleTokenMessage, false);
    window.addEventListener('message', handleTokenMessage, false);

    const host = 'https://sce.toogo.io';
    const xlmns = 'http://epicor.com/webservices/';
    const scriptUrl = '/hp/hostedpayments.js';
    const publicKey = 'pk_0Zq6VpALF3Q7Fp5tW8LyiTZzasAIq2d6'; // Epicor EPX public key (lifted from debugging an EWA token entry)
    const siteID = '00000000'; // Epicor customer Site ID 
    
    // Get and run the server script that will add the IFrame to the DOM and show the credit card entry modal.
    $.getScript(host + scriptUrl, function() {

        var config = [ { xmlns: xlmns },
                       { public_key: publicKey },
                       { ccnamespace: siteID },
                       { name: "@Html.Raw(@Resources.Global.ccTitle)" },
                       { button: "@Html.Raw(Resources.Global.ccSubmit)" },
                       { translation_config: [ { xmlns: xlmns },
                                               { ccnum_lbl: "@Html.Raw(Resources.Global.ccCardNumber)" },
                                               ...
                                               { ccnum_err: "@Html.Raw(Resources.Global.ccCardNumberErr)" } ] },
                       { style_config: [ { xmlns: xlmns },
                                         { control_font: "inherit" },
                                         { control_font_color: "Black" },
                                         ...
                                         { btn_font_background: "White" } ] } ];


        // No idea why it's doing all that below, seems like simple (and useless) obfuscation to me, but 
        // clearly not very effective... I just left it as I found it, seems to work fine, but slow...
        for (var l = config, ccnuPlh = "", c = "", u = "", p = "", h = "", d = "", g = "", m = "", b = "", S = "", y = "", f = "", C = "", w = "", v = "", E = "", T = "", D = "", k = "", A = "", I = "", G = "", _ = 0; _ < l.length; _++)
            void 0 != l[_].public_key && (c = l[_].public_key),
            void 0 != l[_].name && (u = l[_].name),
            void 0 != l[_].button && (p = l[_].button),
            void 0 != l[_].translation_config && (h = l[_].translation_config),
            void 0 != l[_].style_config && (d = l[_].style_config);

        for (var _ = 0; _ < h.length; _++)
            void 0 != h[_].ccnum_lbl && (g = h[_].ccnum_lbl),
            void 0 != h[_].ccnum_plh && (ccnuPlh = h[_].ccnum_plh),
            void 0 != h[_].expiry_lbl && (m = h[_].expiry_lbl),
            void 0 != h[_].expiry_plh && (b = h[_].expiry_plh),
            void 0 != h[_].cvv_lbl && (S = h[_].cvv_lbl),
            void 0 != h[_].cvv_phl && (y = h[_].cvv_phl),
            void 0 != h[_].expiry_err1 && (f = h[_].expiry_err1),
            void 0 != h[_].ccnum_err && (C = h[_].ccnum_err),
            void 0 != h[_].cvv_err && (w = h[_].cvv_err);

        for (var _ = 0; _ < d.length; _++)
            void 0 != d[_].control_font && (v = d[_].control_font),
            void 0 != d[_].control_font_size && (E = d[_].control_font_size),
            void 0 != d[_].control_font_color && (T = d[_].control_font_color),
            void 0 != d[_].control_background && (D = d[_].control_background),
            void 0 != d[_].btn_font && (k = d[_].btn_font),
            void 0 != d[_].btn_font_size && (A = d[_].btn_font_size),
            void 0 != d[_].btn_font_color && (I = d[_].btn_font_color),
            void 0 != d[_].btn_font_background && (G = d[_].btn_font_background);

        HostedPayment.setup({
            response: function (e) {
                // In case there's an error inserting the IFrame in the DOM...
                if (!e.success) alert(e.error);
            },
            service_uri: host
        }).open({
            // Show the modal in the IFrame.
            public_key: c,
            namespace: siteID,
            name: u,
            button: p,
            style_config: {
                control_font: v,
                control_font_size: E,
                control_font_color: T,
                control_background: D,
                btn_font: k,
                btn_font_size: A,
                btn_font_color: I,
                btn_font_background: G
            },
            translation_config: {
                ccnum_lbl: g,
                ccnum_plh: ccnuPlh,
                expiry_lbl: m,
                expiry_plh: b,
                cvv_lbl: S,
                cvv_phl: y,
                expiry_err1: f,
                ccnum_err: C,
                cvv_err: "" == w ? w : "Invalid CVV/CVC."
            }
        });

        
    });
}
1 Like