Skip to content
Open
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
39 changes: 31 additions & 8 deletions apps/backend/src/api/routes/billing.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
import { Organization, User } from '@prisma/client';
import { BillingSubscribeDto } from '@gitroom/nestjs-libraries/dtos/billing/billing.subscribe.dto';
import { BillingCancelDto } from '@gitroom/nestjs-libraries/dtos/billing/billing.cancel.dto';
import { ApiTags } from '@nestjs/swagger';
import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
import { Request } from 'express';
import { AuthService } from '@gitroom/helpers/auth/auth.service';
import dayjs from 'dayjs';

@ApiTags('Billing')
@Controller('/billing')
Expand Down Expand Up @@ -114,16 +116,37 @@ export class BillingController {
async cancel(
@GetOrgFromRequest() org: Organization,
@GetUserFromRequest() user: User,
@Body() body: { feedback: string }
@Body() body: BillingCancelDto
) {
await this._notificationService.sendEmail(
process.env.EMAIL_FROM_ADDRESS,
'Subscription Cancelled',
`Organization ${org.name} has cancelled their subscription because: ${body.feedback}`,
user.email
);
const result = await this._stripeService.setToCancel(org.id);

if (result.cancel_at) {
try {
await this._subscriptionService.saveCancellationFeedback(
org.id,
user.id,
body.feedback
);
} catch (err) {
console.error('Failed to save cancellation feedback', err);
}

try {
await this._notificationService.sendEmail(
user.email,
'Your subscription has been cancelled',
`Your subscription has been cancelled. You will keep access to all features until ${dayjs(
result.cancel_at
).format(
'MMMM D, YYYY'
)}.<br /><br />Changed your mind? You can resubscribe anytime from your <a href="${
process.env.FRONTEND_URL
}/billing">billing page</a>.`
);
} catch (err) {}
}

return this._stripeService.setToCancel(org.id);
return result;
}

@Post('/prorate')
Expand Down
15 changes: 15 additions & 0 deletions libraries/nestjs-libraries/src/database/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ model Organization {
sets Sets[]
signatures Signatures[]
subscription Subscription?
cancellationFeedbacks SubscriptionCancellationFeedback[]
tags Tags[]
thirdParty ThirdParty[]
usedCodes UsedCodes[]
Expand Down Expand Up @@ -113,6 +114,7 @@ model User {
sendFailureEmails Boolean @default(true)
sendStreakEmails Boolean @default(true)
oauthAuthorizations OAuthAuthorization[]
cancellationFeedbacks SubscriptionCancellationFeedback[]

@@unique([email, providerName])
@@index([lastReadNotifications])
Expand Down Expand Up @@ -298,6 +300,19 @@ model Subscription {
@@index([deletedAt])
}

model SubscriptionCancellationFeedback {
id String @id @default(cuid())
organizationId String
userId String
feedback String
createdAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id])
user User @relation(fields: [userId], references: [id])

@@index([organizationId])
@@index([createdAt])
}

model Customer {
id String @id @default(uuid())
name String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,26 @@ export class SubscriptionRepository {
private readonly _organization: PrismaRepository<'organization'>,
private readonly _user: PrismaRepository<'user'>,
private readonly _credits: PrismaRepository<'credits'>,
private _usedCodes: PrismaRepository<'usedCodes'>
private _usedCodes: PrismaRepository<'usedCodes'>,
private readonly _cancellationFeedback: PrismaRepository<'subscriptionCancellationFeedback'>
) {}

saveCancellationFeedback(
organizationId: string,
userId: string,
feedback: string
) {
return this._cancellationFeedback.model.subscriptionCancellationFeedback.create(
{
data: {
organizationId,
userId,
feedback,
},
}
);
}

getUserAccount(userId: string) {
return this._user.model.user.findFirst({
where: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ export class SubscriptionService {
return this._subscriptionRepository.getCode(code);
}

saveCancellationFeedback(
organizationId: string,
userId: string,
feedback: string
) {
return this._subscriptionRepository.saveCancellationFeedback(
organizationId,
userId,
feedback
);
}

async deleteSubscription(customerId: string) {
await this.modifySubscription(
customerId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IsDefined, IsString } from 'class-validator';

export class BillingCancelDto {
@IsString()
@IsDefined()
feedback: string;
}
Loading