Guide

This guide walks you through the optimal client integrations for the Ivy checkout. Including:
  1. Desktop Web - Use the React SDK and iframe to create a seamless experience for customers to complete their payment within your site.
  2. Mobile Web - Redirect your users to the Ivy hosted checkout for a clean payment experience on smaller screens.
  3. Mobile Native App - Redirect users to the Ivy hosted checkout to complete the payment and return directly to your app through deep linking.

SDK Backend Setup

The backend setup provides a secure, server-side integration to handle the communication between your application and Ivy’s API. It receives payment details from your frontend and return a checkout URL where users go to complete their payment.
1

Install Dependencies

First, install our official Node SDK:
npm install @getivy/node-sdk
# or
yarn add @getivy/node-sdk
2

Create a Checkout Session

Create an endpoint on your server that initializes a checkout session:
server/routes/checkout.ts
import express from 'express';
import Ivy from '@getivy/node-sdk';
import { CheckoutsessionCreateParams } from '@getivy/node-sdk/resources/checkoutsession';

const router = express.Router();
const ivy = new Ivy('YOUR_API_KEY');

router.post('/api/checkout', async (req, res) => {
  try {
    const params: CheckoutsessionCreateParams = {
      referenceId: 'order_123', // Your internal unique order reference
      price: {
        total: req.body.amount,
        currency: req.body.currency || 'EUR',
      },
      locale: req.body.locale || 'en', // The language of the checkout. Customers can change this in the checkout as well
      errorCallbackUrl: "https://example.com/error",
      successCallbackUrl: "https://example.com/success",
      customer: {
        email: "john.doe@example.com",
      }
    };

    const session = await ivy.checkoutsession.create(params);
    res.json({ url: session.redirectUrl });
  } catch (error) {
    console.error("Failed to create checkout session", error);
    res.status(400).json({ error: 'Failed to create checkout session' });
  }
});

export default router;

Client Implementations

2.1. Desktop Web

For desktop web applications, iframe integration with our React SDK provides the best user experience and conversion rates.
embedded iframe: If you’re embedding the Ivy checkout into your page always append &iframe=true to the checkout URL you receive back and render.modal iframe: If you’re overlaying your iframe in a modal always append &popup=true to the checkout URL you receive back and render.The API can’t include these parameters by default as it doesn’t know your rendering method.
1

Install Dependencies

npm install @getivy/react-sdk
# or
yarn add @getivy/react-sdk
2

Create IvyCheckout Component

components/IvyCheckout.tsx
import { IvyCheckout } from '@getivy/react-sdk'

export function Checkout({
  amount,
  currency = 'EUR',
  locale = 'de',
  handleSuccess,
  handleCancel
}: {
  amount: number,
  currency?: string,
  locale?: string,
  handleSuccess: (data: { redirectUrl: string, referenceId: string }) => void,
  handleCancel: (data: { redirectUrl: string, referenceId: string }) => void
}) {

  const response = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      amount,
      currency,
      locale
    })
  })

  const { url } = await response.json();

  return (
    <IvyCheckout
      checkoutUrl={url}
      displayOptions={{
        type: "embedded", // or "modal"
      }}
      onSuccess={({ redirectUrl, referenceId }) => {
        handleSuccess({ redirectUrl, referenceId });
      }}
      onCancel={({ redirectUrl, referenceId }) => {
        handleCancel({ redirectUrl, referenceId });
      }}
    />
  )
}
The React SDK automatically handles the &iframe=true parameter for you when using embedded mode.
3

Add Styling

styles/ivy-checkout.css
import "@getivy/react-sdk/dist/index.css";

.ivy-embedded-checkout-screen {
  border: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  width: 100%;
  height: 100%;
}
.ivy-modal-content {
  position: fixed;
  z-index: 999999;
  inset: 0;
  color: #000;
  background-color: rgba(10, 10, 10, 0.25);
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -ms-flex-direction: column;
  flex-direction: column;
  -webkit-box-align: center;
  border: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  width: 100%;
  height: 100%;
}
.ivy-modal-content {
  position: fixed;
  z-index: 999999;
  inset: 0;
  color: #000;
  background-color: rgba(10, 10, 10, 0.25);
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
  -ms-flex-direction: column;
  flex-direction: column;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
}
.ivy-modal-content .ivy-modal-iframe-container {
  border: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border-radius: 16px;
  transform: translateZ(0);
  width: 100%;
  height: 100%;
  background-color: transparent;
}
.ivy-modal-content .ivy-modal-checkout-screen {
  border: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  width: 100% !important;
  height: 100% !important;
}
@media (min-width: 450px) {
  .ivy-modal-content {
    padding: 16px 32px 32px;
  }
}
@media (max-width: 450px) {
  .ivy-modal-content .ivy-modal-iframe-container {
    border-radius: 0px;
    width: 100% !important;
    height: 100% !important;
    max-height: none !important;
  }
}
Plain HTML
checkout.html
<!DOCTYPE html>
<html>
<head>
  <title>Checkout</title>
  <style>
    .ivy-checkout {
      width: 100%;
      max-width: 500px;
      margin: 0 auto;
    }
    .ivy-checkout-frame {
      width: 100%;
      height: 650px;
      border: none;
      border-radius: 8px;
    }
  </style>
