Add NATS account-balance SoT and statement review for customers, CS, and sales
Some checks are pending
offline / test (push) Waiting to run
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:
parent
a1a5b957fd
commit
ac38676645
135 changed files with 3078 additions and 130 deletions
|
|
@ -14,6 +14,9 @@ import { applyMonthlyCredit } from './billing/credit';
|
|||
import { InvoiceRepo } from './invoicing';
|
||||
import { RateCard, TierConfig } from './pricing';
|
||||
import { UsageRepo } from './usage';
|
||||
import { CreditLedger } from './credits';
|
||||
import { composeStatement } from './statement';
|
||||
import { BILLING_SUBJECTS, natsPublish, natsStatement, natsAdjust } from './billing-nats';
|
||||
|
||||
/**
|
||||
* Customer portal API (/portal/api): signup, login with optional TOTP 2FA,
|
||||
|
|
@ -44,6 +47,7 @@ export interface PortalDeps {
|
|||
qr: (uri: string) => Promise<string>;
|
||||
sessionTtlMs?: number;
|
||||
now?: () => number;
|
||||
credits?: CreditLedger;
|
||||
}
|
||||
|
||||
const DEFAULT_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
|
@ -198,6 +202,24 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
res.json({ tiers: deps.tiers(), rateCard: deps.rateCard() });
|
||||
});
|
||||
|
||||
router.get('/statement', async (req, res) => {
|
||||
const fromNats = await natsStatement(req.customer!.id);
|
||||
if (fromNats) {
|
||||
res.json({ ...fromNats, source: 'nats' });
|
||||
return;
|
||||
}
|
||||
res.json({
|
||||
...composeStatement({
|
||||
customerId: req.customer!.id,
|
||||
prepaidCents: req.customer!.balanceCents ?? 0,
|
||||
credits: (deps.credits || new CreditLedger()).list(req.customer!.id),
|
||||
usage: deps.usage.listFor(req.customer!.id),
|
||||
invoices: deps.invoices.list({ customerId: req.customer!.id }),
|
||||
}),
|
||||
source: 'local',
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/invoices/:id', (req, res) => {
|
||||
const invoice = deps.invoices.get(req.params.id);
|
||||
if (!invoice || invoice.customerId !== req.customer!.id) {
|
||||
|
|
@ -263,6 +285,20 @@ export function portalRouter(deps: PortalDeps): Router {
|
|||
balanceCents: (req.customer!.balanceCents ?? 0) + result.creditedCents,
|
||||
};
|
||||
if (result.creditedCents > 0) save(deps, customer);
|
||||
if (result.creditedCents > 0) {
|
||||
natsPublish(BILLING_SUBJECTS.PAYMENT_RECORDED, {
|
||||
customerId: customer.id,
|
||||
cents: result.creditedCents,
|
||||
reason: 'reload',
|
||||
});
|
||||
void natsAdjust({
|
||||
customerId: customer.id,
|
||||
cents: result.creditedCents,
|
||||
reason: 'reload',
|
||||
agent: 'portal',
|
||||
kind: 'payment',
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
balanceCents: customer.balanceCents,
|
||||
mode: result.mode,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue