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, PaymentStatus, RefundTicketStatus, Role } from '@prisma/client' import { DateTimeUtils } from 'src/common/utils/datetime.utils' @Injectable() export class RefundTicketSchema extends PothosSchema { constructor( @Inject(SchemaBuilderToken) private readonly builder: Builder, private readonly prisma: PrismaService, ) { super() } // Types section @PothosRef() refundTicket() { return this.builder.prismaObject('RefundTicket', { fields: (t) => ({ id: t.exposeID('id', { description: 'The ID of the refund ticket.', }), amount: t.exposeFloat('amount', { description: 'The amount of the refund ticket.', }), status: t.expose('status', { type: RefundTicketStatus, description: 'The status of the refund ticket.', }), createdAt: t.expose('createdAt', { type: 'DateTime', description: 'The date and time the refund ticket was created.', }), updatedAt: t.expose('updatedAt', { type: 'DateTime', description: 'The date and time the refund ticket was updated.', }), order: t.relation('order', { description: 'The order for the refund ticket.', }), }), }) } @PothosRef() refundTicketAction() { return this.builder.enumType('RefundTicketAction', { description: 'The action to take on a refund ticket.', values: ['APPROVE', 'REJECT'], }) } // Queries section @Pothos() init(): void { this.builder.queryFields((t) => ({ refundTickets: t.prismaField({ type: [this.refundTicket()], description: 'Retrieve a list of refund tickets with optional filtering, ordering, and pagination.', args: this.builder.generator.findManyArgs('RefundTicket'), resolve: async (query, _root, args, _ctx, _info) => { return await this.prisma.refundTicket.findMany({ ...query, where: args.filter ?? undefined, orderBy: args.orderBy ?? undefined, cursor: args.cursor ?? undefined, take: args.take ?? undefined, skip: args.skip ?? undefined, }) }, }), })) this.builder.mutationFields((t) => ({ requestRefund: t.prismaField({ type: this.refundTicket(), description: 'Request a refund for an order.', args: { orderId: t.arg({ type: 'String', required: true, }), reason: t.arg({ type: 'String', required: true, }), }, resolve: async (_query, _root, args, ctx, _info) => { if (ctx.isSubscription) { throw new Error('Subscription is not allowed') } if (ctx.http.me?.role !== Role.CUSTOMER) { throw new Error('Only customers can request refund') } // check if order exists const order = await this.prisma.order.findUnique({ where: { id: args.orderId }, }) if (!order) { throw new Error('Order not found') } // check if order status is PAID if (order.status !== OrderStatus.PAID) { throw new Error('Order is not paid') } // check if order total is not null if (!order.total || order.total === 0) { throw new Error('Order total is null or free') } // calculate refund amount based on order time: if order is less than 24 hours, refund 100%, if more than 24 hours, less than 48 hours, refund 50%, if more than 72 hours, cannot refund const now = DateTimeUtils.now() const orderDate = DateTimeUtils.fromDate(order.createdAt) const diffTime = Math.abs(now.diff(orderDate).toMillis()) const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) let refundAmount = 0 if (diffDays < 24) refundAmount = order.total else if (diffDays < 48) refundAmount = order.total * 0.5 // create refund ticket const refundTicket = await this.prisma.refundTicket.create({ data: { orderId: order.id, status: RefundTicketStatus.PENDING, amount: refundAmount, }, }) return refundTicket }, }), processRefundTicket: t.prismaField({ type: this.refundTicket(), description: 'Process a refund ticket, can only done by moderator', args: { refundTicketId: t.arg({ type: 'String', required: true, }), action: t.arg({ type: this.refundTicketAction(), required: true, }), }, resolve: async (_query, _root, args, ctx, _info) => { if (ctx.isSubscription) { throw new Error('Subscription is not allowed') } if (ctx.http.me?.role !== Role.MODERATOR) { throw new Error('Only moderators can process refund tickets') } // update refund ticket status const refundTicket = await this.prisma.refundTicket.update({ where: { id: args.refundTicketId }, data: { status: args.action === 'APPROVE' ? RefundTicketStatus.APPROVED : RefundTicketStatus.REJECTED }, }) return refundTicket }, }), })) } }