</head>
<body>
  <div class="ivy-checkout">
    <button onclick="startCheckout()">Pay with Ivy</button>
    <div id="iframe-container"></div>
  </div>

  <script>
    async function startCheckout() {
      try {
        const response = await fetch('/api/checkout', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            locale: 'de'
          })
        });

        const { url } = await response.json();
        const container = document.getElementById('iframe-container');
        
        // Always append iframe=true parameter for iframe rendering
        const iframeUrl = url + '&iframe=true';
        
        container.innerHTML = `
          <iframe
            src="${iframeUrl}"
            class="ivy-checkout-frame"
            sandbox="allow-scripts allow-same-origin allow-popups allow-forms allow-popups-to-escape-sandbox allow-top-navigation"
            allow="clipboard-write"
          ></iframe>
        `;

        window.addEventListener('message', handleMessage);
      } catch (error) {
        console.error('Checkout failed:', error);
      }
    }

    function handleMessage(event) {
      try {
        const { source, type, value, referenceId } = JSON.parse(event.data);
        if (source !== 'ivy' || type !== 'iframe') return;

        if (value === 'success') {
          console.log('Payment successful!', referenceId);
          // Handle success
        } else if (value === 'error') {
          console.log('Payment failed or cancelled');
          // Handle error
        }
      } catch (error) {
        console.error('Error processing message:', error);
      }
    }
  </script>
</body>
</html>
Important: Always append &iframe=true to the checkout URL when rendering in an iframe. The API doesn’t include this parameter by default.
Plain HTML
Instead of redirecting to any callback URLs, the iframe communicates with the parent window via the postMessage API.The iframe will send a message to the parent window with the following fields:
source
string
required
Always equal to "ivy"
type
string
required
Always equal to "iframe"
value
string
required
Either "success" or "error"
referenceId
string
required
Your original checkoutSession field referenceId. Can also be used to fetch a order with the Retrieve an Order endpoint.
Security Considerations
The iframe sandbox attributes are crucial for security. Each attribute serves a specific purpose:
  • allow-scripts: Required for Ivy Checkout to function
  • allow-same-origin: Enables secure communication
  • allow-forms: Required for payment form input
  • allow-popups and allow-popups-to-escape-sandbox: Required for bank redirects
  • allow-top-navigation: Required for completion redirects For customers to be able to copy payment details with a button click we need clipboard access:
  • allow="clipboard-write": Required for Ivy Checkout to copy text into clipboard

2.2. Mobile Web

For mobile web applications, redirecting users to the Ivy checkout URL provides better performance, security, and user experience compared to iframe embedding.
An iframe on mobile web can break the flow for users while going to or returning from their bank.
1

Detect Mobile Devices

// Detect mobile devices
function isMobile() {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
2

Implement Redirect Logic

async function startCheckout() {
  const response = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ amount: 100, currency: 'EUR' })
  });

  const { url } = await response.json();

  if (isMobile()) {
    // Redirect to Ivy checkout on mobile
    window.location.href = url;
  } else {
    // Use iframe on desktop
    const iframeUrl = url + '&iframe=true';
    // ... iframe implementation
  }
}
Mobile redirects provide better performance, security, and user experience compared to iframe embedding on mobile devices.
3

Configure Return URLs

Set up proper return URLs for mobile redirects:
const params = {
  referenceId: 'order_123',
  price: { total: 100, currency: 'EUR' },
  successCallbackUrl: 'https://yourapp.com/payment-success',
  errorCallbackUrl: 'https://yourapp.com/payment-error',
  // ... other parameters
};

2.3. Mobile Native App

For native mobile applications, always open Ivy checkout in the user’s default browser. This is essential for proper deep linking and app redirects - using WebViews in native apps can interfere with:
  • Bank authentication flows
  • Deep linking back to your app
  • Security features and SSL certificate handling
  • User session management
Open Ivy’s checkout in browser for the best deeplinking user experience
// ❌ Don't do this in native apps
<WebView source={{ uri: checkoutUrl }} />

// ✅ Do this instead
Linking.openURL(checkoutUrl);
1

React Native Implementation

// React Native
import { Linking } from 'react-native';

const openCheckoutInBrowser = async () => {
  const response = await fetch('/api/checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ amount: 100, currency: 'EUR' })
  });

  const { url } = await response.json();
  
  // Open in default browser
  const supported = await Linking.canOpenURL(url);
  if (supported) {
    await Linking.openURL(url);
  } else {
    console.error('Cannot open URL:', url);
  }
};
Native App Requirement: Always open Ivy checkout in the user’s default browser, not in a WebView. This is essential for proper deep linking and app redirects.
2

iOS Swift Implementation

// iOS Swift
import SafariServices

func openCheckoutInBrowser() {
    guard let url = URL(string: checkoutUrl) else { return }
    
    let safariVC = SFSafariViewController(url: url)
    safariVC.delegate = self
    present(safariVC, animated: true)
}
3

Android Kotlin Implementation

// Android Kotlin
fun openCheckoutInBrowser() {
    val intent = Intent(Intent.ACTION_VIEW, Uri.parse(checkoutUrl))
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    startActivity(intent)
}
4

Configure Deep Linking

Set up deep linking to handle payment completion:
// React Native
useEffect(() => {
  const handleDeepLink = (url) => {
    if (url.includes('payment-success')) {
      // Navigate to success screen
      navigation.navigate('PaymentSuccess');
    } else if (url.includes('payment-error')) {
      // Navigate to error screen
      navigation.navigate('PaymentError');
    }
  };

  Linking.addEventListener('url', handleDeepLink);
  return () => Linking.removeEventListener('url', handleDeepLink);
}, []);
Configure your app’s deep linking scheme in your app configuration files to handle return URLs from Ivy checkout.

Need Help?