Quick Start
Get the Merso Modal running in your application in under 5 minutes.
Add the Iframe to Your Page
The simplest way to integrate Merso Modal is to embed it as an iframe. Add this HTML to your page:
<!-- Modal Overlay Container -->
<div id="modal-overlay" style="
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
align-items: center;
justify-content: center;
z-index: 1000;
">
<iframe
id="merso-modal"
src="https://modal.dev.merso.io/modal"
name="merso-modal-frame"
width="480"
height="600"
style="border: none; border-radius: 1rem; background: white;"
title="Merso Payment Modal"
allow="payment"
></iframe>
</div>
Get Your JWT Token
Before sending data to the modal, authenticate with the Merso API to get a JWT token:
// Server-side: Get JWT token from Merso API
async function getJwtToken() {
const response = await fetch('https://modal.dev.merso.io/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
game_id: 'YOUR_GAME_ID',
api_key: 'YOUR_API_KEY'
})
});
const data = await response.json();
return data.authResult.token; // Valid for 12 hours
}
Never expose your API Key in client-side code. Generate the JWT token server-side and pass it securely to your frontend.
Send Item/NFT Data
There are two methods to send product data to the modal. Choose the one that best fits your architecture:
3.a via POST (Recommended)
Use a hidden form to POST data directly to the iframe. When a user clicks "Buy" on any product in your store, dynamically fill the form and submit it:
<!-- Your Game Store -->
<div class="store-items">
<div class="item-card" onclick="buyItem({id: 101, name: 'Diamond Sword', price: 29.99})">
<img src="sword.png" alt="Diamond Sword">
<h3>Diamond Sword</h3>
<p>$29.99</p>
<button>Buy Now</button>
</div>
<div class="item-card" onclick="buyItem({id: 102, name: 'Golden Shield', price: 49.99})">
<img src="shield.png" alt="Golden Shield">
<h3>Golden Shield</h3>
<p>$49.99</p>
<button>Buy Now</button>
</div>
<div class="item-card" onclick="buyItem({id: 103, name: 'Health Potion x10', price: 9.99})">
<img src="potion.png" alt="Health Potion">
<h3>Health Potion x10</h3>
<p>$9.99</p>
<button>Buy Now</button>
</div>
</div>
<!-- Hidden form (auto-filled when user clicks Buy) -->
<form id="merso-form" method="POST" action="https://modal.dev.merso.io/modal"
target="merso-modal-frame" style="display:none;">
<input type="hidden" name="jwtToken" id="form-jwt">
<input type="hidden" name="productType" id="form-productType">
<input type="hidden" name="itemPrice" id="form-itemPrice">
<input type="hidden" name="itemId" id="form-itemId">
<input type="hidden" name="itemName" id="form-itemName">
<input type="hidden" name="playerEmail" id="form-playerEmail">
</form>
<script>
// Called when user clicks any "Buy Now" button
function buyItem(item) {
// Fill the hidden form with the selected item's data
document.getElementById('form-jwt').value = JWT_TOKEN;
document.getElementById('form-productType').value = 'ITEM';
document.getElementById('form-itemPrice').value = item.price;
document.getElementById('form-itemId').value = item.id;
document.getElementById('form-itemName').value = item.name;
document.getElementById('form-playerEmail').value = currentUser.email;
// Submit form → loads modal in iframe with item data
document.getElementById('merso-form').submit();
// Show the modal overlay
document.getElementById('modal-overlay').style.display = 'flex';
}
</script>
3.B via postMessage
Send product data via postMessage after the iframe loads. Best for SPAs and dynamic content:
const iframe = document.getElementById('merso-modal');
iframe.onload = () => {
// For Item purchases
iframe.contentWindow.postMessage({
type: 'api-config',
jwtToken: 'YOUR_JWT_TOKEN',
// Item data
productType: 'ITEM',
itemPrice: 29.99,
itemId: 123,
itemName: 'Premium Sword',
playerEmail: 'player@example.com',
playerLevel: 42,
playerCountry: 'US'
}, '*');
};
// For NFT purchases, use this instead:
iframe.onload = () => {
iframe.contentWindow.postMessage({
type: 'api-config',
jwtToken: 'YOUR_JWT_TOKEN',
// NFT data
productType: 'NFT',
walletAddress: '0x1234...abcd',
collectionAddress: '0xNFT...collection',
tokenId: '1',
tokenPrice: '0.05',
tokenName: 'Epic NFT #1',
userEmail: 'user@example.com',
chainId: '1'
}, '*');
};
Listen for Payment Events
The modal communicates payment results via postMessage. Listen for these events:
window.addEventListener('message', (event) => {
// Ignore Stripe's internal messages
if (event.origin === 'https://js.stripe.com') return;
const { data } = event;
switch (data?.type) {
case 'payment-success':
console.log('Payment successful!', {
paymentIntentId: data.paymentIntentId,
totalAmount: data.totalAmount
});
closeModal();
showSuccessMessage();
break;
case 'payment-cancelled':
console.log('Payment cancelled');
closeModal();
break;
case 'crypto-payment-success':
console.log('NFT minted!', {
transactionHash: data.transactionHash,
tokenId: data.tokenId
});
closeModal();
break;
case 'modal-choice':
console.log('User selected:', data.choice);
// Track analytics, etc.
break;
}
});
function closeModal() {
document.getElementById('modal-overlay').style.display = 'none';
}
Complete Examples
Here are complete working examples for both integration methods:
A Complete Example (POST)
<!DOCTYPE html>
<html>
<head>
<title>My Game Shop - POST Method</title>
</head>
<body>
<button id="buy-item-btn">Buy Premium Sword - $29.99</button>
<!-- Modal Overlay -->
<div id="modal-overlay" style="display:none; position:fixed; inset:0;
background:rgba(0,0,0,0.6); align-items:center; justify-content:center; z-index:1000;">
<iframe id="merso-modal" name="merso-modal-frame" width="480" height="600"
style="border:none; border-radius:1rem;"
allow="payment"></iframe>
</div>
<!-- Hidden Form for POST -->
<form id="merso-form" method="POST" action="https://modal.dev.merso.io/modal"
target="merso-modal-frame" style="display:none;">
<input type="hidden" name="jwtToken" id="form-jwt">
<input type="hidden" name="productType" id="form-productType">
<input type="hidden" name="itemPrice" id="form-itemPrice">
<input type="hidden" name="itemId" id="form-itemId">
<input type="hidden" name="itemName" id="form-itemName">
<input type="hidden" name="playerEmail" id="form-playerEmail">
<input type="hidden" name="playerLevel" id="form-playerLevel">
<input type="hidden" name="playerCountry" id="form-playerCountry">
</form>
<script>
let JWT_TOKEN = null;
// Get JWT token from your backend
async function initAuth() {
const response = await fetch('/api/get-merso-token');
const data = await response.json();
JWT_TOKEN = data.token;
}
initAuth();
document.getElementById('buy-item-btn').addEventListener('click', () => {
// Set form values
document.getElementById('form-jwt').value = JWT_TOKEN;
document.getElementById('form-productType').value = 'ITEM';
document.getElementById('form-itemPrice').value = '29.99';
document.getElementById('form-itemId').value = '123';
document.getElementById('form-itemName').value = 'Premium Sword';
document.getElementById('form-playerEmail').value = 'player@example.com';
document.getElementById('form-playerLevel').value = '42';
document.getElementById('form-playerCountry').value = 'US';
// Submit form (POSTs to iframe)
document.getElementById('merso-form').submit();
// Show modal overlay
document.getElementById('modal-overlay').style.display = 'flex';
});
window.addEventListener('message', (event) => {
if (event.origin === 'https://js.stripe.com') return;
if (event.data?.type === 'payment-success') {
alert('Payment successful! Thank you for your purchase.');
document.getElementById('modal-overlay').style.display = 'none';
}
if (event.data?.type === 'payment-cancelled') {
document.getElementById('modal-overlay').style.display = 'none';
}
});
document.getElementById('modal-overlay').addEventListener('click', (e) => {
if (e.target.id === 'modal-overlay') {
e.target.style.display = 'none';
}
});
</script>
</body>
</html>
B Complete Example (postMessage)
<!DOCTYPE html>
<html>
<head>
<title>My Game Shop - postMessage Method</title>
</head>
<body>
<button id="buy-item-btn">Buy Premium Sword - $29.99</button>
<div id="modal-overlay" style="display:none; position:fixed; inset:0;
background:rgba(0,0,0,0.6); align-items:center; justify-content:center; z-index:1000;">
<iframe id="merso-modal" width="480" height="600"
style="border:none; border-radius:1rem;"
allow="payment"></iframe>
</div>
<script>
const MODAL_URL = 'https://modal.dev.merso.io/modal';
let JWT_TOKEN = null;
// Get JWT token from your backend
async function initAuth() {
const response = await fetch('/api/get-merso-token');
const data = await response.json();
JWT_TOKEN = data.token;
}
initAuth();
document.getElementById('buy-item-btn').addEventListener('click', () => {
const iframe = document.getElementById('merso-modal');
iframe.src = MODAL_URL;
iframe.onload = () => {
// Send all data via postMessage
iframe.contentWindow.postMessage({
type: 'api-config',
jwtToken: JWT_TOKEN,
// Item data
productType: 'ITEM',
itemPrice: 29.99,
itemId: 123,
itemName: 'Premium Sword',
playerEmail: 'player@example.com',
playerLevel: 42,
playerCountry: 'US'
}, '*');
};
document.getElementById('modal-overlay').style.display = 'flex';
});
window.addEventListener('message', (event) => {
if (event.origin === 'https://js.stripe.com') return;
if (event.data?.type === 'payment-success') {
alert('Payment successful! Thank you for your purchase.');
document.getElementById('modal-overlay').style.display = 'none';
}
if (event.data?.type === 'payment-cancelled') {
document.getElementById('modal-overlay').style.display = 'none';
}
});
document.getElementById('modal-overlay').addEventListener('click', (e) => {
if (e.target.id === 'modal-overlay') {
e.target.style.display = 'none';
}
});
</script>
</body>
</html>