* feat(admin): add a Billing tab for subscriptions and sales
The admin can now follow subscriptions and sales without opening Stripe:
- Summary tiles: active subscriptions (monthly/annual), MRR with annual
plans counted as 1/12, sales and revenue for 7d/30d/90d/12m/all time,
all-time sales, subscriptions cancelling at period end, and refunds
and disputes.
- A stacked chart of sales per day, week or month by plan family.
- A by-plan table: active grants, period and all-time sales and revenue.
- A paginated list of grants with plan, Stripe status, active/expired/
revoked and user filters, linked to the admin user, repository or
conference.
- One row of webhook health: pending, failed, oldest pending event, last
error and unreviewed payment reversals.
A sale is one checkout or subscription, so a paper pack's three grants
count once. Revenue is each sale's Stripe list price, resolved once per
price and cached in memory. Currencies are never added together. Without
Stripe the page shows counts and marks revenue as unavailable.
GET /api/admin/billing/summary and /grants sit behind the admin check.
Grants get a { purchasedAt, _id } index for the list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: preserve payment history in admin billing totals
* fix: scope billing history and preserve original sale prices
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Subscriptions sold at a replaced price lost their plan, because a
subscription was matched only against the configured price. Resolve
prices through the configured price, STRIPE_LEGACY_PRICES, then a
registry that now also records subscription prices and every configured
price at startup. Store the price on subscription grants.
Review fixes:
- Recover a Never expire add-on found by reconciliation before expiring.
- Open Checkout for the subscription cycle the hosting option shows.
- Report conference hosting only while the package payment stands.
- Keep automatic updates on a paid-slot repository while its owner subscribes.
- Apply the repository expiration limit only to repositories.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Most authors use the service for one paper a few times a year, so a
subscription does not fit. Add cheap one-time options (Paper pack, Extra
repository, Live updates, Extended expiration, Never expire), cap free
dated expiration at 5 months, keep cheaper subscriptions for heavy users,
and make every new conference a paid review package in three sizes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>