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

We are currently developing a web application for one of our customers, that allows reps on the road to create quotes and orders from a tablet, with the bare minimum of fields available to them, and some custom validations. The customer has requested the ability for reps to create new customers as well as enter credit card information for customers when ordering, without the people processing the order having to call the customer to fill in the CC info.

I am familiar and have experience with integrating Paypal PayFlow, and PCI-DSS compliance. I know I cannot simply store the CC info, encrypted or not. Normally I would just embed the PayFlow Pro hosted page and allow the rep to create a token which I would then save to the database. However, with the CRE module, the tokens are created via a hosted page supplied by the Epicor Paygate server. Is there a way that I can invoke the hosted page to create new CC tokens from an external application? If I embed the PayFlow hosted page from the customer’s account, will the token obtained this way directly from PayPal work for future transactions through the Epicor PayGate server?

Huge the CRE supports Payeezy which is basically a CC payment gateway that has an API and can be embeded like PayPal or Stripe and it processes right through your existing Epicor Payment Gateway.

I believe @Mark_Wonsil just did some research on this and may have a little more information. But I’d start there if you already have the Epicor Payment Gateway.

Thanks Jose, unfortunately the customer does NOT use the Epicor Gateway, they use Payflow Pro through the CRE… What Epicor seems to be doing is providing token services for their customer’s accounts. Normally PayFlow Pro charges a storage fee for each token they maintain, but the CRE seems to bypass this and issue tokens itself…

ah! I misunderstood got ya

You should be able to Use REST API (Epicor) to make the call to create the token like Epicor does. Just do a trace on the client and replicate via REST, I’ve done it before.

There is no call. If you go to Order Entry, for example, and open up the Credit Card Entry window (with the CRE installed), that page is a hosted web page hosted directly on sce.toogo.io. Epicor just shows the page, and once the token is created it is simply saved to the database. Pretty much the same as what PayPal does, except I don’t have access to invoke that page. I can invoke the one from Paypal, but the issue is that I doubt a token obtained this way will work for future transactions (through Epicor)…

It won’t, you’d have to store that token in Epicor (the same way they do)

Yep, thought so as well… TBH I don’t even know who to ask about this. I can already predict the response from Support if and when I ask them… So I’m asking here and crossing my fingers an Epicor employee will chime in… :slight_smile:

Well I can tell you that the hosted page is invoked from Erp.UI.Shared.dll, as I don’t work with CRE or the CC transactions in the ERP my technical knowledge is very slim.

1 Like

Well, it seems I may have gotten lucky! Epicor hardcoded the request including authentication (without obfuscation!), so I may be able to reverse-engineer their POST request and show the hosted page after all!

Are you able to call the CRE Credit Card Entry as a Service, not Dialog Form?

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

Thanks, Hugo

we get the below error


Do you know the source of this issue?
Do we need any specific HTML code to run the script?

I get the same, but it works… Seems to be a bug with one of the resources on the service end… Do you still see the modal opening? This is what my console shows me when I invoke the openHostedTokenPage function:

The additionnal output at the bottom is because of the translations to French I pass to the service.

I have one more bug in the console

and the form is opened as below
image

What do you think?

No idea about the blocked autofocus, but you need to edit the code and not use it as is. In my script above, I use Razor to pass translations into the service. You need to change those or remove them from the call if all you want it English.

Also, make sure you change the SiteID in the script! Otherwise your tokens will not be accessible.

Can you send me the Razor page code?

I’m afraid not! lol It’s part of our application, and besides, you don’t need it. Just remove the @ directives from the script.