208 lines · 8.4 KB
import { all, first } from "../db";
import { accountDb } from "../scoped-db";
import { meteredAccountIds } from "./directory";
import { leaderboardRows, PERIOD_USAGE_SQL, usageLeaderboardReady, type UsageRow } from "./leaderboard";
import { forbidden, notFoundError } from "../errors";
import { HttpError } from "../errors";
import type { ErrorDetail } from "../errors";
import { publicUrl } from "../events/emit";
import type { MeterId } from "~/lib/meters";
import type { Plan } from "~/lib/plans";
import type { Viewer } from "~/lib/types";

/**
 * `canEdit` (checkout, portal, limits) is owner-only; `canView` adds site admins, who may inspect usage
 * but never act on a customer's Stripe account.
 */
export type BillingAccount = {
  accountId: string;
  name: string;
  kind: "user" | "org";
  canEdit: boolean;
  canView: boolean;
};

/** The account whose usage `viewer` may see: themselves, or an org they belong to (owners may edit). */
export async function billingAccountFor(viewer: Viewer, org: string | null): Promise<BillingAccount> {
  if (!org) return { accountId: viewer.id, name: viewer.name, kind: "user", canEdit: true, canView: true };
  const account = await first<{ id: string; name: string }>(
    "SELECT id, name FROM accounts WHERE name = ? COLLATE NOCASE AND kind = 'org'",
    org,
  );
  if (!account) throw notFoundError("Organization not found");
  const member = await first<{ role: string }>(
    "SELECT role FROM org_members WHERE org_id = ? AND user_id = ?",
    account.id,
    viewer.id,
  );
  if (!member && !viewer.isAdmin) throw notFoundError("Organization not found");
  return {
    accountId: account.id,