mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-06 20:42:15 +02:00
update billing types
This commit is contained in:
parent
219d4c7901
commit
b49e14fdf8
9 changed files with 37 additions and 26 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
@ -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} />;
|
||||||
}
|
}
|
||||||
|
|
@ -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") {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue