Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add entitlement table and sso stripe feature #8608

Merged
merged 6 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
"semver": "^7.5.4",
"sharp": "^0.32.1",
"slash": "^5.1.0",
"stripe": "^14.17.0",
"stripe": "^17.3.1",
"ts-key-enum": "^2.0.12",
"tslib": "^2.3.0",
"tsup": "^8.2.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddBillingEntitlementTable1732098580545
implements MigrationInterface
{
name = 'AddBillingEntitlementTable1732098580545';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "core"."billingEntitlement" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "key" text NOT NULL, "workspaceId" uuid NOT NULL, "stripeCustomerId" character varying NOT NULL, "value" boolean NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, CONSTRAINT "IndexOnFeatureKeyAndWorkspaceIdUnique" UNIQUE ("key", "workspaceId"), CONSTRAINT "PK_4e6ed788c3ca0bf6610d5022576" PRIMARY KEY ("id"))`,
);

await queryRunner.query(
`ALTER TABLE "core"."billingEntitlement" ADD CONSTRAINT "FK_599121a93d8177b5d713b941982" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."billingEntitlement" DROP CONSTRAINT "FK_599121a93d8177b5d713b941982"`,
);

await queryRunner.query(`DROP TABLE "core"."billingEntitlement"`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { DataSource } from 'typeorm';

import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/billing-entitlement.entity';
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
Expand All @@ -14,7 +15,6 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';

@Injectable()
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
private mainDataSource: DataSource;
Expand All @@ -36,6 +36,7 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
FeatureFlagEntity,
BillingSubscription,
BillingSubscriptionItem,
BillingEntitlement,
PostgresCredentials,
WorkspaceSSOIdentityProvider,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@ import {

import { Response } from 'express';

import { WebhookEvent } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service';
import {
BillingException,
BillingExceptionCode,
} from 'src/engine/core-modules/billing/billing.exception';
import { WebhookEvent } from 'src/engine/core-modules/billing/enums/billing-webhook-events.enum';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { BillingWebhookService } from 'src/engine/core-modules/billing/services/billing-webhook.service';
import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service';

@Controller('billing')
export class BillingController {
protected readonly logger = new Logger(BillingController.name);

constructor(
private readonly stripeService: StripeService,
private readonly billingSubscriptionService: BillingSubscriptionService,
private readonly billingWehbookService: BillingWebhookService,
private readonly billingSubscriptionService: BillingSubscriptionService,
) {}

@Post('/webhooks')
Expand Down Expand Up @@ -63,6 +66,22 @@ export class BillingController {
event.data,
);
}
if (
event.type === WebhookEvent.CUSTOMER_ACTIVE_ENTITLEMENT_SUMMARY_UPDATED
) {
try {
await this.billingWehbookService.processCustomerActiveEntitlement(
event.data,
);
} catch (error) {
anamarn marked this conversation as resolved.
Show resolved Hide resolved
if (
error instanceof BillingException &&
error.code === BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND
) {
res.status(404).end();
}
}
}
res.status(200).end();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* @license Enterprise */

import { CustomException } from 'src/utils/custom-exception';

export class BillingException extends CustomException {
code: BillingExceptionCode;
constructor(message: string, code: BillingExceptionCode) {
super(message, code);
}
}

export enum BillingExceptionCode {
BILLING_CUSTOMER_NOT_FOUND = 'BILLING_CUSTOMER_NOT_FOUND',
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';

import { BillingController } from 'src/engine/core-modules/billing/billing.controller';
import { BillingResolver } from 'src/engine/core-modules/billing/billing.resolver';
import { BillingEntitlement } from 'src/engine/core-modules/billing/entities/billing-entitlement.entity';
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { BillingWorkspaceMemberListener } from 'src/engine/core-modules/billing/listeners/billing-workspace-member.listener';
Expand All @@ -24,6 +25,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
[
BillingSubscription,
BillingSubscriptionItem,
BillingEntitlement,
Workspace,
UserWorkspace,
FeatureFlagEntity,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { UseGuards } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';

import { AvailableProduct } from 'src/engine/core-modules/billing/interfaces/available-product.interface';

import { BillingSessionInput } from 'src/engine/core-modules/billing/dto/billing-session.input';
import { CheckoutSessionInput } from 'src/engine/core-modules/billing/dto/checkout-session.input';
import { ProductPricesEntity } from 'src/engine/core-modules/billing/dto/product-prices.entity';
import { ProductInput } from 'src/engine/core-modules/billing/dto/product.input';
import { SessionEntity } from 'src/engine/core-modules/billing/dto/session.entity';
import { UpdateBillingEntity } from 'src/engine/core-modules/billing/dto/update-billing.entity';
import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum';
import { BillingPortalWorkspaceService } from 'src/engine/core-modules/billing/services/billing-portal.workspace-service';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { StripeService } from 'src/engine/core-modules/billing/stripe/stripe.service';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import Stripe from 'stripe';

import { SubscriptionInterval } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';

@ArgsType()
export class CheckoutSessionInput {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { Field, ObjectType } from '@nestjs/graphql';

import Stripe from 'stripe';

import { SubscriptionInterval } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';

import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
@ObjectType()
export class ProductPriceEntity {
@Field(() => SubscriptionInterval)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ArgsType, Field } from '@nestjs/graphql';

import { IsNotEmpty, IsString } from 'class-validator';

import { AvailableProduct } from 'src/engine/core-modules/billing/interfaces/available-product.interface';
import { AvailableProduct } from 'src/engine/core-modules/billing/enums/billing-available-product.enum';

@ArgsType()
export class ProductInput {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Field, ObjectType } from '@nestjs/graphql';

import { IDField } from '@ptc-org/nestjs-query-graphql';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Relation,
Unique,
UpdateDateColumn,
} from 'typeorm';

import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Entity({ name: 'billingEntitlement', schema: 'core' })
@ObjectType('billingEntitlement')
@Unique('IndexOnFeatureKeyAndWorkspaceIdUnique', ['key', 'workspaceId'])
export class BillingEntitlement {
@IDField(() => UUIDScalarType)
@PrimaryGeneratedColumn('uuid')
id: string;

@Field(() => String)
@Column({ nullable: false, type: 'text' })
key: BillingEntitlementKey;

@Field()
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;

@ManyToOne(() => Workspace, (workspace) => workspace.billingEntitlements, {
onDelete: 'CASCADE',
})
@JoinColumn()
workspace: Relation<Workspace>;

@Column({ nullable: false })
stripeCustomerId: string;

@Field()
@Column({ nullable: false })
value: boolean;

@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;

@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;

@Column({ nullable: true, type: 'timestamptz' })
deletedAt?: Date;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,10 @@ import {

import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { BillingSubscriptionItem } from 'src/engine/core-modules/billing/entities/billing-subscription-item.entity';
import { SubscriptionInterval } from 'src/engine/core-modules/billing/enums/billing-subscription-interval.enum';
import { SubscriptionStatus } from 'src/engine/core-modules/billing/enums/billing-subscription-status.enum';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';

export enum SubscriptionStatus {
Active = 'active',
Canceled = 'canceled',
Incomplete = 'incomplete',
IncompleteExpired = 'incomplete_expired',
PastDue = 'past_due',
Paused = 'paused',
Trialing = 'trialing',
Unpaid = 'unpaid',
}

export enum SubscriptionInterval {
Day = 'day',
Month = 'month',
Week = 'week',
Year = 'year',
}

registerEnumType(SubscriptionStatus, { name: 'SubscriptionStatus' });
registerEnumType(SubscriptionInterval, { name: 'SubscriptionInterval' });

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum BillingEntitlementKey {
SSO = 'SSO',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum SubscriptionInterval {
Day = 'day',
Month = 'month',
Week = 'week',
Year = 'year',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export enum SubscriptionStatus {
Active = 'active',
Canceled = 'canceled',
Incomplete = 'incomplete',
IncompleteExpired = 'incomplete_expired',
PastDue = 'past_due',
Paused = 'paused',
Trialing = 'trialing',
Unpaid = 'unpaid',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export enum WebhookEvent {
CUSTOMER_SUBSCRIPTION_CREATED = 'customer.subscription.created',
CUSTOMER_SUBSCRIPTION_UPDATED = 'customer.subscription.updated',
CUSTOMER_SUBSCRIPTION_DELETED = 'customer.subscription.deleted',
SETUP_INTENT_SUCCEEDED = 'setup_intent.succeeded',
CUSTOMER_ACTIVE_ENTITLEMENT_SUMMARY_UPDATED = 'entitlements.active_entitlement_summary.updated',
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { assert } from 'src/utils/assert';

export enum WebhookEvent {
CUSTOMER_SUBSCRIPTION_CREATED = 'customer.subscription.created',
CUSTOMER_SUBSCRIPTION_UPDATED = 'customer.subscription.updated',
CUSTOMER_SUBSCRIPTION_DELETED = 'customer.subscription.deleted',
SETUP_INTENT_SUCCEEDED = 'setup_intent.succeeded',
}

@Injectable()
export class BillingPortalWorkspaceService {
protected readonly logger = new Logger(BillingPortalWorkspaceService.name);
Expand Down
Loading
Loading