let userData = null; let hasSubscription = false; let currentPlan = null; // --- Top-up configuration (must mirror backend values) --- const TOPUP_PRICE_PER_CREDIT_USD = 0.012; const TOPUP_MIN_AMOUNT_USD = 5; const TOPUP_MAX_AMOUNT_USD = 1200; // --- Billing period toggle ('monthly' | 'yearly') --- let currentBilling = "monthly"; // Libellés d'affichage des plans (display uniquement, les identifiants // côté back-end restent 'essential', 'premium', 'creator', + variantes // '_year'). On remappe ici pour l'UI de la page Credits. const PLAN_DISPLAY_LABELS = { basic: "Basic", essential: "Starter", premium: "Pro", creator: "Agency", }; function getPlanDisplayLabel(planId) { if (!planId) return ""; const base = String(planId).replace(/_year$/, ""); if (PLAN_DISPLAY_LABELS[base]) return PLAN_DISPLAY_LABELS[base]; // Fallback : capitaliser la valeur brute return base.charAt(0).toUpperCase() + base.slice(1); } // Charger les informations utilisateur au chargement document.addEventListener("DOMContentLoaded", function () { // Vérifier les paramètres de succès dans l'URL checkSuccessParameters(); // Attacher les événements aux boutons de plan (une seule fois au chargement) attachPlanButtonEvents(); // Initialiser le switch mensuel / annuel initBillingToggle(); // Initialiser la section top-up (input montant personnalisé) initTopupSection(); // Charger les informations utilisateur loadUserInfo(); }); // Fonction pour vérifier les paramètres de succès dans l'URL function checkSuccessParameters() { const urlParams = new URLSearchParams(window.location.search); const type = urlParams.get("type"); const sessionId = urlParams.get("session_id"); if (type && sessionId) { if (type === "credits_success" || type === "topup_success") { showMessage( "Credit purchase completed successfully! Your credits will be updated shortly.", "success" ); // Recharger les données après un délai pour s'assurer que les crédits sont mis à jour setTimeout(() => { loadUserInfo(); }, 3000); // Nettoyer l'URL après affichage du message setTimeout(() => { const newUrl = window.location.pathname + window.location.search .replace(/[?&]type=[^&]*&session_id=[^&]*/, "") .replace(/^&/, "?"); window.history.replaceState({}, document.title, newUrl); }, 5000); } else if (type === "subscription_success") { showMessage( "Subscription activated successfully! Your subscription is now active.", "success" ); // Recharger les données après un délai pour s'assurer que l'abonnement est mis à jour setTimeout(() => { loadUserInfo(); }, 3000); // Nettoyer l'URL après affichage du message setTimeout(() => { const newUrl = window.location.pathname + window.location.search .replace(/[?&]type=[^&]*&session_id=[^&]*/, "") .replace(/^&/, "?"); window.history.replaceState({}, document.title, newUrl); }, 5000); } } } // Fonction pour charger les informations utilisateur async function loadUserInfo() { try { const tokenResponse = await fetch(ajaxurl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce, }); const tokenData = await tokenResponse.json(); if (!tokenData.success || !tokenData.data.token) { showMessage("Authentication failed", "error"); return; } const jwtToken = tokenData.data.token; const response = await fetch(window.config.apiUrl + "/user/profile", { method: "GET", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, }); if (response.ok) { userData = await response.json(); displayUserInfo(userData?.user); // updateSubscriptionButton() n'est plus nécessaire car updatePlansDisplay() gère tout } else { showMessage("Failed to load user information", "error"); } } catch (error) { console.error("Error loading user info:", error); showMessage("Network error while loading user information", "error"); } } // Afficher les informations utilisateur function displayUserInfo(data) { // Afficher les crédits const creditsDisplay = document.getElementById("current-credits"); const creditsBreakdown = data.aiCredits || { onAccountCreation: 0, monthlySubscription: 0, paid: 0, }; const totalCredits = creditsBreakdown.onAccountCreation + creditsBreakdown.monthlySubscription + creditsBreakdown.paid; if (creditsDisplay) { creditsDisplay.innerHTML = ` ${totalCredits} credits `; } // Afficher le plan const planDisplay = document.getElementById("current-plan"); if (planDisplay) { const plan = data.plan || "basic"; let resiliateText = ""; if (data.resiliateAt) { const resiliateDate = new Date(data.resiliateAt); const now = new Date(); const isEnded = resiliateDate < now.setHours(0, 0, 0, 0); if (isEnded) { resiliateText = `Subscription has ended on ${resiliateDate.toLocaleDateString()}`; } else { resiliateText = `Subscription will end on ${resiliateDate.toLocaleDateString()}`; } } planDisplay.innerHTML = ` ${getPlanDisplayLabel(plan)} ${resiliateText} `; } // Afficher le message dans la carte Subscription (section management) const subscriptionCard = document.querySelector( ".ai-subscription-actions .ai-action-content" ); if (subscriptionCard) { let resiliateMsg = subscriptionCard.querySelector(".resiliate-badge"); if (data.resiliateAt) { if (!resiliateMsg) { resiliateMsg = document.createElement("div"); resiliateMsg.className = "resiliate-badge"; resiliateMsg.innerHTML = `Subscription will end on ${new Date( data.resiliateAt ).toLocaleDateString()}`; subscriptionCard.appendChild(resiliateMsg); } else { resiliateMsg.innerHTML = `Subscription will end on ${new Date( data.resiliateAt ).toLocaleDateString()}`; } } else if (resiliateMsg) { resiliateMsg.remove(); } } // Vérifier si l'utilisateur a un abonnement hasSubscription = data.plan && data.plan !== "basic"; currentPlan = data.plan || "basic"; window.hasResiliate = !!data.resiliateAt; // Si l'utilisateur est sur un plan annuel, pré-sélectionner le switch "Yearly" // pour que son plan s'affiche bien comme "Current Plan". if (isCurrentPlanYearly() && currentBilling !== "yearly") { currentBilling = "yearly"; applyBillingPeriod(); } else { // applyBillingPeriod() appelle déjà updatePlansDisplay() applyBillingPeriod(); } // Mettre à jour l'affichage de la section Buy Credits updateCreditPurchaseSection(); } // Attacher les événements aux boutons de plan function attachPlanButtonEvents() { // Utiliser la délégation d'événements pour éviter les problèmes de duplication const plansContainer = document.getElementById("subscription-plans-container"); if (plansContainer) { // Retirer l'ancien listener s'il existe plansContainer.removeEventListener("click", handlePlanButtonClick); // Ajouter le nouveau listener plansContainer.addEventListener("click", handlePlanButtonClick); } // Bouton d'annulation const cancelBtn = document.getElementById("subscription-btn"); if (cancelBtn) { cancelBtn.onclick = showCancelModal; } } // Gestionnaire de clic pour les boutons de plan (délégation d'événements) function handlePlanButtonClick(event) { const button = event.target.closest(".ai-plan-btn"); if (!button || button.disabled) return; const subscriptionType = button.getAttribute("data-subscription-type"); if (subscriptionType) { // Vérifier dynamiquement l'état de l'abonnement if (hasSubscription && !window.hasResiliate) { // Abonnement actif -> on demande confirmation avant de changer de plan openSwitchSubscriptionModal(subscriptionType, button); } else { // Utilisateur sans abonnement ou abonnement annulé -> nouveau checkout handleSubscription(subscriptionType, button); } } } // --- Switch subscription confirmation modal --- // Contexte mémorisé pendant que la modale est ouverte let pendingSwitch = { subscriptionType: null, buttonElement: null }; function openSwitchSubscriptionModal(subscriptionType, buttonElement) { if (!subscriptionType) return; // Sécurité : si l'utilisateur tente de "switcher" vers son propre plan, // on ne montre pas la modale et on délègue à handleChangeSubscription // qui affichera un message "already on this plan". if (subscriptionType === currentPlan) { handleChangeSubscription(subscriptionType, buttonElement); return; } pendingSwitch = { subscriptionType, buttonElement: buttonElement || null }; const modal = document.getElementById("switch-subscription-modal"); const label = document.getElementById("switch-target-plan-label"); if (label) { const periodSuffix = /_year$/.test(subscriptionType) ? " (yearly)" : " (monthly)"; label.textContent = `${getPlanDisplayLabel(subscriptionType)}${periodSuffix}`; } // Réinitialiser le bouton de confirmation const confirmBtn = document.getElementById("switch-subscription-confirm-btn"); if (confirmBtn) { confirmBtn.disabled = false; const loading = confirmBtn.querySelector(".ai-loading"); const btnText = confirmBtn.querySelector(".btn-text"); if (loading) loading.style.display = "none"; if (btnText) btnText.textContent = "Confirm switch"; } if (modal) modal.style.display = "flex"; } function closeSwitchSubscriptionModal() { const modal = document.getElementById("switch-subscription-modal"); if (modal) modal.style.display = "none"; pendingSwitch = { subscriptionType: null, buttonElement: null }; } async function confirmSwitchSubscription() { const { subscriptionType, buttonElement } = pendingSwitch; if (!subscriptionType) { closeSwitchSubscriptionModal(); return; } const confirmBtn = document.getElementById("switch-subscription-confirm-btn"); const loading = confirmBtn ? confirmBtn.querySelector(".ai-loading") : null; const btnText = confirmBtn ? confirmBtn.querySelector(".btn-text") : null; if (confirmBtn) confirmBtn.disabled = true; if (loading) loading.style.display = "inline-block"; if (btnText) btnText.textContent = "Switching..."; try { await handleChangeSubscription(subscriptionType, buttonElement); } finally { closeSwitchSubscriptionModal(); } } // Helpers pour gérer les plans annuels ( _year ) function getCurrentPlanBase() { if (!currentPlan) return null; return currentPlan.replace(/_year$/, ""); } function isCurrentPlanYearly() { return typeof currentPlan === "string" && /_year$/.test(currentPlan); } // Mettre à jour l'affichage des plans function updatePlansDisplay() { const planWrappers = document.querySelectorAll(".ai-plan-card-wrapper"); const cancelCard = document.getElementById("cancel-subscription-card"); if (!planWrappers || planWrappers.length === 0) { return; // Les plans ne sont pas encore chargés } const planBase = getCurrentPlanBase(); const yearlyCurrent = isCurrentPlanYearly(); planWrappers.forEach((wrapper) => { const planType = wrapper.getAttribute("data-plan"); const button = wrapper.querySelector(".ai-plan-btn"); if (!button) return; const btnText = button.querySelector(".btn-text"); if (!btnText) return; // Réinitialiser l'état wrapper.setAttribute("data-current-plan", "false"); button.classList.remove("current-plan"); button.disabled = false; // Libellé d'affichage du plan pour les CTAs ("Starter", "Pro", "Agency"). const planLabel = getPlanDisplayLabel(planType); // Suffixe selon la période sélectionnée const periodSuffix = currentBilling === "yearly" ? " (yearly)" : ""; // Est-ce que ce plan, dans sa période actuellement affichée, correspond // exactement à l'abonnement en cours ? const isExactCurrent = planBase === planType && yearlyCurrent === (currentBilling === "yearly"); if (hasSubscription && !window.hasResiliate) { if (isExactCurrent) { // Plan + période = abonnement en cours wrapper.setAttribute("data-current-plan", "true"); button.classList.add("current-plan"); btnText.textContent = "Current Plan"; button.disabled = true; } else { // Autre plan / autre période -> option de changement btnText.textContent = `Switch to ${planLabel}${periodSuffix}`; button.disabled = false; } } else { // Pas d'abonnement actif (ou abonnement résilié) btnText.textContent = `Choose ${planLabel}${periodSuffix}`; button.disabled = false; } }); // Afficher/masquer la carte d'annulation if (cancelCard) { if (hasSubscription && !window.hasResiliate) { cancelCard.style.display = "block"; } else { cancelCard.style.display = "none"; } } } // Mettre à jour le bouton d'abonnement (fonction conservée pour compatibilité mais non utilisée) // L'affichage des plans est maintenant géré par updatePlansDisplay() // Cette fonction peut être utilisée pour le bouton d'annulation si nécessaire function updateSubscriptionButton() { const btn = document.getElementById("subscription-btn"); if (!btn) return; // Sécurité : le bouton n'existe pas // Reconstruit le HTML interne du bouton à chaque fois btn.innerHTML = ` `; const btnText = btn.querySelector(".btn-text"); const loading = btn.querySelector(".ai-loading"); if (!btnText || !loading) return; // Sécurité : structure inattendue if (hasSubscription && window.hasResiliate) { btnText.textContent = "Reactivate Subscription"; btn.className = "ai-primary-btn ai-reactivate-btn"; btn.onclick = handleReactivateSubscription; } else if (hasSubscription) { btnText.textContent = "Cancel Subscription"; btn.className = "ai-cancel-subscription-btn"; btn.style.opacity = "0.7"; btn.style.fontWeight = "200"; btn.onclick = showCancelModal; } else { // Ce cas ne devrait plus se produire car nous avons maintenant les boutons de plan individuels btnText.textContent = "Get Subscription"; btn.className = "ai-primary-btn"; btn.onclick = null; // Ne pas assigner handleSubscription sans paramètre } } // Fonction pour réactiver l'abonnement async function handleReactivateSubscription() { setLoading("subscription-btn", true); try { const tokenResponse = await fetch(ajaxurl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce, }); const tokenData = await tokenResponse.json(); const jwtToken = tokenData.data.token; const response = await fetch( window.config.apiUrl + "/payments/resume-subscription", { method: "POST", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, } ); const data = await response.json(); if (response.ok) { showMessage("Subscription reactivated successfully!", "success"); setTimeout(() => { loadUserInfo(); }, 1000); } else { showMessage(data.message || "Failed to reactivate subscription", "error"); } } catch (error) { console.error("Error reactivating subscription:", error); showMessage("Network error while reactivating subscription", "error"); } finally { setLoading("subscription-btn", false); } } // Mettre à jour l'affichage de la section Buy Credits (top-up) function updateCreditPurchaseSection() { const creditCard = document.getElementById("credit-purchase-card"); const buyCreditsBtn = document.getElementById("buy-credits-btn"); const lockOverlay = document.getElementById("topup-lock-overlay"); if (!creditCard || !buyCreditsBtn) return; const amountInput = document.getElementById("topup-amount"); const presets = document.querySelectorAll(".ai-topup-preset"); const locked = !hasSubscription; creditCard.setAttribute("data-locked", locked ? "true" : "false"); if (lockOverlay) { lockOverlay.hidden = !locked; } // Griser / désactiver tous les contrôles quand pas d'abonnement if (amountInput) amountInput.disabled = locked; presets.forEach((btn) => { btn.disabled = locked; }); buyCreditsBtn.disabled = locked; buyCreditsBtn.onclick = locked ? null : handleCreditPurchase; // Rafraîchir l'affichage après changement d'état refreshTopupDisplay(); } // --- Top-up custom amount helpers --- function computeCreditsFromAmount(amountDollars) { if (!Number.isFinite(amountDollars) || amountDollars <= 0) return 0; const amountCents = Math.round(amountDollars * 100); return Math.floor(amountCents / 100 / TOPUP_PRICE_PER_CREDIT_USD); } function clampTopupAmount(value) { const n = Number(value); if (!Number.isFinite(n)) return TOPUP_MIN_AMOUNT_USD; return Math.min( TOPUP_MAX_AMOUNT_USD, Math.max(TOPUP_MIN_AMOUNT_USD, Math.round(n)) ); } function getTopupRawAmount() { const amountInput = document.getElementById("topup-amount"); if (!amountInput) return TOPUP_MIN_AMOUNT_USD; const raw = amountInput.value; if (raw === "" || raw === null) return NaN; return Number(raw); } function refreshTopupDisplay() { const amountInput = document.getElementById("topup-amount"); const payEl = document.getElementById("topup-pay-amount"); const creditsEl = document.getElementById("topup-credits-amount"); const errorEl = document.getElementById("topup-error"); const wrapper = document.querySelector(".ai-topup-amount-wrapper"); const buyBtn = document.getElementById("buy-credits-btn"); const btnText = buyBtn ? buyBtn.querySelector(".btn-text") : null; const presets = document.querySelectorAll(".ai-topup-preset"); if (!amountInput) return; const raw = getTopupRawAmount(); const hasValue = !Number.isNaN(raw); const isBelow = hasValue && raw < TOPUP_MIN_AMOUNT_USD; const isAbove = hasValue && raw > TOPUP_MAX_AMOUNT_USD; const isInvalid = !hasValue || isBelow || isAbove; // Valeur "effective" utilisée pour l'aperçu const effective = hasValue ? Math.min(TOPUP_MAX_AMOUNT_USD, Math.max(TOPUP_MIN_AMOUNT_USD, raw)) : TOPUP_MIN_AMOUNT_USD; const credits = computeCreditsFromAmount(effective); if (payEl) { payEl.textContent = `$${effective.toFixed(2)}`; } if (creditsEl) { creditsEl.textContent = `${credits.toLocaleString("en-US")} credits`; } if (wrapper) { wrapper.classList.toggle("has-error", isInvalid && hasValue); } if (errorEl) { if (!hasValue) { errorEl.textContent = ""; } else if (isBelow) { errorEl.textContent = `Minimum amount is $${TOPUP_MIN_AMOUNT_USD}.`; } else if (isAbove) { errorEl.textContent = `Maximum amount is $${TOPUP_MAX_AMOUNT_USD.toLocaleString( "en-US" )}.`; } else { errorEl.textContent = ""; } } // Mise à jour visuelle du preset actif presets.forEach((btn) => { const presetAmount = Number(btn.getAttribute("data-amount")); btn.classList.toggle( "is-active", hasValue && !isInvalid && presetAmount === raw ); }); // Bouton d'achat if (buyBtn) { const locked = !hasSubscription; const canBuy = !locked && !isInvalid && hasValue && credits > 0; buyBtn.disabled = !canBuy; if (btnText && !buyBtn.classList.contains("is-loading")) { if (locked) { btnText.textContent = "Purchase credits"; } else if (isInvalid || !hasValue) { btnText.textContent = "Enter a valid amount"; } else { btnText.textContent = `Purchase ${credits.toLocaleString( "en-US" )} credits for $${effective.toFixed(2)}`; } } } } function initTopupSection() { const amountInput = document.getElementById("topup-amount"); const presets = document.querySelectorAll(".ai-topup-preset"); if (!amountInput) return; amountInput.addEventListener("input", function () { refreshTopupDisplay(); }); amountInput.addEventListener("blur", function () { const raw = getTopupRawAmount(); if (Number.isNaN(raw)) { amountInput.value = String(TOPUP_MIN_AMOUNT_USD); } else { amountInput.value = String(clampTopupAmount(raw)); } refreshTopupDisplay(); }); presets.forEach((btn) => { btn.addEventListener("click", function () { if (btn.disabled) return; const presetAmount = Number(btn.getAttribute("data-amount")); if (!Number.isFinite(presetAmount)) return; amountInput.value = String(clampTopupAmount(presetAmount)); refreshTopupDisplay(); }); }); refreshTopupDisplay(); } // --- Billing period (monthly / yearly) --- function formatMonthlyEquivalent(yearlyPrice) { // Montant mensuel équivalent (ex: 90 / 12 = 7.5 -> "$7.50") const value = yearlyPrice / 12; const rounded = Math.round(value * 100) / 100; // Affiche sans décimales inutiles: 7.5 -> "$7.50", 15.83 -> "$15.83" const hasCents = Math.round(rounded * 100) % 100 !== 0; return `$${rounded.toFixed(hasCents ? 2 : 0)}`; } function formatPerCreditYearly(perCredit) { // Ex: 0.01 -> "$0.010", 0.00791666 -> "$0.00792", 0.00680555 -> "$0.00681". // On arrondit à 5 décimales puis on retire les zéros de fin en conservant // au minimum 3 décimales pour un affichage cohérent. if (!Number.isFinite(perCredit) || perCredit <= 0) return ""; let str = (Math.round(perCredit * 100000) / 100000).toFixed(5); str = str.replace(/(\.\d{3}\d*?)0+$/, "$1"); return `$${str}`; } function applyBillingPeriod() { const planWrappers = document.querySelectorAll(".ai-plan-card-wrapper"); const isYearly = currentBilling === "yearly"; // État visuel du switch document.querySelectorAll(".ai-billing-option").forEach((btn) => { const active = btn.getAttribute("data-billing") === currentBilling; btn.classList.toggle("is-active", active); btn.setAttribute("aria-selected", active ? "true" : "false"); }); planWrappers.forEach((wrapper) => { const monthlyPrice = Number(wrapper.getAttribute("data-monthly-price")); const yearlyPrice = Number(wrapper.getAttribute("data-yearly-price")); const monthlyCredits = Number( wrapper.getAttribute("data-monthly-credits") ); const monthlyType = wrapper.getAttribute("data-monthly-type"); const yearlyType = wrapper.getAttribute("data-yearly-type"); const amountEl = wrapper.querySelector(".ai-price-amount"); const oldEl = wrapper.querySelector(".ai-price-old"); const billedEl = wrapper.querySelector(".ai-price-billed"); const creditEl = wrapper.querySelector(".ai-price-credit"); const button = wrapper.querySelector(".ai-plan-btn"); // Sauvegarde du libellé mensuel d'origine (hardcodé dans le HTML) pour // pouvoir le restaurer quand on revient en monthly, sans avoir à le // recalculer (les valeurs monthly sont des arrondis marketing). if (creditEl && !creditEl.dataset.monthlyText) { creditEl.dataset.monthlyText = creditEl.textContent.trim(); } if (isYearly && Number.isFinite(yearlyPrice)) { // Prix effectif mensuel à partir du tarif annuel if (amountEl) amountEl.textContent = formatMonthlyEquivalent(yearlyPrice); if (oldEl) { oldEl.textContent = `$${monthlyPrice}`; oldEl.hidden = false; } if (billedEl) { billedEl.textContent = `Billed annually at $${yearlyPrice}/year`; billedEl.hidden = false; } // Prix par crédit recalculé sur la base annuelle : // yearlyPrice / (monthlyCredits * 12) if (creditEl && Number.isFinite(monthlyCredits) && monthlyCredits > 0) { const perCredit = yearlyPrice / (monthlyCredits * 12); const formatted = formatPerCreditYearly(perCredit); if (formatted) { creditEl.textContent = `${formatted} per credit`; } } if (button && yearlyType) { button.setAttribute("data-subscription-type", yearlyType); } } else { if (amountEl) amountEl.textContent = `$${monthlyPrice}`; if (oldEl) { oldEl.hidden = true; } if (billedEl) { billedEl.hidden = true; } // Restaurer le libellé mensuel d'origine (ex: "$0.012 per credit") if (creditEl && creditEl.dataset.monthlyText) { creditEl.textContent = creditEl.dataset.monthlyText; } if (button && monthlyType) { button.setAttribute("data-subscription-type", monthlyType); } } }); // Recalculer les libellés "Current Plan" / "Switch to ..." selon la période updatePlansDisplay(); } function initBillingToggle() { const toggle = document.querySelector(".ai-billing-toggle"); if (!toggle) return; toggle.addEventListener("click", function (event) { const btn = event.target.closest(".ai-billing-option"); if (!btn) return; const billing = btn.getAttribute("data-billing"); if (!billing || billing === currentBilling) return; currentBilling = billing; applyBillingPeriod(); }); // État initial applyBillingPeriod(); } // Gérer l'abonnement (nouveau checkout) async function handleSubscription(subscriptionType, buttonElement) { if (!subscriptionType) { showMessage("Please select a subscription plan", "error"); return; } // Désactiver tous les boutons pendant le traitement const planButtons = document.querySelectorAll(".ai-plan-btn"); planButtons.forEach((btn) => { btn.disabled = true; }); // Afficher le loading sur le bouton cliqué if (buttonElement) { const loading = buttonElement.querySelector(".ai-loading"); const btnText = buttonElement.querySelector(".btn-text"); if (loading) loading.style.display = "inline-block"; if (btnText) { const originalText = btnText.textContent; btnText.textContent = "Processing..."; } } try { const tokenResponse = await fetch(ajaxurl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce, }); const tokenData = await tokenResponse.json(); if (!tokenData.success || !tokenData.data.token) { showMessage("Authentication failed", "error"); return; } const jwtToken = tokenData.data.token; const response = await fetch( window.config.apiUrl + "/payments/create-checkout-session-abo", { method: "POST", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ subscriptionType: subscriptionType, urlFrom: window.location.href, }), } ); const data = await response.json(); if (response.ok && data.url) { window.location.href = data.url; } else { showMessage( data.message || "Failed to create subscription checkout", "error" ); // Réactiver les boutons en cas d'erreur planButtons.forEach((btn) => { btn.disabled = false; }); if (buttonElement) { const loading = buttonElement.querySelector(".ai-loading"); const btnText = buttonElement.querySelector(".btn-text"); if (loading) loading.style.display = "none"; if (btnText) { const subType = buttonElement.getAttribute("data-subscription-type"); const periodSuffix = currentBilling === "yearly" ? " (yearly)" : ""; btnText.textContent = subType ? `Choose ${getPlanDisplayLabel(subType)}${periodSuffix}` : "Choose Plan"; } } } } catch (error) { console.error("Error creating subscription:", error); showMessage("Network error while creating subscription", "error"); // Réactiver les boutons en cas d'erreur const planButtons = document.querySelectorAll(".ai-plan-btn"); planButtons.forEach((btn) => { btn.disabled = false; }); if (buttonElement) { const loading = buttonElement.querySelector(".ai-loading"); const btnText = buttonElement.querySelector(".btn-text"); if (loading) loading.style.display = "none"; if (btnText) { const subType = buttonElement.getAttribute("data-subscription-type"); const periodSuffix = currentBilling === "yearly" ? " (yearly)" : ""; btnText.textContent = subType ? `Choose ${getPlanDisplayLabel(subType)}${periodSuffix}` : "Choose Plan"; } } } } // Gérer le changement d'abonnement async function handleChangeSubscription(subscriptionType, buttonElement) { if (!subscriptionType) { showMessage("Please select a subscription plan", "error"); return; } // Vérifier si c'est le même plan if (subscriptionType === currentPlan) { showMessage("You are already on this plan", "info"); return; } // Désactiver tous les boutons pendant le traitement const planButtons = document.querySelectorAll(".ai-plan-btn"); planButtons.forEach((btn) => { btn.disabled = true; }); // Afficher le loading sur le bouton cliqué if (buttonElement) { const loading = buttonElement.querySelector(".ai-loading"); const btnText = buttonElement.querySelector(".btn-text"); if (loading) loading.style.display = "inline-block"; if (btnText) { const originalText = btnText.textContent; btnText.textContent = "Switching..."; } } try { const tokenResponse = await fetch(ajaxurl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce, }); const tokenData = await tokenResponse.json(); if (!tokenData.success || !tokenData.data.token) { showMessage("Authentication failed", "error"); return; } const jwtToken = tokenData.data.token; const response = await fetch( window.config.apiUrl + "/payments/change-subscription", { method: "POST", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ subscriptionType: subscriptionType, }), } ); const data = await response.json(); if (response.ok) { showMessage("Subscription changed successfully!", "success"); // Recharger les informations utilisateur setTimeout(() => { loadUserInfo(); }, 1000); } else { showMessage( data.message || "Failed to change subscription", "error" ); // Réactiver les boutons en cas d'erreur planButtons.forEach((btn) => { btn.disabled = false; }); if (buttonElement) { const loading = buttonElement.querySelector(".ai-loading"); const btnText = buttonElement.querySelector(".btn-text"); if (loading) loading.style.display = "none"; if (btnText) { const periodSuffix = /_year$/.test(subscriptionType) ? " (yearly)" : ""; btnText.textContent = `Switch to ${getPlanDisplayLabel( subscriptionType )}${periodSuffix}`; } } } } catch (error) { console.error("Error changing subscription:", error); showMessage("Network error while changing subscription", "error"); // Réactiver les boutons en cas d'erreur const planButtons = document.querySelectorAll(".ai-plan-btn"); planButtons.forEach((btn) => { btn.disabled = false; }); if (buttonElement) { const loading = buttonElement.querySelector(".ai-loading"); const btnText = buttonElement.querySelector(".btn-text"); if (loading) loading.style.display = "none"; if (btnText) { const periodSuffix = /_year$/.test(subscriptionType) ? " (yearly)" : ""; btnText.textContent = `Switch to ${getPlanDisplayLabel( subscriptionType )}${periodSuffix}`; } } } } // Acheter des crédits (top-up avec montant personnalisé) async function handleCreditPurchase() { if (!hasSubscription) { showMessage( "An active subscription is required to buy top-up credits.", "error" ); return; } const buyBtn = document.getElementById("buy-credits-btn"); const btnText = buyBtn ? buyBtn.querySelector(".btn-text") : null; const loading = buyBtn ? buyBtn.querySelector(".ai-loading") : null; // Validation du montant côté front (miroir du back) const raw = getTopupRawAmount(); if ( Number.isNaN(raw) || raw < TOPUP_MIN_AMOUNT_USD || raw > TOPUP_MAX_AMOUNT_USD ) { showMessage( `Please enter an amount between $${TOPUP_MIN_AMOUNT_USD} and $${TOPUP_MAX_AMOUNT_USD.toLocaleString( "en-US" )}.`, "error" ); refreshTopupDisplay(); return; } const amountDollars = clampTopupAmount(raw); const credits = computeCreditsFromAmount(amountDollars); if (credits <= 0) { showMessage("Amount too low to grant any credit.", "error"); return; } // Loading state if (buyBtn) { buyBtn.classList.add("is-loading"); buyBtn.disabled = true; } if (loading) loading.style.display = "inline-block"; const previousBtnText = btnText ? btnText.textContent : ""; if (btnText) btnText.textContent = "Redirecting to checkout..."; try { const tokenResponse = await fetch(ajaxurl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce, }); const tokenData = await tokenResponse.json(); if (!tokenData.success || !tokenData.data.token) { showMessage("Authentication failed", "error"); return; } const jwtToken = tokenData.data.token; const urlFrom = window.location.href; const response = await fetch( window.config.apiUrl + "/payments/create-checkout-session-topup", { method: "POST", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ urlFrom, amount: amountDollars, }), } ); const data = await response.json(); if (response.ok && data.url) { window.location.href = data.url; return; } showMessage(data.message || "Failed to create credit checkout", "error"); } catch (error) { console.error("Error creating credit checkout:", error); showMessage("Network error while creating credit checkout", "error"); } finally { if (buyBtn) { buyBtn.classList.remove("is-loading"); buyBtn.disabled = false; } if (loading) loading.style.display = "none"; if (btnText && previousBtnText) btnText.textContent = previousBtnText; refreshTopupDisplay(); } } // Accéder au compte Stripe async function handleStripeAccount() { try { const tokenResponse = await fetch(ajaxurl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce, }); const tokenData = await tokenResponse.json(); const jwtToken = tokenData.data.token; const urlFrom = window.location.href; const response = await fetch( window.config.apiUrl + "/payments/stripe-customer-portal", { method: "POST", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ urlFrom, }), } ); const data = await response.json(); if (response.ok && data.url) { window.open(data.url, "_blank"); } else { showMessage(data.message || "Failed to access Stripe account", "error"); } } catch (error) { console.error("Error accessing Stripe account:", error); showMessage("Network error while accessing Stripe account", "error"); } } // Afficher la modal de confirmation function showCancelModal() { document.getElementById("confirmation-modal").style.display = "flex"; } // Fermer la modal function closeModal() { document.getElementById("confirmation-modal").style.display = "none"; } // Confirmer l'annulation de l'abonnement async function confirmCancelSubscription() { const btn = document.querySelector(".ai-danger-btn"); const loading = btn.querySelector(".ai-loading"); const text = btn.textContent; loading.style.display = "inline-block"; btn.textContent = ""; btn.appendChild(loading); btn.appendChild(document.createTextNode(text)); btn.disabled = true; try { const tokenResponse = await fetch(ajaxurl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "action=aibui_get_token&nonce=" + aiBuilderVars.nonce, }); const tokenData = await tokenResponse.json(); const jwtToken = tokenData.data.token; const response = await fetch( window.config.apiUrl + "/payments/cancel-subscription", { method: "POST", headers: { Authorization: `Bearer ${jwtToken}`, "Content-Type": "application/json", }, } ); const data = await response.json(); if (response.ok) { showMessage("Subscription cancelled successfully", "success"); closeModal(); // Forcer l'état local pour l'UI window.hasResiliate = true; hasSubscription = false; // Mettre à jour l'état local // Recharger les informations utilisateur (qui va mettre à jour l'affichage via updatePlansDisplay) setTimeout(() => { loadUserInfo(); }, 1000); } else { showMessage(data.message || "Failed to cancel subscription", "error"); } } catch (error) { console.error("Error cancelling subscription:", error); showMessage("Network error while cancelling subscription", "error"); } finally { loading.style.display = "none"; btn.textContent = text; btn.disabled = false; } } // Fonctions utilitaires function showMessage(message, type = "info") { const container = document.getElementById("message-container"); container.innerHTML = `
`; container.scrollIntoView({ behavior: "smooth" }); } function setLoading(buttonId, isLoading) { const button = document.getElementById(buttonId); const loading = button.querySelector(".ai-loading"); const text = button.querySelector(".btn-text"); if (isLoading) { loading.style.display = "inline-block"; button.disabled = true; } else { loading.style.display = "none"; button.disabled = false; } }