import { Inject, Injectable } from '@nestjs/common' import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken, } from '@smatch-corp/nestjs-pothos' import { Builder } from '../Graphql/graphql.builder' import { PrismaService } from '../Prisma/prisma.service' import { OrderStatus } from '@prisma/client' import { DateTimeUtils } from '../common/utils/datetime.utils' import { PayosService } from '../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() } // Types section @PothosRef() order() { return this.builder.prismaObject('Order', { description: 'An order in the system.', fields: (t) => ({ id: t.exposeID('id', { description: 'The ID of the order.', }), userId: t.exposeID('userId', { description: 'The ID of the user.', }), serviceId: t.exposeID('serviceId', { description: 'The ID of the service.', }), status: t.expose('status', { type: OrderStatus, description: 'The status of the order.', }), total: t.exposeInt('total', { description: 'The total price of the order.', }), scheduleId: t.exposeID('scheduleId', { description: 'The ID of the schedule.', }), schedule: t.relation('schedule', { description: 'The schedule of the order.', }), createdAt: t.expose('createdAt', { type: 'DateTime', description: 'The date and time the order was created.', }), updatedAt: t.expose('updatedAt', { type: 'DateTime', description: 'The date and time the order was updated.', }), user: t.relation('user', { description: 'The user who made the order.', }), service: t.relation('service', { description: 'The service for the order.', }), refundTicket: t.relation('refundTicket', { description: 'The refund ticket for the order.', }), payment: t.relation('payment', { description: 'The payment for the order.', }), paymentId: t.exposeString('paymentId', { description: 'The ID of the payment.', }), }), }) } @Pothos() init(): void { // query section this.builder.queryFields((t) => ({ orders: t.prismaField({ type: [this.order()], description: 'Retrieve a list of orders with optional filtering, ordering, and pagination.', args: this.builder.generator.findManyArgs('Order'), resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.order.findMany({ ...query, take: args.take ?? undefined, skip: args.skip ?? undefined, orderBy: args.orderBy ?? undefined, where: args.filter ?? undefined, }) }, }), order: t.prismaField({ type: this.order(), args: this.builder.generator.findUniqueArgs('Order'), description: 'Retrieve a single order by its unique identifier.', resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.order.findUnique({ ...query, where: args.where, }) }, }), })) // mutation section this.builder.mutationFields((t) => ({ createOrder: t.prismaField({ type: this.order(), description: 'Create a new order.', args: { data: t.arg({ type: this.builder.generator.getCreateInput('Order', [ 'id', 'user', 'paymentId', 'payment', 'refundTicket', 'status', 'total', 'createdAt', 'updatedAt', ]), required: true, }), }, resolve: async (query, _root, args, ctx, _info) => { if (ctx.isSubscription) { throw new Error('Subscription is not allowed') } if (!args.data.service.connect?.id) { throw new Error('Service not found') } // query service const service = await this.prisma.service.findUnique({ where: { id: args.data.service.connect.id }, }) if (!service) { throw new Error('Service not found') } // check if input schedule has order id then throw error const schedule = await this.prisma.schedule.findUnique({ where: { id: args.data.schedule.connect?.id ?? '' }, }) if (schedule?.orderId) { // check if order status is PAID OR PENDING const order = await this.prisma.order.findUnique({ where: { id: schedule.orderId }, }) if ( order?.status === OrderStatus.PAID || order?.status === OrderStatus.PENDING ) { throw new Error('Schedule already has an order') } } const order = await this.prisma.order.create({ ...query, data: { status: OrderStatus.PENDING, total: service.price, userId: ctx.http.me?.id ?? '', serviceId: service.id, scheduleId: args.data.schedule.connect?.id ?? '', }, }) // check if service is valid if (!args.data.service.connect) { throw new Error('Service not found') } // check if order is free if (order.total === 0) { // assign schedule await this.prisma.schedule.update({ where: { id: args.data.schedule.connect?.id ?? '' }, data: { orderId: order.id, }, }) return order } // random integer const paymentCode = Math.floor(Math.random() * 1000000) // create payment const payment = await this.prisma.payment.create({ data: { orderId: order.id, amount: service.price, paymentCode: paymentCode.toString(), expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toJSDate(), }, }) // generate payment url const paymentData = await this.payosService.createPayment({ orderCode: paymentCode, amount: service.price, description: service.name, buyerName: ctx.http.me?.name ?? '', buyerEmail: ctx.http.me?.email ?? '', returnUrl: `${process.env.PAYOS_RETURN_URL}`.replace( '', service.id, ), cancelUrl: `${process.env.PAYOS_RETURN_URL}`.replace( '', service.id, ), expiredAt: DateTimeUtils.now() .plus({ minutes: 15 }) .toUnixInteger(), }) // update order payment id await this.prisma.order.update({ where: { id: order.id }, data: { paymentId: payment.id, }, }) // update payment url await this.prisma.payment.update({ where: { id: payment.id }, data: { paymentCode: paymentData.paymentLinkId, }, }) // refetch order return await this.prisma.order.findUnique({ where: { id: order.id }, include: { payment: true, }, }) }, }), deleteOrder: t.prismaField({ type: this.order(), description: 'Delete an existing order.', args: { where: t.arg({ type: this.builder.generator.getWhereUnique('Order'), required: true, }), }, resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.order.delete({ ...query, where: args.where, }) }, }), updateOrder: t.prismaField({ type: this.order(), description: 'Update an existing order.', args: { data: t.arg({ type: this.builder.generator.getUpdateInput('Order', [ 'status', 'total', ]), required: true, }), where: t.arg({ type: this.builder.generator.getWhereUnique('Order'), required: true, }), }, resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.order.update({ ...query, data: args.data, where: args.where, }) }, }), })) } }