Documentation Index

Fetch the complete documentation index at: https://docs.monri.com/llms.txt

Use this file to discover all available pages before exploring further.

C2P extension component

Prev Next

Click to Pay — c2p-extension component

The c2p-extension component renders only the Click to Pay experience (card list, OTP, consent) — no card input fields. Use it when you already have your own card form and want to add Click to Pay alongside it.

If you don't have your own card form and want everything in one iframe, use card instead. For shared concepts (enablement, states, accessibility), see the Click to Pay overview.

Prerequisites

Include the Components script, your own card form, and a container for the extension:

<script src="https://ipgtest.monri.com/dist/components.js"></script>

<!-- Your own card form -->
<form id="your-card-form">
  <input name="pan" placeholder="Card number" />
  <input name="expiry" placeholder="MM/YY" />
  <input name="cvv" placeholder="CVV" />
</form>

<!-- c2p-extension mounts here -->
<div id="card-element"></div>

Integration

// Initialize Monri
const monri = Monri('your_authenticity_token', {
  locale: 'en'
});

// Create a components instance
const components = monri.components({
  clientSecret: 'your_client_secret'
});

// Create the c2p-extension component
const card = components.create("c2p-extension", {
  clientSecret: 'your_client_secret',
  locale: 'en',
  environment: 'test',               // 'test' or 'prod'

  // Click to Pay configuration (optional — auto-detected from the merchant's backend settings)
  clickToPay: {
    chPhone: '+385991234567',        // Customer phone (optional)
    chEmail: 'customer@example.com'  // Customer email (optional)
  },

  // Light theme styling (optional)
  style: {
    customComponents: {
      backgroundColor: '#ffffff',
      textColor: '#333333',
      primaryColor: '#8132A7',
      borderColor: '#e0e0e0',
      surfaceColor: 'rgba(0, 0, 0, .02)',
      radioButtonBackgroundColor: 'rgba(0, 0, 0, .02)',
      radioButtonSelectedBackgroundColor: '#FCF5FF',
      secondaryTextColor: '#575757',
      successColor: '#4caf50',
      errorColor: '#f44336',
      errorBackgroundColor: 'rgba(244, 67, 54, 0.1)',
      successBorderColor: '2px solid #4caf50',
      errorBorderColor: '2px solid #f44336'
    }
  },

  // Dark theme styling (optional)
  darkThemeStyle: {
    customComponents: {
      backgroundColor: 'rgba(0, 0, 0, 1)',
      textColor: '#ffffff',
      primaryColor: '#FF66FF',
      borderColor: '#444444',
      surfaceColor: 'rgba(255, 255, 255, .05)',
      radioButtonBackgroundColor: 'rgba(255, 255, 255, .05)',
      radioButtonSelectedBackgroundColor: '#1A001F',
      secondaryTextColor: '#FFFFFF',
      successColor: '#81c784',
      errorColor: '#e57373',
      errorBackgroundColor: 'rgba(229, 115, 115, 0.1)',
      successBorderColor: '2px solid #81c784',
      errorBorderColor: '2px solid #e57373'
    }
  }
});

// Mount the component
card.mount("card-element");

Component options

Property Type Required Description
clientSecret string Yes Your client secret
locale string No Component locale (e.g. 'en', 'hr', 'de')
environment 'test' | 'prod' Yes Environment ('test' for sandbox, 'prod' for production)
clickToPay object No Click to Pay configuration (see below). Optional — auto-detected from the merchant's backend settings. If omitted, Click to Pay still activates when mastercard_c2p_active is true for the merchant. Pass it to provide customer contact info or accessibility preferences.
style object No Light theme styling
darkThemeStyle object No Dark theme styling

Click to Pay configuration

The entire clickToPay object is optional. When omitted, availability is auto-detected from the merchant's backend configuration (mastercard_c2p_active flag in the transaction hash). All fields within the object are also optional.

Note: identity lookup is triggered as soon as either chEmail or chPhone is provided — both are not required. If the first identity finds no saved cards, the lookup automatically retries when the second identity is provided.

