Tutorial: Click to Pay

Click to Pay

Authorizing the payment for a payment transaction using Mastercard Click to Pay using the Transpayrent UI Helper SDK requires the following steps:

  1. Create a Payment Session from the back-end and render the payment page
  2. Include the dependencies for the Transpayrent SDK, Transpayrent UI Helper SDK, the Mastercard Unified Checkout Solutions (UCS) browser SDK, and Mastercard's SRC UI Kit web components
  3. Implement the following Transpayrent UI Helper SDK methods to return the necessary data constructs to the Transpayrent SDK for payment method 205:
  4. Instantiate the Transpayrent SDK, Transpayrent UI Helper SDK, and the Click-to-Pay helper
  5. Recognize the consumer (init + getCards) and render the Click to Pay UI yourself using Mastercard's SRC UI Kit web components (card list, OTP input, loader) — unlike Apple Pay/Google Pay, Click to Pay has no native payment sheet; there is nothing for displayPaymentSheet to show
  6. On Pay, call checkoutWithCard / checkoutWithNewCard, apply the returned checkout handoff, then invoke the Transpayrent UI Helper SDK's completePayment method
  7. Fulfill the order upon receiving the 620021 - Payment Successfully Authorized notification

Important

Transpayrent has complete support for Split Payment (Card + Miles for an Airline, Card + Loyalty Points or Card + Voucher for a Webshop etc.) and the amount in the notification may therefore be equal to or less than the total amount for the order. The Order shouldn't be considered paid until the Sum of the Payment Transaction(s) equal the total amount for the Payment Session. It is strongly recommended to implement validation of the amount before fulfilling the order.

What the helper does and does not do

The Transpayrent Click-to-Pay helper is infrastructure support around the Mastercard UCS browser SDK. It provides:

  • Mastercard passthrough methods with identical names (init, getCards, idLookup, authenticate, initiateValidation, validate, encryptCard, checkoutWithCard, checkoutWithNewCard, signOut)
  • Button rendering via displayPaymentButton (or sdk.displayPaymentButton(205, ...))
  • Transpayrent integration utilities: readiness checks, normalized errors/results, checkout handoff normalization/application, optional recognition storage, and opt-in debug logging

It does not render the recognition screen, card list, OTP screen, manual card entry form, or result screen — that UI is yours to build, using Mastercard's SRC UI Kit web components described below. This is what makes the integration fast: you are not building a card list or OTP widget from scratch, you are wiring Mastercard's existing components to the data the helper's passthrough methods return.

Include the dependencies for the Transpayrent SDK, Mastercard UCS SDK, and Mastercard SRC UI Kit

The Transpayrent UI Helper SDK uses the Transpayrent SDK to securely communicate with Transpayrent's Payment Gateway, which in turn relies on the swagger-js client library. For Click to Pay you additionally need the Mastercard UCS SDK, scoped to your srcDpaId, and Mastercard's SRC UI Kit web components bundle (plus its stylesheet) used to build every Click-to-Pay screen.

<script src="https://storage.googleapis.com/static.[ENVIRONMENT].transpayrent.cloud/v1/swagger-client.js"></script>
<script src="https://storage.googleapis.com/static.[ENVIRONMENT].transpayrent.cloud/v1/transpayrent.js"></script>
<script src="https://[ENVIRONMENT].src.mastercard.com/srci/integration/2/lib.js?srcDpaId={YOUR_SRC_DPA_ID}&locale={en_US}"></script>
<script type="module" src="https://[ENVIRONMENT].src.mastercard.com/srci/integration/components/src-ui-kit/src-ui-kit.esm.js"></script>
<link rel="stylesheet" href="https://[ENVIRONMENT].src.mastercard.com/srci/integration/components/src-ui-kit/src-ui-kit.css">

The Mastercard UCS SDK script defines window.MastercardCheckoutServices / window.Click2Pay, which TranspayrentClickToPayHelper wraps; getReadiness checks for these globals before you show any Click-to-Pay UI. The SRC UI Kit script defines the <src-button>, <src-card-list>, <src-otp-input>, and <src-card> custom elements used throughout this tutorial.

