update transaction

This commit is contained in:
2024-11-21 18:30:32 +07:00
parent cf8051b8a4
commit d56e1f9348
5 changed files with 110 additions and 128 deletions

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common'
import { FinanceSchema } from './finance.schema'
@Module({
providers: [FinanceSchema],
exports: [FinanceSchema],
})
export class FinanceModule {}

View File

@@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common'
import { Pothos, PothosRef } from '@smatch-corp/nestjs-pothos'
@Injectable()
export class FinanceSchema {}

View File

@@ -1,10 +1,5 @@
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable } from '@nestjs/common'
import { import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
Pothos,
PothosRef,
PothosSchema,
SchemaBuilderToken,
} from '@smatch-corp/nestjs-pothos'
import { Builder } from '../Graphql/graphql.builder' import { Builder } from '../Graphql/graphql.builder'
import { PrismaService } from '../Prisma/prisma.service' import { PrismaService } from '../Prisma/prisma.service'
import { OrderStatus } from '@prisma/client' import { OrderStatus } from '@prisma/client'
@@ -81,8 +76,7 @@ export class OrderSchema extends PothosSchema {
this.builder.queryFields((t) => ({ this.builder.queryFields((t) => ({
orders: t.prismaField({ orders: t.prismaField({
type: [this.order()], type: [this.order()],
description: description: 'Retrieve a list of orders with optional filtering, ordering, and pagination.',
'Retrieve a list of orders with optional filtering, ordering, and pagination.',
args: this.builder.generator.findManyArgs('Order'), args: this.builder.generator.findManyArgs('Order'),
resolve: async (query, _root, args, _ctx, _info) => { resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.findMany({ return await this.prisma.order.findMany({
@@ -113,17 +107,7 @@ export class OrderSchema extends PothosSchema {
description: 'Create a new order.', description: 'Create a new order.',
args: { args: {
data: t.arg({ data: t.arg({
type: this.builder.generator.getCreateInput('Order', [ type: this.builder.generator.getCreateInput('Order', ['id', 'user', 'paymentId', 'payment', 'refundTicket', 'status', 'total', 'createdAt', 'updatedAt']),
'id',
'user',
'paymentId',
'payment',
'refundTicket',
'status',
'total',
'createdAt',
'updatedAt',
]),
required: true, required: true,
}), }),
}, },
@@ -150,10 +134,7 @@ export class OrderSchema extends PothosSchema {
const order = await this.prisma.order.findUnique({ const order = await this.prisma.order.findUnique({
where: { id: schedule.orderId }, where: { id: schedule.orderId },
}) })
if ( if (order?.status === OrderStatus.PAID || order?.status === OrderStatus.PENDING) {
order?.status === OrderStatus.PAID ||
order?.status === OrderStatus.PENDING
) {
throw new Error('Schedule already has an order') throw new Error('Schedule already has an order')
} }
} }
@@ -202,17 +183,9 @@ export class OrderSchema extends PothosSchema {
description: service.name, description: service.name,
buyerName: ctx.http.me?.name ?? '', buyerName: ctx.http.me?.name ?? '',
buyerEmail: ctx.http.me?.email ?? '', buyerEmail: ctx.http.me?.email ?? '',
returnUrl: `${process.env.PAYOS_RETURN_URL}`.replace( returnUrl: `${process.env.PAYOS_RETURN_URL}`.replace('<serviceId>', service.id),
'<serviceId>', cancelUrl: `${process.env.PAYOS_RETURN_URL}`.replace('<serviceId>', service.id),
service.id, expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toUnixInteger(),
),
cancelUrl: `${process.env.PAYOS_RETURN_URL}`.replace(
'<serviceId>',
service.id,
),
expiredAt: DateTimeUtils.now()
.plus({ minutes: 15 })
.toUnixInteger(),
}) })
// update order payment id // update order payment id
await this.prisma.order.update({ await this.prisma.order.update({
@@ -230,12 +203,12 @@ export class OrderSchema extends PothosSchema {
}) })
// update orderId for schedule dates // update orderId for schedule dates
await this.prisma.scheduleDate.updateMany({ // await this.prisma.scheduleDate.updateMany({
where: { scheduleId: args.data.schedule.connect?.id ?? '' }, // where: { scheduleId: args.data.schedule.connect?.id ?? '' },
data: { // data: {
orderId: order.id, // orderId: order.id,
}, // },
}) // })
// refetch order // refetch order
return await this.prisma.order.findUnique({ return await this.prisma.order.findUnique({
@@ -267,10 +240,7 @@ export class OrderSchema extends PothosSchema {
description: 'Update an existing order.', description: 'Update an existing order.',
args: { args: {
data: t.arg({ data: t.arg({
type: this.builder.generator.getUpdateInput('Order', [ type: this.builder.generator.getUpdateInput('Order', ['status', 'total']),
'status',
'total',
]),
required: true, required: true,
}), }),
where: t.arg({ where: t.arg({

View File

@@ -2,20 +2,8 @@ import { Inject, Injectable, Logger } from '@nestjs/common'
import { PrismaService } from '../Prisma/prisma.service' import { PrismaService } from '../Prisma/prisma.service'
import PayOS from '@payos/node' import PayOS from '@payos/node'
import type { import type { CheckoutRequestType, CheckoutResponseDataType, WebhookType, WebhookDataType, CancelPaymentLinkRequestType, DataType } from '@payos/node/lib/type'
CheckoutRequestType, import { ChatRoomType, OrderStatus, PaymentStatus, ScheduleStatus } from '@prisma/client'
CheckoutResponseDataType,
WebhookType,
WebhookDataType,
CancelPaymentLinkRequestType,
DataType,
} from '@payos/node/lib/type'
import {
ChatRoomType,
OrderStatus,
PaymentStatus,
ScheduleStatus,
} from '@prisma/client'
export type CreatePaymentBody = CheckoutRequestType export type CreatePaymentBody = CheckoutRequestType
export type CreatePaymentResponse = CheckoutResponseDataType export type CreatePaymentResponse = CheckoutResponseDataType
@Injectable() @Injectable()
@@ -31,74 +19,87 @@ export class PayosService {
async webhook(data: WebhookType) { async webhook(data: WebhookType) {
Logger.log(`Webhook received: ${JSON.stringify(data)}`) Logger.log(`Webhook received: ${JSON.stringify(data)}`)
// check if orderCode = 123 mean it's a test payment and auto response success
/* -------------------------------------------------------------------------- */
/* Test payment */
/* -------------------------------------------------------------------------- */
if (data.data.orderCode === 123) { if (data.data.orderCode === 123) {
return { return {
message: 'Payment received', message: 'Payment received',
} }
} }
// verify checksum
const paymentData = this.payos.verifyPaymentWebhookData(data) const paymentData = this.payos.verifyPaymentWebhookData(data)
if (!paymentData) { if (!paymentData) {
Logger.error(`Invalid checksum: ${JSON.stringify(data)}`) Logger.error(`Invalid checksum: ${JSON.stringify(data)}`)
throw new Error('Invalid checksum') throw new Error('Invalid checksum')
} }
const paymentStatus = const paymentStatus = paymentData.code === '00' ? PaymentStatus.PAID : PaymentStatus.CANCELLED
paymentData.code === '00' ? PaymentStatus.PAID : PaymentStatus.CANCELLED /* ---------------------------- begin transaction --------------------------- */
// update payment status try {
const payment = await this.prisma.payment.update({ await this.prisma.$transaction(async (tx) => {
where: { paymentCode: paymentData.paymentLinkId }, // update payment status
data: { const payment = await tx.payment.update({
status: paymentStatus, where: { paymentCode: paymentData.paymentLinkId },
}, data: {
}) status: paymentStatus,
const orderStatus = },
paymentStatus === PaymentStatus.PAID })
? OrderStatus.PAID const orderStatus = paymentStatus === PaymentStatus.PAID ? OrderStatus.PAID : OrderStatus.FAILED
: OrderStatus.FAILED // update order status
// update order status await tx.order.update({
await this.prisma.order.update({ where: { id: payment.orderId },
where: { id: payment.orderId }, data: {
data: { status: orderStatus,
status: orderStatus, },
}, })
}) const order = await tx.order.findUniqueOrThrow({
const order = await this.prisma.order.findUniqueOrThrow({ where: { id: payment.orderId },
where: { id: payment.orderId }, })
}) const schedule = await tx.schedule.findUnique({
const schedule = await this.prisma.schedule.findUnique({ where: { id: order?.scheduleId },
where: { id: order?.scheduleId }, })
}) // update schedule order id
// update schedule order id await tx.schedule.update({
await this.prisma.schedule.update({ where: { id: schedule?.id },
where: { id: schedule?.id }, data: {
data: { customerId: order?.userId,
customerId: order?.userId, orderId: order?.id,
orderId: order?.id, status: ScheduleStatus.IN_PROGRESS,
status: ScheduleStatus.IN_PROGRESS, },
}, })
}) // get mentor id from managed service
// get mentor id from managed service const managedService = await tx.managedService.findUniqueOrThrow({
const managedService = await this.prisma.managedService.findUniqueOrThrow({ where: { id: schedule?.managedServiceId },
where: { id: schedule?.managedServiceId }, })
}) const mentorId = managedService.mentorId
const mentorId = managedService.mentorId // get center id from order service
// get center id from order service const orderService = await tx.service.findUniqueOrThrow({
const orderService = await this.prisma.service.findUniqueOrThrow({ where: { id: order?.serviceId },
where: { id: order?.serviceId }, })
}) const centerId = orderService.centerId
const centerId = orderService.centerId // create chatroom for support
// create chatroom for service meeting room await tx.chatRoom.create({
await this.prisma.chatRoom.create({ data: {
data: { type: ChatRoomType.SUPPORT,
type: ChatRoomType.SUPPORT, customerId: order.userId,
customerId: order.userId, centerId: centerId,
centerId: centerId, mentorId: mentorId,
mentorId: mentorId, },
}, })
}) // update orderId for schedule dates
return { await tx.scheduleDate.updateMany({
message: 'Payment received', where: { scheduleId: schedule?.id },
data: {
orderId: order.id,
},
})
return {
message: 'Payment received',
}
})
} catch (error) {
Logger.error(`Transaction failed: ${error}`)
throw error
} }
} }
@@ -114,10 +115,7 @@ export class PayosService {
return await this.payos.getPaymentLinkInformation(orderId) return await this.payos.getPaymentLinkInformation(orderId)
} }
async cancelPaymentURL( async cancelPaymentURL(orderId: string | number, cancellationReason?: string) {
orderId: string | number,
cancellationReason?: string,
) {
return await this.payos.cancelPaymentLink(orderId, cancellationReason) return await this.payos.cancelPaymentLink(orderId, cancellationReason)
} }

View File

@@ -384,15 +384,16 @@ export class UserSchema extends PothosSchema {
email: t.arg({ type: 'String', required: true }), email: t.arg({ type: 'String', required: true }),
}, },
resolve: async (_parent, args, ctx) => { resolve: async (_parent, args, ctx) => {
// check context
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
// check context is admin
if (ctx.http.me?.role !== 'ADMIN') {
throw new UnauthorizedException(`Only admin can invite moderator`)
}
return this.prisma.$transaction(async (tx) => { return this.prisma.$transaction(async (tx) => {
// check context
if (ctx.isSubscription) {
throw new Error('Not allowed')
}
// check context is admin
if (ctx.http.me?.role !== 'ADMIN') {
throw new UnauthorizedException(`Only admin can invite moderator`)
}
let user let user
// perform update role // perform update role
try { try {