Skip to content

Commit

Permalink
feature:FUR-33 [BE][Web] API payment for upgrading GenAI Premium Plan (
Browse files Browse the repository at this point in the history
  • Loading branch information
MinhhTien committed Jun 14, 2024
1 parent 226d758 commit ff0d87c
Show file tree
Hide file tree
Showing 15 changed files with 779 additions and 246 deletions.
18 changes: 15 additions & 3 deletions src/ai-generation/ai-generation.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { AIGenerationTextToModelService } from './services/text-to-model.service
import { AIGenerationRepository } from './repositories/ai-generation.repository'
import { AIGenerationTextToImageController } from './controllers/text-to-image.controller'
import { AIGenerationTextToImageService } from './services/text-to-image.service'
import { AIGenerationPricingService } from './services/pricing.service'
import { AIGenerationPricingController } from './controllers/pricing.controller'

@Global()
@Module({
Expand All @@ -16,8 +18,18 @@ import { AIGenerationTextToImageService } from './services/text-to-image.service
HttpModule,
CustomerModule
],
controllers: [AIGenerationTextToModelController, AIGenerationTextToImageController],
providers: [AIGenerationTextToModelService, AIGenerationTextToImageService, AIGenerationRepository],
exports: [AIGenerationTextToModelService, AIGenerationTextToImageService, AIGenerationRepository]
controllers: [AIGenerationTextToModelController, AIGenerationTextToImageController, AIGenerationPricingController],
providers: [
AIGenerationTextToModelService,
AIGenerationTextToImageService,
AIGenerationPricingService,
AIGenerationRepository
],
exports: [
AIGenerationTextToModelService,
AIGenerationTextToImageService,
AIGenerationPricingService,
AIGenerationRepository
]
})
export class AIGenerationModule {}
15 changes: 15 additions & 0 deletions src/ai-generation/contracts/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,19 @@ export const DEFAULT_CREDITS = 50;
export enum AIGenerationPricing {
TEXT_TO_MODEL = 15,
TEXT_TO_IMAGE = 10
}

export enum AIPricingPlan {
PERSONAL = 'PERSONAL',
PREMIUM = 'PREMIUM'
}

export const AIPricingPlanCost = {
[AIPricingPlan.PERSONAL] : 2000, // 199000,
[AIPricingPlan.PREMIUM] : 499000
}

export const AIPricingPlanCredits = {
[AIPricingPlan.PERSONAL] : 250,
[AIPricingPlan.PREMIUM] : 600
}
29 changes: 29 additions & 0 deletions src/ai-generation/controllers/pricing.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'
import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'
import * as _ from 'lodash'
import { Roles } from '@auth/decorators/roles.decorator'
import { UserRole } from '@common/contracts/constant'
import { RolesGuard } from '@auth/guards/roles.guard'
import { JwtAuthGuard } from '@auth/guards/jwt-auth.guard'
import { GenerateTextToImageDto, TextToImageResponseDto } from '@ai-generation/dtos/text-to-image.dto'
import { AIGenerationPricingService } from '@ai-generation/services/pricing.service'
import { CreateCreditPurchaseDto, CreditPurchaseResponseDto } from '@ai-generation/dtos/pricing.dto'

@ApiTags('AIGeneration - Pricing')
@ApiBearerAuth()
@Roles(UserRole.CUSTOMER)
@UseGuards(JwtAuthGuard.ACCESS_TOKEN, RolesGuard)
@Controller('pricing')
export class AIGenerationPricingController {
constructor(private readonly aiGenerationPricingService: AIGenerationPricingService) {}

@ApiOperation({
summary: 'Create payment for credits purchase'
})
@ApiOkResponse({ type: CreditPurchaseResponseDto })
@Post()
createPaymentForCreditsPurchase(@Req() req, @Body() createCreditPurchaseDto: CreateCreditPurchaseDto) {
createCreditPurchaseDto.customerId = _.get(req, 'user._id')
return this.aiGenerationPricingService.createPayment(createCreditPurchaseDto)
}
}
51 changes: 51 additions & 0 deletions src/ai-generation/dtos/pricing.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ApiProperty } from '@nestjs/swagger'
import { DataResponse } from '@common/contracts/openapi-builder'
import { IsEnum, IsNotEmpty, MaxLength } from 'class-validator'
import { AIPricingPlan } from '@ai-generation/contracts/constant'
import { PayOSStatus, PaymentMethod } from '@payment/contracts/constant'

