// HelcimPay.js integration composable export const useHelcimPay = () => { let checkoutToken = null; let secretToken = null; // Initialize HelcimPay.js session const initializeHelcimPay = async (customerId, customerCode, amount = 0) => { try { const response = await $fetch("/api/helcim/initialize-payment", { method: "POST", body: { customerId, customerCode, amount, }, }); if (response.success) { checkoutToken = response.checkoutToken; secretToken = response.secretToken; return true; } throw new Error("Failed to initialize payment session"); } catch (error) { console.error("Payment initialization error:", error); throw error; } }; // Initialize payment for event ticket purchase const initializeTicketPayment = async ( eventId, email, amount, eventTitle = null, ) => { try { const response = await $fetch("/api/helcim/initialize-payment", { method: "POST", body: { customerId: null, customerCode: email, // Use email as customer code for event tickets amount, metadata: { type: "event_ticket", eventId, email, eventTitle, }, }, }); if (response.success) { checkoutToken = response.checkoutToken; secretToken = response.secretToken; return { success: true, checkoutToken: response.checkoutToken, }; } throw new Error("Failed to initialize ticket payment session"); } catch (error) { console.error("Ticket payment initialization error:", error); throw error; } }; // Show payment modal const showPaymentModal = () => { return new Promise((resolve, reject) => { if (!checkoutToken) { reject( new Error("Payment not initialized. Call initializeHelcimPay first."), ); return; } // Add CSS to style Helcim overlay - needs to target the actual elements Helcim creates // Helcim injects a div with inline styles directly into body if (!document.getElementById("helcim-overlay-fix")) { const style = document.createElement("style"); style.id = "helcim-overlay-fix"; style.textContent = ` /* Style any fixed position div that Helcim creates */ body > div[style] { background-color: rgba(0, 0, 0, 0.75) !important; } /* Target specifically iframes from Helcim */ body > div[style] > iframe[src*="helcim"], body > div[style] iframe[src*="secure.helcim.app"] { background: white !important; border: none !important; border-radius: 8px !important; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3) !important; } `; document.head.appendChild(style); } // Load HelcimPay.js modal script if (!window.appendHelcimPayIframe) { console.log("HelcimPay script not loaded, loading now..."); const script = document.createElement("script"); script.src = "https://secure.helcim.app/helcim-pay/services/start.js"; script.async = true; script.onload = () => { console.log("HelcimPay script loaded successfully!"); console.log( "Available functions:", Object.keys(window).filter( (key) => key.includes("Helcim") || key.includes("helcim"), ), ); console.log( "appendHelcimPayIframe available:", typeof window.appendHelcimPayIframe, ); openModal(resolve, reject); }; script.onerror = () => { reject(new Error("Failed to load HelcimPay.js")); }; document.head.appendChild(script); } else { console.log("HelcimPay script already loaded, calling openModal"); openModal(resolve, reject); } }); }; // Open the payment modal const openModal = (resolve, reject) => { try { console.log("Trying to open modal with checkoutToken:", checkoutToken); if (typeof window.appendHelcimPayIframe === "function") { // Set up event listener for HelcimPay.js responses const helcimPayJsIdentifierKey = "helcim-pay-js-" + checkoutToken; const handleHelcimPayEvent = (event) => { console.log("Received window message:", event.data); if (event.data.eventName === helcimPayJsIdentifierKey) { console.log("HelcimPay event received:", event.data); // Remove event listener to prevent multiple responses window.removeEventListener("message", handleHelcimPayEvent); // Close the Helcim modal if (typeof window.removeHelcimPayIframe === "function") { window.removeHelcimPayIframe(); } if (event.data.eventStatus === "SUCCESS") { console.log("Payment success:", event.data.eventMessage); // Parse the JSON string eventMessage let paymentData; try { paymentData = JSON.parse(event.data.eventMessage); console.log("Parsed payment data:", paymentData); } catch (parseError) { console.error("Failed to parse eventMessage:", parseError); reject(new Error("Invalid payment response format")); return; } // Extract transaction details from nested data structure const transactionData = paymentData.data?.data || {}; console.log("Transaction data:", transactionData); resolve({ success: true, transactionId: transactionData.transactionId, cardToken: transactionData.cardToken, cardLast4: transactionData.cardNumber ? transactionData.cardNumber.slice(-4) : undefined, cardType: transactionData.cardType || "unknown", }); } else if (event.data.eventStatus === "ABORTED") { console.log("Payment aborted:", event.data.eventMessage); reject(new Error(event.data.eventMessage || "Payment failed")); } else if (event.data.eventStatus === "HIDE") { console.log("Modal closed without completion"); reject(new Error("Payment cancelled by user")); } } }; // Add event listener window.addEventListener("message", handleHelcimPayEvent); // Set up a MutationObserver to fix Helcim's overlay styling immediately const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1 && node.tagName === "DIV") { const computedStyle = window.getComputedStyle(node); // Check if this is Helcim's overlay (fixed position, full screen) if ( computedStyle.position === "fixed" && computedStyle.inset === "0px" ) { // Fix the background to show semi-transparent overlay node.style.setProperty( "background-color", "rgba(0, 0, 0, 0.75)", "important", ); // Ensure proper centering node.style.setProperty("display", "flex", "important"); node.style.setProperty("align-items", "center", "important"); node.style.setProperty( "justify-content", "center", "important", ); observer.disconnect(); // Stop observing once we've found and fixed it } } }); }); }); // Start observing body for child additions observer.observe(document.body, { childList: true }); // Open the HelcimPay iframe modal console.log("Calling appendHelcimPayIframe with token:", checkoutToken); window.appendHelcimPayIframe(checkoutToken, true); console.log( "appendHelcimPayIframe called, waiting for window messages...", ); // Clean up observer after a timeout setTimeout(() => observer.disconnect(), 5000); // Add timeout to clean up if no response setTimeout(() => { console.log("60 seconds passed, cleaning up event listener..."); window.removeEventListener("message", handleHelcimPayEvent); reject(new Error("Payment timeout - no response received")); }, 60000); } else { reject(new Error("appendHelcimPayIframe function not available")); } } catch (error) { console.error("Error opening modal:", error); reject(error); } }; // Process payment verification const verifyPayment = async () => { try { return await showPaymentModal(); } catch (error) { throw error; } }; // Cleanup tokens const cleanup = () => { checkoutToken = null; secretToken = null; }; return { initializeHelcimPay, initializeTicketPayment, verifyPayment, cleanup, }; };