Property Type Required Description
chPhone string No Customer phone number (E.164 format) — used for identity lookup
chEmail string No Customer email address — used for identity lookup
isHighContrast 'true' | 'false' No High-contrast mode for accessibility
dyslexicFont 'true' | 'false' No Dyslexia-friendly font
fontSize 'normal' | 'large' | 'larger' No Font-size scaling

Minimal integration:

const card = components.create("c2p-extension", {
  clientSecret: 'your_client_secret',
  environment: 'test'
});

Styling properties

Both style.customComponents and darkThemeStyle.customComponents accept:

Property Type Description
backgroundColor string Main background color
textColor string Primary text color
primaryColor string Brand/accent color
borderColor string Border color for inputs
surfaceColor string Surface/card background
radioButtonBackgroundColor string Radio button background
radioButtonSelectedBackgroundColor string Selected radio button background
secondaryTextColor string Secondary text color
successColor string Success state color
errorColor string Error state color
errorBackgroundColor string Error background color
successBorderColor string Success border style (e.g. '2px solid #4caf50')
errorBorderColor string Error border style (e.g. '2px solid #f44336')

Layout & sizing

Viewport breakpoints

The component detects the parent window's viewport width (not the iframe's own width):

Viewport Parent width CSS class on iframe body
Mobile <= 575px viewport-mobile
Tablet 576px – 760px viewport-tablet
Desktop > 760px viewport-desktop

Width

The c2p-extension iframe uses width: 1px; min-width: 100% — it fills its parent container. Size the parent <div> to control the component's width. All states render at 100% width on all viewports; plan your own card-form layout accordingly.

Height

The iframe height adjusts dynamically per state. Because c2p-extension has no card input fields, heights are smaller than the card component. Base heights (normal font size):

State Mobile (<=575px) Desktop (>575px) Notes
empty 180px 180px Click to Pay hidden, minimal height
loading 160px 160px Loading spinner only
consent ~130px+ ~110px+ Consent text + checkbox (dynamic)
card-list ~90px/card ~90px/card Per-card height, paginated at 3
otp-input 300px 300px +125–135px if validation channels shown
new-card ~90px+ ~70px+ New-card consent only (dynamic)
not-your-card ~200px+ ~200px+ Identity switch form (dynamic)

Heights scale proportionally with font size (large ~1.14×, larger ~1.29×). Error messages add ~54px+ when visible.

Layout recommendations

Because c2p-extension renders alongside your own card form, use the state_change event to coordinate layout:

  • empty / new-card / consent — your card form is visible; stack the component below or beside it. Both consent and new-card appear only after the shopper enters a valid PAN (via updateActiveBrand).
  • card-list / otp-input / not-your-card — your card form should be hidden; the component handles the full flow.
  • loading — optionally show your card form while Click to Pay initializes.

Events

State change

Fires when the Click to Pay UI transitions between states. Use it to show or hide your own card form.

card.addChangeListener('state_change', (event) => {
  const state = event.data.state;
  const showOwnForm = (state === 'empty' || state === 'new-card' || state === 'consent');
  document.getElementById('your-card-form').hidden = !showOwnForm;
});
State Description Show your own card form?
'loading' The Click to Pay SDK is initializing No
'empty' Initial state, or SDK failed / no identity found. Consent appears once the shopper enters a valid PAN (via updateActiveBrand) Yes
'consent' New-user consent screen — appears when a valid card brand is detected from PAN input; disappears if the PAN is cleared Yes (alongside Click to Pay)
'card-list' Returning shopper sees saved cards No
'otp-input' OTP verification screen No
'new-card' Shopper is adding a new card — appears when a valid card brand is detected from PAN input; disappears if the PAN is cleared Yes (alongside Click to Pay)
'not-your-card' Shopper is switching identity No

Installments

Installments ownership is split between your form and the c2p-extension iframe depending on the active state; each path is gated by a different backend flag.