export class CreateCreditPurchaseDto {
@ApiProperty({
enum: AIPricingPlan,
example: 'PERSONAL | PREMIUM'
})
@IsNotEmpty()
@IsEnum(AIPricingPlan)
plan: string

@ApiProperty({ enum: PaymentMethod, example: 'PAY_OS | MOMO' })
@IsNotEmpty()
@IsEnum(PaymentMethod)
paymentMethod?: PaymentMethod

customerId?: string
}

export class CreditPurchaseDto {
@ApiProperty()
bin: string
@ApiProperty()
accountNumber: string
@ApiProperty()
accountName: string
@ApiProperty()
amount: number
@ApiProperty()
description: string
@ApiProperty()
orderCode: number
@ApiProperty()
currency: string
@ApiProperty()
paymentLinkId: string
@ApiProperty({
enum: PayOSStatus
})
status: PayOSStatus
@ApiProperty()
checkoutUrl: string
@ApiProperty()
qrCode: string
}

export class CreditPurchaseResponseDto extends DataResponse(CreditPurchaseDto) {}
75 changes: 75 additions & 0 deletions src/ai-generation/services/pricing.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { AIPricingPlanCost } from '@ai-generation/contracts/constant'
import { TransactionStatus } from '@common/contracts/constant'
import { CreateCreditPurchaseDto } from '@ai-generation/dtos/pricing.dto'
import { Connection } from 'mongoose'
import { InjectConnection } from '@nestjs/mongoose'
import { CheckoutRequestType, CheckoutResponseDataType, PaymentLinkDataType } from '@payos/node/lib/type'
import { CreateMomoPaymentResponse } from '@payment/dto/momo-payment.dto'
import { PaymentMethod, PaymentType } from '@payment/contracts/constant'
import { PaymentService } from '@payment/services/payment.service'
import { PaymentRepository } from '@payment/repositories/payment.repository'

