update transaction
This commit is contained in:
8
src/Finance/finance.module.ts
Normal file
8
src/Finance/finance.module.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { FinanceSchema } from './finance.schema'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [FinanceSchema],
|
||||||
|
exports: [FinanceSchema],
|
||||||
|
})
|
||||||
|
export class FinanceModule {}
|
||||||
5
src/Finance/finance.schema.ts
Normal file
5
src/Finance/finance.schema.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
import { Pothos, PothosRef } from '@smatch-corp/nestjs-pothos'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FinanceSchema {}
|
||||||
@@ -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({
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user