additional stripe changes and Billing info cleanup

This commit is contained in:
Corey Blais
2026-04-16 20:44:53 -04:00
parent c757132cbd
commit 96e5694b01
11 changed files with 258 additions and 76 deletions
+43 -17
View File
@@ -1,5 +1,5 @@
import { db } from '../db/client.js';
import type { BillingPlan, RescueVerificationStatus, SubscriptionStatus, UserRow, WorkspaceMemberRow, WorkspaceRow, WorkspaceType } from '../types.js';
import type { BillingInterval, BillingPlan, RescueVerificationStatus, SubscriptionStatus, UserRow, WorkspaceMemberRow, WorkspaceRow, WorkspaceType } from '../types.js';
export const getNextWorkspaceId = async () => {
const result = await db.query<{ next_id: number }>('SELECT COALESCE(MAX(id), 0) + 1 AS next_id FROM workspaces');
@@ -8,7 +8,7 @@ export const getNextWorkspaceId = async () => {
export const getWorkspaceById = async (workspaceId: number) => {
const result = await db.query<WorkspaceRow>(
`SELECT id, name, workspace_type, billing_email, billing_plan, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at
`SELECT id, name, workspace_type, billing_email, billing_plan, billing_interval, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at
FROM workspaces
WHERE id = $1`,
[workspaceId],
@@ -36,6 +36,7 @@ export const listMembershipsForUser = async (userId: string) => {
workspace_type: WorkspaceType;
billing_email: string | null;
billing_plan: BillingPlan;
billing_interval: BillingInterval;
subscription_status: WorkspaceRow['subscription_status'];
stripe_customer_id: string | null;
stripe_subscription_id: string | null;
@@ -57,6 +58,7 @@ export const listMembershipsForUser = async (userId: string) => {
workspaces.workspace_type,
workspaces.billing_email,
workspaces.billing_plan,
workspaces.billing_interval,
workspaces.subscription_status,
workspaces.stripe_customer_id,
workspaces.stripe_subscription_id,
@@ -103,8 +105,8 @@ export const ensurePersonalWorkspaceForUser = async (user: UserRow) => {
if (!unclaimed.rowCount) {
await db.query(
`INSERT INTO workspaces (id, name, workspace_type, billing_plan, billing_email, subscription_status, rescue_verification_status)
VALUES ($1, $2, 'standard', 'household_basic', $3, 'active', 'not_required')`,
`INSERT INTO workspaces (id, name, workspace_type, billing_plan, billing_interval, billing_email, subscription_status, rescue_verification_status)
VALUES ($1, $2, 'standard', 'household_basic', 'monthly', $3, 'active', 'not_required')`,
[workspaceId, `${user.name}'s Flock`, user.email],
);
} else {
@@ -113,6 +115,7 @@ export const ensurePersonalWorkspaceForUser = async (user: UserRow) => {
SET name = $2,
workspace_type = 'standard',
billing_plan = 'household_basic',
billing_interval = 'monthly',
billing_email = $3,
subscription_status = 'active',
rescue_verification_status = 'not_required',
@@ -154,6 +157,7 @@ export const createWorkspace = async ({
workspaceType,
billingEmail,
billingPlan,
billingInterval,
owner,
}: {
id: number;
@@ -161,17 +165,19 @@ export const createWorkspace = async ({
workspaceType: WorkspaceType;
billingEmail: string | null;
billingPlan: BillingPlan;
billingInterval: BillingInterval;
owner: UserRow;
}) => {
await db.query(
`INSERT INTO workspaces (id, name, workspace_type, billing_email, billing_plan, subscription_status, rescue_verification_status)
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
`INSERT INTO workspaces (id, name, workspace_type, billing_email, billing_plan, billing_interval, subscription_status, rescue_verification_status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
[
id,
name,
workspaceType,
billingEmail,
billingPlan,
billingInterval,
workspaceType === 'rescue' ? 'active' : 'active',
workspaceType === 'rescue' ? 'pending' : 'not_required',
],
@@ -192,12 +198,14 @@ export const updateWorkspace = async ({
workspaceType,
billingEmail,
billingPlan,
billingInterval,
}: {
workspaceId: number;
name: string;
workspaceType: WorkspaceType;
billingEmail: string | null;
billingPlan: BillingPlan;
billingInterval: BillingInterval;
}) => {
const result = await db.query<WorkspaceRow>(
`WITH input AS (
@@ -206,13 +214,15 @@ export const updateWorkspace = async ({
$2::varchar AS name,
$3::varchar AS workspace_type,
$4::varchar AS billing_email,
$5::varchar AS billing_plan
$5::varchar AS billing_plan,
$6::varchar AS billing_interval
)
UPDATE workspaces
SET name = input.name,
workspace_type = input.workspace_type,
billing_email = input.billing_email,
billing_plan = input.billing_plan,
billing_interval = input.billing_interval,
rescue_verification_status = CASE
WHEN input.workspace_type = 'rescue' AND workspaces.rescue_verification_status = 'not_required' THEN 'pending'
WHEN input.workspace_type = 'standard' THEN 'not_required'
@@ -221,8 +231,8 @@ export const updateWorkspace = async ({
updated_at = CURRENT_TIMESTAMP
FROM input
WHERE workspaces.id = input.workspace_id
RETURNING workspaces.id, workspaces.name, workspaces.workspace_type, workspaces.billing_email, workspaces.billing_plan, workspaces.subscription_status, workspaces.stripe_customer_id, workspaces.stripe_subscription_id, workspaces.rescue_verification_status, workspaces.created_at, workspaces.updated_at`,
[workspaceId, name, workspaceType, billingEmail, billingPlan],
RETURNING workspaces.id, workspaces.name, workspaces.workspace_type, workspaces.billing_email, workspaces.billing_plan, workspaces.billing_interval, workspaces.subscription_status, workspaces.stripe_customer_id, workspaces.stripe_subscription_id, workspaces.rescue_verification_status, workspaces.created_at, workspaces.updated_at`,
[workspaceId, name, workspaceType, billingEmail, billingPlan, billingInterval],
);
return result.rows[0] ?? null;
@@ -256,7 +266,7 @@ export const findAlternateWorkspaceForUser = async (userId: string, excludeWorks
export const listOwnedWorkspacesByOwnerEmail = async (ownerEmail: string, excludeWorkspaceId: number) => {
const result = await db.query<WorkspaceRow>(
`SELECT workspaces.id, workspaces.name, workspaces.workspace_type, workspaces.billing_email, workspaces.billing_plan, workspaces.subscription_status, workspaces.stripe_customer_id, workspaces.stripe_subscription_id, workspaces.rescue_verification_status, workspaces.created_at, workspaces.updated_at
`SELECT workspaces.id, workspaces.name, workspaces.workspace_type, workspaces.billing_email, workspaces.billing_plan, workspaces.billing_interval, workspaces.subscription_status, workspaces.stripe_customer_id, workspaces.stripe_subscription_id, workspaces.rescue_verification_status, workspaces.created_at, workspaces.updated_at
FROM workspace_members
INNER JOIN workspaces ON workspaces.id = workspace_members.workspace_id
WHERE LOWER(COALESCE(workspace_members.invite_email, workspace_members.email)) = LOWER($1)
@@ -356,6 +366,7 @@ export const listRescueWorkspacesForAdmin = async () => {
workspaces.workspace_type,
workspaces.billing_email,
workspaces.billing_plan,
workspaces.billing_interval,
workspaces.subscription_status,
workspaces.stripe_customer_id,
workspaces.stripe_subscription_id,
@@ -397,6 +408,10 @@ export const updateRescueVerificationStatus = async (workspaceId: number, status
WHEN $2 = 'rejected' THEN 'household_basic'
ELSE billing_plan
END,
billing_interval = CASE
WHEN $2 = 'rejected' THEN 'monthly'
ELSE billing_interval
END,
rescue_verification_status = CASE
WHEN $2 = 'rejected' THEN 'not_required'
ELSE $2
@@ -404,7 +419,7 @@ export const updateRescueVerificationStatus = async (workspaceId: number, status
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
AND workspace_type = 'rescue'
RETURNING id, name, workspace_type, billing_email, billing_plan, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
RETURNING id, name, workspace_type, billing_email, billing_plan, billing_interval, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
[workspaceId, status],
);
@@ -416,12 +431,13 @@ export const cancelRescueVerificationRequest = async (workspaceId: number) => {
`UPDATE workspaces
SET workspace_type = 'standard',
billing_plan = 'household_basic',
billing_interval = 'monthly',
rescue_verification_status = 'not_required',
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
AND workspace_type = 'rescue'
AND rescue_verification_status = 'pending'
RETURNING id, name, workspace_type, billing_email, billing_plan, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
RETURNING id, name, workspace_type, billing_email, billing_plan, billing_interval, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
[workspaceId],
);
@@ -434,7 +450,7 @@ export const setWorkspaceStripeCustomerId = async (workspaceId: number, stripeCu
SET stripe_customer_id = $2,
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING id, name, workspace_type, billing_email, billing_plan, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
RETURNING id, name, workspace_type, billing_email, billing_plan, billing_interval, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
[workspaceId, stripeCustomerId],
);
@@ -446,21 +462,27 @@ export const setWorkspaceStripeSubscription = async ({
stripeCustomerId,
stripeSubscriptionId,
subscriptionStatus,
billingPlan,
billingInterval,
}: {
workspaceId: number;
stripeCustomerId: string | null;
stripeSubscriptionId: string;
subscriptionStatus: SubscriptionStatus;
billingPlan?: BillingPlan | null;
billingInterval?: BillingInterval | null;
}) => {
const result = await db.query<WorkspaceRow>(
`UPDATE workspaces
SET stripe_customer_id = COALESCE($2, stripe_customer_id),
stripe_subscription_id = $3,
subscription_status = $4,
billing_plan = COALESCE($5, billing_plan),
billing_interval = COALESCE($6, billing_interval),
updated_at = CURRENT_TIMESTAMP
WHERE id = $1
RETURNING id, name, workspace_type, billing_email, billing_plan, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
[workspaceId, stripeCustomerId, stripeSubscriptionId, subscriptionStatus],
RETURNING id, name, workspace_type, billing_email, billing_plan, billing_interval, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
[workspaceId, stripeCustomerId, stripeSubscriptionId, subscriptionStatus, billingPlan ?? null, billingInterval ?? null],
);
return result.rows[0] ?? null;
@@ -469,14 +491,18 @@ export const setWorkspaceStripeSubscription = async ({
export const setWorkspaceSubscriptionStatusByStripeSubscriptionId = async (
stripeSubscriptionId: string,
subscriptionStatus: SubscriptionStatus,
billingPlan?: BillingPlan | null,
billingInterval?: BillingInterval | null,
) => {
const result = await db.query<WorkspaceRow>(
`UPDATE workspaces
SET subscription_status = $2,
billing_plan = COALESCE($3, billing_plan),
billing_interval = COALESCE($4, billing_interval),
updated_at = CURRENT_TIMESTAMP
WHERE stripe_subscription_id = $1
RETURNING id, name, workspace_type, billing_email, billing_plan, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
[stripeSubscriptionId, subscriptionStatus],
RETURNING id, name, workspace_type, billing_email, billing_plan, billing_interval, subscription_status, stripe_customer_id, stripe_subscription_id, rescue_verification_status, created_at, updated_at`,
[stripeSubscriptionId, subscriptionStatus, billingPlan ?? null, billingInterval ?? null],
);
return result.rows[0] ?? null;