Search code examples
javascriptsingle-page-applicationmastercardmpgs-session-js

How to work with MPGS session.js in an SPA


Following the docs from MPGS session we implemented the "session.js" SKD in a Single Page Application (SPA).

Some unexpected behaviour takes place... and the SDK malfuncitons as describbed below.

As we know modern SPA's add/remove html elements as the user navigates its pages. Keep this in the back of you mind while reading the issue description and MPGS's support answer below.

The first time (let us call it FIRST_CALL) the user arrives at the payment page we issue the PaymentSession.configure command as follows (the details object contains the correct ids of existing readonly input fields as required by the SDK):

PaymentSession.configure(
    {
        session: details.sessionId,
        fields: {
            card: {
                number: `#${details.htmlElementIdCardNumber}`,
                securityCode: `#${details.htmlElementIdCardCCV}`,
                expiryMonth: `#${details.htmlElementIdCardExpiryMonth}`,
                expiryYear: `#${details.htmlElementIdCardExpiryYear}`,
                nameOnCard: `#${details.htmlElementIdCardNameOnCard}`,
            },
        },
        frameEmbeddingMitigation: ['javascript'],
        callbacks: {
            initialized: (response: any) => {
               console.log('PaymentSession initialized', response);
            },
            formSessionUpdate: (response: any) => {
               console.log('PaymentSession formSessionUpdate', response);
            },
        },
        interaction: {
            displayControl: {
                formatCard: 'EMBOSSED',
                invalidFieldCharacters: 'REJECT',
            },
        },
    }

With this call the UI elements are changed by the SDK as expected, and a payment can be performed.

Should the user decide not to click a "pay" button (which would call PaymentSession.updateSessionFromForm('card') and successfully store the payment details as necessary) and navigates away ==> the misbahaviour is triggered... Just to make it more clear, when the user naviagates away from the payment page, the HTML elements which the SDK modified are removed from the DOM (these are named in the details object above).

When the user once more comes to the payment page and it issues again the above call (let us call it SECOND_CALL), the UI is not changed and the payment details cannot be inserted in the html input fields (as these remain read-only and detached from the SDK).

It looks like the "session.js" SDK does not clear its linkage to the elements present when FIRST_CALL was issued (and which were removed from the DOM)... So when SECOND_CALL happes, the html elements are not updated

Is there a way to clear the linkage of the "PaymentSession" SDK Objects from the elements of the FIRST_CALL ?

==> This is an SPA and a page refresh should not be an option.

I have asked MPGS support on what to do and this is their current response (which I do not find acceptable):

The current behavior of session SDK (session.js) is by design, it won’t support multiple calls to configure() if card details fields are deleted and recreated. This will require enhancement to session SDK. We have asked product to review it.

As we mentioned before, this scenario can be supported if merchant hide/show card details fields instead of deleting/recreating. We don’t understand why it can’t be done in SPA. SPA doesn’t mandate to delete/recreate fields, it depends on the underlying implementation.


Solution

  • I got it to work...

    The only way I found was to completelly remove the session.js objects from the DOM and re-add its reference:

    mpgsScriptId = 'mpgs-session-js'
    
    detachSdkFromUi() {
        const script = document.getElementById(mpgsScriptId);
        if (script) {
            script.remove();
            PaymentSession = undefined;
        }
    }
    
    addMpgsScript() {
        detachSdkFromUi();
    
        return new Promise<void>((resolve, reject) => {
            const script = document.createElement('script');
            script.id = mpgsScriptId;
            script.async = false;
            script.src = '<session.js url>';
            script.onload = () => {
                resolve();
            };
            // this call inserts the "PaymentSession" object into the window.
            document.body.append(script);
        });
    }
    
    async attachSdkToUi(details: any) {
        await addMpgsScript();
    
        return new Promise<void>((resolve, reject) =>
            PaymentSession.configure({
                session: details.sessionId,
                fields: {
                    card: {
                        number: `#${details.htmlElementIdCardNumber}`,
                        securityCode: `#${details.htmlElementIdCardCCV}`,
                        expiryMonth: `#${details.htmlElementIdCardExpiryMonth}`,
                        expiryYear: `#${details.htmlElementIdCardExpiryYear}`,
                        nameOnCard: `#${details.htmlElementIdCardNameOnCard}`,
                    },
                },
                frameEmbeddingMitigation: ['javascript'],
                callbacks: {
                    initialized: (response: any) => {
                        console.log('PaymentSession initialized', response);
                        if (response?.status !== 'ok') {
                            reject();
                            return;
                        }
    
                        resolve();
                    },
                    formSessionUpdate: (response: any) => {
                        console.log('PaymentSession formSessionUpdate', response);
                    },
                },
                interaction: {
                    displayControl: {
                        formatCard: 'EMBOSSED',
                        invalidFieldCharacters: 'REJECT',
                    },
                },
            })
        );
    }
    

    When MPGS releases a version compatible with SPA's I might revisit this... It will do for now.