update payment

This commit is contained in:
2024-11-03 20:28:14 +07:00
parent e8c0e0d312
commit bc2eda7490
10 changed files with 208 additions and 14 deletions

View File

@@ -152,7 +152,6 @@ export class Builder extends SchemaBuilder<SchemaBuilderOption> {
this.queryType({})
this.mutationType({})
this.subscriptionType({})
this.globalConnectionField('totalCount', (t) =>
t.int({
nullable: true,

View File

@@ -1,7 +1,8 @@
import { Module } from '@nestjs/common'
import { OrderSchema } from './order.schema'
import { PayosModule } from 'src/Payos/payos.module'
@Module({
imports: [PayosModule],
providers: [OrderSchema],
exports: [OrderSchema],
})

View File

@@ -8,11 +8,14 @@ import {
import { Builder } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service'
import { OrderStatus } from '@prisma/client'
import { DateTimeUtils } from 'src/common/utils/datetime.utils'
import { PayosService } from 'src/Payos/payos.service'
@Injectable()
export class OrderSchema extends PothosSchema {
constructor(
@Inject(SchemaBuilderToken) private readonly builder: Builder,
private readonly prisma: PrismaService,
private readonly payosService: PayosService,
) {
super()
}
@@ -121,11 +124,24 @@ export class OrderSchema extends PothosSchema {
required: true,
}),
},
resolve: async (query, _root, args, _ctx, _info) => {
resolve: async (query, _root, args, ctx, _info) => {
return this.prisma.$transaction(async (prisma) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!args.data.service.connect?.id) {
throw new Error('Service not found')
}
const order = await prisma.order.create({
...query,
data: args.data,
data: {
status: OrderStatus.PENDING,
total:
(args.data.service.connect?.price as number | undefined) ?? 0,
userId: ctx.http.me.id,
serviceId: args.data.service.connect.id,
scheduleId: args.data.scheduleId,
},
})
// check if service is valid
if (!args.data.service.connect) {
@@ -135,15 +151,32 @@ export class OrderSchema extends PothosSchema {
if (args.data.service.connect.price === 0) {
return order
}
// generate payment code by prefix 'EPESS' + 6 hex digits
const paymentCode = 'EPESS' + Math.random().toString(16).slice(2, 8)
// random integer
const paymentCode = Math.floor(Math.random() * 1000000)
// create payment
await prisma.payment.create({
const payment = await prisma.payment.create({
data: {
orderId: order.id,
amount: args.data.service.connect.price as number,
paymentCode: paymentCode,
expiredAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
paymentCode: paymentCode.toString(),
expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toJSDate(),
},
})
// generate payment url
const paymentData = await this.payosService.createPayment({
orderCode: paymentCode,
amount: args.data.service.connect.price as number,
description: args.data.service.connect.name as string,
buyerName: ctx.http.me.name as string,
buyerEmail: ctx.http.me.email as string,
returnUrl: `${process.env.PAYOS_WEBHOOK_URL}/return`,
cancelUrl: `${process.env.PAYOS_WEBHOOK_URL}/cancel`,
})
// update payment url
await prisma.payment.update({
where: { id: payment.id },
data: {
paymentCode: paymentData.paymentLinkId,
},
})
return order

View File

@@ -32,4 +32,18 @@ export class PayosController {
async ping() {
return this.payosService.ping()
}
// test create payment url
@Post('create-payment-url')
@ApiOperation({ summary: 'Test create payment url' })
async createPaymentURL(@Body() body: any) {
return this.payosService.createPaymentURL(body)
}
// get payment status
@Get('get-payment-status/:orderId')
@ApiOperation({ summary: 'Get payment status' })
async getPaymentStatus(@Param('orderId') orderId: string | number) {
return this.payosService.getPaymentStatus(orderId)
}
}

View File

@@ -1,9 +1,24 @@
import { Module } from '@nestjs/common'
import { PayosController } from './payos.controller'
import { PayosService } from './payos.service'
import { HttpModule } from '@nestjs/axios'
import PayOS from '@payos/node'
@Module({
providers: [PayosService],
imports: [HttpModule],
providers: [
PayosService,
{
provide: 'PayOS',
useFactory: () => {
return new PayOS(
process.env.PAYOS_CLIENT_ID ?? '',
process.env.PAYOS_API_KEY ?? '',
process.env.PAYOS_CHECKSUM_KEY ?? '',
)
},
},
],
controllers: [PayosController],
exports: [PayosService],
})

View File

@@ -1,10 +1,19 @@
import { Injectable, Logger } from '@nestjs/common'
import { Inject, Injectable, Logger } from '@nestjs/common'
import { PrismaService } from '../Prisma/prisma.service'
import PayOS from '@payos/node'
import type {
CheckoutRequestType,
CheckoutResponseDataType,
} from '@payos/node/lib/type'
export type CreatePaymentBody = CheckoutRequestType
export type CreatePaymentResponse = CheckoutResponseDataType
@Injectable()
export class PayosService {
constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
@Inject('PayOS') private readonly payos: PayOS,
) {}
async ping() {
return 'pong'
@@ -16,7 +25,15 @@ export class PayosService {
}
async createPaymentURL(body: any) {
return body
return await this.payos.createPaymentLink(body)
}
async createPayment(body: CreatePaymentBody): Promise<CreatePaymentResponse> {
return await this.payos.createPaymentLink(body)
}
async getPaymentStatus(orderId: string | number) {
return await this.payos.getPaymentLinkInformation(orderId)
}
async cancelPaymentURL(body: any) {

View File

@@ -72,6 +72,20 @@ export class ScheduleSchema extends PothosSchema {
})
}
@PothosRef()
scheduleConnection() {
return this.builder.simpleObject('ScheduleConnection', {
fields: (t) => ({
totalCount: t.int({
nullable: true,
}),
schedules: t.field({
type: [this.schedule()],
}),
}),
})
}
@PothosRef()
scheduleSlot() {
return this.builder.simpleObject('ScheduleSlot', {
@@ -326,6 +340,28 @@ d72a864e-2f41-45ab-9c9b-bf0512a31883,e9be51fd-2382-4e43-9988-74e76fde4b56,2024-1
})
},
}),
updateScheduleStatus: t.prismaField({
type: this.schedule(),
description: 'Update a schedule status.',
args: {
scheduleId: t.arg({
type: 'String',
required: true,
}),
status: t.arg({
type: ScheduleStatus,
required: true,
}),
},
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.schedule.update({
...query,
where: { id: args.scheduleId },
data: { status: args.status },
})
},
}),
}))
}
}

View File

@@ -26,6 +26,14 @@ export type TimeType = {
@Injectable()
export class DateTimeUtils {
static nowAsJSDate(): Date {
return DateTime.now().toJSDate()
}
static now(): DateTime {
return DateTime.now()
}
static getOverlapRange(
startA: DateTime,
endA: DateTime,