UNPKG

storenest-commerce

Version:

Complete e-commerce SDK for Storenest platform with React components, multi-language support, secure checkout, and enterprise-grade security

823 lines (798 loc) 32.9 kB
"use strict"; // Simple CheckoutBox that doesn't rely on React hooks // This can be used as a fallback or in environments where React is not available Object.defineProperty(exports, "__esModule", { value: true }); exports.SimpleCheckoutBox = void 0; const utils_1 = require("../utils"); // Localization strings const translations = { hr: { loading: 'Učitavanje sigurnog naplatnog sustava...', secureCheckout: 'Sigurni naplatni sustav', shipping: 'Dostava', payment: 'Plaćanje', review: 'Pregled', shippingInformation: 'Informacije o dostavi', firstName: 'Ime *', lastName: 'Prezime *', emailAddress: 'Email adresa *', address: 'Adresa *', city: 'Grad *', zipCode: 'Poštanski broj *', country: 'Država *', croatia: 'Hrvatska', shippingMethod: 'Način dostave', paymentMethod: 'Način plaćanja', orderReview: 'Pregled narudžbe', orderSummary: 'Sažetak narudžbe', subtotal: 'Međuzbroj', shippingCost: 'Dostava', tax: 'Porez', total: 'Ukupno', previous: '← Prethodno', continue: 'Nastavi →', placeOrder: 'Naruči', processing: 'Obrađujem...', secureCheckoutPoweredBy: 'Sigurni naplatni sustav pokretan od Storenest', paymentInfoEncrypted: 'Vaši podaci o plaćanju su šifrirani i sigurni', loadingPaymentMethods: 'Učitavanje načina plaćanja...', notSelected: 'Nije odabrano', shippingInfo: 'Informacije o dostavi', paymentInfo: 'Informacije o plaćanju' }, en: { loading: 'Loading secure checkout...', secureCheckout: 'Secure Checkout', shipping: 'Shipping', payment: 'Payment', review: 'Review', shippingInformation: 'Shipping Information', firstName: 'First Name *', lastName: 'Last Name *', emailAddress: 'Email Address *', address: 'Address *', city: 'City *', zipCode: 'Zip Code *', country: 'Country *', croatia: 'Croatia', shippingMethod: 'Shipping Method', paymentMethod: 'Payment Method', orderReview: 'Order Review', orderSummary: 'Order Summary', subtotal: 'Subtotal', shippingCost: 'Shipping', tax: 'Tax', total: 'Total', previous: '← Previous', continue: 'Continue →', placeOrder: 'Place Order', processing: 'Processing...', secureCheckoutPoweredBy: 'Secure checkout powered by Storenest', paymentInfoEncrypted: 'Your payment information is encrypted and secure', loadingPaymentMethods: 'Loading payment methods...', notSelected: 'Not selected', shippingInfo: 'Shipping Information', paymentInfo: 'Payment Method' }, de: { loading: 'Sichere Kasse wird geladen...', secureCheckout: 'Sichere Kasse', shipping: 'Versand', payment: 'Zahlung', review: 'Überprüfung', shippingInformation: 'Versandinformationen', firstName: 'Vorname *', lastName: 'Nachname *', emailAddress: 'E-Mail-Adresse *', address: 'Adresse *', city: 'Stadt *', zipCode: 'PLZ *', country: 'Land *', croatia: 'Kroatien', shippingMethod: 'Versandart', paymentMethod: 'Zahlungsart', orderReview: 'Bestellübersicht', orderSummary: 'Bestellübersicht', subtotal: 'Zwischensumme', shippingCost: 'Versand', tax: 'Steuer', total: 'Gesamt', previous: '← Zurück', continue: 'Weiter →', placeOrder: 'Bestellung aufgeben', processing: 'Wird verarbeitet...', secureCheckoutPoweredBy: 'Sichere Kasse von Storenest', paymentInfoEncrypted: 'Ihre Zahlungsinformationen sind verschlüsselt und sicher', loadingPaymentMethods: 'Zahlungsarten werden geladen...', notSelected: 'Nicht ausgewählt', shippingInfo: 'Versandinformationen', paymentInfo: 'Zahlungsart' } }; class SimpleCheckoutBox { constructor(config = {}, callbacks = {}) { this.container = null; this.config = { theme: 'modern', currency: 'EUR', locale: 'en', showOrderSummary: true, enableGuestCheckout: true, ...config }; this.callbacks = callbacks; } // Get translations based on locale getTranslations() { const locale = this.config.locale || 'en'; return translations[locale] || translations.en; } // Render the checkout form into a container render(containerId) { const container = document.getElementById(containerId); if (!container) { if ((0, utils_1.getDebugMode)()) { console.error(`Container with id "${containerId}" not found`); } return; } const t = this.getTranslations(); // Clear container container.innerHTML = ''; // Create checkout container const checkoutContainer = document.createElement('div'); checkoutContainer.style.cssText = ` max-width: 900px; margin: 0 auto; background: #fff; border-radius: 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; `; // Header const header = document.createElement('div'); header.style.cssText = ` background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 32px; text-align: center; color: white; `; header.innerHTML = ` <div style="font-size: 28px; font-weight: 700; margin-bottom: 8px;">Storenest</div> <div style="font-size: 16px; opacity: 0.9; margin-bottom: 24px;">${t.secureCheckout}</div> <div style="display: flex; justify-content: center; gap: 32px;"> <div style="display: flex; align-items: center; gap: 8px;"> <div style="width: 36px; height: 36px; border-radius: 50%; background: rgba(255,255,255,0.2); border: 2px solid rgba(255,255,255,0.8); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 16px;">1</div> <span style="font-size: 14px; font-weight: 500;">${t.shipping}</span> </div> <div style="display: flex; align-items: center; gap: 8px;"> <div style="width: 36px; height: 36px; border-radius: 50%; background: rgba(255,255,255,0.1); border: 2px solid rgba(255,255,255,0.3); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 16px;">2</div> <span style="font-size: 14px; font-weight: 500;">${t.payment}</span> </div> <div style="display: flex; align-items: center; gap: 8px;"> <div style="width: 36px; height: 36px; border-radius: 50%; background: rgba(255,255,255,0.1); border: 2px solid rgba(255,255,255,0.3); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 16px;">3</div> <span style="font-size: 14px; font-weight: 500;">${t.review}</span> </div> </div> `; // Main content const mainContent = document.createElement('div'); mainContent.style.cssText = ` display: flex; min-height: 600px; `; // Form section const formSection = document.createElement('div'); formSection.style.cssText = ` flex: 1; padding: 40px; `; formSection.innerHTML = ` <h3 style="margin: 0 0 32px 0; color: #2d3748; font-size: 24px; font-weight: 600;">${t.shippingInformation}</h3> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 32px;"> <div style="min-width: 0;"> <label style="display: block; margin-bottom: 12px; font-weight: 600; color: #4a5568;">${t.firstName}</label> <input type="text" id="firstName" placeholder="First name" style="width: 100%; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 16px; transition: all 0.2s; outline: none; box-sizing: border-box;"> </div> <div style="min-width: 0;"> <label style="display: block; margin-bottom: 12px; font-weight: 600; color: #4a5568;">${t.lastName}</label> <input type="text" id="lastName" placeholder="Last name" style="width: 100%; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 16px; transition: all 0.2s; outline: none; box-sizing: border-box;"> </div> </div> <div style="margin-bottom: 32px;"> <label style="display: block; margin-bottom: 12px; font-weight: 600; color: #4a5568;">${t.emailAddress}</label> <input type="email" id="email" placeholder="your@email.com" style="width: 100%; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 16px; transition: all 0.2s; outline: none; box-sizing: border-box;"> </div> <div style="margin-bottom: 32px;"> <label style="display: block; margin-bottom: 12px; font-weight: 600; color: #4a5568;">${t.address}</label> <input type="text" id="address" placeholder="Street address" style="width: 100%; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 16px; transition: all 0.2s; outline: none; box-sizing: border-box;"> </div> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 32px;"> <div style="min-width: 0;"> <label style="display: block; margin-bottom: 12px; font-weight: 600; color: #4a5568;">${t.city}</label> <input type="text" id="city" placeholder="City" style="width: 100%; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 16px; transition: all 0.2s; outline: none; box-sizing: border-box;"> </div> <div style="min-width: 0;"> <label style="display: block; margin-bottom: 12px; font-weight: 600; color: #4a5568;">${t.zipCode}</label> <input type="text" id="zipCode" placeholder="Zip code" style="width: 100%; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 16px; transition: all 0.2s; outline: none; box-sizing: border-box;"> </div> </div> <div style="margin-bottom: 32px;"> <label style="display: block; margin-bottom: 12px; font-weight: 600; color: #4a5568;">${t.country}</label> <select id="country" style="width: 100%; padding: 16px; border: 2px solid #e2e8f0; border-radius: 12px; font-size: 16px; transition: all 0.2s; outline: none; background: white;"> <option value="HR">${t.croatia}</option> <option value="US">United States</option> <option value="CA">Canada</option> <option value="GB">United Kingdom</option> <option value="DE">Germany</option> <option value="FR">France</option> </select> </div> <div style="margin-top: 40px; display: flex; justify-content: flex-end;"> <button id="submitBtn" style="padding: 16px 40px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; border: none; border-radius: 12px; cursor: pointer; font-size: 16px; font-weight: 600; transition: all 0.2s;"> ${t.placeOrder} </button> </div> `; // Order summary sidebar const orderSummary = document.createElement('div'); orderSummary.style.cssText = ` width: 320px; background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%); padding: 32px; border-left: 1px solid #e2e8f0; `; orderSummary.innerHTML = ` <div style="text-align: center; margin-bottom: 32px;"> <div style="font-size: 24px; font-weight: 700; color: #2d3748; margin-bottom: 8px;">Storenest</div> <div style="font-size: 14px; color: #718096;">${t.secureCheckout}</div> </div> <h3 style="margin: 0 0 24px 0; color: #2d3748; font-size: 20px; font-weight: 600;">${t.orderSummary}</h3> <div style="margin-bottom: 32px;"> <div style="display: flex; justify-content: space-between; margin-bottom: 12px; color: #4a5568;"> <span>${t.subtotal}</span> <span>€80.00</span> </div> <div style="display: flex; justify-content: space-between; margin-bottom: 12px; color: #4a5568;"> <span>${t.shippingCost}</span> <span>€5.99</span> </div> <div style="display: flex; justify-content: space-between; margin-bottom: 12px; color: #4a5568;"> <span>${t.tax}</span> <span>€6.40</span> </div> <div style="display: flex; justify-content: space-between; padding-top: 16px; border-top: 2px solid #e2e8f0; font-weight: 700; font-size: 18px; color: #2d3748;"> <span>${t.total}</span> <span>€92.39</span> </div> </div> <div style="font-size: 14px; color: #718096; line-height: 1.6;"> <div style="margin-bottom: 12px; display: flex; align-items: center; gap: 8px;"> <span style="font-size: 16px;">🔒</span> <span>${t.secureCheckoutPoweredBy}</span> </div> <div style="display: flex; align-items: center; gap: 8px;"> <span style="font-size: 16px;">🛡️</span> <span>${t.paymentInfoEncrypted}</span> </div> </div> `; // Assemble the checkout mainContent.appendChild(formSection); mainContent.appendChild(orderSummary); checkoutContainer.appendChild(header); checkoutContainer.appendChild(mainContent); container.appendChild(checkoutContainer); // Add event listeners this.addEventListeners(); } addEventListeners() { const submitBtn = document.getElementById('submitBtn'); if (submitBtn) { submitBtn.addEventListener('click', (e) => this.handleSubmit(e)); } // Add input focus effects const inputs = document.querySelectorAll('input, select'); inputs.forEach(input => { input.addEventListener('focus', (e) => { e.target.style.borderColor = '#667eea'; }); input.addEventListener('blur', (e) => { e.target.style.borderColor = '#e2e8f0'; }); }); } // Render the checkout form renderCheckoutForm() { if (!this.container) return; const theme = this.config.theme || 'modern'; const currency = this.config.currency || 'EUR'; this.container.innerHTML = ` <div style=" max-width: 800px; margin: 0 auto; background: #fff; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; "> <!-- Header --> <div style=" background: ${theme === 'modern' ? '#0070f3' : '#f8f9fa'}; padding: 24px; border-bottom: 1px solid #e1e5e9; "> <h2 style=" margin: 0; color: ${theme === 'modern' ? '#fff' : '#333'}; font-size: 24px; font-weight: 600; "> Secure Checkout </h2> <div style=" display: flex; justify-content: space-between; margin-top: 16px; "> <div style=" display: flex; align-items: center; color: ${theme === 'modern' ? '#fff' : '#666'}; "> <div style=" width: 32px; height: 32px; border-radius: 50%; background: #fff; color: #0070f3; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; ">1</div> <span style="margin-left: 8px; font-size: 14px;">Shipping</span> </div> <div style=" display: flex; align-items: center; color: ${theme === 'modern' ? '#fff' : '#666'}; "> <div style=" width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.3); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; ">2</div> <span style="margin-left: 8px; font-size: 14px;">Payment</span> </div> <div style=" display: flex; align-items: center; color: ${theme === 'modern' ? '#fff' : '#666'}; "> <div style=" width: 32px; height: 32px; border-radius: 50%; background: rgba(255,255,255,0.3); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; ">3</div> <span style="margin-left: 8px; font-size: 14px;">Review</span> </div> </div> </div> <div style="display: flex;"> <!-- Main Content --> <div style="flex: 1; padding: 32px;"> <div> <h3 style="margin: 0 0 24px 0; color: #333;">Shipping Information</h3> <form id="checkout-form"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;"> <div> <label style="display: block; margin-bottom: 8px; font-weight: 500;"> First Name * </label> <input type="text" name="firstName" required style=" width: 100%; padding: 12px; border: 1px solid #dee2e6; border-radius: 6px; font-size: 16px; box-sizing: border-box; " placeholder="First name" /> </div> <div> <label style="display: block; margin-bottom: 8px; font-weight: 500;"> Last Name * </label> <input type="text" name="lastName" required style=" width: 100%; padding: 12px; border: 1px solid #dee2e6; border-radius: 6px; font-size: 16px; box-sizing: border-box; " placeholder="Last name" /> </div> </div> <div style="margin-bottom: 24px;"> <label style="display: block; margin-bottom: 8px; font-weight: 500;"> Email Address * </label> <input type="email" name="email" required style=" width: 100%; padding: 12px; border: 1px solid #dee2e6; border-radius: 6px; font-size: 16px; box-sizing: border-box; " placeholder="your@email.com" /> </div> <div style="margin-bottom: 24px;"> <label style="display: block; margin-bottom: 8px; font-weight: 500;"> Address * </label> <input type="text" name="address" required style=" width: 100%; padding: 12px; border: 1px solid #dee2e6; border-radius: 6px; font-size: 16px; box-sizing: border-box; " placeholder="Street address" /> </div> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px;"> <div> <label style="display: block; margin-bottom: 8px; font-weight: 500;"> City * </label> <input type="text" name="city" required style=" width: 100%; padding: 12px; border: 1px solid #dee2e6; border-radius: 6px; font-size: 16px; box-sizing: border-box; " placeholder="City" /> </div> <div> <label style="display: block; margin-bottom: 8px; font-weight: 500;"> Zip Code * </label> <input type="text" name="zipCode" required style=" width: 100%; padding: 12px; border: 1px solid #dee2e6; border-radius: 6px; font-size: 16px; box-sizing: border-box; " placeholder="Zip code" /> </div> </div> <div style="margin-bottom: 24px;"> <label style="display: block; margin-bottom: 8px; font-weight: 500;"> Country * </label> <select name="country" required style=" width: 100%; padding: 12px; border: 1px solid #dee2e6; border-radius: 6px; font-size: 16px; box-sizing: border-box; " > <option value="US">United States</option> <option value="CA">Canada</option> <option value="GB">United Kingdom</option> <option value="DE">Germany</option> <option value="FR">France</option> </select> </div> <!-- Shipping Methods --> <div style="margin-bottom: 24px;"> <h4 style="margin: 0 0 16px 0; color: #333;">Shipping Method</h4> <div> <label style=" display: flex; align-items: center; padding: 16px; border: 1px solid #0070f3; border-radius: 6px; margin-bottom: 8px; cursor: pointer; background: #f0f8ff; "> <input type="radio" name="shippingMethod" value="standard" checked style="margin-right: 12px;" /> <div style="flex: 1;"> <div style="font-weight: 500;">Standard Shipping</div> <div style="color: #666; font-size: 14px;"> 3-5 business days • ${this.formatPrice(5.99)} </div> </div> </label> </div> </div> <!-- Payment Methods --> <div style="margin-bottom: 24px;"> <h4 style="margin: 0 0 16px 0; color: #333;">Payment Method</h4> <div> <label style=" display: flex; align-items: center; padding: 16px; border: 1px solid #0070f3; border-radius: 6px; margin-bottom: 8px; cursor: pointer; background: #f0f8ff; "> <input type="radio" name="paymentMethod" value="card" checked style="margin-right: 12px;" /> <div style="flex: 1;"> <div style="font-weight: 500;">Credit / Debit Card</div> <div style="color: #666; font-size: 14px;"> Pay with Visa, Mastercard, American Express </div> </div> </label> </div> </div> <!-- Submit Button --> <div style="display: flex; justify-content: space-between; margin-top: 32px;"> <button type="button" onclick="window.history.back()" style=" padding: 12px 24px; border: 1px solid #dee2e6; background: #fff; border-radius: 6px; cursor: pointer; font-size: 16px; " > ← Back </button> <button type="submit" style=" padding: 12px 32px; background: #0070f3; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; font-weight: 500; " > Place Order </button> </div> </form> </div> </div> <!-- Order Summary Sidebar --> ${this.config.showOrderSummary ? ` <div style=" width: 300px; background: #f8f9fa; padding: 24px; border-left: 1px solid #e1e5e9; "> <h3 style="margin: 0 0 24px 0; color: #333;">Order Summary</h3> <div style="margin-bottom: 24px;"> <div style="display: flex; justify-content: space-between; margin-bottom: 8px;"> <span>Subtotal</span> <span>${this.formatPrice(80)}</span> </div> <div style="display: flex; justify-content: space-between; margin-bottom: 8px;"> <span>Shipping</span> <span>${this.formatPrice(5.99)}</span> </div> <div style="display: flex; justify-content: space-between; margin-bottom: 8px;"> <span>Tax</span> <span>${this.formatPrice(6.88)}</span> </div> <div style=" display: flex; justify-content: space-between; padding-top: 16px; border-top: 1px solid #dee2e6; font-weight: 600; font-size: 18px; "> <span>Total</span> <span>${this.formatPrice(92.87)}</span> </div> </div> <div style="font-size: 14px; color: #666; line-height: 1.5;"> <div style="margin-bottom: 8px;">🔒 Secure checkout powered by Storenest</div> <div>Your payment information is encrypted and secure</div> </div> </div> ` : ''} </div> </div> `; // Add form submission handler const form = this.container.querySelector('#checkout-form'); if (form) { form.addEventListener('submit', this.handleSubmit.bind(this)); } } // Handle form submission handleSubmit(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); // Validate form if (!this.validateForm(formData)) { return; } // Simulate order processing this.processOrder(formData); } // Validate form data validateForm(formData) { const requiredFields = ['firstName', 'lastName', 'email', 'address', 'city', 'zipCode', 'country']; for (const field of requiredFields) { if (!formData.get(field)) { alert(`Please fill in ${field.replace(/([A-Z])/g, ' $1').toLowerCase()}`); return false; } } const email = formData.get('email'); if (!/\S+@\S+\.\S+/.test(email)) { alert('Please enter a valid email address'); return false; } return true; } // Process the order async processOrder(formData) { try { // Show loading state const submitButton = this.container?.querySelector('button[type="submit"]'); if (submitButton) { submitButton.textContent = 'Processing...'; submitButton.disabled = true; } // Simulate API call await new Promise(resolve => setTimeout(resolve, 2000)); // Generate order ID const orderId = `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Call success callback this.callbacks.onSuccess?.(orderId); // Show success message if (this.container) { this.container.innerHTML = ` <div style=" text-align: center; padding: 48px; background: #fff; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); "> <div style="font-size: 48px; margin-bottom: 24px;"></div> <h2 style="margin: 0 0 16px 0; color: #333;">Order Placed Successfully!</h2> <p style="margin: 0 0 24px 0; color: #666;"> Your order has been placed and is being processed. </p> <div style=" background: #f8f9fa; padding: 16px; border-radius: 8px; margin-bottom: 24px; "> <strong>Order ID:</strong> ${orderId} </div> <button onclick="window.location.href='/'" style=" padding: 12px 24px; background: #0070f3; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; " > Continue Shopping </button> </div> `; } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'An error occurred'; this.callbacks.onError?.(errorMessage); alert(`Checkout error: ${errorMessage}`); } } // Format price formatPrice(amount) { return new Intl.NumberFormat(this.config.locale || 'en-US', { style: 'currency', currency: this.config.currency || 'EUR' }).format(amount); } // Destroy the component destroy() { if (this.container) { this.container.innerHTML = ''; this.container = null; } } } exports.SimpleCheckoutBox = SimpleCheckoutBox; // Export for use exports.default = SimpleCheckoutBox;