211 lines
7.8 KiB
TypeScript
211 lines
7.8 KiB
TypeScript
import { getFeatureFlags, getFrontendOrigin } from '@/modules/shared/helpers/envHelper'
|
|
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
|
import { pricingTable } from '@/modules/gatekeeper/domain/workspacePricing'
|
|
import { authorizeResolver } from '@/modules/shared'
|
|
import { Roles, throwUncoveredError } from '@speckle/shared'
|
|
import {
|
|
countWorkspaceRoleWithOptionalProjectRoleFactory,
|
|
getWorkspaceFactory
|
|
} from '@/modules/workspaces/repositories/workspaces'
|
|
import { WorkspaceNotFoundError } from '@/modules/workspaces/errors/workspace'
|
|
import { db } from '@/db/knex'
|
|
import {
|
|
createCheckoutSessionFactory,
|
|
createCustomerPortalUrlFactory,
|
|
reconcileWorkspaceSubscriptionFactory
|
|
} from '@/modules/gatekeeper/clients/stripe'
|
|
import {
|
|
getWorkspacePlanPrice,
|
|
getStripeClient,
|
|
getWorkspacePlanProductId
|
|
} from '@/modules/gatekeeper/stripe'
|
|
import { startCheckoutSessionFactory } from '@/modules/gatekeeper/services/checkout'
|
|
import {
|
|
deleteCheckoutSessionFactory,
|
|
getWorkspaceCheckoutSessionFactory,
|
|
getWorkspacePlanFactory,
|
|
getWorkspaceSubscriptionFactory,
|
|
saveCheckoutSessionFactory,
|
|
upsertPaidWorkspacePlanFactory,
|
|
upsertWorkspaceSubscriptionFactory
|
|
} from '@/modules/gatekeeper/repositories/billing'
|
|
import { canWorkspaceAccessFeatureFactory } from '@/modules/gatekeeper/services/featureAuthorization'
|
|
import { upgradeWorkspaceSubscriptionFactory } from '@/modules/gatekeeper/services/subscriptions'
|
|
import { isWorkspaceReadOnlyFactory } from '@/modules/gatekeeper/services/readOnly'
|
|
import { calculateSubscriptionSeats } from '@/modules/gatekeeper/domain/billing'
|
|
import { WorkspacePaymentMethod } from '@/test/graphql/generated/graphql'
|
|
import { LogicError } from '@/modules/shared/errors'
|
|
|
|
const { FF_GATEKEEPER_MODULE_ENABLED, FF_BILLING_INTEGRATION_ENABLED } =
|
|
getFeatureFlags()
|
|
|
|
const getWorkspacePlan = getWorkspacePlanFactory({ db })
|
|
|
|
export = FF_GATEKEEPER_MODULE_ENABLED
|
|
? ({
|
|
Query: {
|
|
workspacePricingPlans: async () => {
|
|
return pricingTable
|
|
}
|
|
},
|
|
Workspace: {
|
|
plan: async (parent) => {
|
|
const workspacePlan = await getWorkspacePlanFactory({ db })({
|
|
workspaceId: parent.id
|
|
})
|
|
if (!workspacePlan) return null
|
|
let paymentMethod: WorkspacePaymentMethod
|
|
switch (workspacePlan.name) {
|
|
case 'starter':
|
|
case 'plus':
|
|
case 'business':
|
|
paymentMethod = WorkspacePaymentMethod.Billing
|
|
break
|
|
case 'unlimited':
|
|
case 'academia':
|
|
paymentMethod = WorkspacePaymentMethod.Unpaid
|
|
break
|
|
case 'starterInvoiced':
|
|
case 'plusInvoiced':
|
|
case 'businessInvoiced':
|
|
paymentMethod = WorkspacePaymentMethod.Invoice
|
|
break
|
|
default:
|
|
throwUncoveredError(workspacePlan)
|
|
}
|
|
return { ...workspacePlan, paymentMethod }
|
|
},
|
|
subscription: async (parent) => {
|
|
const workspaceId = parent.id
|
|
const subscription = await getWorkspaceSubscriptionFactory({ db })({
|
|
workspaceId
|
|
})
|
|
if (!subscription) return subscription
|
|
const seats = calculateSubscriptionSeats({
|
|
subscriptionData: subscription.subscriptionData,
|
|
guestSeatProductId: getWorkspacePlanProductId({ workspacePlan: 'guest' })
|
|
})
|
|
return { ...subscription, seats }
|
|
},
|
|
customerPortalUrl: async (parent) => {
|
|
const workspaceId = parent.id
|
|
const workspaceSubscription = await getWorkspaceSubscriptionFactory({ db })({
|
|
workspaceId
|
|
})
|
|
if (!workspaceSubscription) return null
|
|
const workspace = await getWorkspaceFactory({ db })({ workspaceId })
|
|
if (!workspace)
|
|
throw new LogicError(
|
|
'This cannot be, if there is a sub, there is a workspace'
|
|
)
|
|
return await createCustomerPortalUrlFactory({
|
|
stripe: getStripeClient(),
|
|
frontendOrigin: getFrontendOrigin()
|
|
})({
|
|
workspaceId: workspaceSubscription.workspaceId,
|
|
workspaceSlug: workspace.slug,
|
|
customerId: workspaceSubscription.subscriptionData.customerId
|
|
})
|
|
},
|
|
hasAccessToFeature: async (parent, args) => {
|
|
const hasAccess = await canWorkspaceAccessFeatureFactory({
|
|
getWorkspacePlan: getWorkspacePlanFactory({ db })
|
|
})({
|
|
workspaceId: parent.id,
|
|
workspaceFeature: args.featureName
|
|
})
|
|
return hasAccess
|
|
},
|
|
readOnly: async (parent) => {
|
|
if (!FF_BILLING_INTEGRATION_ENABLED) return false
|
|
return await isWorkspaceReadOnlyFactory({ getWorkspacePlan })({
|
|
workspaceId: parent.id
|
|
})
|
|
}
|
|
},
|
|
WorkspaceMutations: {
|
|
billing: () => ({})
|
|
},
|
|
WorkspaceBillingMutations: {
|
|
cancelCheckoutSession: async (parent, args, ctx) => {
|
|
const { workspaceId, sessionId } = args.input
|
|
|
|
await authorizeResolver(
|
|
ctx.userId,
|
|
workspaceId,
|
|
Roles.Workspace.Admin,
|
|
ctx.resourceAccessRules
|
|
)
|
|
await deleteCheckoutSessionFactory({ db })({ checkoutSessionId: sessionId })
|
|
return true
|
|
},
|
|
createCheckoutSession: async (parent, args, ctx) => {
|
|
const { workspaceId, workspacePlan, billingInterval, isCreateFlow } =
|
|
args.input
|
|
const workspace = await getWorkspaceFactory({ db })({ workspaceId })
|
|
|
|
if (!workspace) throw new WorkspaceNotFoundError()
|
|
|
|
await authorizeResolver(
|
|
ctx.userId,
|
|
workspaceId,
|
|
Roles.Workspace.Admin,
|
|
ctx.resourceAccessRules
|
|
)
|
|
|
|
const createCheckoutSession = createCheckoutSessionFactory({
|
|
stripe: getStripeClient(),
|
|
frontendOrigin: getFrontendOrigin(),
|
|
getWorkspacePlanPrice
|
|
})
|
|
|
|
const countRole = countWorkspaceRoleWithOptionalProjectRoleFactory({ db })
|
|
|
|
const session = await startCheckoutSessionFactory({
|
|
getWorkspaceCheckoutSession: getWorkspaceCheckoutSessionFactory({ db }),
|
|
getWorkspacePlan: getWorkspacePlanFactory({ db }),
|
|
countRole,
|
|
createCheckoutSession,
|
|
saveCheckoutSession: saveCheckoutSessionFactory({ db }),
|
|
deleteCheckoutSession: deleteCheckoutSessionFactory({ db })
|
|
})({
|
|
workspacePlan,
|
|
workspaceId,
|
|
workspaceSlug: workspace.slug,
|
|
isCreateFlow: isCreateFlow || false,
|
|
billingInterval
|
|
})
|
|
|
|
return session
|
|
},
|
|
upgradePlan: async (parent, args, ctx) => {
|
|
const { workspaceId, workspacePlan, billingInterval } = args.input
|
|
await authorizeResolver(
|
|
ctx.userId,
|
|
workspaceId,
|
|
Roles.Workspace.Admin,
|
|
ctx.resourceAccessRules
|
|
)
|
|
const stripe = getStripeClient()
|
|
|
|
const countWorkspaceRole = countWorkspaceRoleWithOptionalProjectRoleFactory({
|
|
db
|
|
})
|
|
await upgradeWorkspaceSubscriptionFactory({
|
|
getWorkspacePlan: getWorkspacePlanFactory({ db }),
|
|
reconcileSubscriptionData: reconcileWorkspaceSubscriptionFactory({
|
|
stripe
|
|
}),
|
|
countWorkspaceRole,
|
|
getWorkspaceSubscription: getWorkspaceSubscriptionFactory({ db }),
|
|
getWorkspacePlanPrice,
|
|
getWorkspacePlanProductId,
|
|
upsertWorkspacePlan: upsertPaidWorkspacePlanFactory({ db }),
|
|
updateWorkspaceSubscription: upsertWorkspaceSubscriptionFactory({ db })
|
|
})({ workspaceId, targetPlan: workspacePlan, billingInterval })
|
|
return true
|
|
}
|
|
}
|
|
} as Resolvers)
|
|
: {}
|