getHtml static method
Implementation
static String getHtml({
required String publicKey,
required int amount,
required bool sandbox,
String currency = 'xof',
required String companyName,
String? color,
String? logoUrl,
String? callbackUrl,
}) {
final cleanColor = color?.replaceAll('#', '') ?? 'green';
final safeCompanyName = _escapeHtml(companyName);
final safeLogoUrl = _escapeHtml(logoUrl ?? '');
final safeCallbackUrl = _escapeHtml(callbackUrl ?? '');
return '''
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Paiement Intram</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
#loading {
text-align: center;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-left-color: #29b3a6;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
color: #666;
font-size: 16px;
}
#error-container {
display: none;
background: #fff;
border-radius: 12px;
padding: 30px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
text-align: center;
max-width: 400px;
}
.error-icon { font-size: 50px; margin-bottom: 15px; }
.error-title { font-size: 20px; font-weight: 600; color: #e74c3c; margin-bottom: 10px; }
.error-message { color: #666; line-height: 1.6; margin-bottom: 20px; font-size: 15px; }
.retry-button {
background: #29b3a6;
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
.sandbox-badge {
position: fixed;
top: 10px;
left: 10px;
background: #ff9800;
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
z-index: 1000;
}
#success-container {
display: none;
background: #fff;
border-radius: 12px;
padding: 40px 30px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
text-align: center;
max-width: 400px;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.success-icon {
font-size: 70px;
margin-bottom: 20px;
animation: scaleIn 0.5s ease-out;
}
@keyframes scaleIn {
from { transform: scale(0); }
to { transform: scale(1); }
}
.success-title {
font-size: 24px;
font-weight: 700;
color: #27ae60;
margin-bottom: 15px;
}
.success-message {
color: #666;
line-height: 1.6;
margin-bottom: 20px;
font-size: 15px;
}
.transaction-id {
background: #f8f9fa;
padding: 12px;
border-radius: 8px;
font-family: monospace;
font-size: 13px;
color: #333;
margin-bottom: 20px;
word-break: break-all;
}
.close-button {
background: #27ae60;
color: white;
border: none;
padding: 12px 30px;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
margin-top: 10px;
}
</style>
</head>
<body>
${sandbox ? '<div class="sandbox-badge">đź§Ş MODE TEST</div>' : ''}
<div id="loading">
<div class="spinner"></div>
<div class="loading-text">Initialisation du paiement...</div>
</div>
<div id="error-container">
<div class="error-icon">⚠️</div>
<div class="error-title">Erreur de paiement</div>
<div class="error-message" id="error-message"></div>
<button class="retry-button" onclick="retryPayment()">🔄 Réessayer</button>
</div>
<div id="success-container">
<div class="success-icon">âś…</div>
<div class="success-title">Paiement réussi !</div>
<div class="success-message">
Votre transaction a été effectuée avec succès.
</div>
<div class="transaction-id" id="transaction-display"></div>
<button class="close-button" onclick="closeAfterSuccess()">Terminer</button>
</div>
<div id="widget-container"></div>
<script>
var config = {
public_key: '$publicKey',
amount: $amount,
sandbox: $sandbox,
currency: '$currency',
callback_url: '$safeCallbackUrl',
company: {
name: '$safeCompanyName',
template: 'default+',
color: '$cleanColor',
logo_url: '$safeLogoUrl'
}
};
var sdkLoaded = false;
var paymentInitialized = false;
var paymentSuccessData = null;
function showLoading() {
document.getElementById('loading').style.display = 'block';
document.getElementById('error-container').style.display = 'none';
document.getElementById('success-container').style.display = 'none';
document.getElementById('widget-container').style.display = 'none';
}
function hideLoading() {
document.getElementById('loading').style.display = 'none';
document.getElementById('widget-container').style.display = 'block';
}
function showError(message) {
document.getElementById('loading').style.display = 'none';
document.getElementById('widget-container').style.display = 'none';
document.getElementById('success-container').style.display = 'none';
document.getElementById('error-container').style.display = 'block';
document.getElementById('error-message').textContent = message || 'Une erreur est survenue';
}
function showSuccess(transactionId, amount) {
document.getElementById('loading').style.display = 'none';
document.getElementById('widget-container').style.display = 'none';
document.getElementById('error-container').style.display = 'none';
document.getElementById('success-container').style.display = 'block';
var displayText = 'Transaction ID: ' + transactionId;
if (amount) {
displayText += '\\nMontant: ' + amount + ' XOF';
}
document.getElementById('transaction-display').textContent = displayText;
}
function closeAfterSuccess() {
if (paymentSuccessData) {
sendMessageToFlutter({
type: 'close_success',
data: paymentSuccessData.data,
transaction_id: paymentSuccessData.transaction_id,
timestamp: paymentSuccessData.timestamp,
message: 'User confirmed payment success'
});
} else {
sendMessageToFlutter({
type: 'close_success',
message: 'User confirmed payment success'
});
}
}
function sendMessageToFlutter(message) {
try {
var messageStr = JSON.stringify(message);
if (window.FlutterChannel && window.FlutterChannel.postMessage) {
window.FlutterChannel.postMessage(messageStr);
}
if (window.flutter_inappwebview && window.flutter_inappwebview.callHandler) {
window.flutter_inappwebview.callHandler('paymentHandler', message);
}
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.FlutterChannel) {
window.webkit.messageHandlers.FlutterChannel.postMessage(message);
}
console.log('📤 Message to Flutter:', message);
} catch (error) {
console.error('❌ Error sending message:', error);
}
}
function initIntramPayment() {
if (paymentInitialized) {
console.warn('⚠️ Payment already initialized');
return;
}
try {
console.log('🚀 Initializing payment...', config);
if (typeof intramOpenWidget === 'undefined') {
throw new Error('SDK Intram non chargé. Vérifiez votre connexion Internet.');
}
paymentInitialized = true;
sendMessageToFlutter({
type: 'loading',
message: 'Initialisation du widget...'
});
intramOpenWidget.init(config)
.then(function(response) {
console.log('âś… Payment response:', response);
var transactionId = response.transactionId || response.transaction_id || response.id || 'N/A';
var amount = response.amount || config.amount;
paymentSuccessData = {
type: 'success',
data: response,
transaction_id: transactionId,
timestamp: new Date().toISOString()
};
showSuccess(transactionId, amount);
sendMessageToFlutter(paymentSuccessData);
})
.catch(function(error) {
console.error('❌ Payment error:', error);
paymentInitialized = false;
showError(error.message || 'Erreur lors du paiement');
sendMessageToFlutter({
type: 'error',
message: error.message || 'Unknown error',
code: 'PAYMENT_ERROR',
timestamp: new Date().toISOString()
});
});
} catch (error) {
console.error('❌ Init error:', error);
paymentInitialized = false;
showError(error.message);
sendMessageToFlutter({
type: 'error',
message: error.message,
code: 'INIT_ERROR',
timestamp: new Date().toISOString()
});
}
}
function retryPayment() {
console.log('🔄 Retrying payment...');
paymentInitialized = false;
document.getElementById('error-container').style.display = 'none';
showLoading();
setTimeout(initIntramPayment, 500);
}
window.onerror = function(message, source, lineno, colno, error) {
console.error('đź’Ą Global error:', { message, source, lineno, colno, error });
sendMessageToFlutter({
type: 'error',
message: message.toString(),
code: 'GLOBAL_ERROR',
timestamp: new Date().toISOString()
});
return false;
};
function loadIntramSDK() {
var script = document.createElement('script');
script.src = 'https://cdn.intram.org/sdk-javascript.js';
script.async = true;
script.onload = function() {
console.log('âś… Intram SDK loaded');
sdkLoaded = true;
sendMessageToFlutter({
type: 'ready',
message: 'SDK loaded successfully',
timestamp: new Date().toISOString()
});
setTimeout(initIntramPayment, 500);
};
script.onerror = function() {
console.error('❌ Failed to load Intram SDK');
showError('Impossible de charger le SDK Intram. Vérifiez votre connexion Internet.');
sendMessageToFlutter({
type: 'error',
message: 'Failed to load Intram SDK from CDN',
code: 'SDK_LOAD_ERROR',
timestamp: new Date().toISOString()
});
};
document.body.appendChild(script);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
loadIntramSDK();
});
} else {
loadIntramSDK();
}
</script>
</body>
</html>
''';
}