130 lines
4.5 KiB
JavaScript
130 lines
4.5 KiB
JavaScript
/**
|
|
* Billing & Subscription Logic
|
|
*/
|
|
|
|
const billing = {
|
|
state: {
|
|
subscription: null,
|
|
invoices: []
|
|
},
|
|
|
|
init: async () => {
|
|
console.log('💳 Initializing Billing...');
|
|
await billing.fetchSubscription();
|
|
await billing.fetchInvoices();
|
|
billing.renderUI();
|
|
},
|
|
|
|
fetchSubscription: async () => {
|
|
try {
|
|
const headers = auth.getAuthHeader();
|
|
const res = await fetch('/api/billing/subscription', { headers });
|
|
if (res.ok) {
|
|
billing.state.subscription = await res.json();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch subscription', error);
|
|
}
|
|
},
|
|
|
|
fetchInvoices: async () => {
|
|
try {
|
|
const headers = auth.getAuthHeader();
|
|
const res = await fetch('/api/billing/invoices', { headers });
|
|
if (res.ok) {
|
|
billing.state.invoices = await res.json();
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch invoices', error);
|
|
}
|
|
},
|
|
|
|
renderUI: () => {
|
|
const sub = billing.state.subscription;
|
|
if (!sub) return;
|
|
|
|
// 1. Update Current Plan Badges
|
|
document.querySelectorAll('.current-plan-name').forEach(el => el.textContent = sub.plan);
|
|
|
|
// 2. Render Plan Cards logic
|
|
const plans = ['FREE', 'STARTER', 'PRO', 'ENTERPRISE'];
|
|
plans.forEach(p => {
|
|
const card = document.getElementById(`plan-card-${p.toLowerCase()}`);
|
|
if (card) {
|
|
const btn = card.querySelector('.plan-btn');
|
|
if (p === sub.plan) {
|
|
card.classList.add('border-blue-500/50');
|
|
if (btn) {
|
|
btn.textContent = 'Current Plan';
|
|
btn.disabled = true;
|
|
btn.classList.add('opacity-50');
|
|
}
|
|
} else {
|
|
card.classList.remove('border-blue-500/50');
|
|
if (btn) {
|
|
btn.textContent = p === 'ENTERPRISE' ? 'Contact Sales' : 'Upgrade Now';
|
|
btn.disabled = false;
|
|
btn.classList.remove('opacity-50');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// 3. Render Invoice History
|
|
const tbody = document.getElementById('invoice-table-body');
|
|
if (tbody) {
|
|
if (billing.state.invoices.length === 0) {
|
|
tbody.innerHTML = `<tr><td colspan="4" class="py-10 text-center text-slate-500 text-xs">No transactions yet.</td></tr>`;
|
|
} else {
|
|
tbody.innerHTML = billing.state.invoices.map(inv => `
|
|
<tr class="border-b border-white/[0.02] hover:bg-white/[0.01]">
|
|
<td class="py-4 text-xs font-medium">${new Date(inv.createdAt).toLocaleDateString()}</td>
|
|
<td class="py-4 text-xs font-bold">$${inv.amount}</td>
|
|
<td class="py-4 text-xs">
|
|
<span class="px-2 py-0.5 rounded bg-slate-800 text-[10px] uppercase font-black">${inv.provider}</span>
|
|
</td>
|
|
<td class="py-4 text-xs">
|
|
<span class="status-badge ${inv.status.toLowerCase()}">${inv.status}</span>
|
|
</td>
|
|
</tr>
|
|
`).join('');
|
|
}
|
|
}
|
|
},
|
|
|
|
startCheckout: async (plan, provider) => {
|
|
try {
|
|
const btn = event.target;
|
|
const originalText = btn.textContent;
|
|
btn.textContent = 'Processing...';
|
|
btn.disabled = true;
|
|
|
|
const headers = {
|
|
'Content-Type': 'application/json',
|
|
...auth.getAuthHeader()
|
|
};
|
|
|
|
const res = await fetch('/api/billing/checkout', {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({ plan, provider })
|
|
});
|
|
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
if (data.checkoutUrl) {
|
|
window.location.href = data.checkoutUrl;
|
|
}
|
|
} else {
|
|
alert('Checkout failed. Please try again.');
|
|
}
|
|
|
|
btn.textContent = originalText;
|
|
btn.disabled = false;
|
|
} catch (error) {
|
|
console.error('Checkout error', error);
|
|
alert('An error occurred during checkout.');
|
|
}
|
|
}
|
|
};
|