This repository has been archived by the owner on Apr 19, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 199
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bed199b
commit 62ee163
Showing
6 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { | ||
Body, | ||
Controller, | ||
Get, | ||
Param, | ||
ParseIntPipe, | ||
Patch, | ||
} from '@nestjs/common'; | ||
import { Expose } from 'src/modules/prisma/prisma.interface'; | ||
import Stripe from 'stripe'; | ||
import { Scopes } from '../auth/scope.decorator'; | ||
import { UpdateBillingDto } from './stripe.dto'; | ||
import { StripeService } from './stripe.service'; | ||
|
||
@Controller('groups/:groupId/billing') | ||
export class StripeController { | ||
constructor(private stripeService: StripeService) {} | ||
|
||
@Get() | ||
@Scopes('group-{groupId}:read-billing') | ||
async billingInfo( | ||
@Param('groupId', ParseIntPipe) groupId: number, | ||
): Promise<Expose<Stripe.Customer>> { | ||
return this.stripeService.getCustomer(groupId); | ||
} | ||
|
||
@Patch() | ||
@Scopes('group-{groupId}:write-billing') | ||
async updateBillingInfo( | ||
@Param('groupId', ParseIntPipe) groupId: number, | ||
@Body() data: UpdateBillingDto, | ||
): Promise<Expose<Stripe.Customer>> { | ||
return this.stripeService.updateCustomer(groupId, data); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { | ||
IsNotEmpty, | ||
IsObject, | ||
IsOptional, | ||
IsString, | ||
Length, | ||
ValidateNested, | ||
} from 'class-validator'; | ||
|
||
class Address { | ||
@IsString() | ||
@IsNotEmpty() | ||
line1: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
city?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
@Length(2) | ||
country?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
line2?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
postal_code?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
state?: string; | ||
} | ||
|
||
export class UpdateBillingDto { | ||
@IsString() | ||
@IsOptional() | ||
default_source?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
email?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
name?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
phone?: string; | ||
|
||
@IsString() | ||
@IsOptional() | ||
promotion_code?: string; | ||
|
||
@IsObject() | ||
@ValidateNested() | ||
@IsOptional() | ||
address?: Address; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { ConfigModule } from '@nestjs/config'; | ||
import { PrismaModule } from '../prisma/prisma.module'; | ||
import { StripeService } from './stripe.service'; | ||
|
||
@Module({ | ||
imports: [ConfigModule, PrismaModule], | ||
providers: [StripeService], | ||
exports: [StripeService], | ||
}) | ||
export class StripeModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { | ||
BadRequestException, | ||
Injectable, | ||
NotFoundException, | ||
} from '@nestjs/common'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import Stripe from 'stripe'; | ||
import { PrismaService } from '../prisma/prisma.service'; | ||
|
||
@Injectable() | ||
export class StripeService { | ||
stripe = new Stripe(this.configService.get<string>('payments.stripeApiKey'), { | ||
apiVersion: null, | ||
}); | ||
|
||
constructor( | ||
private configService: ConfigService, | ||
private prisma: PrismaService, | ||
) {} | ||
|
||
async getCustomer(groupId: number) { | ||
const stripeId = await this.stripeId(groupId); | ||
const result = await this.stripe.customers.retrieve(stripeId); | ||
if (result.deleted) | ||
throw new NotFoundException('This customer has been deleted'); | ||
return result as Stripe.Response<Stripe.Customer>; | ||
} | ||
|
||
async updateCustomer(groupId: number, data: Stripe.CustomerUpdateParams) { | ||
const stripeId = await this.stripeId(groupId); | ||
const result = await this.stripe.customers.update(stripeId, data); | ||
return result as Stripe.Response<Stripe.Customer>; | ||
} | ||
|
||
/** Get the Stripe customer ID from a group or throw an error */ | ||
private async stripeId(groupId: number): Promise<string> { | ||
const team = await this.prisma.groups.findOne({ | ||
where: { id: groupId }, | ||
select: { attributes: true }, | ||
}); | ||
const attributes = team.attributes as { stripeCustomerId?: string }; | ||
if (!attributes?.stripeCustomerId) | ||
throw new BadRequestException('Billing account is not set up'); | ||
return attributes.stripeCustomerId; | ||
} | ||
} |