/*! * Younium Self Service Embedded SDK * Version: 3.0.0 * (c) 2025 Younium * Released under the MIT License */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.YouniumEmbedded = {})); })(this, (function (exports) { 'use strict'; /** * SDK Configuration and Defaults */ const DEFAULT_THEME = { primaryColor: '#DB7013', fontFamily: 'Roboto, sans-serif', borderRadius: '8px', buttonStyle: 'rounded', colorMode: 'light', }; const DEFAULT_CONFIG = { apiEndpoint: 'https://api.selfservice.younium.com', debug: false, locale: 'en-US', currency: 'USD', refreshThreshold: 300, // 5 minutes before expiry retryAttempts: 3, retryDelay: 1000, theme: DEFAULT_THEME, }; const COMPONENT_TYPES = { INVOICE_LIST: 'invoice-list', ACCOUNT_INFO: 'account-info', SUBSCRIPTION_LIST: 'subscription-list', }; const SDK_VERSION = '3.0.0'; /** * API Client for Self Service API communication */ class ApiClient { constructor(config) { this.token = null; this.config = config; } setToken(token) { this.token = token; } getToken() { return this.token; } updateConfig(config) { this.config = { ...this.config, ...config }; } /** * Make an API call with retry logic and error handling */ async call(options) { const { method, endpoint, body, retryCount = 0 } = options; const url = `${this.config.apiEndpoint}${endpoint}`; const fetchOptions = { method, headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', 'X-Younium-Embedded': 'true', }, }; if (body) { fetchOptions.body = JSON.stringify(body); } try { if (this.config.debug) { console.log('[YouniumSDK] API call:', method, url, body); } const response = await fetch(url, fetchOptions); if (!response.ok) { await this.handleErrorResponse(response); } const data = await response.json(); if (this.config.debug) { console.log('[YouniumSDK] API response:', data); } return data; } catch (error) { // Retry logic with exponential backoff if (retryCount < this.config.retryAttempts && this.shouldRetry(error)) { const delay = this.config.retryDelay * Math.pow(2, retryCount); if (this.config.debug) { console.log(`[YouniumSDK] Retrying API call in ${delay}ms (attempt ${retryCount + 1})`); } await this.sleep(delay); return this.call({ ...options, retryCount: retryCount + 1 }); } throw error; } } async handleErrorResponse(response) { if (response.status === 401) { // Token expired if (this.config.onTokenExpired) { this.config.onTokenExpired(); } throw new Error('Token expired'); } if (response.status === 403) { try { const errorData = await response.json(); if (errorData.errorCode === 'INSUFFICIENT_SCOPE') { throw new Error(`Access denied: ${errorData.errorMessage}`); } } catch { // If we can't parse the error, fall back to generic message } throw new Error('Access denied. Please check your API credentials and permissions.'); } throw new Error(`API error: ${response.status} ${response.statusText}`); } shouldRetry(error) { // Don't retry auth errors or permission errors if (error instanceof Error) { const message = error.message.toLowerCase(); if (message.includes('token expired') || message.includes('access denied') || message.includes('401') || message.includes('403')) { return false; } } return true; } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * JWT Token Management */ class TokenManager { constructor(config) { this.tokenExpiry = null; this.refreshTimer = null; this.config = config; } updateConfig(config) { this.config = config; } /** * Parse a JWT token and extract expiry time */ parseToken(token) { try { const parts = token.split('.'); if (parts.length !== 3) { return { expiry: null, payload: null }; } const payload = JSON.parse(atob(parts[1])); const expiry = payload.exp ? payload.exp * 1000 : null; return { expiry, payload }; } catch (e) { console.error('[YouniumSDK] Failed to parse token:', e); return { expiry: null, payload: null }; } } /** * Set the current token and set up refresh timer */ setToken(token) { const { expiry } = this.parseToken(token); this.tokenExpiry = expiry; if (this.config.debug && expiry) { console.log('[YouniumSDK] Token set, expires at:', new Date(expiry)); } // Clear existing timer this.clearRefreshTimer(); // Set up new refresh timer this.setupRefreshTimer(); } /** * Check if the token is expired or about to expire */ isTokenExpired() { if (!this.tokenExpiry) { return false; // Can't determine, assume valid } const now = Date.now(); const buffer = this.config.refreshThreshold * 1000; return now >= (this.tokenExpiry - buffer); } /** * Get the token expiry timestamp */ getExpiry() { return this.tokenExpiry; } /** * Set up automatic token refresh before expiry */ setupRefreshTimer() { if (!this.tokenExpiry || !this.config.onTokenExpired) { return; } const now = Date.now(); const refreshTime = this.tokenExpiry - (this.config.refreshThreshold * 1000); const delay = refreshTime - now; if (delay > 0) { this.refreshTimer = setTimeout(() => { if (this.config.debug) { console.log('[YouniumSDK] Token about to expire, requesting refresh'); } if (this.config.onTokenExpired) { this.config.onTokenExpired(); } }, delay); } } /** * Clear the refresh timer */ clearRefreshTimer() { if (this.refreshTimer) { clearTimeout(this.refreshTimer); this.refreshTimer = null; } } /** * Clean up resources */ destroy() { this.clearRefreshTimer(); this.tokenExpiry = null; } } /** * Dynamic Style Generation */ const STYLE_ID = 'yem-styles'; /** * Get button border radius based on style */ function getButtonRadius(theme) { switch (theme.buttonStyle) { case 'pill': return '9999px'; case 'square': return '0px'; default: return theme.borderRadius; } } /** * Generate CSS styles based on theme configuration */ function generateStyles(theme) { const isDark = theme.colorMode === 'dark'; const buttonRadius = getButtonRadius(theme); return ` .yem-component.yem-component { font-family: ${theme.fontFamily}; color: ${isDark ? '#ffffff' : '#1a1a1a'} !important; line-height: 1.5; background: ${isDark ? '#1a1a1a' : '#ffffff'} !important; border-radius: ${theme.borderRadius}; box-shadow: 0 1px 3px rgba(0, 0, 0, ${isDark ? '0.3' : '0.1'}); overflow: hidden; } .yem-loading { display: flex; align-items: center; justify-content: center; padding: 40px; color: ${isDark ? '#999' : '#666'}; } .yem-spinner { width: 24px; height: 24px; border: 3px solid ${isDark ? '#333' : '#f3f3f3'}; border-top: 3px solid ${theme.primaryColor}; border-radius: 50%; animation: yem-spin 1s linear infinite; margin-right: 12px; } @keyframes yem-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .yem-error { padding: 20px; background-color: ${isDark ? '#4a1c1c' : '#fee'}; border-left: 4px solid ${isDark ? '#ff6b6b' : '#f44336'}; color: ${isDark ? '#ff6b6b' : '#c00'}; border-radius: ${theme.borderRadius}; } .yem-error-title { font-weight: 600; margin-bottom: 8px; } .yem-component .yem-header, .yem-header.yem-header { padding: 20px; border-bottom: 1px solid ${isDark ? '#333' : '#e0e0e0'}; position: relative; min-height: 60px; background-color: ${isDark ? '#1a1a1a' : '#ffffff'} !important; } .yem-component .yem-title, .yem-title.yem-title { font-size: 20px; font-weight: 600; color: ${isDark ? '#ffffff' : '#1a1a1a'} !important; margin: 0; opacity: 1 !important; } .yem-subtitle { font-size: 14px; color: ${isDark ? '#999999' : '#666666'} !important; margin: 0 !important; white-space: nowrap; flex-shrink: 0; opacity: 1 !important; } .yem-component .yem-content, .yem-content.yem-content { padding: 20px; background-color: ${isDark ? '#1a1a1a' : '#ffffff'} !important; } .yem-component .yem-search-bar, .yem-search-bar.yem-search-bar { margin-bottom: 20px; display: flex; gap: 10px; } .yem-component .yem-content .yem-search-bar input.yem-search-input, .yem-component input.yem-search-input, input.yem-search-input.yem-search-input { flex: 1; padding: 8px 12px; border: 1px solid ${isDark ? '#444' : '#ddd'} !important; border-radius: ${theme.borderRadius}; background: ${isDark ? '#2a2a2a' : '#fff'} !important; background-color: ${isDark ? '#2a2a2a' : '#fff'} !important; color: ${isDark ? '#fff' : '#333'} !important; font-family: ${theme.fontFamily}; } .yem-table { width: 100%; border-collapse: collapse; } .yem-table th { background-color: ${isDark ? '#2a2a2a' : '#f5f5f5'} !important; padding: 12px; text-align: left; font-weight: 600; font-size: 14px; color: ${isDark ? '#cccccc' : '#666666'} !important; border-bottom: 2px solid ${isDark ? '#444' : '#e0e0e0'}; opacity: 1 !important; } .yem-table td { padding: 12px; border-bottom: 1px solid ${isDark ? '#333' : '#f0f0f0'}; font-size: 14px; color: ${isDark ? '#dddddd' : '#333333'} !important; opacity: 1 !important; } .yem-table tbody tr:hover { background-color: ${isDark ? '#2a2a2a' : '#f9f9f9'}; } .yem-status { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; text-transform: uppercase; } .yem-status-paid, .yem-status-active { background-color: ${isDark ? '#1a4d2e' : '#d4edda'}; color: ${isDark ? '#4ade80' : '#155724'}; } .yem-status-posted, .yem-status-pending { background-color: ${isDark ? '#4d4a1a' : '#fff3cd'}; color: ${isDark ? '#fbbf24' : '#856404'}; } .yem-status-draft { background-color: ${isDark ? '#1a3d4d' : '#d1ecf1'}; color: ${isDark ? '#60a5fa' : '#0c5460'}; } .yem-status-overdue, .yem-status-cancelled, .yem-status-expired { background-color: ${isDark ? '#4d1a1a' : '#f8d7da'}; color: ${isDark ? '#f87171' : '#721c24'}; } .yem-button { display: inline-block; padding: 6px 12px; background-color: ${theme.primaryColor}; color: white; text-decoration: none; border-radius: ${buttonRadius}; font-size: 14px; border: none; cursor: pointer; transition: all 0.2s; font-family: ${theme.fontFamily}; } .yem-button:hover { opacity: 0.9; transform: translateY(-1px); } .yem-button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .yem-button-secondary { background-color: ${isDark ? '#4a5568' : '#6c757d'}; } .yem-button-secondary:hover { background-color: ${isDark ? '#2d3748' : '#545b62'}; } .yem-empty { padding: 60px 20px; text-align: center; color: ${isDark ? '#999' : '#999'}; } .yem-empty-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.3; } .yem-pagination { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-top: 1px solid ${isDark ? '#333' : '#e0e0e0'}; } .yem-pagination-info { color: ${isDark ? '#999' : '#666'}; font-size: 14px; } .yem-pagination-controls { display: flex; gap: 8px; } .yem-info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; } .yem-info-item { padding: 16px; background: ${isDark ? '#2a2a2a' : '#f9f9f9'} !important; border-radius: ${theme.borderRadius}; } .yem-info-label { font-size: 12px; color: ${isDark ? '#999999' : '#666666'} !important; text-transform: uppercase; margin-bottom: 4px; opacity: 1 !important; } .yem-info-value { font-size: 16px; color: ${isDark ? '#ffffff' : '#1a1a1a'} !important; font-weight: 500; opacity: 1 !important; } .yem-section { margin-bottom: 24px; } .yem-component .yem-section-title, .yem-section-title.yem-section-title { font-size: 16px; font-weight: 600; color: ${isDark ? '#ffffff' : '#1a1a1a'} !important; margin: 0 0 12px 0; opacity: 1 !important; } .yem-info-item-inline { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .yem-edit-btn { background: transparent; border: 1px solid ${theme.primaryColor}; color: ${theme.primaryColor}; padding: 6px 12px; border-radius: ${theme.borderRadius}; font-size: 12px; cursor: pointer; font-family: ${theme.fontFamily}; transition: all 0.2s ease; white-space: nowrap; } .yem-edit-btn:hover { background: ${theme.primaryColor}; color: white; } .yem-form-group { margin-bottom: 16px; } .yem-form-group-full { grid-column: 1 / -1; } .yem-form-label { display: block; font-size: 12px; font-weight: 500; color: ${isDark ? '#999999' : '#666666'} !important; margin-bottom: 4px; opacity: 1 !important; } .yem-component .yem-content input.yem-form-input, .yem-component input.yem-form-input, input.yem-form-input.yem-form-input { width: 100%; padding: 8px 12px; border: 1px solid ${isDark ? '#444' : '#ddd'} !important; border-radius: ${theme.borderRadius}; background: ${isDark ? '#2a2a2a' : '#fff'} !important; background-color: ${isDark ? '#2a2a2a' : '#fff'} !important; color: ${isDark ? '#fff' : '#333'} !important; font-family: ${theme.fontFamily}; font-size: 14px; box-sizing: border-box; } .yem-component .yem-content input.yem-form-input:focus, .yem-component input.yem-form-input:focus, input.yem-form-input.yem-form-input:focus { outline: none; border-color: ${theme.primaryColor} !important; box-shadow: 0 0 0 2px ${theme.primaryColor}20; } .yem-form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; } .yem-form-actions { display: flex; gap: 8px; margin-top: 16px; } .yem-btn { padding: 8px 16px; border-radius: ${buttonRadius}; font-family: ${theme.fontFamily}; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: none; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 8px; } .yem-btn-primary { background: ${theme.primaryColor}; color: white; } .yem-btn-primary:hover { opacity: 0.9; } .yem-btn-secondary { background: transparent; color: ${isDark ? '#999' : '#666'}; border: 1px solid ${isDark ? '#444' : '#ddd'}; } .yem-btn-secondary:hover { background: ${isDark ? '#333' : '#f5f5f5'}; } .yem-message { padding: 12px 16px; border-radius: ${theme.borderRadius}; margin-top: 16px; font-size: 14px; } .yem-message-success { background: ${isDark ? '#1a4d3a' : '#d4edda'}; color: ${isDark ? '#4caf50' : '#155724'}; border-left: 4px solid ${isDark ? '#4caf50' : '#4caf50'}; } .yem-message-error { background: ${isDark ? '#4a1c1c' : '#f8d7da'}; color: ${isDark ? '#ff6b6b' : '#721c24'}; border-left: 4px solid ${isDark ? '#ff6b6b' : '#f44336'}; } .yem-message-info { background: ${isDark ? '#1a3a4a' : '#d1ecf1'}; color: ${isDark ? '#29b6f6' : '#0c5460'}; border-left: 4px solid ${isDark ? '#29b6f6' : '#2196f3'}; } /* Subscription-specific styles */ .yem-subscription-card { border: 1px solid ${isDark ? '#333' : '#e5e5e5'}; border-radius: ${theme.borderRadius}; margin-bottom: 24px; overflow: hidden; background: ${isDark ? '#1f1f1f' : '#ffffff'}; } .yem-subscription-header { padding: 20px 24px; display: flex; justify-content: space-between; align-items: flex-start; border-bottom: none; } .yem-subscription-title { font-size: 20px; font-weight: 600; color: ${isDark ? '#ffffff' : '#1a1a1a'} !important; margin: 0; } .yem-subscription-meta { font-size: 14px; color: ${isDark ? '#999' : '#666'}; margin-top: 4px; } /* Products section */ .yem-subscription-products { padding: 0 24px 20px; } .yem-products-header { font-size: 14px; font-weight: 500; color: ${isDark ? '#999' : '#666'}; margin-bottom: 12px; } .yem-products-list { border: 1px solid ${isDark ? '#333' : '#e5e5e5'}; border-radius: ${theme.borderRadius}; overflow: hidden; } .yem-product-line { padding: 16px; border-bottom: 1px solid ${isDark ? '#333' : '#e5e5e5'}; cursor: pointer; transition: background-color 0.15s; background: ${isDark ? '#1f1f1f' : '#ffffff'}; } .yem-product-line:last-child { border-bottom: none; } .yem-product-line:hover { background: ${isDark ? '#2a2a2a' : '#f9f9f9'}; } .yem-product-line.yem-expanded { background: ${isDark ? '#252525' : '#f5f5f5'}; } .yem-product-summary { display: flex; justify-content: space-between; align-items: center; } .yem-product-info { display: flex; align-items: baseline; gap: 12px; } .yem-product-name { font-weight: 500; font-size: 15px; color: ${isDark ? '#ffffff' : '#1a1a1a'}; } .yem-charge-type { font-size: 13px; color: ${isDark ? '#888' : '#999'}; } .yem-product-price { text-align: right; } .yem-price-amount { font-size: 16px; font-weight: 600; color: ${isDark ? '#ffffff' : '#1a1a1a'}; } .yem-price-usage { font-size: 13px; font-style: italic; color: ${isDark ? '#888' : '#666'}; } /* Line item details (expanded) */ .yem-line-details { margin-top: 16px; padding-top: 16px; border-top: 1px solid ${isDark ? '#333' : '#e5e5e5'}; display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 16px; } .yem-line-detail { font-size: 13px; } .yem-line-detail-label { color: ${isDark ? '#999' : '#666'}; margin-bottom: 4px; font-size: 12px; } .yem-line-detail-value { font-weight: 500; color: ${isDark ? '#fff' : '#1a1a1a'}; } .yem-line-actions { margin-top: 16px; display: flex; gap: 8px; justify-content: flex-end; } /* Modal styles */ .yem-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; } .yem-modal { background: ${isDark ? '#1a1a1a' : '#ffffff'}; border-radius: ${theme.borderRadius}; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); max-width: 500px; width: 90%; max-height: 90vh; overflow: auto; } .yem-modal-header { padding: 20px; border-bottom: 1px solid ${isDark ? '#333' : '#e0e0e0'}; } .yem-modal-title { font-size: 18px; font-weight: 600; color: ${isDark ? '#ffffff' : '#1a1a1a'}; margin: 0; } .yem-modal-body { padding: 20px; } .yem-modal-footer { padding: 16px 20px; border-top: 1px solid ${isDark ? '#333' : '#e0e0e0'}; display: flex; gap: 8px; justify-content: flex-end; } /* Price calculation preview */ .yem-price-preview { background: ${isDark ? '#2a2a2a' : '#f9f9f9'}; border-radius: ${theme.borderRadius}; padding: 16px; margin: 16px 0; } .yem-price-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid ${isDark ? '#333' : '#e0e0e0'}; } .yem-price-row:last-child { border-bottom: none; font-weight: 600; } .yem-price-label { color: ${isDark ? '#999' : '#666'}; } .yem-price-value { color: ${isDark ? '#fff' : '#1a1a1a'}; } .yem-price-positive { color: ${isDark ? '#4ade80' : '#155724'}; } .yem-price-negative { color: ${isDark ? '#f87171' : '#721c24'}; } /* Confirmation modal styles */ .yem-confirm-intro { color: ${isDark ? '#999' : '#6b7280'}; margin-bottom: 16px; } .yem-confirm-details { background: ${isDark ? '#2a2a2a' : '#f9fafb'}; border: 1px solid ${isDark ? '#333' : '#e5e7eb'}; border-radius: ${theme.borderRadius}; padding: 16px; margin-bottom: 16px; } .yem-confirm-product { font-weight: 600; color: ${isDark ? '#fff' : '#111827'}; margin: 0 0 4px 0; } .yem-confirm-subscription { font-size: 12px; color: ${isDark ? '#666' : '#9ca3af'}; margin: 0 0 16px 0; } .yem-confirm-changes { padding-top: 12px; border-top: 1px solid ${isDark ? '#444' : '#e5e7eb'}; } .yem-confirm-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; font-size: 14px; color: ${isDark ? '#999' : '#6b7280'}; } .yem-confirm-value { font-weight: 500; color: ${isDark ? '#fff' : '#111827'}; } .yem-confirm-pricing { margin-top: 12px; padding-top: 12px; border-top: 1px solid ${isDark ? '#444' : '#e5e7eb'}; } .yem-confirm-total { font-weight: 600; font-size: 15px; padding-top: 12px; margin-top: 8px; border-top: 1px solid ${isDark ? '#444' : '#e5e7eb'}; } .yem-confirm-note { font-size: 13px; color: ${isDark ? '#666' : '#6b7280'}; margin-top: 16px; } /* Add-on product list styles */ .yem-addon-intro { font-size: 14px; color: ${isDark ? '#999' : '#6b7280'}; margin-bottom: 12px; } .yem-addon-list { display: flex; flex-direction: column; gap: 8px; } .yem-addon-card { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border: 1px solid ${isDark ? '#333' : '#e5e7eb'}; border-radius: ${theme.borderRadius}; cursor: pointer; transition: all 0.15s ease; } .yem-addon-card:hover { background: ${isDark ? '#2a2a2a' : '#f0f7ff'}; border-color: ${theme.primaryColor}; } .yem-addon-card-content { display: flex; flex-direction: column; gap: 2px; } .yem-addon-name { font-weight: 500; font-size: 14px; color: ${isDark ? '#fff' : '#111827'}; } .yem-addon-charges { font-size: 12px; color: ${isDark ? '#666' : '#9ca3af'}; } .yem-addon-arrow { font-size: 20px; color: ${isDark ? '#444' : '#d1d5db'}; font-style: normal; } .yem-back-btn { background: none; border: none; color: ${theme.primaryColor}; font-size: 14px; cursor: pointer; padding: 0; margin-bottom: 16px; display: flex; align-items: center; gap: 4px; } .yem-back-btn:hover { text-decoration: underline; } .yem-selected-product { margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid ${isDark ? '#333' : '#e5e7eb'}; } .yem-selected-product-name { font-size: 16px; font-weight: 600; color: ${isDark ? '#fff' : '#111827'}; margin: 0 0 4px 0; } .yem-selected-product-desc { font-size: 13px; color: ${isDark ? '#999' : '#6b7280'}; margin: 0; } /* Addon confirmation modal charge rows */ .yem-confirm-charges { margin-top: 12px; padding-top: 12px; border-top: 1px solid ${isDark ? '#333' : '#e5e7eb'}; } .yem-confirm-charge-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid ${isDark ? '#333' : '#f3f4f6'}; } .yem-confirm-charge-row:last-child { border-bottom: none; } .yem-confirm-charge-info { display: flex; align-items: baseline; gap: 8px; } .yem-confirm-charge-name { font-size: 14px; color: ${isDark ? '#fff' : '#111827'}; } .yem-confirm-charge-qty { font-size: 12px; color: ${isDark ? '#999' : '#6b7280'}; } .yem-confirm-charge-price { font-size: 14px; font-weight: 500; color: ${isDark ? '#fff' : '#111827'}; } .yem-confirm-total { display: flex; justify-content: space-between; margin-top: 12px; padding-top: 12px; border-top: 1px solid ${isDark ? '#444' : '#e5e7eb'}; font-weight: 600; font-size: 15px; color: ${isDark ? '#fff' : '#111827'}; } .yem-confirm-charge-type { font-size: 12px; color: ${isDark ? '#888' : '#6b7280'}; display: block; margin-top: 2px; } /* Addon price in product list */ .yem-addon-price { font-size: 13px; color: ${isDark ? '#999' : '#6b7280'}; } .yem-addon-meta { font-size: 12px; color: ${isDark ? '#888' : '#9ca3af'}; } /* Plan info in charge config */ .yem-plan-info { font-size: 13px; color: ${isDark ? '#999' : '#6b7280'}; margin: 4px 0 0 0; } /* Charge configuration layout */ .yem-charge-config-list { border: 1px solid ${isDark ? '#333' : '#e5e7eb'}; border-radius: ${theme.borderRadius}; overflow: hidden; margin-top: 16px; } .yem-charge-config-header { display: grid; grid-template-columns: 1fr 100px 80px 100px; gap: 12px; padding: 10px 16px; background: ${isDark ? '#2a2a2a' : '#f9fafb'}; border-bottom: 1px solid ${isDark ? '#333' : '#e5e7eb'}; font-size: 12px; font-weight: 600; color: ${isDark ? '#999' : '#6b7280'}; text-transform: uppercase; } .yem-charge-config-row { display: grid; grid-template-columns: 1fr 100px 80px 100px; gap: 12px; padding: 12px 16px; align-items: center; border-bottom: 1px solid ${isDark ? '#333' : '#f3f4f6'}; } .yem-charge-config-row:last-child { border-bottom: none; } .yem-charge-config-info { display: flex; flex-direction: column; } .yem-charge-config-name { font-weight: 500; font-size: 14px; color: ${isDark ? '#fff' : '#111827'}; } .yem-charge-config-type { font-size: 12px; color: ${isDark ? '#888' : '#6b7280'}; } .yem-charge-config-price { font-size: 13px; color: ${isDark ? '#999' : '#6b7280'}; } .yem-charge-config-qty { display: flex; align-items: center; } .yem-qty-input { width: 70px !important; padding: 6px 8px !important; text-align: center; } .yem-charge-config-total { font-weight: 600; font-size: 14px; color: ${isDark ? '#fff' : '#111827'}; text-align: right; } .yem-charge-config-footer { display: flex; justify-content: space-between; padding: 12px 16px; background: ${isDark ? '#2a2a2a' : '#f9fafb'}; border-top: 1px solid ${isDark ? '#333' : '#e5e7eb'}; font-weight: 600; font-size: 15px; color: ${isDark ? '#fff' : '#111827'}; } .yem-empty-state { text-align: center; padding: 32px 16px; color: ${isDark ? '#888' : '#6b7280'}; } .yem-empty-state-hint { font-size: 0.75rem; color: ${isDark ? '#666' : '#9ca3af'}; margin-top: 8px; } `; } /** * Inject styles into the document head */ function injectStyles(theme) { let styleElement = document.getElementById(STYLE_ID); if (styleElement) { // Update existing styles styleElement.textContent = generateStyles(theme); } else { // Create new style element styleElement = document.createElement('style'); styleElement.id = STYLE_ID; styleElement.textContent = generateStyles(theme); document.head.appendChild(styleElement); } } /** * Remove injected styles */ function removeStyles() { const styleElement = document.getElementById(STYLE_ID); if (styleElement) { styleElement.remove(); } } /** * UI Utility Functions */ /** * Sanitize HTML to prevent XSS attacks * Escapes special HTML characters in user-controlled data */ function sanitizeHtml(str) { if (str === null || str === undefined) { return ''; } return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * Format a date according to a format string * Supports: YYYY, MM, DD */ function formatDate(dateString, format = 'YYYY-MM-DD') { if (!dateString) return ''; const date = new Date(dateString); if (isNaN(date.getTime())) return ''; const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return format .replace('YYYY', String(year)) .replace('MM', month) .replace('DD', day); } /** * Format a number as currency */ function formatCurrency(amount, currency, locale = 'en-US') { return new Intl.NumberFormat(locale, { style: 'currency', currency: currency, }).format(amount); } /** * Generate a unique ID for components */ function generateId(prefix = 'yem') { return `${prefix}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Common UI Renderers */ /** * Render loading state */ function renderLoading(container, message = 'Loading...') { container.innerHTML = `
| Invoice # | Date | Due Date | Amount | ${this.options.showPaymentStatus ? 'Status | ' : ''} ${this.options.allowPdfDownload ? 'Actions | ' : ''}
|---|
${data.totalCount} subscription${data.totalCount !== 1 ? 's' : ''}
| From | To | Unit Price | ${targetLine.priceTiers.some(t => t.flatPrice) ? 'Flat Price | ' : ''}
|---|---|---|---|
| ${tier.fromQuantity} | ${tier.toQuantity ?? '∞'} | ${formatCurrency(tier.unitPrice, targetSub.currency, this.config.locale)} | ${tier.flatPrice !== undefined ? `${formatCurrency(tier.flatPrice, targetSub.currency, this.config.locale)} | ` : ''}
You are about to change the quantity for:
${sanitizeHtml(line.chargeName || line.productName)}
Subscription: ${sanitizeHtml(subscription.subscriptionNumber)}
This change will take effect on your next billing cycle.
`; const modal = renderModal('confirm-quantity', 'Confirm Quantity Change', content, ` `); document.body.insertAdjacentHTML('beforeend', modal); } /** * Cancel confirmation and go back to edit modal */ cancelConfirmQuantityChange() { const confirmOverlay = document.getElementById('confirm-quantity-overlay'); confirmOverlay?.remove(); // Re-open the edit modal with current state if (this.editState) { this.showEditQuantityModal(this.editState.subscriptionId, this.editState.lineId); } } async submitQuantityChange() { if (!this.editState) return; const id = this.componentId; const submitBtn = document.getElementById(`${id}-confirm-btn`); if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Submitting...'; } try { // Calculate effective date: use subscription start date if it hasn't started yet, otherwise use today const effectiveDate = this.calculateEffectiveDate(this.editState.subscriptionStartDate); const response = await this.apiClient.call({ method: 'POST', endpoint: '/subscriptions/request-change', body: { subscriptionId: this.editState.subscriptionId, changeType: 'QuantityChange', effectiveDate, quantityChanges: [{ catalogChargeId: this.editState.catalogChargeId, orderChargeId: this.editState.orderChargeId, newQuantity: this.editState.newQuantity, }], }, }); // Check both success flag and status (API may return success:true but status:'Failed') if (response.success && response.status !== 'Failed') { // Close modal and reload data silently (no loading spinner) const confirmOverlay = document.getElementById('confirm-quantity-overlay'); confirmOverlay?.remove(); this.editState = null; await this.silentReload(); } else { const errorEl = document.getElementById(`${id}-confirm-error`); if (errorEl) { errorEl.innerHTML = ``; } if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Confirm Change'; } } } catch (error) { const errorEl = document.getElementById(`${id}-confirm-error`); if (errorEl) { const errorMessage = this.getErrorMessage(error, 'Failed to submit change request'); errorEl.innerHTML = ``; } if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Confirm Change'; } } } /** * Extract user-friendly error message from an error */ getErrorMessage(error, defaultMessage) { if (error instanceof Error) { // Check for permission-related errors if (error.message.includes('Access denied') || error.message.includes('403')) { return 'Insufficient permissions. The API credentials do not have write access to subscriptions.'; } if (error.message.includes('Token expired') || error.message.includes('401')) { return 'Your session has expired. Please refresh the page and try again.'; } // Return the error message if it's meaningful if (error.message && !error.message.startsWith('API error:')) { return error.message; } } return defaultMessage; } /** * Reload data without showing loading spinner */ async silentReload() { try { this.instance.data = await this.fetchData(); this.renderContent(); } catch (error) { // On error, fall back to normal reload with loading state await this.reload(); } } /** * Get distinct charge plans from an addon product */ getDistinctChargePlans(addon) { const plansMap = new Map(); for (const charge of addon.charges) { if (charge.chargePlanId && !plansMap.has(charge.chargePlanId)) { plansMap.set(charge.chargePlanId, charge.chargePlanName || charge.chargePlanId); } } return Array.from(plansMap.entries()).map(([id, name]) => ({ chargePlanId: id, chargePlanName: name, })); } /** * Check if addon has multiple charge plans */ hasMultipleChargePlans(addon) { return this.getDistinctChargePlans(addon).length > 1; } /** * Get visible charges for current addon state (filtered by selected plan and currency) */ getVisibleCharges(addon, chargePlanId, currency) { let charges = addon.charges; // If product has charge plan info if (addon.charges.some(c => c.chargePlanId)) { if (!chargePlanId) { // Has charge plans but none selected - show nothing return []; } // Filter to selected charge plan charges = charges.filter(c => c.chargePlanId === chargePlanId); } // Filter to charges with pricing in subscription currency return charges.filter(c => { const price = c.pricesByCurrency?.[currency]; return price !== undefined; }); } /** * Get price for a charge in a specific currency */ getChargePrice(charge, currency) { return charge.pricesByCurrency?.[currency] ?? charge.price ?? 0; } /** * Check if an addon product has valid pricing for a given currency * - Multi-plan products: Show if ANY plan has all charges with pricing * - Single-plan products (including multi-charge): ALL charges must have pricing */ hasValidPricingForCurrency(addon, currency) { const hasMultiplePlans = this.hasMultipleChargePlans(addon); if (hasMultiplePlans) { // For multi-plan products: Show if ANY plan has all charges with pricing const chargePlans = this.getDistinctChargePlans(addon); return chargePlans.some(cp => { const planCharges = addon.charges.filter(c => c.chargePlanId === cp.chargePlanId); return planCharges.length > 0 && planCharges.every(c => c.pricesByCurrency?.[currency] !== undefined); }); } else { // For single-plan products (including multi-charge): ALL charges must have pricing // This prevents showing products where only some charges have the currency return addon.charges.length > 0 && addon.charges.every(c => c.pricesByCurrency?.[currency] !== undefined); } } showAddAddonModal(subscriptionId) { const data = this.instance.data; const subscription = data.subscriptions.find(s => s.id === subscriptionId); if (!data.availableAddons || data.availableAddons.length === 0 || !subscription) { return; } this.addonState = { subscriptionId, selectedProduct: null, selectedChargePlanId: null, quantities: {}, subscriptionStartDate: subscription.startDate, // Store for effective date calculation }; const id = this.componentId; const currency = subscription.currency; // CRITICAL: Filter addons based on currency availability // - Multi-plan products: Show if ANY plan has all charges with pricing // - Single-plan products (including multi-charge): ALL charges must have pricing const currencyFilteredAddons = data.availableAddons.filter(addon => this.hasValidPricingForCurrency(addon, currency)); if (currencyFilteredAddons.length === 0) { // No products available for this currency - show empty state const content = `No products available for ${sanitizeHtml(currency)} subscriptions
Products must have pricing configured in ${sanitizeHtml(currency)}
Select a product to add:
No plans available for ${sanitizeHtml(currency)} subscriptions
${sanitizeHtml(addon.description)}
` : ''}Select a plan:
Plan: ${sanitizeHtml(selectedPlan.chargePlanName)}
`; } } // Build charge rows with prices let totalPrice = 0; const chargeRows = visibleCharges.map(charge => { const unitPrice = this.getChargePrice(charge, currency); const qty = this.addonState?.quantities[charge.chargeId] || 1; const lineTotal = unitPrice * qty; totalPrice += lineTotal; if (charge.isQuantityBased) { return `You are about to add the following product to your subscription:
${sanitizeHtml(addon.productName)}
${planInfo}This product will be added to your subscription immediately.
`; const modal = renderModal('confirm-addon', 'Confirm Product Addition', content, ` `); document.body.insertAdjacentHTML('beforeend', modal); } /** * Go back from confirmation to addon config */ backToAddonConfig() { // Close confirm modal, the add-addon modal is still there const confirmOverlay = document.getElementById('confirm-addon-overlay'); confirmOverlay?.remove(); } /** * Go back to addon product list */ backToAddonList() { if (!this.addonState) return; const id = this.componentId; // Clear selection this.addonState.selectedProduct = null; this.addonState.selectedChargePlanId = null; this.addonState.quantities = {}; // Show product list, hide configuration and plans const listEl = document.getElementById(`${id}-addon-list`); const introEl = document.querySelector('.yem-addon-intro'); const configEl = document.getElementById(`${id}-addon-config`); const plansEl = document.getElementById(`${id}-addon-plans`); if (listEl) listEl.style.display = 'block'; if (introEl) introEl.style.display = 'block'; if (configEl) { configEl.style.display = 'none'; configEl.innerHTML = ''; } if (plansEl) { plansEl.style.display = 'none'; plansEl.innerHTML = ''; } // Hide back button, disable submit button const backBtn = document.getElementById(`${id}-addon-back`); const submitBtn = document.getElementById(`${id}-addon-submit`); if (backBtn) backBtn.style.display = 'none'; if (submitBtn) submitBtn.disabled = true; } async confirmAddon() { if (!this.addonState?.selectedProduct) return; const id = this.componentId; const submitBtn = document.getElementById(`${id}-addon-confirm-submit`); if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Adding...'; } // Use quantities already collected in showConfirmAddonModal const chargeQuantities = this.addonState.quantities; // Build addon request with optional chargePlanId const addonRequest = { addonProductId: this.addonState.selectedProduct.productId, chargeQuantities, }; // Include chargePlanId if a plan was selected (for multi-plan products) if (this.addonState.selectedChargePlanId) { addonRequest.chargePlanId = this.addonState.selectedChargePlanId; } try { // Calculate effective date: use subscription start date if it hasn't started yet, otherwise use today const effectiveDate = this.calculateEffectiveDate(this.addonState.subscriptionStartDate); const response = await this.apiClient.call({ method: 'POST', endpoint: '/subscriptions/request-change', body: { subscriptionId: this.addonState.subscriptionId, changeType: 'AddAddon', effectiveDate, addonsToAdd: [addonRequest], }, }); // Check both success flag and status (API may return success:true but status:'Failed') if (response.success && response.status !== 'Failed') { // Close both modals and reload const confirmOverlay = document.getElementById('confirm-addon-overlay'); confirmOverlay?.remove(); const addonOverlay = document.getElementById('add-addon-overlay'); addonOverlay?.remove(); this.addonState = null; await this.silentReload(); } else { const errorEl = document.getElementById(`${id}-addon-confirm-error`); if (errorEl) { errorEl.innerHTML = ``; } if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Confirm'; } } } catch (error) { const errorEl = document.getElementById(`${id}-addon-confirm-error`); if (errorEl) { const errorMessage = this.getErrorMessage(error, 'Failed to add product'); errorEl.innerHTML = ``; } if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Confirm'; } } } } /** * Main YouniumEmbedded SDK Class */ class YouniumEmbeddedSDK { constructor(configOrToken = { token: '' }) { // Support both simple token string and full config object const inputConfig = typeof configOrToken === 'string' ? { token: configOrToken } : configOrToken; // Merge config with defaults this.config = { ...DEFAULT_CONFIG, ...inputConfig, theme: { ...DEFAULT_THEME, ...(inputConfig.theme || {}), }, }; this.apiClient = new ApiClient(this.config); this.tokenManager = new TokenManager(this.config); this.components = new Map(); this.accountComponents = new Map(); this.subscriptionComponents = new Map(); this.initialized = false; this.styleInjected = false; if (this.config.token) { this.setToken(this.config.token); this.injectStyles(); this.initialized = true; if (this.config.debug) { console.log('[YouniumSDK] Initialized', { version: SDK_VERSION, config: this.config }); } } } /** * Static initialization method */ static init(configOrToken) { const instance = new YouniumEmbeddedSDK(configOrToken); // Store instance for static method access window.YouniumEmbedded._instance = instance; return instance; } /** * Get SDK version */ static getVersion() { return SDK_VERSION; } /** * Set or update the authentication token */ setToken(token) { this.config.token = token; this.apiClient.setToken(token); this.tokenManager.setToken(token); if (!this.initialized) { this.injectStyles(); this.initialized = true; } } /** * Inject component styles */ injectStyles() { if (this.styleInjected) { // Update existing styles injectStyles(this.config.theme); return; } injectStyles(this.config.theme); this.styleInjected = true; } /** * Render a component */ async renderComponent(componentType, config) { if (!this.initialized) { throw new Error('SDK not initialized. Provide token in constructor or call setToken().'); } const { containerId } = config; let component; switch (componentType) { case COMPONENT_TYPES.INVOICE_LIST: component = new InvoiceListComponent(this.config, this.apiClient, containerId, config.options); break; case COMPONENT_TYPES.ACCOUNT_INFO: component = new AccountInfoComponent(this.config, this.apiClient, containerId, config.options); // Store reference for static method binding const accountComp = component; this.accountComponents.set(accountComp.getComponentId(), accountComp); break; case COMPONENT_TYPES.SUBSCRIPTION_LIST: component = new SubscriptionListComponent(this.config, this.apiClient, containerId, config.options); // Store reference for static method binding const subComp = component; this.subscriptionComponents.set(subComp.getComponentId(), subComp); break; default: throw new Error(`Unknown component type: ${componentType}`); } const componentId = await component.render(); this.components.set(componentId, component); return componentId; } /** * Get a component by ID */ getComponent(componentId) { const component = this.components.get(componentId); return component?.getInstance(); } /** * Destroy a specific component */ destroyComponent(componentId) { const component = this.components.get(componentId); if (component) { component.destroy(); this.components.delete(componentId); // Clean up from specialized maps if (component instanceof AccountInfoComponent) { this.accountComponents.delete(component.getComponentId()); } if (component instanceof SubscriptionListComponent) { this.subscriptionComponents.delete(component.getComponentId()); } } } /** * Destroy all components and clean up */ destroy() { for (const [componentId] of this.components) { this.destroyComponent(componentId); } this.tokenManager.destroy(); removeStyles(); this.initialized = false; this.styleInjected = false; } // ========================================================================= // Static helper methods (exposed globally for onclick handlers) // ========================================================================= /** * Search invoices */ static async searchInvoices(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); const component = sdk?.components.get(componentId); if (component instanceof InvoiceListComponent) { const searchInput = document.getElementById(`search-input-${componentId}`); await component.search(searchInput?.value || ''); } } /** * Clear invoice search */ static async clearSearch(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); const component = sdk?.components.get(componentId); if (component instanceof InvoiceListComponent) { const searchInput = document.getElementById(`search-input-${componentId}`); if (searchInput) searchInput.value = ''; await component.clearSearch(); } } /** * Change page */ static async changePage(componentId, page) { const sdk = YouniumEmbeddedSDK.getInstance(); const component = sdk?.components.get(componentId); if (component instanceof InvoiceListComponent) { await component.changePage(page); } } /** * Download invoice */ static async downloadInvoice(invoiceId) { const sdk = YouniumEmbeddedSDK.getInstance(); // Find any invoice list component to use its download method for (const component of sdk?.components.values() || []) { if (component instanceof InvoiceListComponent) { await component.downloadInvoice(invoiceId); return; } } } // Account component methods static startEditEmail(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.accountComponents.get(componentId)?.startEditEmail(); } static cancelEditEmail(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.accountComponents.get(componentId)?.cancelEditEmail(); } static async saveEmail(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); await sdk?.accountComponents.get(componentId)?.saveEmail(); } static startEditAddress(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.accountComponents.get(componentId)?.startEditAddress(); } static cancelEditAddress(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.accountComponents.get(componentId)?.cancelEditAddress(); } static async saveAddress(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); await sdk?.accountComponents.get(componentId)?.saveAddress(); } // Subscription component methods static toggleSubscription(componentId, subscriptionId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.toggleSubscription(subscriptionId); } static toggleLine(componentId, lineId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.toggleLine(lineId); } static showTieredPricing(componentId, lineId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.showTieredPricing(lineId); } static closeModal(modalId) { const overlay = document.getElementById(`${modalId}-overlay`); overlay?.remove(); } static showEditQuantityModal(componentId, subscriptionId, lineId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.showEditQuantityModal(subscriptionId, lineId); } static async calculatePriceChange(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); await sdk?.subscriptionComponents.get(componentId)?.calculatePriceChange(); } static showConfirmQuantityChange(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.showConfirmQuantityChange(); } static cancelConfirmQuantityChange(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.cancelConfirmQuantityChange(); } static async submitQuantityChange(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); await sdk?.subscriptionComponents.get(componentId)?.submitQuantityChange(); } static showAddAddonModal(componentId, subscriptionId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.showAddAddonModal(subscriptionId); } static selectAddon(componentId, productId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.selectAddon(productId); } static selectChargePlan(componentId, chargePlanId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.selectChargePlan(chargePlanId); } static updateAddonQuantity(componentId, chargeId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.updateAddonQuantity(chargeId); } static addonBack(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.addonBack(); } static backToAddonList(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.backToAddonList(); } static showConfirmAddonModal(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.showConfirmAddonModal(); } static backToAddonConfig(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); sdk?.subscriptionComponents.get(componentId)?.backToAddonConfig(); } static async confirmAddon(componentId) { const sdk = YouniumEmbeddedSDK.getInstance(); await sdk?.subscriptionComponents.get(componentId)?.confirmAddon(); } // Message helpers static showMessage(componentId, message, type) { const messageEl = document.getElementById(`${componentId}-message`); if (messageEl) { messageEl.textContent = message; messageEl.className = `yem-message yem-message-${type}`; messageEl.style.display = 'block'; } } static hideMessage(componentId) { const messageEl = document.getElementById(`${componentId}-message`); if (messageEl) { messageEl.style.display = 'none'; } } /** * Get the current SDK instance */ static getInstance() { const global = window; return global.YouniumEmbedded?._instance || null; } } /** * Younium Self Service Embedded SDK * * @example * // Initialize with token * const sdk = YouniumEmbedded.init({ * token: 'your-jwt-token', * apiEndpoint: 'https://api.selfservice.younium.com', * theme: { * primaryColor: '#DB7013', * colorMode: 'light' * } * }); * * // Render components * sdk.renderComponent('invoice-list', { * containerId: 'invoice-container', * options: { pageSize: 10, showSearch: true } * }); */ // Create the SDK object with static methods bound const YouniumEmbedded = { // Constructor and initialization init: YouniumEmbeddedSDK.init.bind(YouniumEmbeddedSDK), getVersion: YouniumEmbeddedSDK.getVersion.bind(YouniumEmbeddedSDK), // Component types constant COMPONENT_TYPES, VERSION: SDK_VERSION, // Store for the active instance _instance: null, // Invoice list methods searchInvoices: YouniumEmbeddedSDK.searchInvoices.bind(YouniumEmbeddedSDK), clearSearch: YouniumEmbeddedSDK.clearSearch.bind(YouniumEmbeddedSDK), changePage: YouniumEmbeddedSDK.changePage.bind(YouniumEmbeddedSDK), downloadInvoice: YouniumEmbeddedSDK.downloadInvoice.bind(YouniumEmbeddedSDK), // Account info methods startEditEmail: YouniumEmbeddedSDK.startEditEmail.bind(YouniumEmbeddedSDK), cancelEditEmail: YouniumEmbeddedSDK.cancelEditEmail.bind(YouniumEmbeddedSDK), saveEmail: YouniumEmbeddedSDK.saveEmail.bind(YouniumEmbeddedSDK), startEditAddress: YouniumEmbeddedSDK.startEditAddress.bind(YouniumEmbeddedSDK), cancelEditAddress: YouniumEmbeddedSDK.cancelEditAddress.bind(YouniumEmbeddedSDK), saveAddress: YouniumEmbeddedSDK.saveAddress.bind(YouniumEmbeddedSDK), // Subscription methods toggleSubscription: YouniumEmbeddedSDK.toggleSubscription.bind(YouniumEmbeddedSDK), toggleLine: YouniumEmbeddedSDK.toggleLine.bind(YouniumEmbeddedSDK), showTieredPricing: YouniumEmbeddedSDK.showTieredPricing.bind(YouniumEmbeddedSDK), showEditQuantityModal: YouniumEmbeddedSDK.showEditQuantityModal.bind(YouniumEmbeddedSDK), calculatePriceChange: YouniumEmbeddedSDK.calculatePriceChange.bind(YouniumEmbeddedSDK), showConfirmQuantityChange: YouniumEmbeddedSDK.showConfirmQuantityChange.bind(YouniumEmbeddedSDK), cancelConfirmQuantityChange: YouniumEmbeddedSDK.cancelConfirmQuantityChange.bind(YouniumEmbeddedSDK), submitQuantityChange: YouniumEmbeddedSDK.submitQuantityChange.bind(YouniumEmbeddedSDK), showAddAddonModal: YouniumEmbeddedSDK.showAddAddonModal.bind(YouniumEmbeddedSDK), selectAddon: YouniumEmbeddedSDK.selectAddon.bind(YouniumEmbeddedSDK), selectChargePlan: YouniumEmbeddedSDK.selectChargePlan.bind(YouniumEmbeddedSDK), updateAddonQuantity: YouniumEmbeddedSDK.updateAddonQuantity.bind(YouniumEmbeddedSDK), addonBack: YouniumEmbeddedSDK.addonBack.bind(YouniumEmbeddedSDK), backToAddonList: YouniumEmbeddedSDK.backToAddonList.bind(YouniumEmbeddedSDK), showConfirmAddonModal: YouniumEmbeddedSDK.showConfirmAddonModal.bind(YouniumEmbeddedSDK), backToAddonConfig: YouniumEmbeddedSDK.backToAddonConfig.bind(YouniumEmbeddedSDK), confirmAddon: YouniumEmbeddedSDK.confirmAddon.bind(YouniumEmbeddedSDK), // Modal helpers closeModal: YouniumEmbeddedSDK.closeModal.bind(YouniumEmbeddedSDK), // Message helpers showMessage: YouniumEmbeddedSDK.showMessage.bind(YouniumEmbeddedSDK), hideMessage: YouniumEmbeddedSDK.hideMessage.bind(YouniumEmbeddedSDK), // SDK class for advanced usage YouniumEmbeddedSDK, }; exports.YouniumEmbeddedSDK = YouniumEmbeddedSDK; exports.default = YouniumEmbedded; Object.defineProperty(exports, '__esModule', { value: true }); // Flatten default export to top-level for direct access if (exports.default) { Object.keys(exports.default).forEach(function(key) { if (key !== 'default' && !(key in exports)) { exports[key] = exports.default[key]; } }); } })); //# sourceMappingURL=younium-embedded-sdk.js.map