f501cc4ad5
* WIP can create project * WIP can create project more work * complete body, stencil tests * feat(shared): move workspace plan types into shared * test progress wip * feat(shared): add more logic to canCreateWorkspaceProject * a few more tests, as a treat * chore(authz): round out tests * fixed loaders, new GQL checks, dataLoaders in auth loaders * fix(authz): get workspace limits loader * chore(authz): update loaders * frontend fixed up to snuff * fix(authz): fix workspace plans for tests * fix(authz): classic * fix(authz): 0 counts --------- Co-authored-by: Chuck Driesler <chuck@speckle.systems> Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
250 lines
6.4 KiB
TypeScript
250 lines
6.4 KiB
TypeScript
import {
|
|
WorkspacePlanProductPrices,
|
|
WorkspacePricingProducts
|
|
} from '@/modules/gatekeeperCore/domain/billing'
|
|
import { Workspace, WorkspaceAcl } from '@/modules/workspacesCore/domain/types'
|
|
import {
|
|
Nullable,
|
|
Optional,
|
|
PaidWorkspacePlan,
|
|
PaidWorkspacePlans,
|
|
TrialWorkspacePlan,
|
|
UnpaidWorkspacePlan,
|
|
WorkspacePlan,
|
|
WorkspacePlanBillingIntervals
|
|
} from '@speckle/shared'
|
|
import { OverrideProperties } from 'type-fest'
|
|
import { z } from 'zod'
|
|
|
|
export type GetWorkspacePlan = (args: {
|
|
workspaceId: string
|
|
}) => Promise<WorkspacePlan | null>
|
|
|
|
export type GetWorkspaceWithPlan = (args: {
|
|
workspaceId: string
|
|
}) => Promise<Optional<Workspace & { plan: Nullable<WorkspacePlan> }>>
|
|
|
|
export type UpsertTrialWorkspacePlan = (args: {
|
|
workspacePlan: TrialWorkspacePlan
|
|
}) => Promise<void>
|
|
|
|
export type UpsertPaidWorkspacePlan = (args: {
|
|
workspacePlan: PaidWorkspacePlan
|
|
}) => Promise<void>
|
|
|
|
export type UpsertUnpaidWorkspacePlan = (args: {
|
|
workspacePlan: UnpaidWorkspacePlan
|
|
}) => Promise<void>
|
|
|
|
export type UpsertWorkspacePlan = (args: {
|
|
workspacePlan: WorkspacePlan
|
|
}) => Promise<void>
|
|
|
|
export type SessionInput = {
|
|
id: string
|
|
}
|
|
|
|
export type SessionPaymentStatus = 'paid' | 'unpaid'
|
|
|
|
export type CheckoutSession = SessionInput & {
|
|
url: string
|
|
workspaceId: string
|
|
workspacePlan: PaidWorkspacePlans
|
|
paymentStatus: SessionPaymentStatus
|
|
billingInterval: WorkspacePlanBillingIntervals
|
|
createdAt: Date
|
|
updatedAt: Date
|
|
}
|
|
|
|
export type SaveCheckoutSession = (args: {
|
|
checkoutSession: CheckoutSession
|
|
}) => Promise<void>
|
|
|
|
export type GetCheckoutSession = (args: {
|
|
sessionId: string
|
|
}) => Promise<CheckoutSession | null>
|
|
|
|
export type DeleteCheckoutSession = (args: {
|
|
checkoutSessionId: string
|
|
}) => Promise<void>
|
|
|
|
export type GetWorkspaceCheckoutSession = (args: {
|
|
workspaceId: string
|
|
}) => Promise<CheckoutSession | null>
|
|
|
|
export type UpdateCheckoutSessionStatus = (args: {
|
|
sessionId: string
|
|
paymentStatus: SessionPaymentStatus
|
|
}) => Promise<void>
|
|
|
|
// Remove with FF_WORKSPACES_NEW_PLANS_ENABLED
|
|
export type CreateCheckoutSessionOld = (args: {
|
|
workspaceId: string
|
|
workspaceSlug: string
|
|
seatCount: number
|
|
guestCount: number
|
|
workspacePlan: PaidWorkspacePlans
|
|
billingInterval: WorkspacePlanBillingIntervals
|
|
isCreateFlow: boolean
|
|
}) => Promise<CheckoutSession>
|
|
export type CreateCheckoutSession = (args: {
|
|
workspaceId: string
|
|
workspaceSlug: string
|
|
editorsCount: number
|
|
workspacePlan: PaidWorkspacePlans
|
|
billingInterval: WorkspacePlanBillingIntervals
|
|
isCreateFlow: boolean
|
|
}) => Promise<CheckoutSession>
|
|
|
|
export type WorkspaceSubscription = {
|
|
workspaceId: string
|
|
createdAt: Date
|
|
updatedAt: Date
|
|
currentBillingCycleEnd: Date
|
|
billingInterval: WorkspacePlanBillingIntervals
|
|
subscriptionData: SubscriptionData
|
|
}
|
|
const subscriptionProduct = z.object({
|
|
productId: z.string(),
|
|
subscriptionItemId: z.string(),
|
|
priceId: z.string(),
|
|
quantity: z.number()
|
|
})
|
|
|
|
export type SubscriptionProduct = z.infer<typeof subscriptionProduct>
|
|
|
|
export const SubscriptionData = z.object({
|
|
subscriptionId: z.string().min(1),
|
|
customerId: z.string().min(1),
|
|
cancelAt: z.date().nullable(),
|
|
status: z.union([
|
|
z.literal('incomplete'),
|
|
z.literal('incomplete_expired'),
|
|
z.literal('trialing'),
|
|
z.literal('active'),
|
|
z.literal('past_due'),
|
|
z.literal('canceled'),
|
|
z.literal('unpaid'),
|
|
z.literal('paused')
|
|
]),
|
|
products: subscriptionProduct.array(),
|
|
currentPeriodEnd: z.coerce.date()
|
|
})
|
|
// this abstracts the stripe sub data
|
|
export type SubscriptionData = z.infer<typeof SubscriptionData>
|
|
|
|
export const calculateSubscriptionSeats = ({
|
|
subscriptionData,
|
|
guestSeatProductId
|
|
}: {
|
|
subscriptionData: SubscriptionData
|
|
guestSeatProductId: string
|
|
}): { plan: number; guest: number } => {
|
|
const guestProduct = subscriptionData.products.find(
|
|
(p) => p.productId === guestSeatProductId
|
|
)
|
|
|
|
const planProduct = subscriptionData.products.find(
|
|
(p) => p.productId !== guestSeatProductId
|
|
)
|
|
return { guest: guestProduct?.quantity || 0, plan: planProduct?.quantity || 0 }
|
|
}
|
|
|
|
export type UpsertWorkspaceSubscription = (args: {
|
|
workspaceSubscription: WorkspaceSubscription
|
|
}) => Promise<void>
|
|
|
|
export type GetWorkspaceSubscription = (args: {
|
|
workspaceId: string
|
|
}) => Promise<WorkspaceSubscription | null>
|
|
|
|
export type GetWorkspaceSubscriptions = () => Promise<WorkspaceSubscription[]>
|
|
|
|
export type GetWorkspaceSubscriptionBySubscriptionId = (args: {
|
|
subscriptionId: string
|
|
}) => Promise<WorkspaceSubscription | null>
|
|
|
|
export type GetSubscriptionData = (args: {
|
|
subscriptionId: string
|
|
}) => Promise<SubscriptionData>
|
|
|
|
export type GetWorkspacePlanPriceId = (args: {
|
|
workspacePlan: WorkspacePricingProducts
|
|
billingInterval: WorkspacePlanBillingIntervals
|
|
}) => string
|
|
|
|
export type GetWorkspacePlanProductId = (args: {
|
|
workspacePlan: WorkspacePricingProducts
|
|
}) => string
|
|
|
|
type Products = 'guest' | 'starter' | 'plus' | 'business' | 'team' | 'pro'
|
|
|
|
export type GetWorkspacePlanProductAndPriceIds = () => Omit<
|
|
Record<Products, { productId: string; monthly: string; yearly: string }>,
|
|
'team' | 'pro'
|
|
> & {
|
|
team?: { productId: string; monthly: string }
|
|
pro?: { productId: string; monthly: string; yearly: string }
|
|
}
|
|
|
|
export type SubscriptionDataInput = OverrideProperties<
|
|
SubscriptionData,
|
|
{
|
|
products: OverrideProperties<SubscriptionProduct, { subscriptionItemId?: string }>[]
|
|
}
|
|
>
|
|
|
|
export type ReconcileSubscriptionData = (args: {
|
|
subscriptionData: SubscriptionDataInput
|
|
prorationBehavior: 'always_invoice' | 'create_prorations' | 'none'
|
|
}) => Promise<void>
|
|
|
|
export const WorkspaceSeatType = <const>{
|
|
Viewer: 'viewer',
|
|
Editor: 'editor'
|
|
}
|
|
export type WorkspaceSeatType =
|
|
(typeof WorkspaceSeatType)[keyof typeof WorkspaceSeatType]
|
|
|
|
export type WorkspaceSeat = {
|
|
workspaceId: string
|
|
userId: string
|
|
type: WorkspaceSeatType
|
|
createdAt: Date
|
|
updatedAt: Date
|
|
}
|
|
// Prices
|
|
export type GetRecurringPrices = () => Promise<
|
|
{
|
|
id: string
|
|
currency: string
|
|
unitAmount: number
|
|
productId: string
|
|
}[]
|
|
>
|
|
|
|
export type GetWorkspacePlanProductPrices = () => Promise<WorkspacePlanProductPrices>
|
|
|
|
export type GetWorkspaceRolesAndSeats = (params: {
|
|
workspaceId: string
|
|
userIds?: string[]
|
|
}) => Promise<{
|
|
[userId: string]: {
|
|
role: WorkspaceAcl
|
|
seat: Nullable<WorkspaceSeat>
|
|
userId: string
|
|
}
|
|
}>
|
|
|
|
export type GetWorkspaceRoleAndSeat = (params: {
|
|
workspaceId: string
|
|
userId: string
|
|
}) => Promise<
|
|
| {
|
|
role: WorkspaceAcl
|
|
seat: Nullable<WorkspaceSeat>
|
|
userId: string
|
|
}
|
|
| undefined
|
|
>
|