import { useSyncExternalStore } from "react";
/** The structured part of a 402 Payment Required, as sent by the server (see paymentRequired in errors.ts). */
export type PaymentDetail = {
message: string;
meter: string;
reason: "plan_limit" | "spending_limit" | "paid_feature";
/** Whether the viewer can pay for the blocked account; false for, say, a collaborator on someone's repo. */
manageable: boolean;
account: { name: string; kind: string } | null;
billingPath: string | null;
};
const HTTP_PAYMENT_REQUIRED = 402;
/** The payment detail of a thrown server error, or null when it is not an out-of-budget 402. */
export function paymentDetail(error: unknown): PaymentDetail | null {
if (!(error instanceof Error)) return null;
const { status, detail } = error as Error & { status?: number; detail?: Record<string, unknown> | null };
if (status !== HTTP_PAYMENT_REQUIRED || !detail || detail.code !== "payment_required") return null;
if (typeof detail.meter !== "string") return null;
const reason = detail.reason === "spending_limit" || detail.reason === "paid_feature" ? detail.reason : "plan_limit";
const account = detail.account as { name?: unknown; kind?: unknown } | undefined;
return {
message: error.message,
meter: detail.meter,
reason,
manageable: detail.manageable === true,
account:
account && typeof account.name === "string" ? { name: account.name, kind: String(account.kind ?? "user") } : null,
billingPath: typeof detail.billingPath === "string" ? detail.billingPath : null,
};
}
/** For plain fetch routes (uploads): opens the upsell when a JSON 402 comes back. Returns whether it did. */
export function upsellFromResponse(status: number, payload: unknown): boolean {
if (status !== HTTP_PAYMENT_REQUIRED || !payload || typeof payload !== "object") return false;
const body = payload as Record<string, unknown>;
const error = Object.assign(new Error(typeof body.error === "string" ? body.error : "Payment required"), {
status,