PostMessage Integration
The Charge SDK uses PostMessage API for secure cross-origin communication between the merchant's frontend application and the payment processing iframe/popup window. This enables real-time payment status updates without requiring page refreshes or polling.
PostMessage Communication Flow
Message Types
1. SDK_LOADED
Sent when the payment SDK has successfully loaded and is ready for user interaction.
Message Structure:
{
type: 'SDK_LOADED',
timestamp: '2024-01-15T10:30:00.000Z'
}
When to expect: Immediately after the payment window opens and the SDK initializes.
Merchant Action: Update loading status to indicate the payment form is ready.
2. PAYMENT_COMPLETED / PAYMENT_SUCCESS
Sent when a payment has been successfully processed and completed.
Message Structure:
{
type: 'PAYMENT_COMPLETED', // or 'PAYMENT_SUCCESS'
chargeId: 'c4767bf0-83a0-4b53-aece-23565cddff90',
orderId: 'dba445e8-a780-4b38-8527-507f4429676a',
amount: 1200000, // Amount in lowest denominator
currency: 'IDR',
status: 'completed',
timestamp: '2024-01-15T10:35:00.000Z'
}
When to expect: After successful payment processing.
Merchant Action:
- Close payment window
- Redirect to success/confirmation page
- Update order status in your system
- Display success message to user
3. PAYMENT_CANCELLED
Sent when the user cancels the payment process.
Message Structure:
{
type: 'PAYMENT_CANCELLED',
reason: 'user_cancelled', // or 'timeout', 'error'
timestamp: '2024-01-15T10:32:00.000Z'
}
When to expect: When user closes payment window or clicks cancel.
Merchant Action:
- Close payment window
- Return user to checkout page
- Display appropriate message
4. PAYMENT_FAILED
Sent when a payment attempt fails due to processing errors.
Message Structure:
{
type: 'PAYMENT_FAILED',
error: {
code: 'INSUFFICIENT_FUNDS',
message: 'Insufficient funds in account'
},
timestamp: '2024-01-15T10:33:00.000Z'
}
When to expect: When payment processing encounters an error.
Merchant Action:
- Display error message
- Allow user to retry or choose different payment method
- Log error for debugging
Implementation Guide
Step 1: Set Up PostMessage Listener
Add a message event listener to your merchant frontend application:
window.addEventListener('message', function(event) {
console.log('Received postMessage:', event);
// Verify origin for security.
// The full list of origins for your configuration is provided during onboarding.
const allowedOrigins = [
'http://localhost:3000',
'https://dmg-ecommerce-backend.ecomm-stg.dmgsecure.io',
window.location.origin
];
if (!allowedOrigins.includes(event.origin)) {
console.warn('Received message from unauthorized origin:', event.origin);
return;
}
const data = event.data;
handlePaymentMessage(data);
});
Step 2: Handle Different Message Types
Create a message handler function:
function handlePaymentMessage(data) {
console.log('Processing message type:', data.type);
switch (data.type) {
case 'SDK_LOADED':
handleSDKLoaded(data);
break;
case 'PAYMENT_COMPLETED':
case 'PAYMENT_SUCCESS':
handlePaymentSuccess(data);
break;
case 'PAYMENT_CANCELLED':
handlePaymentCancelled(data);
break;
case 'PAYMENT_FAILED':
handlePaymentFailed(data);
break;
default:
console.log('Unknown message type:', data.type);
}
}
Step 3: Implement Message Handlers
function handleSDKLoaded(data) {
console.log('SDK has loaded successfully');
// Clear any loading timeouts
if (sdkLoadTimeout) {
clearTimeout(sdkLoadTimeout);
sdkLoadTimeout = null;
}
// Update UI to show payment is ready
updatePaymentStatus('Waiting for payment...', 'loading');
}
function handlePaymentSuccess(data) {
console.log('Payment completed successfully:', data);
// Update UI to show success
updatePaymentStatus('Payment completed successfully!', 'success');
// Close payment window and redirect after delay
setTimeout(() => {
closePaymentWindow();
window.location.href = `payment-confirmation.html?transaction_id=${data.chargeId}&amount=${data.amount}`;
}, 2000);
}
function handlePaymentCancelled(data) {
console.log('Payment was cancelled:', data);
// Update UI to show cancellation
updatePaymentStatus('Payment was cancelled', 'error');
// Close payment window after delay
setTimeout(() => {
closePaymentWindow();
}, 2000);
}
function handlePaymentFailed(data) {
console.log('Payment failed:', data);
// Update UI to show error
updatePaymentStatus(`Payment failed: ${data.error?.message || 'Unknown error'}`, 'error');
// Close payment window after delay
setTimeout(() => {
closePaymentWindow();
}, 2000);
}
Step 4: Payment Window Management
let paymentWindow = null;
let paymentWindowCheckInterval = null;
function openPaymentWindow(paymentLink) {
// Open payment window with specific dimensions
paymentWindow = popupPayment({
url: paymentLink,
target: 'paymentWindow',
w: 500,
h: 630
});
// Start monitoring the payment window
startPaymentWindowMonitoring();
}
function popupPayment({url, target, w, h}) {
const top = Math.max(0, (window.screen.height - h) / 2);
const left = Math.max(0, (window.screen.width - w) / 2);
const newWindow = window.open(url, target, `
scrollbars=yes,
width=${w},
height=${h},
top=${top},
left=${left}
`);
if (window.focus) {
newWindow.focus();
}
return newWindow;
}
function startPaymentWindowMonitoring() {
// Check if payment window is closed every 1 second
paymentWindowCheckInterval = setInterval(() => {
if (paymentWindow && paymentWindow.closed) {
console.log('Payment window was closed by user');
updatePaymentStatus('Payment window was closed', 'error');
// Clean up and hide loader
setTimeout(() => {
closePaymentWindow();
}, 2000);
}
}, 1000);
}
function closePaymentWindow() {
// Clean up intervals
if (paymentWindowCheckInterval) {
clearInterval(paymentWindowCheckInterval);
paymentWindowCheckInterval = null;
}
// Close payment window if still open
if (paymentWindow && !paymentWindow.closed) {
paymentWindow.close();
paymentWindow = null;
}
// Hide any loading overlays
hidePaymentLoader();
}
Security Considerations
Origin Validation
Always validate the origin of incoming messages to prevent security vulnerabilities:
const allowedOrigins = [
'https://your-payment-domain.com',
'https://staging-payment-domain.com',
window.location.origin // For same-origin scenarios
];
if (!allowedOrigins.includes(event.origin)) {
console.warn('Received message from unauthorized origin:', event.origin);
return;
}
Message Validation
Validate message structure and content:
function isValidMessage(data) {
// Check if message has required type field
if (!data || typeof data.type !== 'string') {
return false;
}
// Validate specific message types
switch (data.type) {
case 'PAYMENT_COMPLETED':
return data.chargeId && data.orderId && typeof data.amount === 'number';
case 'PAYMENT_FAILED':
return data.error && data.error.code;
default:
return true;
}
}
Timeout Handling
Implement timeouts to handle scenarios where messages might not be received:
let sdkLoadTimeout = setTimeout(() => {
console.warn('SDK load timeout - no SDK_LOADED message received');
updatePaymentStatus('Payment page loading timeout', 'warning');
}, 10000); // 10 second timeout
Troubleshooting
Common Issues
1. Messages Not Received
Problem: PostMessage events are not being received.
Solutions:
- Verify origin validation is not blocking legitimate messages
- Check browser console for JavaScript errors
- Ensure event listener is added before opening payment window
- Verify payment window is opening correctly
2. Origin Validation Errors
Problem: Messages are being blocked due to origin mismatch.
Solutions:
- Add all legitimate payment domain origins to allowedOrigins array
- Check for protocol mismatches (http vs https)
- Verify subdomain configurations
3. Payment Window Closes Immediately
Problem: Payment window closes before user can complete payment.
Solutions:
- Check for popup blockers
- Verify payment URL is valid and accessible
- Ensure window dimensions are appropriate
- Check for JavaScript errors in payment window
4. Timeout Issues
Problem: SDK_LOADED message never received.
Solutions:
- Increase timeout duration
- Check network connectivity
- Verify payment service is running
- Check for CORS issues
Debug Mode
Enable debug logging for troubleshooting to make sure the orgin whitelisting is correct and nothing else is blocking the postmessaging from the application side:
const DEBUG_MODE = true; // Set to false in production
function debugLog(message, data = null) {
if (DEBUG_MODE) {
console.log(`[Payment Debug] ${message}`, data);
}
}
// Use in message handler
window.addEventListener('message', function(event) {
debugLog('Received message', {
origin: event.origin,
type: event.data?.type,
data: event.data
});
// ... rest of handler
});