Implement the Transpayrent UI Helper SDK's callback methods to return the necessary data constructs to the Transpayrent SDK

The code snippet below illustrates how the payment page may implement the necessary methods required to complete a payment with Click to Pay. Unlike a Payment Card or Google Pay, the authenticate and authorize requests must be enriched with the Click-to-Pay checkout handoff (merchant_transaction_id / src_flow_id) via applyCheckoutHandoff before being returned.

<script>
    /* ========== COMMON DATA START ========== */
    var address = { street : 'Arne Jacobsens Allé 7',
                    appartment : '5. sal',
                    city : 'Copenhagen S',
                    postal_code : '2300',
                    state : 'CO',
                    country: 208 };                 // Denmark
    var phone = { international_dialing_code: 45,   // Denmark
                  phone_number: 12345678 };
    var email = 'hello@transpayrent.dk';
    var nationalIdentificationNumber = '31892554313';
    /* ========== COMMON DATA END ========== */

    /* ========== CREATE PAYMENT TRANSACTION DATA START ========== */
    var createTransactionRequests = new Array();
    // Click to Pay
    createTransactionRequests[205] = { correlation_id : 'TP-'+ transpayrentConfig.sessionId,
                                       amount: { currency : 208,          // DKK
                                                 value : 100 } };
    /* ========== CREATE PAYMENT TRANSACTION DATA END ========== */

    /* ========== INITIALIZE PAYMENT DATA START ========== */
    var initializePaymentRequests = new Array();
    // Click to Pay: card enrollment/linking is handled by Mastercard's own vault via checkoutWithNewCard,
    // not by Transpayrent's Secure Vault, so `save` is always false here.
    initializePaymentRequests[205] = { payment_method_id : 205,
                                       save : false };
    /* ========== INITIALIZE PAYMENT DATA END ========== */

    /* ========== AUTHENTICATE CONSUMER DATA START ========== */
    var authenticateConsumerRequests = new Array();
    // Click to Pay
    authenticateConsumerRequests[205] = { payment_details : { payment_method_id : 205 },
                                          billing_address : address,                    // Optional
                                          shipping_address : address,                   // Optional
                                          contact : { mobile : phone,                   // Optional
                                                      work : phone,                     // Optional
                                                      home : phone,                     // Optional
                                                      email : email } };                // Optional
    /* ========== AUTHENTICATE CONSUMER DATA END ========== */

    /* ========== AUTHORIZE PAYMENT DATA START ========== */
    // Click to Pay
    authorizePaymentRequests[205] = { payment_method_id : 205,
                                      national_identification_number : nationalIdentificationNumber,    // Optional: Required in Brazil
                                      contact : { email : email },  // Optional: Required in Brazil
                                      installments : 2 };           // Optional: 2 - 12
    /* ========== AUTHORIZE PAYMENT DATA END ========== */

    /* ========== IMPLEMENT TRANSPAYRENT UI HELPER SDK METHODS START ========== */
    TranspayrentUIHelper.prototype.displayAuthenticationFailure = function (result) {
        this._sdk.getLocalizedStatusMessage(result.status_code)
            .then(text => {
                if (this._container) {
                    this._container.style.visibility = 'hidden';
                }
                var message = document.createElement('h1');
                message.className = 'failure';
                message.innerHTML = '<img id="loader" src="/failure.png" width="30" height="30" alt="- completed -" />';
                message.innerHTML += ` ${text} (${result.status_code})`;
                document.body.appendChild(message);
            });
    }
    TranspayrentUIHelper.prototype.displayPaymentConfirmation = function (authorization) {
        this._sdk.getLocalizedMessage(`Payment transaction: ${authorization.transaction_id} successfully authorized`)
            .then(text => {
                if (this._container) {
                    this._container.style.visibility = 'hidden';
                }
                var message = document.createElement('h1');
                message.className = 'success';
                message.innerHTML = '<img id="loader" src="/success.png" width="30" height="30" alt="- completed -" />';
                message.innerHTML += ` ${text}`;
                document.body.appendChild(message);
            });
    }
    // Override UI Helper SDK methods to return the correct request for the specified payment method
    TranspayrentUIHelper.prototype.getCreateTransactionRequest = function (paymentMethodId) {
        return createTransactionRequests[paymentMethodId];
    }
    TranspayrentUIHelper.prototype.getInitializePaymentRequest = function (paymentMethodId) {
        return initializePaymentRequests[paymentMethodId];
    }
    // Enrich authenticate/authorize with the saved Click-to-Pay checkout handoff for payment method 205
    TranspayrentUIHelper.prototype.getAuthenticateConsumerRequest = function (paymentMethodId) {
        var request = authenticateConsumerRequests[paymentMethodId];
        if (paymentMethodId === 205) {
            return c2pHelper.applyCheckoutHandoff(request);
        }
        return request;
    }
    TranspayrentUIHelper.prototype.getAuthorizePaymentRequest = function (paymentMethodId) {
        var request = authorizePaymentRequests[paymentMethodId];
        if (paymentMethodId === 205) {
            return c2pHelper.applyCheckoutHandoff(request);
        }
        return request;
    }
    /* ========== IMPLEMENT TRANSPAYRENT UI HELPER SDK METHODS END ========== */
