Files
speckle-server/packages/server/modules/gatekeeper/domain/billing.ts
T
Gergő Jedlicska da7f0dda0e gergo/web 2047 user joins the workspace event (#3412)
* feat(gatekeeper): add gatekeeper module feature flag

* feat(gatekeeper): add workspace pricing table domain

* feat(gatekeeper): add checkout session creation

* feat(gatekeeper): verify stripe signature

* wip(gatekeeper): checkout callbacks

* feat(gatekeeper): add unlimited and academia plan types

* refactor(envHelper): getStringFromEnv helper

* chore(gatekeeper): add future todos

* feat(gatekeeper): add productId to the subscription domain

* feat(gatekeeper): add in memory repositories

* feat(gatekeeper): add more errors

* feat(gatekeeper): complete checkout session service

* feat(gatekeeper): add stripe client implementation

* feat(gatekeeper): add checkout session completion webhook callback path

* feat(gendo): fix not needing env vars if gendo module is not enabled

* feat(gatekeeper): require a license for billing

* chore(gatekeeper): cleanup before testing

* feat(gatekeeper): subscriptionData parsing model

* ci: add billing integration and gatekeeper modules to test config

* test(gatekeeper): add checkout service tests

* feat(gatekeeper): make completeCheckout callback idempotent properly

* feat(gatekeeper): move to knex based repositories

* test(gatekeeper): billing repository tests

* feat(gatekeeper): add yearly billing cycle toggle

* feat(ci): add stripe integration context to test job

* feat(billingPage): conditionally render the checkout CTAs

* fix(gatekeeper): remove flaky test condition

* feat(helm): add billing integration feature flag

* WIP billing gql api

* feat(gatekeeper): cancel checkout session api

* feat(gatekeeper): handle existing checkout sessions, when trying to create a new one

* feat(gatekeeper): add workspace plans gql api

* feat(gatekeeper): handle cancelation and subscription updates

* fix(gatekeeper): scope initialization

* fix(gatekeeper): eliminate stripe client import sideeffect

* fix(gatekeeper): eliminate stripe client import sideeffect 2

* feat(gatekeeper): upsize subscription on workspace role change

* feat(shared): add command pattern implementation

* refactor(eventBus): remove return capabilities from the event bus

* refactor(workspaces): use new commandFactory in workspace resolver

* feat(core): facelift taskLock

* feat(gatekeeper): shedule subscription downscale

* feat(gatekeeper): manage subscription downscale

* feat(gatekeeper): get workspace subscriptions, that are about to expire

* feat(gatekeeper): manage subscription downscale

* fix(gatekeeper): do not update subscription to canceled subs

* ci: bump postgres and max connections

* feat(workspaces): fix command factory event bugs
2024-10-30 15:51:40 +01:00

180 lines
4.6 KiB
TypeScript

import {
TrialWorkspacePlans,
PaidWorkspacePlans,
UnpaidWorkspacePlans,
WorkspacePlanBillingIntervals,
WorkspacePricingPlans
} from '@/modules/gatekeeper/domain/workspacePricing'
import { OverrideProperties } from 'type-fest'
import { z } from 'zod'
export type UnpaidWorkspacePlanStatuses = 'valid'
export type PaidWorkspacePlanStatuses =
| UnpaidWorkspacePlanStatuses
// | 'paymentNeeded' // unsure if this is needed
| 'paymentFailed'
| 'cancelationScheduled'
| 'canceled'
export type TrialWorkspacePlanStatuses = 'trial' | 'expired'
type BaseWorkspacePlan = {
workspaceId: string
}
export type PaidWorkspacePlan = BaseWorkspacePlan & {
name: PaidWorkspacePlans
status: PaidWorkspacePlanStatuses
}
export type TrialWorkspacePlan = BaseWorkspacePlan & {
name: TrialWorkspacePlans
status: TrialWorkspacePlanStatuses
}
export type UnpaidWorkspacePlan = BaseWorkspacePlan & {
name: UnpaidWorkspacePlans
status: UnpaidWorkspacePlanStatuses
}
export type WorkspacePlan = PaidWorkspacePlan | TrialWorkspacePlan | UnpaidWorkspacePlan
export type GetWorkspacePlan = (args: {
workspaceId: string
}) => Promise<WorkspacePlan | null>
export type UpsertTrialWorkspacePlan = (args: {
workspacePlan: TrialWorkspacePlan
}) => Promise<void>
export type UpsertPaidWorkspacePlan = (args: {
workspacePlan: PaidWorkspacePlan
}) => 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>
export type CreateCheckoutSession = (args: {
workspaceId: string
workspaceSlug: string
seatCount: number
guestCount: number
workspacePlan: PaidWorkspacePlans
billingInterval: WorkspacePlanBillingIntervals
}) => 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()
})
// this abstracts the stripe sub data
export type SubscriptionData = z.infer<typeof subscriptionData>
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 GetWorkspacePlanPrice = (args: {
workspacePlan: WorkspacePricingPlans
billingInterval: WorkspacePlanBillingIntervals
}) => string
export type GetWorkspacePlanProductId = (args: {
workspacePlan: WorkspacePricingPlans
}) => string
export type SubscriptionDataInput = OverrideProperties<
SubscriptionData,
{
products: OverrideProperties<SubscriptionProduct, { subscriptionItemId?: string }>[]
}
>
export type ReconcileSubscriptionData = (args: {
subscriptionData: SubscriptionDataInput
applyProrotation: boolean
}) => Promise<void>