IPS - Instant Payment Serbia

Prev Next

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:

  1. Bank selection dropdown has value
  2. Bank logo is visible (display !== 'none')
  3. 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>