</script>

Instantiate the Transpayrent SDK, Transpayrent UI Helper SDK, and Click-to-Pay helper

The code snippet below illustrates how to instantiate the Transpayrent SDK, the Transpayrent UI Helper SDK, and the Click-to-Pay helper. The Click-to-Pay helper is kept in its own variable (c2pHelper) — it is a different object from the UI Helper (helper) used above and elsewhere in this tutorial; do not confuse the two.

<script>
    // SDK configuration
    const transpayrentConfig = {
        merchantId: [UNIQUE MERCHANT ID ASSIGNED BY TRANSPAYRENT],
        sessionId: [ID IN THE RESPONSE FROM "Create Payment Session"],
        accessToken: '[x-transpayrent-access-token HTTP HEADER IN THE RESPONSE FROM "Create Payment Session"]'
    };
    const url = 'https://generator.[ENVIRONMENT].transpayrent.cloud/v1/'+ transpayrentConfig.merchantId +'/system/PAYMENT_GATEWAY/sdk/CLIENT';

    // Instantiate SDK and UI Helper SDK
    const sdk = new Transpayrent(url, transpayrentConfig);
    const helper = new TranspayrentUIHelper(sdk, document.getElementById('container'), document.getElementById('loader') );

    // Instantiate the Click-to-Pay helper (a thin wrapper around the Mastercard UCS SDK)
    const c2pHelper = sdk.getClickToPayHelper({ debugLogging: false });
</script>

Recognize the consumer and render Click to Pay UI with Mastercard's SRC UI Kit

Every Click-to-Pay session starts the same way — check readiness, then init() followed by getCards() — and then branches into one of three shapes depending on what Mastercard returns:

