getHtml static method

String getHtml({
  1. required String publicKey,
  2. required int amount,
  3. required bool sandbox,
  4. String currency = 'xof',
  5. required String companyName,
  6. String? color,
  7. String? logoUrl,
  8. String? callbackUrl,
  9. String? inlineSdkScript,
})

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,
  String? inlineSdkScript,
}) {
  final cleanColor = color?.replaceAll('#', '') ?? 'green';
  final safeCompanyName = _escapeHtml(companyName);
  final safeLogoUrl = _escapeHtml(logoUrl ?? '');
  final safeCallbackUrl = _escapeHtml(callbackUrl ?? '');
  // Wrapped in a <script> tag so it can be dropped directly into <head>.
  final sdkBlock =
      inlineSdkScript != null ? '<script>\n$inlineSdkScript\n</script>' : '';

  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>
  $sdkBlock
  <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;
      }
  </style>
</head>
<body>

  <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="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 paymentDone = false;
      var modalObserver = null;
      var capturedModal = null;

      function startModalObserver() {
          var knownNodes = new Set(Array.from(document.body.children));

          modalObserver = new MutationObserver(function(mutations) {
              mutations.forEach(function(mutation) {
                  mutation.addedNodes.forEach(function(node) {
                      if (node.nodeType === 1 && !knownNodes.has(node)) {
                          capturedModal = node;
                      }
                  });
                  mutation.removedNodes.forEach(function(node) {
                      if (node === capturedModal && !paymentDone) {
                          modalObserver.disconnect();
                          sendMessageToFlutter({
                              type: 'close_success',
                              cancelled: true,
                              timestamp: new Date().toISOString()
                          });
                      }
                  });
              });
          });

          modalObserver.observe(document.body, { childList: true });
      }

      function showLoading() {
          document.getElementById('loading').style.display = 'block';
          document.getElementById('error-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('error-container').style.display = 'block';
          document.getElementById('error-message').textContent = message || 'Une erreur est survenue';
      }

      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...'
              });

              startModalObserver();

              intramOpenWidget.init(config)
                  .then(function(response) {
                      paymentDone = true;
                      if (modalObserver) modalObserver.disconnect();
                      console.log(response, '****** responses');

                      var transactionId = response.transactionId || response.transaction_id || response.id || 'N/A';

                      sendMessageToFlutter({
                          type: 'close_success',
                          data: response,
                          transaction_id: transactionId,
                          timestamp: new Date().toISOString()
                      });
                  })
                  .catch(function(error) {
                      paymentDone = true;
                      if (modalObserver) modalObserver.disconnect();
                      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() {
          // SDK may have been inlined by Flutter (pre-fetched to avoid WebView SSL issues).
          if (typeof intramOpenWidget !== 'undefined') {
              console.log('✅ Intram SDK loaded (inline)');
              sdkLoaded = true;
              sendMessageToFlutter({
                  type: 'ready',
                  message: 'SDK loaded successfully',
                  timestamp: new Date().toISOString()
              });
              setTimeout(initIntramPayment, 300);
              return;
          }

          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>
  ''';
}