update billing types

This commit is contained in:
Ramnique Singh 2025-08-23 09:59:14 +05:30
parent 219d4c7901
commit b49e14fdf8
9 changed files with 37 additions and 26 deletions

View file

@ -21,9 +21,8 @@ import {
ModelsResponse ModelsResponse
} from "../lib/types/billing_types"; } from "../lib/types/billing_types";
import { z } from "zod"; import { z } from "zod";
import { WithStringId } from "../lib/types/types";
export async function getCustomer(): Promise<WithStringId<z.infer<typeof Customer>>> { export async function getCustomer(): Promise<z.infer<typeof Customer>> {
const user = await authCheck(); const user = await authCheck();
if (!user.billingCustomerId) { if (!user.billingCustomerId) {
throw new Error("Customer not found"); throw new Error("Customer not found");
@ -41,7 +40,7 @@ export async function authorizeUserAction(request: z.infer<typeof AuthorizeReque
} }
const customer = await getCustomer(); const customer = await getCustomer();
const response = await authorize(customer._id, request); const response = await authorize(customer.id, request);
return response; return response;
} }
@ -51,7 +50,7 @@ export async function logUsage(request: z.infer<typeof LogUsageRequest>) {
} }
const customer = await getCustomer(); const customer = await getCustomer();
await libLogUsage(customer._id, request); await libLogUsage(customer.id, request);
return; return;
} }
@ -61,7 +60,7 @@ export async function getCustomerPortalUrl(returnUrl: string): Promise<string> {
} }
const customer = await getCustomer(); const customer = await getCustomer();
return await createCustomerPortalSession(customer._id, returnUrl); return await createCustomerPortalSession(customer.id, returnUrl);
} }
export async function getPrices(): Promise<z.infer<typeof PricesResponse>> { export async function getPrices(): Promise<z.infer<typeof PricesResponse>> {
@ -80,7 +79,7 @@ export async function updateSubscriptionPlan(plan: z.infer<typeof SubscriptionPl
const customer = await getCustomer(); const customer = await getCustomer();
const request: z.infer<typeof UpdateSubscriptionPlanRequest> = { plan, returnUrl }; const request: z.infer<typeof UpdateSubscriptionPlanRequest> = { plan, returnUrl };
const url = await libUpdateSubscriptionPlan(customer._id, request); const url = await libUpdateSubscriptionPlan(customer.id, request);
return url; return url;
} }
@ -90,6 +89,6 @@ export async function getEligibleModels(): Promise<z.infer<typeof ModelsResponse
} }
const customer = await getCustomer(); const customer = await getCustomer();
const response = await libGetEligibleModels(customer._id); const response = await libGetEligibleModels(customer.id);
return response; return response;
} }

View file

@ -8,7 +8,6 @@ import { z } from "zod";
import { tokens } from "@/app/styles/design-tokens"; import { tokens } from "@/app/styles/design-tokens";
import { SectionHeading } from "@/components/ui/section-heading"; import { SectionHeading } from "@/components/ui/section-heading";
import { HorizontalDivider } from "@/components/ui/horizontal-divider"; import { HorizontalDivider } from "@/components/ui/horizontal-divider";
import { WithStringId } from "@/app/lib/types/types";
import clsx from 'clsx'; import clsx from 'clsx';
import { getCustomerPortalUrl } from "../actions/billing.actions"; import { getCustomerPortalUrl } from "../actions/billing.actions";
import { useState } from "react"; import { useState } from "react";
@ -31,7 +30,7 @@ const planDetails = {
}; };
interface BillingPageProps { interface BillingPageProps {
customer: WithStringId<z.infer<typeof Customer>>; customer: z.infer<typeof Customer>;
usage: z.infer<typeof UsageResponse>; usage: z.infer<typeof UsageResponse>;
} }

View file

@ -13,7 +13,7 @@ export default async function Page(
) { ) {
const searchParams = await props.searchParams; const searchParams = await props.searchParams;
const customer = await requireBillingCustomer(); const customer = await requireBillingCustomer();
await syncWithStripe(customer._id); await syncWithStripe(customer.id);
const redirectUrl = searchParams.redirect as string; const redirectUrl = searchParams.redirect as string;
redirect(redirectUrl || '/projects'); redirect(redirectUrl || '/projects');
} }

View file

@ -12,6 +12,6 @@ export default async function Page() {
} }
const customer = await requireBillingCustomer(); const customer = await requireBillingCustomer();
const usage = await getUsage(customer._id); const usage = await getUsage(customer.id);
return <BillingPage customer={customer} usage={usage} />; return <BillingPage customer={customer} usage={usage} />;
} }

View file

@ -1,4 +1,3 @@
import { WithStringId } from './types/types';
import { z } from 'zod'; import { z } from 'zod';
import { Customer, AuthorizeRequest, AuthorizeResponse, LogUsageRequest, UsageResponse, CustomerPortalSessionResponse, PricesResponse, UpdateSubscriptionPlanRequest, UpdateSubscriptionPlanResponse, ModelsResponse, UsageItem } from './types/billing_types'; import { Customer, AuthorizeRequest, AuthorizeResponse, LogUsageRequest, UsageResponse, CustomerPortalSessionResponse, PricesResponse, UpdateSubscriptionPlanRequest, UpdateSubscriptionPlanResponse, ModelsResponse, UsageItem } from './types/billing_types';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
@ -14,7 +13,7 @@ const BILLING_API_KEY = process.env.BILLING_API_KEY || 'test';
let logCounter = 1; let logCounter = 1;
const GUEST_BILLING_CUSTOMER = { const GUEST_BILLING_CUSTOMER = {
_id: "guest-user", id: "guest-user",
userId: "guest-user", userId: "guest-user",
name: "Guest", name: "Guest",
email: "guest@rowboatlabs.com", email: "guest@rowboatlabs.com",
@ -23,7 +22,6 @@ const GUEST_BILLING_CUSTOMER = {
subscriptionPlan: "free" as const, subscriptionPlan: "free" as const,
subscriptionStatus: "active" as const, subscriptionStatus: "active" as const,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
}; };
@ -41,7 +39,7 @@ export class UsageTracker{
} }
} }
export async function getCustomerForUserId(userId: string): Promise<WithStringId<z.infer<typeof Customer>> | null> { export async function getCustomerForUserId(userId: string): Promise<z.infer<typeof Customer> | null> {
const usersRepository = container.resolve<IUsersRepository>("usersRepository"); const usersRepository = container.resolve<IUsersRepository>("usersRepository");
const user = await usersRepository.fetch(userId); const user = await usersRepository.fetch(userId);
@ -64,10 +62,10 @@ export async function getCustomerIdForProject(projectId: string): Promise<string
if (!customer) { if (!customer) {
throw new Error("User has no billing customer id"); throw new Error("User has no billing customer id");
} }
return customer._id; return customer.id;
} }
export async function getBillingCustomer(id: string): Promise<WithStringId<z.infer<typeof Customer>> | null> { export async function getBillingCustomer(id: string): Promise<z.infer<typeof Customer> | null> {
const response = await fetch(`${BILLING_API_URL}/api/customers/${id}`, { const response = await fetch(`${BILLING_API_URL}/api/customers/${id}`, {
method: 'GET', method: 'GET',
headers: { headers: {
@ -86,7 +84,7 @@ export async function getBillingCustomer(id: string): Promise<WithStringId<z.inf
return parseResult.data; return parseResult.data;
} }
async function createBillingCustomer(userId: string, email: string): Promise<WithStringId<z.infer<typeof Customer>>> { async function createBillingCustomer(userId: string, email: string): Promise<z.infer<typeof Customer>> {
const response = await fetch(`${BILLING_API_URL}/api/customers`, { const response = await fetch(`${BILLING_API_URL}/api/customers`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -266,7 +264,7 @@ export async function getEligibleModels(customerId: string): Promise<z.infer<typ
* const billingCustomer = await requireBillingCustomer(); * const billingCustomer = await requireBillingCustomer();
* ``` * ```
*/ */
export async function requireBillingCustomer(): Promise<WithStringId<z.infer<typeof Customer>>> { export async function requireBillingCustomer(): Promise<z.infer<typeof Customer>> {
const user = await requireAuth(); const user = await requireAuth();
const usersRepository = container.resolve<IUsersRepository>("usersRepository"); const usersRepository = container.resolve<IUsersRepository>("usersRepository");
@ -283,7 +281,7 @@ export async function requireBillingCustomer(): Promise<WithStringId<z.infer<typ
} }
// fetch or create customer // fetch or create customer
let customer: WithStringId<z.infer<typeof Customer>> | null; let customer: z.infer<typeof Customer> | null;
if (user.billingCustomerId) { if (user.billingCustomerId) {
customer = await getBillingCustomer(user.billingCustomerId); customer = await getBillingCustomer(user.billingCustomerId);
} else { } else {
@ -291,7 +289,7 @@ export async function requireBillingCustomer(): Promise<WithStringId<z.infer<typ
console.log("created billing customer", JSON.stringify({ userId: user.id, customer })); console.log("created billing customer", JSON.stringify({ userId: user.id, customer }));
// update customer id in db // update customer id in db
await usersRepository.updateBillingCustomerId(user.id, customer._id); await usersRepository.updateBillingCustomerId(user.id, customer.id);
} }
if (!customer) { if (!customer) {
throw new Error("Failed to fetch or create billing customer"); throw new Error("Failed to fetch or create billing customer");
@ -312,7 +310,7 @@ export async function requireBillingCustomer(): Promise<WithStringId<z.infer<typ
* const billingCustomer = await requireActiveBillingSubscription(); * const billingCustomer = await requireActiveBillingSubscription();
* ``` * ```
*/ */
export async function requireActiveBillingSubscription(): Promise<WithStringId<z.infer<typeof Customer>>> { export async function requireActiveBillingSubscription(): Promise<z.infer<typeof Customer>> {
const billingCustomer = await requireBillingCustomer(); const billingCustomer = await requireBillingCustomer();
if (USE_BILLING && billingCustomer.subscriptionStatus !== "active" && billingCustomer.subscriptionStatus !== "past_due") { if (USE_BILLING && billingCustomer.subscriptionStatus !== "active" && billingCustomer.subscriptionStatus !== "past_due") {

View file

@ -1,3 +1,17 @@
/**
* 🚨 ATTENTION: DO NOT MODIFY THIS FILE! 🚨
*
* This file contains billing types that are manually copied
* from the billing service repository. Any manual changes will be
* overwritten during the next sync.
*
* If you need to modify billing types:
* 1. Make changes in the billing service repo
* 2. Copy the updated file from there
* 3. Never edit this file directly
*
* This file is a manual copy - keep it in sync with the source!
*/
import { z } from "zod"; import { z } from "zod";
export const SubscriptionPlan = z.enum(["free", "starter", "pro"]); export const SubscriptionPlan = z.enum(["free", "starter", "pro"]);
@ -57,7 +71,7 @@ export const LogUsageRequest = z.object({
export const CustomerUsageData = z.record(z.string(), z.number()); export const CustomerUsageData = z.record(z.string(), z.number());
export const Customer = z.object({ export const Customer = z.object({
_id: z.string(), id: z.string(),
userId: z.string(), userId: z.string(),
email: z.string(), email: z.string(),
stripeCustomerId: z.string(), stripeCustomerId: z.string(),
@ -65,7 +79,7 @@ export const Customer = z.object({
subscriptionPlan: SubscriptionPlan.optional(), subscriptionPlan: SubscriptionPlan.optional(),
subscriptionStatus: z.enum([ 'active', 'past_due' ]).optional(), subscriptionStatus: z.enum([ 'active', 'past_due' ]).optional(),
createdAt: z.string().datetime(), createdAt: z.string().datetime(),
updatedAt: z.string().datetime(), updatedAt: z.string().datetime().optional(),
subscriptionPlanUpdatedAt: z.string().datetime().optional(), subscriptionPlanUpdatedAt: z.string().datetime().optional(),
usage: CustomerUsageData.optional(), usage: CustomerUsageData.optional(),
usageUpdatedAt: z.string().datetime().optional(), usageUpdatedAt: z.string().datetime().optional(),

View file

@ -47,7 +47,7 @@ export default async function Page(
let eligibleModels: z.infer<typeof ModelsResponse> | "*" = '*'; let eligibleModels: z.infer<typeof ModelsResponse> | "*" = '*';
if (USE_BILLING) { if (USE_BILLING) {
eligibleModels = await getEligibleModels(customer._id); eligibleModels = await getEligibleModels(customer.id);
} }
console.log('/workflow page.tsx serve'); console.log('/workflow page.tsx serve');

View file

@ -165,6 +165,7 @@ export class RunConversationTurnUseCase implements IRunConversationTurnUseCase {
} }
} finally { } finally {
// Log billing usage // Log billing usage
console.log('finally logging billing usage');
if (USE_BILLING && billingCustomerId) { if (USE_BILLING && billingCustomerId) {
await logUsage(billingCustomerId, { await logUsage(billingCustomerId, {
items: usageTracker.flush(), items: usageTracker.flush(),

View file

@ -65,7 +65,7 @@ export class CreateProjectUseCase implements ICreateProjectUseCase {
} }
// validate enough credits // validate enough credits
const result = await authorize(customer._id, { const result = await authorize(customer.id, {
type: "create_project", type: "create_project",
data: { data: {
existingProjectCount: count, existingProjectCount: count,