Bind each customer to a Verae userId for hop tracing
Some checks are pending
offline / test (push) Waiting to run

Signup registers/binds a Verae central user and stores veraeUserId. Public access stays the zappier API key. Chain JWTs stay server-side behind tokenRef. Authz, billing, and jobs.watch carry veraeUserId.
This commit is contained in:
George Lambert 2026-09-11 16:18:06 -04:00
parent 1b199ca4d4
commit 345aeeead9
79 changed files with 703 additions and 95 deletions

View file

@ -5,16 +5,23 @@ export class AccountBooks {
constructor() {
/** @type {Record<string, number>} */
this.prepaid = {};
/** @type {Record<string, string>} */
this.veraeUserIds = {};
this.credits = [];
this.usage = [];
this.payments = [];
}
remember(customerId, veraeUserId) {
if (customerId && veraeUserId) this.veraeUserIds[customerId] = veraeUserId;
}
prepaidCents(customerId) {
return this.prepaid[customerId] ?? 0;
}
adjust({ customerId, cents, reason, agent, kind = 'credit' }) {
adjust({ customerId, cents, reason, agent, kind = 'credit', veraeUserId }) {
this.remember(customerId, veraeUserId);
const delta = Math.trunc(Number(cents) || 0);
const next = this.prepaidCents(customerId) + delta;
this.prepaid[customerId] = next;
@ -34,6 +41,7 @@ export class AccountBooks {
}
recordUsage(entry) {
this.remember(entry.customerId, entry.veraeUserId);
const cents = Number(entry.cents) || 0;
const customerId = entry.customerId;
const next = this.prepaidCents(customerId) - cents;
@ -63,6 +71,7 @@ export class AccountBooks {
const match = (rows) => rows.filter((r) => r.customerId === customerId).slice(0, 100);
return {
customerId,
veraeUserId: this.veraeUserIds[customerId],
prepaidCents: this.prepaidCents(customerId),
credits: match(this.credits),
usage: match(this.usage),
@ -74,6 +83,7 @@ export class AccountBooks {
export function handle(subject, payload, books) {
const p = payload || {};
if (subject.endsWith('balance.get') || subject.endsWith('statement.get')) {
books.remember(p.customerId, p.veraeUserId);
return books.statement(p.customerId);
}
if (subject.endsWith('balance.adjust') || subject.endsWith('credit.applied')) {

View file

@ -5,11 +5,16 @@ import { SUBJECTS } from '../src/subjects.js';
test('adjust credits prepaid and statement lists credits usage payments', () => {
const books = new AccountBooks();
handle(SUBJECTS.BALANCE_ADJUST, { customerId: 'c1', cents: 500, reason: 'goodwill', agent: 'cs' }, books);
handle(
SUBJECTS.BALANCE_ADJUST,
{ customerId: 'c1', veraeUserId: 'vu_abc', cents: 500, reason: 'goodwill', agent: 'cs' },
books,
);
handle(SUBJECTS.USAGE_RECORDED, { customerId: 'c1', endpointId: 'timestamp', cents: 4 }, books);
handle(SUBJECTS.PAYMENT_RECORDED, { customerId: 'c1', cents: 1000, reason: 'reload' }, books);
const st = handle(SUBJECTS.STATEMENT_GET, { customerId: 'c1' }, books);
assert.equal(st.prepaidCents, 1496);
assert.equal(st.veraeUserId, 'vu_abc');
assert.equal(st.credits[0].cents, 500);
assert.equal(st.usage[0].endpointId, 'timestamp');
assert.equal(st.payments[0].kind, 'payment');