feat(billing): consume plan variant catalog

Fetch the public billing catalog during account config load and use durable plan IDs from /v1/me for display, analytics, and upgrade/manage labels.

Renderer billing surfaces now resolve plan display data from the catalog and show Unknown when the backend returns an unmapped plan ID.
This commit is contained in:
Ramnique Singh 2026-06-19 16:42:56 +05:30
parent aa8dfb74ad
commit f65f7e8fc8
7 changed files with 52 additions and 26 deletions

View file

@ -1,7 +1,26 @@
import { z } from 'zod';
export const BillingPlanSchema = z.enum(['free', 'starter', 'pro']);
export type BillingPlan = z.infer<typeof BillingPlanSchema>;
export const BillingPlanCategorySchema = z.enum(['free', 'starter', 'pro']);
export type BillingPlanCategory = z.infer<typeof BillingPlanCategorySchema>;
export const BillingPlanIdSchema = z.string().min(1);
export type BillingPlanId = z.infer<typeof BillingPlanIdSchema>;
export const BillingCatalogPlanSchema = z.object({
id: BillingPlanIdSchema,
category: BillingPlanCategorySchema,
displayName: z.string(),
monthlyCredits: z.number(),
dailyCredits: z.number(),
monthlyPriceCents: z.number().nullable(),
archived: z.boolean().optional(),
});
export type BillingCatalogPlan = z.infer<typeof BillingCatalogPlanSchema>;
export const BillingCatalogSchema = z.object({
plans: z.array(BillingCatalogPlanSchema),
});
export type BillingCatalog = z.infer<typeof BillingCatalogSchema>;
export const BillingUsageBucketSchema = z.object({
sanctionedCredits: z.number(),
@ -13,12 +32,21 @@ export type BillingUsageBucket = z.infer<typeof BillingUsageBucketSchema>;
export const BillingInfoSchema = z.object({
userEmail: z.string().nullable(),
userId: z.string().nullable(),
subscriptionPlan: BillingPlanSchema.nullable(),
subscriptionPlanId: BillingPlanIdSchema.nullable(),
subscriptionStatus: z.string().nullable(),
trialExpiresAt: z.string().nullable(),
catalog: BillingCatalogSchema,
monthly: BillingUsageBucketSchema,
daily: BillingUsageBucketSchema.extend({
usageDay: z.string(),
}),
});
export type BillingInfo = z.infer<typeof BillingInfoSchema>;
export function getBillingPlanData(
catalog: BillingCatalog,
planId: string | null | undefined,
): BillingCatalogPlan | null {
if (!planId) return null;
return catalog.plans.find((plan) => plan.id === planId) ?? null;
}

View file

@ -1,7 +1,9 @@
import { z } from 'zod';
import { BillingCatalogSchema } from './billing.js';
export const RowboatApiConfig = z.object({
appUrl: z.string(),
websocketApiUrl: z.string(),
supabaseUrl: z.string(),
billing: BillingCatalogSchema,
});