The IPS-RS (Instant Payment Serbia) component is a comprehensive payment solution that enables real-time payments
in Serbia. This enhanced version includes full internationalization support with 8 languages, complete CSS customization
capabilities with 100+ CSS variables, and precise responsive behavior.
Setup & Integration
Step 1: Include Monri.js Script
The ips-rs component is available as part of Monri.js. Include the script directly from monri.com for PCI
compliance:
Test Environment:
<script src="https://ipgtest.monri.com/dist/components.js"></script>
Production Environment:
<script src="https://ipg.monri.com/dist/components.js"></script>
Step 2: Backend Requirements
When generating the client_secret on your backend via the /v2/payment/new endpoint, ensure:
{
"ch_full_name": "Customer Full Name",
"supported_payment_methods": [
"ips-rs"
],
"amount": 100,
"currency": "RSD"
}
Critical: If ch_full_name is missing, the client_secret will be invalid and the component will fail to
initialize.
Step 3: Initialize Monri and Components
const monri = Monri('<authenticity-token>', {
locale: 'ba-hr' // Set default language
});
const components = monri.components({clientSecret: '<client-secret>'});
Step 4: Create Payment Form Container
<div id="ips-rs-element"></div>
Step 5: Component Creation with Enhanced Options
const ips = components.create("ips-rs", {
trx_token: "<trx_token>",
environment: isTestSystem ? "test" : "prod",
lang: "ba-hr", // Set initial language
style: {
// See complete styling section below
container: {
maxWidth: "600px",
margin: "0 auto"
}
// ... additional styling options
}
});
// Mount the component
ips.mount("ips-rs-element");
Component Dimensions & Responsive Behavior
The IPS payment component automatically adjusts its dimensions based on content and user interactions. Here are the
minimum requirements for proper integration:
Minimum Dimensions by Platform
Mobile Devices (≤768px width)
Initial State (no bank selected):
- Minimum width: 340px
- Minimum height: 360px
Bank Selected State:
- Minimum width: 340px
- Minimum height: 420px
Desktop/Web (>768px width)
All States:
- Minimum width: 400px
- Minimum height: 250px (dynamically adjusts based on content)
Bank Selection Logic
The component detects when a bank is selected by checking:
- Bank selection dropdown has value
- Bank logo is visible (display !== 'none')
- Bank logo has valid source URL
Auto-Resize Functionality
- Component automatically sends RESIZE_IFRAME messages to parent element
- Height adjustments happen in real-time based on content changes
- Minimum thresholds are always maintained regardless of content
###️ Integration Requirements
Container Setup:
Ensure your iframe container can accommodate:
/* Mobile */
@media (max-width: 768px) {
.payment-iframe {
min-width: 340px;
min-height: 420px; /* Allows expansion for bank selection */
}
}
/* Desktop */
@media (min-width: 769px) {
.payment-iframe {
min-width: 400px;
min-height: 250px;
}
}
Important Notes
- Always reserve space for expanded state (420px height on mobile)
- Component internally handles responsive behavior
- Minimum widths are enforced to ensure payment form usability
- Bank selection triggers immediate height recalculation
- Due to integration fixes, changes will be live soon - recommend adjusting code per above settings
Language Management
Dynamic Language Switching
The enhanced IPS-RS component supports real-time language switching:
// Set language during component creation
const ips = components.create("ips-rs", {
trx_token: "<trx_token>",
environment: "test",
lang: "hr" // Croatian
});
// Change language after component is mounted
ips.setLanguage("de"); // Switch to German
ips.setLanguage("en"); // Switch to English
Supported Languages
| Language Code | Language | Region |
|---|---|---|
en |
English | International |
ba-hr |
Bosnian-Croatian | Bosnia and Herzegovina |
hr |
Croatian | Croatia |
de |
German | Germany |
me |
Montenegrin | Montenegro |
sl |
Slovenian | Slovenia |
sr |
Serbian | Serbia |
sr-Cyrl |
Serbian (Cyrillic) | Serbia |
mk |
Macedonian | North Macedonia |
Translation Keys
All UI text uses these translation keys:
| Key | English | Serbian (BA-HR) |
|---|---|---|
MBANKING_INSTRUCTION |
"Using the mbanking application..." | "Mbanking aplikacijom koju imate instaliranu..." |
CLICK_AND_REFRESH |
"Click and refresh" | "Klikni i obnovi" |
REFRESH_QR_CODE |
"Refresh QR code" | "Obnovi QR kôd" |
INDIVIDUAL_PERSON |
"Individual" | "Fizičko lice" |
LEGAL_ENTITY |
"Legal entity or entrepreneur" | "Pravno lice ili preduzetnik" |
SELECT_BANK |
"Select bank..." | "Izaberite banku..." |
PAY_WITH_APP |
"Pay with app" | "Plati aplikacijom" |
ENV_NOT_SET |
"Environment is not set" | "Okruženje nije postavljeno" |
LANG_NOT_SET |
"Language is not set" | "Jezik nije postavljen" |
Complete CSS Customization Guide
CSS Custom Properties Overview
The IPS-RS component supports 100+ CSS custom properties for complete visual customization. All values can be
overridden using CSS custom properties (CSS variables).
CSS Customization Categories
1. Layout & Container Styling
:root {
/* Main container */
--ips-container-width: 100%;
--ips-container-display: block;
--ips-container-flex-direction: column;
--ips-container-max-width: none;
--ips-container-margin: 0;
--ips-container-gap: 0;
--ips-container-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
/* Content layout */
--ips-content-width: 100%;
/* Desktop layout */
--desktop-layout-display: flex;
--desktop-layout-flex-direction: row;
--desktop-layout-gap: 1rem;
/* Mobile layout */
--mobile-layout-display: flex;
--mobile-layout-flex-direction: column;
--mobile-layout-gap: 1rem;
--mobile-layout-padding: 16px;
}
2. Loading State Styling
:root {
/* Loading wrapper */
--loading-wrapper-display: flex;
--loading-wrapper-justify-content: center;
--loading-wrapper-align-items: center;
--loading-wrapper-min-height: 200px;
/* Loading spinner */
--loading-spinner-width: 40px;
--loading-spinner-height: 40px;
--loading-spinner-border-width: 4px;
--loading-spinner-border-color: #f3f3f3;
--loading-spinner-border-top-color: #3498db;
--loading-spinner-border-radius: 50%;
--loading-spinner-animation: spin 1s linear infinite;
}
3. Text & Typography Styling
:root {
/* Instruction text */
--instruction-text-display: block;
--instruction-text-font-size: 14px;
--instruction-text-line-height: 1.6;
--instruction-text-color: #333;
--instruction-text-text-align: left;
--instruction-text-margin-bottom: 1rem;
--instruction-text-font-family: inherit;
/* Mobile instruction text specific */
--mobile-instruction-margin-bottom: 1rem;
--mobile-instruction-font-size: 14px;
--mobile-instruction-color: #333;
--mobile-instruction-display: block;
--mobile-instruction-font-family: inherit;
}
4. Logo & Image Styling
:root {
/* IPS logo */
--ips-logo-width: 120px; /* Desktop: 120px, Mobile: 35vw */
--ips-logo-max-width: 120px; /* Desktop: none, Mobile: 120px */
--ips-logo-align-self: flex-start;
--ips-logo-display: flex;
--ips-logo-margin-bottom: 0;
/* Mobile logo specific */
--ips-logo-mobile-width: 120px;
--ips-logo-mobile-align-self: center;
/* Bank logo */
--bank-logo-display: block;
--bank-logo-width: 100%;
--bank-logo-height: 70px;
--bank-logo-object-fit: contain;
/* Bank logo container */
--bank-logo-container-margin: 0 auto 1rem;
--bank-logo-container-width: 100%;
--bank-logo-container-display: flex;
--bank-logo-container-justify-content: center;
--bank-logo-container-mobile-width: 200px;
}
5. QR Code Styling
:root {
/* QR section */
--qr-section-display: flex;
--qr-section-justify-content: center;
/* QR container */
--qr-container-display: flex;
--qr-container-justify-content: center;
--qr-container-margin-bottom: 0;
/* QR regeneration */
--qr-regenerate-display: flex;
--qr-regenerate-justify-content: center;
--qr-regenerate-margin-bottom: 0;
--qr-regenerate-cursor: pointer;
/* QR regeneration overlay */
--qr-regenerate-overlay-position: relative;
--qr-regenerate-overlay-max-height: 164px;
/* QR placeholder (164x164px - matches QR code size) */
--qr-placeholder-display: block;
--qr-placeholder-width: 164px;
--qr-placeholder-height: 164px;
}
6. Button Styling
:root {
/* Regenerate button (QR overlay) */
--regenerate-button-position: absolute;
--regenerate-button-z-index: 2;
--regenerate-button-left: 0;
--regenerate-button-top: 0;
--regenerate-button-right: 0;
--regenerate-button-bottom: 0;
--regenerate-button-background: rgba(255, 255, 255, 0.85) url(https://uac-test.app.monri.com/ips-rs-images/images/refresh.svg) no-repeat center 33%;
--regenerate-button-border: none;
--regenerate-button-cursor: pointer;
--regenerate-button-text-transform: uppercase;
--regenerate-button-backdrop-filter: blur(3px);
--regenerate-button-font-size: 1rem;
--regenerate-button-font-weight: bold;
--regenerate-button-color: #333;
/* Pay button */
--pay-button-width: 100%;
--pay-button-margin: auto;
--pay-button-padding: 12px 24px;
--pay-button-max-width: 150px;
--pay-button-background-color: #FCF5FF;
--pay-button-color: white;
--pay-button-border-radius: 4px;
--pay-button-cursor: pointer;
--pay-button-transition: background-color 0.3s;
--pay-button-font-size: 16px;
--pay-button-font-weight: bold;
--pay-button-font-family: inherit, serif;
/* Pay button disabled state */
--pay-button-disabled-color: #ACACAC;
--pay-button-disabled-background-color: #d8d8d8;
--pay-button-disabled-cursor: not-allowed;
/* Refresh button */
--refresh-button-flex: 1;
--refresh-button-padding: 12px 24px;
--refresh-button-background-color: #b36cd5;
--refresh-button-color: white;
--refresh-button-border-color: transparent;
--refresh-button-border-radius: 4px;
--refresh-button-font-size: 14px;
--refresh-button-font-weight: bold;
--refresh-button-transition: background-color 0.3s;
--refresh-button-font-family: inherit;
--refresh-button-max-width: none;
/* Refresh button hover state */
--refresh-button-hover-background-color: #8132A7FF;
--refresh-button-hover-color: white;
}
7. Form Elements Styling
:root {
/* User type selection */
--user-type-selection-margin-bottom: 1rem;
--user-type-selection-display: flex;
--user-type-selection-gap: 1rem;
--user-type-selection-flex-wrap: wrap;
/* Mobile user type selection */
--user-type-selection-mobile-flex-direction: column;
--user-type-selection-mobile-gap: 0.5rem;
/* Radio labels */
--radio-label-display: inline-flex;
--radio-label-align-items: center;
--radio-label-cursor: pointer;
--radio-label-font-size: 14px;
--radio-label-margin-right: 0;
--radio-label-font-family: inherit;
--radio-label-color: inherit;
/* Radio inputs */
--radio-input-scale: 1.2;
--radio-input-margin: 0 0.5rem 0 0;
--radio-input-padding: 0;
/* Bank selection dropdown */
--bank-select-width: 100%;
--bank-select-margin-bottom: 1rem;
--bank-select-padding: 12px;
--bank-select-border-width: 1px;
--bank-select-border-color: #ddd;
--bank-select-border-radius: 4px;
--bank-select-background-color: white;
--bank-select-color: inherit;
--bank-select-font-weight: normal;
--bank-select-cursor: pointer;
--bank-select-font-family: inherit;
/* Bank select focus state */
--bank-select-focus-outline: none;
--bank-select-focus-border-color: #007bff;
--bank-select-focus-box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
8. Timer & Actions Styling
:root {
/* Timer */
--timer-opacity: 0;
--timer-text-align: center;
--timer-font-size: 16px;
--timer-font-weight: bold;
--timer-color: #666;
--timer-margin-top: 0;
--timer-font-family: inherit;
/* Actions container */
--actions-display: flex;
--actions-gap: 1rem;
--actions-opacity: 0;
/* Visible class */
--visible-opacity: 1;
}
9. Layout Containers Styling
:root {
/* Left content (desktop) */
--left-content-flex: 1;
--left-content-display: flex;
--left-content-flex-direction: column;
--left-content-gap: 1rem;
--left-content-max-width: 300px;
/* Right container (desktop) */
--right-container-flex-shrink: 0;
--right-container-display: flex;
--right-container-flex-direction: column;
--right-container-align-items: center;
--right-container-gap: 0.5rem;
/* Header (mobile) */
--header-display: flex;
--header-gap: 1rem;
--header-margin-bottom: 1rem;
--header-justify-content: space-between;
--header-align-items: center;
/* Mobile header specific */
--header-mobile-flex-direction: column;
--header-mobile-gap: 0.5rem;
/* Mobile section */
--mobile-section-display: block;
--mobile-section-margin-bottom: 1rem;
}
10. Error State Styling
:root {
/* Error messages */
--error-padding: 16px;
--error-background-color: #f8d7da;
--error-color: #721c24;
--error-border-width: 1px;
--error-border-color: #f5c6cb;
--error-border-radius: 4px;
--error-margin-bottom: 1rem;
--error-font-family: inherit;
--error-font-size: inherit;
}
11. Responsive Breakpoint
:root {
/* Mobile breakpoint */
--mobile-breakpoint: 768px;
}
Style Configuration Object
Instead of using CSS variables, you can also pass styles directly to the component:
const ips = components.create("ips-rs", {
trx_token: "<trx_token>",
environment: "test",
style: {
// Use any of these keys for direct styling
container: { /* CSS properties */},
loadingWrapper: { /* CSS properties */},
loadingSpinner: { /* CSS properties */},
ipsContent: { /* CSS properties */},
desktopLayout: { /* CSS properties */},
leftContent: { /* CSS properties */},
rightContainer: { /* CSS properties */},
mobileLayout: { /* CSS properties */},
header: { /* CSS properties */},
text: { /* CSS properties */},
logo: { /* CSS properties */},
qrSection: { /* CSS properties */},
qrCodeContainer: { /* CSS properties */},
qrRegenerate: { /* CSS properties */},
qrRegenerateOverlay: { /* CSS properties */},
regenerateButton: { /* CSS properties */},
qrPlaceholder: { /* CSS properties */},
timer: { /* CSS properties */},
ifMobile: { /* CSS properties */},
mobileLabel: { /* CSS properties */},
userTypeSelection: { /* CSS properties */},
radioLabel: { /* CSS properties */},
radioInput: { /* CSS properties */},
bankSelect: { /* CSS properties */},
bankLogoContainer: { /* CSS properties */},
bankLogo: { /* CSS properties */},
generateQRButton: { /* CSS properties */},
payButton: { /* CSS properties */},
actions: { /* CSS properties */},
error: { /* CSS properties */}
}
});
Advanced Styling Examples
Example 1: Modern Theme
:root {
/* Modern color palette */
--ips-container-background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--ips-container-border-radius: 20px;
--ips-container-padding: 30px;
--ips-container-box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
/* Modern typography */
--instruction-text-color: white;
--instruction-text-font-family: 'Inter', 'Segoe UI', sans-serif;
--instruction-text-font-size: 16px;
--instruction-text-line-height: 1.7;
/* Modern buttons */
--pay-button-background-color: #4facfe;
--pay-button-border-radius: 25px;
--pay-button-padding: 15px 40px;
--pay-button-font-size: 18px;
--pay-button-box-shadow: 0 10px 30px rgba(79, 172, 254, 0.3);
--pay-button-transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* Modern forms */
--bank-select-background-color: rgba(255, 255, 255, 0.1);
--bank-select-border: 2px solid rgba(255, 255, 255, 0.2);
--bank-select-border-radius: 12px;
--bank-select-color: white;
--bank-select-backdrop-filter: blur(10px);
/* Modern QR container */
--qr-container-background: rgba(255, 255, 255, 0.95);
--qr-container-border-radius: 15px;
--qr-container-padding: 25px;
--qr-container-backdrop-filter: blur(10px);
}
Example 2: Corporate Theme
:root {
/* Corporate colors */
--ips-container-background-color: #ffffff;
--ips-container-border: 2px solid #e1e5e9;
--ips-container-border-radius: 8px;
/* Corporate typography */
--instruction-text-color: #2c3e50;
--instruction-text-font-family: 'Roboto', 'Arial', sans-serif;
--instruction-text-font-size: 15px;
--instruction-text-line-height: 1.6;
/* Corporate buttons */
--pay-button-background-color: #3498db;
--pay-button-border-radius: 6px;
--pay-button-font-weight: 600;
--pay-button-text-transform: uppercase;
--pay-button-letter-spacing: 0.5px;
/* Corporate forms */
--bank-select-border: 1px solid #bdc3c7;
--bank-select-border-radius: 4px;
--bank-select-padding: 12px 16px;
--bank-select-font-size: 14px;
/* Corporate spacing */
--mobile-layout-padding: 24px;
--desktop-layout-gap: 2rem;
--user-type-selection-gap: 1.5rem;
}
Example 3: Dark Theme
:root {
/* Dark theme colors */
--ips-container-background-color: #2c3e50;
--ips-container-color: #ecf0f1;
/* Dark typography */
--instruction-text-color: #ecf0f1;
--mobile-instruction-color: #bdc3c7;
--radio-label-color: #ecf0f1;
/* Dark buttons */
--pay-button-background-color: #e74c3c;
--pay-button-color: #ffffff;
--refresh-button-background-color: #f39c12;
/* Dark forms */
--bank-select-background-color: #34495e;
--bank-select-color: #ecf0f1;
--bank-select-border-color: #4a5f7a;
/* Dark QR container */
--qr-container-background: rgba(52, 73, 94, 0.8);
--qr-container-border: 1px solid #4a5f7a;
/* Dark error states */
--error-background-color: #c0392b;
--error-color: #ecf0f1;
--error-border-color: #a93226;
}
Event Handling
Component Events
// Listen for component events
ips.on('change', (event) => {
console.log('Component state changed:', event);
});
ips.on('ready', () => {
console.log('Component is ready');
});
// Listen for iframe resize events
window.addEventListener('message', (event) => {
if (event.data.type === 'RESIZE_IFRAME') {
console.log('Component height changed to:', event.data.data.minHeight);
// Optional: Handle resize if needed
}
});
// Listen for payment completion
window.addEventListener('message', (event) => {
if (event.data.type === 'PAYMENT_RESULT') {
const {transaction} = event.data;
if (transaction.status === 'approved') {
console.log('Payment successful:', transaction);
} else {
console.log('Payment failed:', transaction);
}
}
});
Integration Examples
Example 1: Basic Integration with Responsive Styling
<!DOCTYPE html>
<html>
<head>
<title>IPS-RS Integration</title>
<script src="https://ipgtest.monri.com/dist/components.js"></script>
<style>
/* Required iframe container styling */
.payment-container {
width: 100%;
max-width: 600px;
margin: 0 auto;
}
/* Mobile */
@media (max-width: 768px) {
.payment-iframe {
min-width: 340px;
min-height: 240px;
max-height: 380px
width: 100%;
border: none;
}
}
/* Desktop */
@media (min-width: 769px) {
.payment-iframe {
min-width: 400px;
min-height: 260px;
width: 100%;
border: none;
}
}
/* Custom IPS styling */
:root {
--ips-container-max-width: 600px;
--ips-container-margin: 0 auto;
--ips-container-padding: 20px;
--pay-button-background-color: #007bff;
--instruction-text-font-size: 16px;
}
</style>
</head>
<body>
<div class="payment-container">
<h2>Complete Your Payment</h2>
<div id="ips-rs-element"></div>
<!-- Language switcher -->
<div class="language-switcher">
<button onclick="changeLanguage('ba-hr')">Bosanski/Hrvatski</button>
<button onclick="changeLanguage('sr')">Српски</button>
<button onclick="changeLanguage('en')">English</button>
</div>
</div>
<script>
let ipsComponent;
// Initialize component
function initializeIPS() {
const monri = window.Monri('your-authenticity-token', {
locale: 'ba-hr'
});
const components = monri.components({
clientSecret: 'your-client-secret'
});
ipsComponent = components.create('ips-rs', {
trx_token: 'your-transaction-token',
environment: 'test',
lang: 'ba-hr'
});
ipsComponent.mount('ips-rs-element');
}
// Language switching function
function changeLanguage(langCode) {
if (ipsComponent) {
ipsComponent.setLanguage(langCode);
}
}
// Handle resize events
window.addEventListener('message', (event) => {
if (event.data.type === 'RESIZE_IFRAME') {
console.log('Component resized to:', event.data.data.minHeight);
}
});
// Initialize when page loads
document.addEventListener('DOMContentLoaded', initializeIPS);
</script>
</body>
</html>