New-card / consent state (shopper is typing a PAN in your own form)

Your form owns the installments UI here — c2p-extension has no PAN input. Gated by merchant.installment_enabled (top-level merchant setting).

  1. When the PAN reaches 11+ digits, your form calls /pan-info
  2. Use the response's installments array to render your own installments dropdown
  3. Required: call card.setPanInfo(panInfoResponse) to push the pan-info into the iframe. The iframe's checkout manager uses three_ds_info from this response (acquirerBIN, acquirerMerchantId, merchantCategoryCode, merchantCountryCode) when calling the Mastercard SDK. Without this call, new-card checkout fails with 'Acquirer data not available for this card'
  4. When the shopper confirms, include the selected installment value in NewCreditCardPayment.cardDetails.installments; it flows through to v2/transaction

Card-list state (returning shopper with saved cards)

The iframe handles this path autonomously. Gated by mastercard_c2p_settings.allow_installments — a separate flag inside the Click to Pay merchant config, independent of merchant.installment_enabled.

  1. Pass showInstallmentsSelection: true in the component options (controls the card-list BIN lookup inside the iframe)
  2. mastercard_c2p_settings.allow_installments: true in the transaction hash
  3. /pan-info returns more than one installment option for the selected card's BIN
const card = components.create("c2p-extension", {
  clientSecret: 'your_client_secret',
  environment: 'test',
  showInstallmentsSelection: true
});

When these conditions are met, the iframe fetches /pan-info for all saved cards in parallel during the loading state, shows the dropdown immediately on card selection, and includes the selected installment value in the checkout_data event payload (event.data.installments). Your form applies it before calling v2/transaction. Cards remain selectable regardless of BIN data availability; if there are no installment options, the payment proceeds as a single charge.

Checkout data

Fires when a Click to Pay payment completes. Contains the transaction result — forward it to your backend for processing.

card.addChangeListener('checkout_data', (event) => {
  console.log('Payment result:', event.data);
});
// checkout_data event payload
type CheckoutDataEvent = {
  data: {
    response: {
      result: {
        transaction: {
          id: number;
          acquirer: string;
          order_number: string;
          amount: number;
          currency: string;
          approval_code: string;
          response_code: string;
          response_message: string;
          cc_type: string;
          status: string;          // 'approved' | 'declined'
          payment_method: string;  // 'mastercard-c2p'
        };
      } | null;
      error?: string;
    };
  };
};

Not your card

Fires when the shopper wants to switch accounts or enter a card manually.

card.addChangeListener('not_your_card', (event) => {
  const { inputType, inputValue } = event.data;
  // inputType: 'email' | 'phone' | 'manual'
  // inputValue: string | null
});

Payment progress

Fires during checkout to report progress.

card.addChangeListener('payment_progress', (event) => {
  console.log('Progress:', event.data.state, event.data.message);
});
type PaymentProgressPayload = {
  data: {
    state:
      | 'CHECKOUT_IN_PROGRESS'
      | 'USER_CANCELLED'
      | 'USER_CHANGING_CARD'
      | 'AWAITING_CARD_SELECTION'
      | 'PROCESSING_TRANSACTION'
      | 'COMPLETE';
    message?: string;
  };
};

Payment processing

Because c2p-extension has no card input fields, payment depends on the path the shopper took.

Click to Pay path (shopper selected a saved card or completed Click to Pay checkout)

Handled automatically through the Mastercard SDK. Listen for checkout_data — no additional API call is needed from your side.

Traditional path (shopper fills your own card form)

When the state is 'empty', 'new-card', or 'consent', the shopper may fill your own card form instead. Submit the payment through submitPayment:

card.submitPayment({
  clientSecret: 'your_client_secret',
  address: "123 Main Street",
  city: "New York",
  email: "customer@example.com",
  fullName: "John Doe",
  phone: "+1-555-123-4567",
  country: "US",
  zip: "10001",
  orderInfo: "Order #12345"
}).then((response) => {
  console.log('Payment response:', response);
});

