Add NATS account-balance SoT and statement review for customers, CS, and sales
Some checks are pending
offline / test (push) Waiting to run

Internal billing now uses verae.billing.* request-reply and pubs. zappier-account-balance tracks prepaid, credits, usage, and payments. Portal, admin, CS, and sales all review the same statement. Independent Forgejo repos stay split via push-module-repos.
This commit is contained in:
George Lambert 2026-09-11 15:35:37 -04:00
parent a1a5b957fd
commit ac38676645
135 changed files with 3078 additions and 130 deletions

View file

@ -1,4 +1,4 @@
const state = { me: null, usage: null, invoices: [], pricing: null };
const state = { me: null, usage: null, invoices: [], pricing: null, statement: null };
const TOKEN_KEY = 'zappier-portal-token';
let authMode = 'login'; // 'login' | 'signup'
@ -118,8 +118,10 @@ async function load() {
state.usage = await api('/usage');
state.invoices = (await api('/invoices')).invoices;
state.pricing = await api('/pricing');
state.statement = await api('/statement');
renderDashboard();
renderInvoices();
renderStatement();
renderBilling();
renderSecurity();
renderDocs();
@ -200,6 +202,34 @@ async function viewInvoice(id) {
window.open(URL.createObjectURL(blob), '_blank');
}
/* ---------------- statement ---------------- */
function renderStatement() {
const st = state.statement || { prepaidCents: 0, credits: [], usage: [], payments: [] };
const row = (list, cols) =>
(list || [])
.map((r) => `<tr>${cols.map((c) => `<td>${r[c] ?? ''}</td>`).join('')}</tr>`)
.join('') || `<tr><td colspan="${cols.length}" style="color:var(--muted)">None yet.</td></tr>`;
document.getElementById('statement').innerHTML = `
<h2>Statement</h2>
<p class="lede">Prepaid balance, customer-service credits, metered usage, and payments. ${st.source === 'nats' ? 'Live from account-balance over NATS.' : 'From this portals ledger.'}</p>
<div class="stat-grid">
<div class="stat"><div class="k">Prepaid balance</div><div class="v">${fmt(st.prepaidCents || 0)}</div></div>
<div class="stat"><div class="k">Credits</div><div class="v">${st.credits?.length || 0}</div></div>
<div class="stat"><div class="k">Usage events</div><div class="v">${st.usage?.length || 0}</div></div>
<div class="stat"><div class="k">Payments</div><div class="v">${st.payments?.length || 0}</div></div>
</div>
<div class="card"><h3>Credits</h3><table>
<thead><tr><th>Cents</th><th>Reason</th><th>Agent</th><th>When</th></tr></thead>
<tbody>${row(st.credits, ['cents', 'reason', 'agent', 'at'])}</tbody></table></div>
<div class="card"><h3>Usage</h3><table>
<thead><tr><th>Endpoint</th><th>Cents</th><th>When</th></tr></thead>
<tbody>${row(st.usage, ['endpointId', 'cents', 'at'])}</tbody></table></div>
<div class="card"><h3>Payments</h3><table>
<thead><tr><th>Cents</th><th>Kind</th><th>Reason</th><th>When</th></tr></thead>
<tbody>${row(st.payments, ['cents', 'kind', 'reason', 'at'])}</tbody></table></div>`;
}
/* ---------------- billing ---------------- */
function renderBilling() {