export const SUBJECTS = { STATEMENT_GET: 'verae.billing.statement.get', BALANCE_ADJUST: 'verae.billing.balance.adjust', AUTHZ_CHECK: 'verae.access.authz.check', }; const PLANE = 'staff'; async function authz(subject, payload) { const http = process.env.AUTHZ_URL; if (!http && !process.env.NATS_URL) return { allow: true, subject }; if (http) { const r = await fetch(`${http.replace(/\/$/, '')}/check`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ plane: PLANE, subject, kind: payload?.kind, payload, principal: payload?.agent }), }); return r.json(); } return { allow: true, subject }; } export async function billingRequest(subject, payload) { const decision = await authz(subject, payload); if (decision && decision.allow === false) { const err = new Error(decision.reason || 'denied'); err.status = 403; err.decision = decision; throw err; } const url = process.env.NATS_URL; if (!url) return null; try { const { connect, StringCodec } = await import('nats'); const nc = await connect({ servers: url.split(','), name: 'zappier-customer-service' }); const sc = StringCodec(); const m = await nc.request(subject, sc.encode(JSON.stringify({ ...payload, plane: PLANE })), { timeout: 2000 }); const out = JSON.parse(sc.decode(m.data) || '{}'); await nc.close(); return out; } catch (err) { if (err.status === 403) throw err; return null; } }