billing-http.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
1 day ago
| 1 | /** |
| 2 | * GET /api/v1/billing/summary |
| 3 | */ |
| 4 | import { |
| 5 | billingEnforced, |
| 6 | COST_BREAKDOWN, |
| 7 | INDEXING_TOKENS_POLICY, |
| 8 | MONTHLY_INCLUDED_CENTS_BY_TIER, |
| 9 | NOTE_CAP_BY_TIER, |
| 10 | } from './billing-constants.mjs'; |
| 11 | import { |
| 12 | defaultUserRecord, |
| 13 | effectiveMonthlyIndexingTokensIncluded, |
| 14 | effectiveMonthlySearchesIncluded, |
| 15 | effectiveMonthlyIndexJobsIncluded, |
| 16 | effectiveMonthlyConsolidationPassesIncluded, |
| 17 | inferPackConsolidationPassesFromIndexingTokenBalance, |
| 18 | normalizeBillingUser, |
| 19 | } from './billing-logic.mjs'; |
| 20 | import { loadBillingDb, resetMonthlyTokensIfNeeded, mutateBillingDb } from './billing-store.mjs'; |
| 21 | |
| 22 | function effectiveMonthlyIncludedCents(u) { |
| 23 | if (u.tier === 'free') return MONTHLY_INCLUDED_CENTS_BY_TIER.free ?? 0; |
| 24 | return Math.max(0, Math.floor(Number(u.monthly_included_cents) || 0)); |
| 25 | } |
| 26 | |
| 27 | export async function handleBillingSummary(req, res, getUserId) { |
| 28 | const uid = getUserId(req); |
| 29 | if (!uid) return res.status(401).json({ error: 'Unauthorized', code: 'UNAUTHORIZED' }); |
| 30 | |
| 31 | await resetMonthlyTokensIfNeeded(uid); |
| 32 | |
| 33 | // One-time backfill: purchases before pack consolidation passes were stored in the ledger |
| 34 | // left pack_indexing_tokens_balance > 0 with pack_consolidation_passes_balance === 0. |
| 35 | await mutateBillingDb((dbMut) => { |
| 36 | const user = dbMut.users[uid] || defaultUserRecord(uid); |
| 37 | normalizeBillingUser(user); |
| 38 | dbMut.users[uid] = user; |
| 39 | if (user.pack_consolidation_legacy_inferred === true) return; |
| 40 | const packTok = Math.max(0, Math.floor(Number(user.pack_indexing_tokens_balance) || 0)); |
| 41 | const packPass = Math.max(0, Math.floor(Number(user.pack_consolidation_passes_balance) || 0)); |
| 42 | if (packPass > 0 || packTok <= 0) return; |
| 43 | const inferred = inferPackConsolidationPassesFromIndexingTokenBalance(packTok); |
| 44 | if (inferred > 0) { |
| 45 | user.pack_consolidation_passes_balance = inferred; |
| 46 | user.pack_consolidation_legacy_inferred = true; |
| 47 | } |
| 48 | }); |
| 49 | |
| 50 | const db = await loadBillingDb(); |
| 51 | const u = normalizeBillingUser(db.users[uid] || defaultUserRecord(uid)); |
| 52 | |
| 53 | const tier = u.tier || 'beta'; |
| 54 | const noteCapRaw = NOTE_CAP_BY_TIER[tier]; |
| 55 | const noteCap = noteCapRaw === undefined ? null : noteCapRaw; |
| 56 | |
| 57 | return res.json({ |
| 58 | tier, |
| 59 | period_start: u.period_start, |
| 60 | period_end: u.period_end, |
| 61 | monthly_included_cents: u.monthly_included_cents, |
| 62 | monthly_included_effective_cents: effectiveMonthlyIncludedCents(u), |
| 63 | monthly_used_cents: u.monthly_used_cents, |
| 64 | addon_cents: u.addon_cents, |
| 65 | billing_enforced: billingEnforced(), |
| 66 | stripe_configured: Boolean(process.env.STRIPE_SECRET_KEY), |
| 67 | has_active_subscription: Boolean(u.stripe_subscription_id), |
| 68 | credit_policy: |
| 69 | '1 credit = $1 of platform metered usage. Credits are prepaid balance for Knowtation hosted only; not tradable, not a security.', |
| 70 | monthly_indexing_tokens_included: effectiveMonthlyIndexingTokensIncluded(u), |
| 71 | monthly_indexing_tokens_used: Math.max(0, Math.floor(Number(u.monthly_indexing_tokens_used) || 0)), |
| 72 | pack_indexing_tokens_balance: Math.max(0, Math.floor(Number(u.pack_indexing_tokens_balance) || 0)), |
| 73 | pack_consolidation_passes_balance: Math.max(0, Math.floor(Number(u.pack_consolidation_passes_balance) || 0)), |
| 74 | monthly_searches_used: Math.max(0, Math.floor(Number(u.monthly_searches_used) || 0)), |
| 75 | monthly_searches_included: effectiveMonthlySearchesIncluded(u), |
| 76 | monthly_index_jobs_used: Math.max(0, Math.floor(Number(u.monthly_index_jobs_used) || 0)), |
| 77 | monthly_index_jobs_included: effectiveMonthlyIndexJobsIncluded(u), |
| 78 | monthly_consolidation_jobs_used: Math.max(0, Math.floor(Number(u.monthly_consolidation_jobs_used) || 0)), |
| 79 | monthly_consolidation_jobs_included: effectiveMonthlyConsolidationPassesIncluded(u), |
| 80 | consolidation_last_pass_at: u.consolidation_last_pass_at ?? null, |
| 81 | note_cap: noteCap, |
| 82 | indexing_tokens_policy: INDEXING_TOKENS_POLICY, |
| 83 | cost_breakdown: COST_BREAKDOWN, |
| 84 | usage_chart_status: |
| 85 | 'planned: time-series usage + chart in Hub (not required for launch); shadow logs via BILLING_SHADOW_LOG for research.', |
| 86 | }); |
| 87 | } |
File History
2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6
docs: accept Calendar Events v0 spec with Phase 0 security …
Human
1 day ago