From 78d8e132501a3de83445c9b32d8e9de487f8ce18 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Sun, 18 May 2025 03:34:05 +0700 Subject: [PATCH] feat: WIP stripe billing refactor update for 2025 --- apps/api/src/api-v1/index.ts | 3 ++ apps/api/src/lib/billing/upsert-consumer.ts | 31 +++++++++++++++------ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/apps/api/src/api-v1/index.ts b/apps/api/src/api-v1/index.ts index 3e767ad4..65b2bec1 100644 --- a/apps/api/src/api-v1/index.ts +++ b/apps/api/src/api-v1/index.ts @@ -8,6 +8,7 @@ import { registerV1AdminConsumersGetConsumerByToken } from './consumers/admin-ge import { registerV1ConsumersCreateConsumer } from './consumers/create-consumer' import { registerV1ConsumersGetConsumer } from './consumers/get-consumer' import { registerV1ProjectsListConsumers } from './consumers/list-consumers' +import { registerV1ConsumersRefreshConsumerToken } from './consumers/refresh-consumer-token' import { registerV1ConsumersUpdateConsumer } from './consumers/update-consumer' import { registerHealthCheck } from './health-check' import { registerV1ProjectsCreateProject } from './projects/create-project' @@ -90,6 +91,7 @@ registerV1ProjectsUpdateProject(privateRouter) registerV1ConsumersGetConsumer(privateRouter) registerV1ConsumersCreateConsumer(privateRouter) registerV1ConsumersUpdateConsumer(privateRouter) +registerV1ConsumersRefreshConsumerToken(privateRouter) registerV1ProjectsListConsumers(privateRouter) // Webhook event handlers @@ -131,4 +133,5 @@ export type ApiRoutes = | ReturnType | ReturnType | ReturnType + | ReturnType | ReturnType diff --git a/apps/api/src/lib/billing/upsert-consumer.ts b/apps/api/src/lib/billing/upsert-consumer.ts index 611cb077..c43b9f21 100644 --- a/apps/api/src/lib/billing/upsert-consumer.ts +++ b/apps/api/src/lib/billing/upsert-consumer.ts @@ -1,9 +1,11 @@ import { parseFaasIdentifier } from '@agentic/validators' import { and, db, eq, schema } from '@/db' -import { assert, sha256 } from '@/lib/utils' +import { assert } from '@/lib/utils' import type { AuthenticatedContext } from '../types' +import { acl } from '../acl' +import { createConsumerToken } from '../create-consumer-token' import { upsertStripeConnectCustomer } from './upsert-stripe-connect-customer' import { upsertStripeCustomer } from './upsert-stripe-customer' import { upsertStripePricing } from './upsert-stripe-pricing' @@ -16,12 +18,12 @@ export async function upsertConsumer( deploymentId, consumerId }: { - plan?: string + plan: string deploymentId?: string consumerId?: string } ) { - assert(consumerId || deploymentId, 400) + assert(consumerId || deploymentId, 400, 'Missing required "deploymentId"') const userId = c.get('userId') let projectId: string | undefined @@ -33,7 +35,6 @@ export async function upsertConsumer( if (!consumerId) { assert(projectId, 400, 'Missing required "deploymentId"') - assert(plan, 400, 'Missing required "plan"') } const [{ user, stripeCustomer }, existingConsumer] = await Promise.all([ @@ -52,18 +53,24 @@ export async function upsertConsumer( if (consumerId) { assert(existingConsumer, 404, `Consumer not found "${consumerId}"`) assert(existingConsumer.id === consumerId, 403) + await acl(c, existingConsumer, { label: 'Consumer' }) if (projectId) { assert( existingConsumer.projectId === projectId, 400, - `Deployment "${deploymentId}" does not belong to with consumer "${consumerId}" project "${existingConsumer.projectId}"` + `Deployment "${deploymentId}" does not belong to project "${existingConsumer.projectId}" for consumer "${consumerId}"` ) } - consumerId = existingConsumer.id deploymentId ??= existingConsumer.deploymentId projectId ??= existingConsumer.projectId + } else { + assert( + !existingConsumer, + 409, + `User "${user.email}" already has a subscription for project "${projectId ?? ''}"` + ) } assert(consumerId) @@ -99,6 +106,15 @@ export async function upsertConsumer( `Deployment has been disabled by its owner "${deployment.id}"` ) + if (plan) { + const pricingPlan = deployment.pricingPlanMap[plan] + assert( + pricingPlan, + 400, + `Pricing plan "${plan}" not found for deployment "${deploymentId}"` + ) + } + let consumer = existingConsumer if (consumer) { @@ -116,8 +132,7 @@ export async function upsertConsumer( userId, projectId, deploymentId, - // TODO: refactor / improve token generation - token: sha256().slice(0, 24), + token: createConsumerToken(), _stripeCustomerId: stripeCustomer.id }) }