Consumer state What getCards() / idLookup() return What you show
Recognized getCards() returns cards immediately Card list (<src-card-list>), then the Mastercard checkout popup on checkoutWithCard
Returning, no cards yet getCards() empty, but idLookup() finds a profile (consumerPresent = true) Verify the consumer via either initiateValidation/validate or authenticate (see below), then the card list
New / no profile idLookup() finds no profile (consumerPresent = false) Manual card entry; optionally enroll with encryptCard + checkoutWithNewCard, or pay without enrolling
<src-loader id="c2pLoader" style="display: none;"></src-loader>
<script>
    // Required by checkoutWithCard / checkoutWithNewCard - your merchant privacy notice and terms of use.
    var complianceSettings = {
        privacy: { acceptedVersion: 'LATEST', latestVersion: 'LATEST', latestVersionUri: 'https://merchant.example.com/privacy' },
        tnc: { acceptedVersion: 'LATEST', latestVersion: 'LATEST', latestVersionUri: 'https://merchant.example.com/terms' }
    };

    /* ========== READINESS CHECK START ========== */
    var readiness = c2pHelper.getReadiness();
    if (!readiness.ready) {
        console.warn('Click to Pay unavailable:', readiness.issues);
        // Hide the Click to Pay entry point and fall back to standard card payment
    }
    /* ========== READINESS CHECK END ========== */

    /* ========== RECOGNITION START ========== */
    // A dedicated <src-loader id="c2pLoader"> - separate from the `loader` element passed to
    // `new TranspayrentUIHelper(sdk, container, loader)`, which the UI Helper manages itself.
    document.getElementById('c2pLoader').style.display = 'block';

    var initRequest = {
        srcDpaId: '{YOUR_SRC_DPA_ID}',              // same id used in the Mastercard UCS script tag above
        cardBrands: ['mastercard', 'maestro'],       // brands your merchant account accepts
        dpaData: { dpaName: 'My Merchant' },
        dpaLocale: 'en_US'
    };

    c2pHelper.init(initRequest)
        .then(() => c2pHelper.getCards())
        .then(cards => {
            document.getElementById('c2pLoader').style.display = 'none';
            if (cards.length > 0) {
                showCardList(cards);            // Recognized consumer
            }
            else {
                return c2pHelper.idLookup({ email: email })
                    .then(lookup => {
                        if (lookup.consumerPresent) {
                            // Returning consumer: verify via merchant-hosted OTP (startOtpValidation, below)
                            // or Mastercard-hosted authenticate() (see "authenticate: the Mastercard-hosted
                            // alternative", below) - pick one.
                            startOtpValidation();
                        }
                        else {
                            showManualCardEntry();  // New consumer, no Click to Pay profile
                        }
                    });
            }
        })
        .catch(error => console.error('Click to Pay error:', error.api, error.status, error.messages));
    /* ========== RECOGNITION END ========== */
</script>

Card list (<src-card-list>)

<src-card-list id="cardList"
                card-brands="mastercard,maestro"
                card-selection-type="radioButton"
                display-sign-out="true"
                display-add-card="true"
                display-cancel-option="false"></src-card-list>
<script>
    var selectedCardId = null;

    function showCardList(cards) {
        var cardList = document.getElementById('cardList');
        cardList.loadCards(cards);

        cardList.addEventListener('selectSrcDigitalCardId', event => {
            selectedCardId = event.detail;   // srcDigitalCardId to pass into checkoutWithCard
        });
        cardList.addEventListener('clickAddCardLink', () => showManualCardEntry() );
        cardList.addEventListener('clickSignOutLink', () => {
            c2pHelper.signOut().then(() => {
                // Clear local card/session state, then re-run recognition for a different identity
            });
        });
    }

    function payWithSelectedCard(windowRef) {
        return c2pHelper.checkoutWithCard({ srcDigitalCardId: selectedCardId,
                                            windowRef: windowRef,               // required: window/iframe opened for checkout
                                            complianceSettings: complianceSettings, // required
                                            recognitionTokenRequested: true });
    }
</script>

OTP input (<src-otp-input>)

<src-otp-input id="otpInput"
                display-remember-me="true"
                display-cancel-option="false"></src-otp-input>
<script>
    var currentOtp = '';

    function startOtpValidation() {
        c2pHelper.initiateValidation().then(result => {
            document.getElementById('otpInput').loadSupportedValidationChannels(result.supportedValidationChannels);
        });

        var otpInput = document.getElementById('otpInput');
        otpInput.addEventListener('otpChanged', event => { currentOtp = event.detail || ''; });
        otpInput.addEventListener('continue', () => {
            c2pHelper.validate({ value: currentOtp }).then(cards => showCardList(cards) );
        });
        otpInput.addEventListener('resendOtp', () => c2pHelper.initiateValidation() );
        otpInput.addEventListener('notYouRequested', () => {
            c2pHelper.signOut().then(() => { /* route back to identity entry */ });
        });
    }
</script>

authenticate: the Mastercard-hosted alternative

initiateValidation / validate above is the merchant-hosted approach — you own the OTP screen, rendered inline with <src-otp-input> on your page. authenticate is the other option: the Mastercard-hosted approach. You open a popup/iframe and hand it to Mastercard; Mastercard shows and drives whatever challenge it decides is appropriate (OTP, passkey, etc.) inside that window, and resolves directly with cards — there is no <src-otp-input> to build or wire up for this path.

Mastercard-hosted authenticate is less integration work (no OTP UI at all) at the cost of sending the consumer into a Mastercard-controlled window instead of staying on your page; merchant-hosted initiateValidation/validate keeps the consumer on your page but means you own the OTP screen, error states, and resend flow yourself.

<script>
    function authenticateWithPopup(identityValue, identityType, windowRef) {
        // identityType: 'EMAIL_ADDRESS' or 'MOBILE_PHONE_NUMBER'
        return c2pHelper.authenticate({
            accountReference: { consumerIdentity: { identityType: identityType, identityValue: identityValue } },
            windowRef: windowRef,              // required for the Mastercard-hosted UI
            requestRecognitionToken: true
        }).then(result => {
            if (result.cards) {
                showCardList(result.cards);
            }
        });
    }
</script>

Manual card entry and linking a new card

Whether the consumer has no Click-to-Pay profile at all, or is simply adding a card from the card list / OTP screen ("Add card"), the pattern is the same: show a plain card entry form, encrypt the card, then check out with it.

<script>
    function showManualCardEntry() {
        document.getElementById('manualCardEntryForm').style.display = 'block';
    }

    function linkNewCard(windowRef) {
        var cardDetails = {
            primaryAccountNumber: document.getElementById('cardNumber').value,
            panExpirationMonth: document.getElementById('expiryMonth').value,   // e.g. '09'
            panExpirationYear: document.getElementById('expiryYear').value,     // e.g. '2028'
            cardSecurityCode: document.getElementById('cvv').value,
            cardholderFirstName: document.getElementById('firstName').value,
            cardholderLastName: document.getElementById('lastName').value
        };

        return c2pHelper.encryptCard(cardDetails)
            .then(encrypted => c2pHelper.checkoutWithNewCard({ encryptedCard: encrypted.encryptedCard,  // JWE string, not the whole response
                                                               cardBrand: encrypted.cardBrand,          // required
                                                               windowRef: windowRef,                    // required
                                                               complianceSettings: complianceSettings,  // required
                                                               recognitionTokenRequested: true }) );
    }
</script>

encryptCard field validation errors (invalid PAN, expired card, etc.) reject with a Transpayrent-normalized error before any Mastercard checkout window opens — show them next to the relevant form field.

Checkout and complete the payment

checkoutWithCard / checkoutWithNewCard return a normalized checkout result or throw a Transpayrent-normalized SDK error. On checkoutActionCode === 'COMPLETE', the helper stores the checkout handoff automatically only when both merchantTransactionId and srcFlowId are present — that stored handoff is what getAuthenticateConsumerRequest / getAuthorizePaymentRequest apply via applyCheckoutHandoff further up this tutorial. Both payWithSelectedCard (saved card) and linkNewCard (new card) converge on the same completion step:

<script>
    // Shared by both the "Pay" button on the card list and the "Pay" button on the manual card entry form
    function completeCheckout(checkoutPromise) {
        var popup = window.open('', 'click-to-pay-checkout');   // Merchant-owned checkout window
        return checkoutPromise(popup)
            .then(checkoutResult => {
                popup.close();
                if (checkoutResult.checkoutActionCode !== 'COMPLETE') {
                    return; // CANCEL, CHANGE_CARD, or SWITCH_CONSUMER - return to the card list / identity screen
                }
                return helper.completePayment(205, false);
            })
            .catch(error => {
                popup.close();
                console.error('Click to Pay checkout failed:', error.api, error.status, error.messages);
            });
    }

    // e.g. from the card list's "Pay" button:
    completeCheckout(payWithSelectedCard);
    // e.g. from the manual card entry form's "Pay" button:
    completeCheckout(linkNewCard);