@Injectable()
export class AIGenerationPricingService {
constructor(
@InjectConnection() readonly connection: Connection,
private readonly configService: ConfigService,
private readonly paymentService: PaymentService,
private readonly paymentRepository: PaymentRepository
) {}

async createPayment(createCreditPurchaseDto: CreateCreditPurchaseDto) {
const { customerId, plan } = createCreditPurchaseDto
try {
// 1. Calculate plan cost
const totalAmount = AIPricingPlanCost[plan]

// 2. Process transaction
let paymentResponseData: CreateMomoPaymentResponse | PaymentLinkDataType
let checkoutData: CreateMomoPaymentResponse | CheckoutResponseDataType
const MAX_VALUE = 9_007_199_254_740_991
const MIM_VALUE = 1_000_000_000_000_000
const orderCode = Math.floor(MIM_VALUE + Math.random() * (MAX_VALUE - MIM_VALUE))
createCreditPurchaseDto['paymentMethod'] = PaymentMethod.PAY_OS
switch (createCreditPurchaseDto.paymentMethod) {
case PaymentMethod.PAY_OS:
default:
this.paymentService.setStrategy(PaymentMethod.PAY_OS)
const checkoutRequestType: CheckoutRequestType = {
orderCode: orderCode,
amount: totalAmount,
description: `FUR - Purchase credits`,
items: [
{
name: plan,
quantity: 1,
price: totalAmount
}
],
cancelUrl: `${this.configService.get('WEB_URL')}/pricing`,
returnUrl: `${this.configService.get('WEB_URL')}/ai`
}
checkoutData = await this.paymentService.createTransaction(checkoutRequestType)
paymentResponseData = await this.paymentService.getTransaction(checkoutData['orderCode'])
break
}

// 3. Create payment
await this.paymentRepository.create({
customerId,
transactionStatus: TransactionStatus.DRAFT,
transaction: paymentResponseData,
transactionHistory: [paymentResponseData],
paymentMethod: createCreditPurchaseDto.paymentMethod,
amount: totalAmount,
paymentType: PaymentType.AI
})
return checkoutData
} catch (error) {
console.error(error)
throw error
}
}
}
6 changes: 4 additions & 2 deletions src/order/services/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { CartService } from '@cart/services/cart.service'
import { InjectConnection } from '@nestjs/mongoose'
import { ProductRepository } from '@product/repositories/product.repository'
import { PaymentRepository } from '@payment/repositories/payment.repository'
import { PaymentMethod } from '@payment/contracts/constant'
import { PaymentMethod, PaymentType } from '@payment/contracts/constant'
import { PaymentService } from '@payment/services/payment.service'
import { CreateMomoPaymentResponse, QueryMomoPaymentDto } from '@payment/dto/momo-payment.dto'
import { ConfigService } from '@nestjs/config'
Expand Down Expand Up @@ -174,11 +174,13 @@ export class OrderService {
// 5. Create payment
const payment = await this.paymentRepository.create(
{
customerId: createOrderDto.customer?._id,
transactionStatus: TransactionStatus.DRAFT,
transaction: paymentResponseData,
transactionHistory: [paymentResponseData],
paymentMethod: createOrderDto.paymentMethod,
amount: totalAmount
amount: totalAmount,
paymentType: PaymentType.ORDER
},
{
session
Expand Down
5 changes: 5 additions & 0 deletions src/payment/contracts/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export enum PaymentMethod {
ZALO_PAY = 'ZALO_PAY'
}

export enum PaymentType {
ORDER = 'ORDER',
AI = 'AI'
}

export enum MomoResultCode {
SUCCESS = 0,
AUTHORIZED = 9000,
Expand Down
6 changes: 4 additions & 2 deletions src/payment/controllers/payment.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export class PaymentController {
if (!result) return false

//2. Process webhook
return this.paymentService.processWebhook(PaymentMethod.MOMO, momoPaymentResponseDto)
this.paymentService.setStrategy(PaymentMethod.MOMO)
return this.paymentService.processWebhook(momoPaymentResponseDto)
}

@ApiOperation({
Expand All @@ -63,7 +64,8 @@ export class PaymentController {
if (!result) return false

//2. Process webhook
return this.paymentService.processWebhook(PaymentMethod.PAY_OS, webhookData)
this.paymentService.setStrategy(PaymentMethod.PAY_OS)
return this.paymentService.processWebhook(webhookData)
}

// @ApiOperation({
Expand Down
4 changes: 3 additions & 1 deletion src/payment/payment.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { OrderModule } from '@order/order.module'
import { CartModule } from '@cart/cart.module'
import { ProductModule } from '@product/product.module'
import { PayOSPaymentStrategy } from '@payment/strategies/payos.strategy'
import { CustomerModule } from '@customer/customer.module'

@Global()
@Module({
Expand All @@ -19,7 +20,8 @@ import { PayOSPaymentStrategy } from '@payment/strategies/payos.strategy'
HttpModule,
OrderModule,
CartModule,
ProductModule
ProductModule,
CustomerModule
],
controllers: [PaymentController],
providers: [PaymentService, PaymentRepository, ZaloPayPaymentStrategy, MomoPaymentStrategy, PayOSPaymentStrategy],
Expand Down
11 changes: 10 additions & 1 deletion src/payment/schemas/payment.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HydratedDocument } from 'mongoose'
import * as paginate from 'mongoose-paginate-v2'
import { Transform } from 'class-transformer'
import { TransactionStatus } from '@common/contracts/constant'
import { PaymentMethod } from '@payment/contracts/constant'
import { PaymentMethod, PaymentType } from '@payment/contracts/constant'

export type PaymentDocument = HydratedDocument<Payment>

Expand All @@ -23,6 +23,9 @@ export class Payment {
@Transform(({ value }) => value?.toString())
_id: string

@Prop({ type: String })
customerId: string

@Prop({ enum: TransactionStatus, default: TransactionStatus.DRAFT })
transactionStatus: TransactionStatus

Expand All @@ -38,6 +41,12 @@ export class Payment {
})
paymentMethod: PaymentMethod

@Prop({
enum: PaymentType,
default: PaymentType.ORDER
})
paymentType: PaymentType

@Prop({ type: Number, required: true })
amount: number
}
Expand Down
Loading

0 comments on commit ff0d87c

Please sign in to comment.