Click to Pay — card component
The card component renders card input fields (PAN, expiry, CVV) and Click to Pay in a single iframe. Use it when you do not have your own card form — the component provides everything.
If you already have your own card form and only want to add Click to Pay alongside it, use c2p-extension instead. For shared concepts (enablement, states, accessibility), see the Click to Pay overview.
Prerequisites
Include the Components script and add a container element:
<script src="https://ipgtest.monri.com/dist/components.js"></script>
<div id="card-element"></div>
Integration
// Initialize Monri
const monri = Monri('your_authenticity_token', {
locale: 'en' // or 'hr', 'de', etc.
});
// Create a components instance
const components = monri.components({
clientSecret: 'your_client_secret'
});
// Create the card component with Click to Pay
const card = components.create("card", {
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");
Click to Pay configuration
The entire clickToPay object is optional. When omitted, Click to Pay availability is auto-detected from the merchant's backend configuration (mastercard_c2p_active flag in the transaction hash). If the merchant has Click to Pay enabled, it activates automatically — even without passing clickToPay. All fields within the object are also optional.
Note: identity lookup is triggered as soon as either
chEmailorchPhoneis 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 (Click to Pay auto-detected, no customer data pre-filled):
const card = components.create("card", {
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) and applies responsive behaviour:
| Viewport | Parent width | CSS class on iframe body |
|---|---|---|
| Mobile | <= 575px | viewport-mobile |
| Tablet | 576px – 760px | viewport-tablet |
| Desktop | > 760px | viewport-desktop |
Width
The card iframe uses width: 1px; min-width: 100% — it fills its parent container. The Click to Pay content inside also fills 100% width. Size the parent <div> to control the component's width. All states render at 100% width on all viewports.
Height
The iframe height adjusts dynamically per state. Base heights (normal font size):
| State | Mobile (<=575px) | Desktop (>575px) | Notes |
|---|---|---|---|
empty |
220px | 200px | Card form only, Click to Pay hidden |
loading |
200px | 180px | Card form + loading spinner |
consent |
~290px+ | ~260px+ | Card form + consent text (dynamic) |
card-list |
~170px/card | ~160px/card | Per-card height, paginated at 3 |
otp-input |
320px | 310px | +110–120px if validation channels shown |
new-card |
~280px+ | ~260px+ | Card form + consent (dynamic) |
not-your-card |
~240px+ | ~240px+ | Identity switch form (dynamic) |
Heights scale proportionally with font size (large ~1.14×, larger ~1.29×). Error messages add ~54px+ when visible.
Events
State change
Fires when the Click to Pay UI transitions between states.
card.addChangeListener('state_change', (event) => {
console.log('C2P State:', event.data.state);
});
Possible states:
| State | Description |
|---|---|
'loading' |
The Click to Pay SDK is initializing |
'empty' |
Initial state, or SDK failed / no identity found — the traditional card form is shown |
'consent' |
New-user consent screen — appears when a valid card brand is entered in the PAN field; disappears if the PAN is cleared |
'card-list' |
Returning shopper sees their saved cards |
'otp-input' |
OTP verification screen |
'new-card' |
Shopper is adding a new card — appears when a valid card brand is entered in the PAN field; disappears if the PAN is cleared |
'not-your-card' |
Shopper is switching identity |
Checkout data
Fires when a Click to Pay payment completes. Contains the v2/transaction result.
card.addChangeListener('checkout_data', (event) => {
const { response } = event.data;
if (response.error) {
console.error('C2P payment failed:', response.error.message);
} else {
console.log('C2P payment result:', response.result);
}
});
// checkout_data event payload (Click to Pay path)
type CheckoutDataPayload = {
data: {
response: {
result: {
status: string; // 'approved' | 'declined' | 'action_required'
id: string;
action_required?: {
redirect_to: string;
acs_url: string;
};
payment_result?: {
amount: number;
currency: string;
};
} | null;
error: { message: string } | null;
};
};
};
Installments
Installments are controlled by different flags depending on which state is active:
- New-card / consent state — gated by
merchant.installment_enabled(backend merchant setting) - Card-list state — gated by
mastercard_c2p_settings.allow_installments(a separate backend setting inside the Click to Pay config)
Both paths also require showInstallmentsSelection: true in the component options and a /pan-info response returning more than one installment option.
New-card / consent state (shopper is typing a new PAN)
- Pass
showInstallmentsSelection: truein the component options merchant.installment_enabled: truein the transaction hash. If no dropdown ever appears, installments are disabled for your merchant account — contact Monri support/pan-inforeturns more than one installment option for the entered PAN
When the shopper enters a PAN with more than 10 digits, the component calls /pan-info internally and renders a standard installments dropdown alongside the card form.
const card = components.create("card", {
clientSecret: 'your_client_secret',
environment: 'test',
showInstallmentsSelection: true
});
card.addChangeListener('installments', (event) => {
console.log('Selected installment:', event.data.selectedInstallment);
// 1 = single payment, 2, 3, etc. = number of installments
});
The selected value is automatically included in the v2/transaction request (number_of_installments) when the shopper confirms payment.
Card-list state (returning shopper with saved cards)
- Pass
showInstallmentsSelection: truein the component options mastercard_c2p_settings.allow_installments: truein the transaction hash (separate frommerchant.installment_enabled)/pan-inforeturns more than one installment option for the selected card's BIN
Because a saved card has no PAN input, the component fetches /pan-info for all saved cards in parallel while the loading state is shown. When the shopper selects a card, the dropdown appears immediately using the pre-fetched data, and the selected value is included in the v2/transaction request automatically. Cards remain selectable regardless of BIN data availability; if there are no installment options, the payment proceeds as a single charge.
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 processing
Traditional card payment
When the shopper enters card details manually (not through Click to Pay), use confirmPayment:
monri.confirmPayment(card, {
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",
language: 'en'
}).then((result) => {
if (result.error) {
console.error('Payment failed:', result.error);
} else if (result.result.status === "approved") {
console.log('Payment successful:', result.result);
}
}).catch((error) => {
console.error('Payment error:', error);
});
// confirmPayment result model
type ConfirmPaymentResult = {
error: { message: string } | null;
result: {
status: string; // 'approved' | 'declined' | 'action_required' | 'executed' | 'payment_method_required'
id: string;
action_required?: {
redirect_to: string;
acs_url: string;
};
payment_result?: {
amount: number;
currency: string;
};
} | null;
};
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 |
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'); // sync active card brand with the Click to Pay UI
Accessibility
Click to Pay includes comprehensive accessibility support:
- High-contrast mode — set via
isHighContrastortoggleTheme()at runtime - Dyslexic font — OpenDyslexic font family, via
dyslexicFontortoggleDyslexicFont() - Font-size scaling —
normal,large,larger, viafontSizeortoggleFontSize() - Theme support — light and dark variants with automatic contrast adjustments
const card = components.create("card", {
environment: 'test',
clickToPay: {
isHighContrast: localStorage.getItem('highContrast') ?? 'false',
dyslexicFont: localStorage.getItem('dyslexicFont') ?? 'false',
fontSize: localStorage.getItem('fontSizeState') ?? 'normal'
}
});