</script>

Access other account (sign out and switch identity)

When the consumer picks "not your cards" from the card list, or "not you" from the OTP screen, sign out of the current Click-to-Pay session and re-run recognition for a different identity:

<script>
    function switchAccount(newEmail) {
        return c2pHelper.signOut()
            .then(() => c2pHelper.init(initRequest) )   // same request built during initial recognition, above
            .then(() => c2pHelper.idLookup({ email: newEmail }) )
            .then(lookup => lookup.consumerPresent ? startOtpValidation() : showManualCardEntry() );
    }
</script>

Debug logging

Pass debugLogging: true when instantiating the Click-to-Pay helper (sdk.getClickToPayHelper({ debugLogging: true })) when troubleshooting an integration. This logs Mastercard SDK request/response details to the browser console. Do not enable it in normal merchant production usage unless you are intentionally debugging.

The CVV/security code is always hidden from these logs, regardless of this flag. Every other field — including the PAN — is logged in full, so treat any captured console output as sensitive when sharing it (screenshots, tickets, etc.).

c2pHelper.setDebugLogging(true);

Recognition storage

Opt in through sdk.getClickToPayHelper({ recognitionStorage: { ... } }). The helper then exposes recognitionStorage for identity-scoped recognition tokens and remember-me preferences, so a recognized consumer can skip OTP on their next visit.

  • When recognitionStorage is omitted, the helper does not create browser storage (recognitionStorage is null).
  • When recognitionStorage: {} is passed, the helper uses default localStorage with built-in key prefixes and normalizers.
<script>
    const c2pHelper = sdk.getClickToPayHelper({
        recognitionStorage: {
            recognitionTokenKeyPrefix: 'recognitionToken:',
            rememberMeKeyPrefix: 'c2p.rememberMe:',
            identityNormalizer: identity => identity.trim().toLowerCase()
        }
    });

    // Before init(): pass any stored token so a recognized consumer skips OTP
    var storedToken = c2pHelper.recognitionStorage && c2pHelper.recognitionStorage.getRecognitionToken(email);
    c2pHelper.init(Object.assign({}, initRequest, { recognitionToken: storedToken }));

    // After a successful checkoutWithCard / checkoutWithNewCard, inside its .then(checkoutResult => { ... }):
    if (c2pHelper.recognitionStorage && checkoutResult.recognitionToken) {
        c2pHelper.recognitionStorage.setRecognitionToken(email, checkoutResult.recognitionToken);
        c2pHelper.recognitionStorage.setRememberMe(email, true);
    }
</script>

The helper never writes to recognition storage automatically. Read and update it from your merchant-owned Click-to-Pay flow when needed — e.g. pass the stored token into init({ recognitionToken: token }) on the next visit.

Button rendering

sdk.displayPaymentButton(205, container, paymentMethodIds, { buttonText, onClick }) renders the SRC UI Kit <src-button> when available. When it is not, a plain HTML button is used as a visual/click target fallback only. That fallback does not indicate Mastercard runtime readiness; always call c2pHelper.getReadiness() before showing merchant-owned Click-to-Pay UI.

Flow options such as debugLogging and recognitionStorage belong in getClickToPayHelper config, not in the displayPaymentButton config for payment method 205.

Not supported

Transpayrent.displayPaymentSheet(205, details) is not supported for Click to Pay — there is no native Mastercard payment sheet to display. The SDK rejects it with PAYMENT_SHEET_NOT_SUPPORTED. Build the merchant-owned flow described above with getClickToPayHelper instead.