Praxis Wiki logo

Hosted Payment Fields (HPF) Usage Guide HPF Usage Guide


Overview

Praxis Hosted Payment Fields (hereafter HPF) is a new form of implementing credit card payment flows with Praxis.

It allows merchants to fully customize the credit card form, while keeping the payment details secure and out of band to your website and platform, thus they do not require you to be PCI-DSS certified.

HPF is used along with the Praxis Direct API to send the transaction for processing to any (server to server) Credit Card payment solution while still supporting the Praxis smart routing and cascading features which help our customers increase their approval ratios.

INTEGRATION

In the following sequence diagram, you can understand the high level integration flow:

image

HPF is using encryption and secure server-side transfer for the sensitive data. With that, HTTPS is the only allowed protocol of the host (page where the HFP is integrated).

Server to server call to initiate HPF

First step would be to open an HPF session. The API documentation for this step is documented in this section open-hpf-session. The purpose for this call is to initiate the HPF session and a successful response will provide a session_token that can be used to initialize the SDK in the next step.

{danger.fa-exclamation-triangle} Please note that any domain is an important part of the HPF initialization process. The page (domain) where HPF is open, needs to be whitelisted for your API account beforehand.

Include HPF SDK in payment page

Second step would be to use the received session_token to request the HPF session when the HPF SDK is initialized. With this SDK you will be able to customize the look and feel of the payment fields to fit your needs. The HPF SDK allows specifying custom CSS which can override the settings in the Praxis backoffice. You control everything outside the payment fields in your website code, while the HPF SDK provides the interface and the events required to take relevant actions such as the rendering of the card templates, reacting to validation errors, and so on.

Implement your workflow

While developing the user journey, you will use information from events emitted by the SDK, to implement certain parts of the user experience, such as rendering of the stored cards, input of new card details, and signaling to tokenize the card using the tokenizeCard method at the end to receive as a result hpf_auth_token. At the end of this workflow, you call an endpoint on your server, to kick off the next step to execute the transaction.

{danger.fa-exclamation-triangle} NOTE: the direct API call must be a server to server call, not to be called from the user's browser.

Please refer to the HPF SDK reference section for more details javascript_sdk.

Direct API Call to execute transaction

Finally, the last step would be to use the hpf_auth_token and request a Direct API Call to process the transaction Direct API card.

Example

To understand how you can go about creating your own payment page with HPF, you may want to check this code sample.

{danger.fa-exclamation-triangle} IMPORTANT: To make this sample work, at minimum you need to define and replace the following parameters: YOUR_PAYMENT_FORM_SUBMISSION_URL PAYLOAD_SRC SESSION_TOKEN

Code Sample:

<html>
    <head>
        <style>
            .hpf-view {
                font-size: 16px;
                line-height: 1;
            }
            .hpf-view .hpf-form-container {
                width: 100%;
                max-width: 550px;
                margin: 50px auto;
                background-color: #fff;
                padding: 30px;
                box-shadow: 0 8px 8px 0px rgb(0 0 0 / 5%);
                border-radius: 8px;
                position: relative;
                min-height: 350px;
            }
            .hpf-view .hpf-form-container .hpf-loader-container {
                position: absolute;
                top: 0;
                right: 0;
                left: 0;
                bottom: 0;
                width: 100%;
                height: 100%;
                z-index: 2;
                display: flex;
                align-items: center;
                justify-content: center;
                background-color: rgba(255,255,255,0.7);
            }
            .hpf-view .hpf-form-container .hpf-loader-container .hpf-loader-icon {
                display: inline-block;
                width: 40px;
                height: 40px;
            }
            .hpf-view .hpf-form-container .hpf-loader-container .hpf-loader-icon:after {
                content: " ";
                display: block;
                width: 35px;
                height: 35px;
                margin: 8px;
                border-radius: 50%;
                border: 6px solid #fff;
                border-color: #f0ecec transparent #f0ecec transparent;
                animation: lds-dual-ring 1.2s linear infinite;
            }
            @keyframes  lds-dual-ring {
                0% {
                    transform: rotate(0deg);
                }
                100% {
                    transform: rotate(360deg);
                }
            }
            .hpf-view .hpf-form-container .hpf-title {
                text-align: center;
                padding-bottom: 15px;
                margin: 0;
                font-size: 24px;
                font-weight: 700;
            }
            .hpf-view .hpf-form .input-item .hpf-input-error {
                width: 100%;
                font-size: 11px;
                transition: 0.15s all ease;
                font-style: italic;
                color: red;
                height: 15px;
                opacity: 0;
            }
            .hpf-view .hpf-form .input-item.has-error .hpf-input-error {
                opacity: 1;
            }
            .hpf-view .hpf-form .btn-container {
                display: flex;
                justify-content: center;
                align-items: center;
                padding: 15px 10px;
            }
            .hpf-view .hpf-form .btn {
                background-color: rgb(251 146 60);
                height: 50px;
                width: 100%;
                max-width: 240px;
                border-radius: 10px;
                border: none;
                font-size: 16px;
                font-weight: 700;
                cursor: pointer;
                color: white;
                transition: 0.5s;
                outline: none;
                opacity: 1;
            }
            .hpf-view .hpf-form .btn:hover {
                background-color: rgb(249 115 22);
            }
            .hpf-view .hpf-form .hpf-fields-container {
                display: flex;
                flex-direction: column;
                gap: 0.25rem;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item iframe {
                height:55px;
                width: 100%;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item {
                position: relative;
                min-height: 72px;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-list-dropdown {
                position: absolute;
                -webkit-user-select: none;
                user-select: none;
                width: 30px;
                height: 30px;
                padding: 5px;
                top: 22px;
                right: 2%;
                max-height: 100%;
                display: none;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                opacity: 1;
                transition: 0.3s;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-list-dropdown.active {
                rotate: 180deg;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-list-dropdown:hover {
                background-color: rgba(238, 238, 238, 0.31);
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-list-dropdown .svg-dropdown {
                width: 15px;
                height: 15px;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-templates-list {
                width: 100%;
                padding: 10px 0;
                position: absolute;
                top: calc(100% - 10px);
                left: 0;
                border-radius: 4px;
                box-shadow: 0 2px 3px rgb(0 0 0 / 50%);
                -webkit-touch-callout: none;
                -webkit-user-select: none;
                user-select: none;
                max-height: 160px;
                overflow: auto;
                z-index: 1;
                background-color: #f8f8f8;
                display: none;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-templates-list.show {
                display: block;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-templates-list .hpf-card-list-item {
                padding: 5px 10px;
                cursor: pointer;
                font-size: 14px;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-templates-list .hpf-card-list-item:hover{
                background-color: #f0ecec;
            }
            .hpf-view .hpf-form .hpf-fields-container .input-item.card_number .card-templates-list .hpf-card-list-item.active {
                font-weight: 600;
                background-color: #f0ecec;
            }
        </style>
        <script src="PAYLOAD_SRC?t=${new Date().getTime()}"></script>
    </head>
    <body>
        <div class="hpf-view">
            <div id="hpf-container-id" class="hpf-form-container">
                <div id="hpf-loader-id" class="hpf-loader-container">
                    <span class="hpf-loader-icon"></span>
                </div>
                <h4 class="hpf-title">Pay with Credit Card</h4>
                <form id="hpf-form-id" class="hpf-form" method="post">
                    <div class="hpf-fields-container">
                        <div class="input-item card_number">
                            <div class="card-input-container">
                                <div id="hpf-card-number"></div>
                                <div class="hpf-input-error">Invalid value</div>
                            </div>
                            <span id="hpf-card-dropdown" class="card-list-dropdown">
                                            <svg class="svg-dropdown" x="0px" y="0px" width="451.847px" height="451.847px" viewBox="0 0 451.847 451.847"><g><path d="M225.923,354.706c-8.098,0-16.195-3.092-22.369-9.263L9.27,151.157c-12.359-12.359-12.359-32.397,0-44.751 c12.354-12.354,32.388-12.354,44.748,0l171.905,171.915l171.906-171.909c12.359-12.354,32.391-12.354,44.744,0 c12.365,12.354,12.365,32.392,0,44.751L248.292,345.449C242.115,351.621,234.018,354.706,225.923,354.706z"></path></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></svg>
                                        </span>
                            <div id="hpf-card-list" class="card-templates-list"></div>
                        </div>
                        <div class="input-item card_holder">
                            <div id="hpf-card-holder"></div>
                            <div class="hpf-input-error">Invalid value</div>
                        </div>
                        <div class="input-item exp">
                            <div id="hpf-exp"></div>
                            <div class="hpf-input-error">Invalid value</div>
                        </div>
                        <div class="input-item cvv">
                            <div id="hpf-cvv"></div>
                            <div class="hpf-input-error">Invalid value</div>
                        </div>
                    </div>
                    <div class="btn-container">
                        <button type="submit" class="btn">Deposit</button>
                    </div>
                </form>
            </div>
        </div>
        <script>
            const transaction_type = 'payment'; // define here your transaction type
            let PraxisGateInstance;
            const praxisValidationObj = {
                card_number: false,
                card_holder: false,
                exp: false,
                cvv: false
            }

            const praxisGateInterval = setInterval(async function () {
                if (typeof window.PraxisGate !== 'undefined') {
                    clearInterval(praxisGateInterval)
                    PraxisGateInstance = new PraxisGate({
                        sessionToken: 'SESSION_TOKEN', // token from open_session request
                        containers: {
                            card_number: '#hpf-card-number',
                            card_holder: '#hpf-card-holder',
                            exp: '#hpf-exp',
                            cvv: transaction_type === 'payment' ? '#hpf-cvv' : null // cvv container should not be provided if it's withdraw
                        },
                        onLoad: async function () { // trigger when all fields loaded, before init validation
                            const list = await PraxisGateInstance.getCustomerCards(); // list of available templates also can be received by Agent API get_customer_cards request
                            renderPraxisCardListTemplates(list);
                            document.getElementById('hpf-card-dropdown').style.display = 'flex';
                            document.getElementById('hpf-loader-id').style.display = 'none';
                        },
                        onValid: async function () { // trigger when all fields are valid
                            Object.keys(praxisValidationObj)?.forEach((key) => {
                                praxisHideHpfError(key)
                            })
                        },
                        onValidationSuccess: function(fieldName) { // triggers on field change
                            praxisValidationObj[fieldName] = true;
                            praxisHideHpfError(fieldName);
                        },
                        onValidationError: function(fieldName) { // triggers on field change
                            praxisValidationObj[fieldName] = false;
                        },
                        labels: {
                            display: [window.PG_INPUT_SHOW_LABELS, window.PG_INPUT_SHOW_PLACEHOLDERS],
                            label_card_number: 'Credit Card',
                            label_card_holder: 'Card Holder',
                            label_exp: 'Expires',
                            label_cvv: 'CSC/CVV',
                            placeholder_card_number: '0000 0000 0000 0000',
                            placeholder_card_holder: 'Card Holder',
                            placeholder_exp: 'MM/YY',
                            placeholder_cvv: 'CVV'
                        }
                    });
                }
            }, 150);

            // dropdown click handler
            document.getElementById('hpf-card-dropdown')?.addEventListener('click', () => {
                const cardListDropdown = document.getElementById('hpf-card-dropdown');
                if(cardListDropdown) {
                    const container = document.getElementById('hpf-card-list');
                    if(container) {
                        if(container.classList.contains('show')) {
                            cardListDropdown.classList.remove('active');
                            container.classList.remove('show');
                        } else {
                            cardListDropdown.classList.add('active');
                            container.classList.add('show');
                        }
                    } else {
                        console.error('container for cardListDropdown not found')
                    }
                } else {
                    console.error('cardListDropdown not found')
                }
            })

           // render card list template
            const renderPraxisCardListTemplates = (templates = []) => {
                const container = document.getElementById('hpf-card-list');
                if(container) {
                    const newItem = document.createElement('div');
                    newItem.setAttribute('class', 'hpf-card-list-item new-card-option');
                    newItem.innerText = '+ New card';
                    container.appendChild(newItem);
                    newItem.addEventListener('click', () => selectPraxisCardListTemplate(newItem, []));

                    if(templates?.length > 0) {
                        templates?.forEach((templateItem) => {
                            const item = document.createElement('div');
                            item.setAttribute('class', `hpf-card-list-item ${templateItem?.is_default ? 'active' : ''}`);
                            item.innerText = templateItem?.card_number;
                            container.appendChild(item);
                            item.addEventListener('click', () => selectPraxisCardListTemplate(item, templates));
                        })
                    }
                } else {
                    console.error('container for render list not found')
                }
            }

           // select template from list
            const selectPraxisCardListTemplate = (item, templates = []) => {
                if(item?.classList.contains('active')) return;
                const currentCard = templates?.find((card) => card['card_number'] === item.innerText);
                const currentCardToken = currentCard?.card_token ?? null;
                PraxisGateInstance.pickCustomerCard(currentCardToken); // HPF SDK event to trigger event change
                item.classList.add('active');
                document.getElementById('hpf-card-dropdown')?.classList.remove('active');
                item.parentElement.classList.remove('show');
                Array.from(item.parentElement.children).forEach((node) => {
                    if(node !== item && node.classList.contains('active')) {
                        node.classList.remove('active');
                    }
                })
                Object.keys(praxisValidationObj)?.forEach((key) => {
                    praxisHideHpfError(key)
                })
            }

            // function to show field error
            const praxisShowHpfError = (field = '') => {
                if(!field) return;
                const container = document.querySelector(`.hpf-fields-container .input-item.${field}`);
                if(container && !container?.classList.contains('has-error')) {
                    container.classList.add('has-error');
                }
            }

            // function to hide field error
            const praxisHideHpfError = (field = '') => {
                if(!field) return;
                const container = document.querySelector(`.hpf-fields-container .input-item.${field}`);
                if(container && container?.classList.contains('has-error')) {
                    container.classList.remove('has-error');
                }
            }

            // submit form
            document.getElementById('hpf-form-id')?.addEventListener('submit', async (event) => {
                event.preventDefault();
                // in case of success, return hpf_auth_token equal to session_token from open_session request, also can be triggered in onValid HPF SDK event
                const token = await PraxisGateInstance.tokenizeCard();
                const canSubmit = token === 'SESSION_TOKEN';
                const url = 'YOUR_PAYMENT_FORM_SUBMISSION_URL';

                if(canSubmit) {
                    console.log('submit')
                    // do card Direct API Call for HPF
                } else {
                    document.getElementById('hpf-loader-id').style.display = 'none';
                    if(!token && !Object.values(praxisValidationObj).some(val => val === false)) console.error('PraxisGateInstance.tokenizeCard() failed');
                    Object.keys(praxisValidationObj)?.forEach((key) => {
                        if(!praxisValidationObj[key]) {
                            praxisShowHpfError(key)
                        } else {
                            praxisHideHpfError(key)
                        }
                    })
                }
            })
        </script>
    </body>
</html>

The hpf_auth_token should be passed inside the card_data object. Also, as long as no actual card details are collected or passed over API, you can ignore the requirement of PCI DSS, which otherwise applies to Direct API for credit cards.

Samples of rendered payment form:

image image

MIT and Zero-Authorization Support

HPF can be also used for MIT and Zero-Authorization transactions!
All you need is to extend the Direct API request by adding the MIT object into the payload. Please refer to Direct API card for more details.
For subsequent MIT transactions, there’s no need to display the HPF form as all the necessary information will be retrieved from the reference CIT or Zero-Authorization transaction. HPF is mandatory for the first transaction for non-PCI DSS merchants.

* If you don't keep a record of transaction history, you'll need to identify which transaction to use as a reference for subsequent MITs. To help with this, the /get_customer_cards API call includes a list of successful 0-Authorization and CIT transactions as part of its response.