Example checkout response

{
  "response": {
    "result": {
      "transaction": {
        "id": 1283,
        "acquirer": "Erste HR",
        "order_number": "random1760453433569",
        "amount": 100,
        "currency": "EUR",
        "approval_code": "165101",
        "response_code": "0000",
        "response_message": "Approved or completed successfully",
        "cc_type": "master",
        "status": "approved",
        "payment_method": "mastercard-c2p"
      }
    }
  }
}

The checkoutActionCode in the Mastercard response indicates the outcome:

Action code Meaning
COMPLETE Checkout completed successfully
CANCEL Shopper cancelled the checkout
CHANGE_CARD Shopper wants to select a different card
ADD_CARD Shopper wants to add a new card

Card-brand sync (required)

Because c2p-extension has no card input fields, it cannot detect what card brand the shopper is entering in your own form. You must call updateActiveBrand() whenever the card number changes so the consent screen can appear at the right time.

Per Mastercard requirement 8.4.0, the consent UI (both the consent and new-card states) must appear dynamically while the shopper enters their PAN — not immediately on page load. The component relies on updateActiveBrand() to know when a valid card brand is detected or when the PAN is cleared.

What to do

  1. Listen for changes on your card-number input
  2. Detect the card brand from the entered digits (e.g. 5xxx = Mastercard, 4xxx = Visa)
  3. Call card.updateActiveBrand(detectedBrand) when a brand is recognized
  4. Call card.updateActiveBrand('') when the PAN is cleared or the brand is no longer recognized
const panInput = document.getElementById('pan');

panInput.addEventListener('input', () => {
  const value = panInput.value.replace(/\s/g, '');
  const brand = detectCardBrand(value); // your brand-detection logic
  card.updateActiveBrand(brand || '');
});

function detectCardBrand(pan) {
  if (/^5[1-5]/.test(pan) || /^2[2-7]/.test(pan)) return 'mastercard';
  if (/^4/.test(pan)) return 'visa';
  return '';
}

How it affects the state

PAN input state updateActiveBrand() call State transition
Empty / no brand detected card.updateActiveBrand('') Stays in or transitions to 'empty'
Valid brand detected (new user) card.updateActiveBrand('mastercard') 'empty''consent'
Valid brand detected (after "Add card") card.updateActiveBrand('mastercard') 'empty''new-card'
PAN cleared after brand was detected card.updateActiveBrand('') 'consent' or 'new-card''empty'

Important: if you do not call updateActiveBrand(), neither the consent nor the new-card screen appears, and the shopper is not offered Click to Pay for new-card enrolment. This only affects the consent/new-card flow — returning shoppers with saved cards (card-list, OTP) are unaffected.

Runtime methods

card.setLanguage('hr');                  // update language
card.toggleTheme(true);                  // true = dark, false = light
card.toggleDyslexicFont(true);           // dyslexic font
card.toggleFontSize('large');            // 'normal' | 'large' | 'larger'
card.clientPhoneUpdate('+385991234567'); // update contact — re-runs identity lookup
card.clientEmailUpdate('new@example.com');
card.updateActiveBrand('mastercard');    // brand detected
card.updateActiveBrand('');              // PAN cleared or no brand

Accessibility

Click to Pay includes comprehensive accessibility support:

  • High-contrast mode — set via isHighContrast or toggleTheme() at runtime
  • Dyslexic font — OpenDyslexic font family, via dyslexicFont or toggleDyslexicFont()
  • Font-size scalingnormal, large, larger, via fontSize or toggleFontSize()
  • Theme support — light and dark variants with automatic contrast adjustments
const card = components.create("c2p-extension", {
  clientSecret: 'your_client_secret',
  environment: 'test',
  clickToPay: {
    isHighContrast: localStorage.getItem('highContrast') ?? 'false',
    dyslexicFont: localStorage.getItem('dyslexicFont') ?? 'false',
    fontSize: localStorage.getItem('fontSizeState') ?? 'normal'
  }
});