chore: add payment module
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
"singleQuote": false,
|
||||
"trailingComma": "all",
|
||||
"endOfLine": "auto",
|
||||
"printWidth": 140,
|
||||
"printWidth": 150,
|
||||
"arrowParens": "always",
|
||||
"semi": true,
|
||||
"tabWidth": 2
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
registerDecorator,
|
||||
} from "class-validator";
|
||||
import { ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, registerDecorator } from "class-validator";
|
||||
|
||||
export function isValidIranianNationalCode(nationalCode: string): boolean {
|
||||
if (!/^\d{10}$/.test(nationalCode)) return false;
|
||||
|
||||
@@ -299,30 +299,6 @@ export const enum TicketMessageEnum {
|
||||
CATEGORY_DELETED = "دسته بندی با موفقیت حذف شد",
|
||||
}
|
||||
|
||||
export const enum WalletMessage {
|
||||
WALLET_NOT_FOUND = "کیف پول یافت نشد",
|
||||
AMOUNT_MUST_BE_INTEGER = "مبلغ شارژ باید یک عدد صحیح باشد",
|
||||
AMOUNT_REQUIRED = "مبلغ شارژ مورد نیاز است",
|
||||
DEPOSIT_TYPE_REQUIRED = "نوع شارژ مورد نیاز است",
|
||||
AMOUNT_MINIMUM = "حداقل مبلغ شارژ ۱۰۰,۰۰۰ تومان است",
|
||||
GATEWAY_REQUIRED = "درگاه پرداخت مورد نیاز است",
|
||||
RECEIPT_URL_SHOULD_BE_URL = "آدرس فیش باید یک آدرس یو آر ال باشد",
|
||||
RECEIPT_URL_REQUIRED = "آدرس فیش مورد نیاز است",
|
||||
BANK_ACCOUNT_ID_REQUIRED = "شناسه حساب بانکی مورد نیاز است",
|
||||
BANK_ACCOUNT_ID_SHOULD_BE_UUID = "شناسه حساب بانکی باید یک UUID معتبر باشد",
|
||||
ERROR_IN_CHARGE_WALLET = "خطا در شارژ کیف پول",
|
||||
DEPOSIT_WALLET_IPG = "شارژ کیف پول از طریق درگاه",
|
||||
TRANSFER_METHOD_REQUIRED = "روش انتفال اجباری است",
|
||||
GATEWAY_ID_SHOULD_BE_UUID = "آیدی درگاه پرداخت باید UUID باشد",
|
||||
DEPOSIT_WALLET_TRANSFER = "شارژ کیف پول از طریق انتقال بانکی",
|
||||
INSUFFICIENT_BALANCE = "موجودی کیف پول کافی نیست",
|
||||
SUBSCRIPTION_WALLET_TRANSFER = "پرداخت اشتراک از طریق کیف پول",
|
||||
SUPPORT_PLAN_WALLET_TRANSFER = "پرداخت پلن پشتیبانی از طریق کیف پول",
|
||||
INVOICE_WALLET_TRANSFER = "پرداخت صورت حساب از طریق کیف پول",
|
||||
TRANSACTION_NOT_FOUND = "تراکنش پیدا نشد",
|
||||
REFERRAL_REWARD_WALLET_TRANSFER = "پرداخت پاداش ارجاع از طریق کیف پول",
|
||||
}
|
||||
|
||||
export const enum PaymentMessage {
|
||||
PAYMENT_GATEWAY_NOT_FOUND = "درگاه پرداخت یافت نشد",
|
||||
ERROR_IN_PROCESS_PAYMENT = "خطا در پردازش پرداخت",
|
||||
@@ -357,6 +333,12 @@ export const enum PaymentMessage {
|
||||
TRANSFER_METHOD_NOT_ALLOWED = "روش انتقال انتخابی مجاز نیست",
|
||||
DEPOSIT_ALREADY_APPROVED = "درخواست واریز قبلا تایید شده است",
|
||||
DEPOSIT_ALREADY_REJECTED = "درخواسن واریز قبلا رد شده است",
|
||||
GATEWAY_REQUIRED = "درگاه پرداخت مورد نیاز است",
|
||||
GATEWAY_ID_SHOULD_BE_UUID = "آیدی درگاه پرداخت باید UUID باشد",
|
||||
INVOICE_ID_REQUIRED = "صورت حساب مورد نیاز است",
|
||||
INVOICE_ID_SHOULD_BE_UUID = "صورت حساب باید UUID باشد",
|
||||
ERROR_IN_PAYMENT = "خطا در پرداخت",
|
||||
CHECKOUT_INVOICE_MESSAGE = "پرداخت صورت حساب شماره [invoiceNumber] ثبت شد",
|
||||
}
|
||||
|
||||
export const enum SettingMessageEnum {
|
||||
|
||||
@@ -4,7 +4,6 @@ export function zarinpalConfig() {
|
||||
return {
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
merchantId: configService.getOrThrow<string>("ZARINPAL_MERCHANT_ID"),
|
||||
gatewayApiUrl: configService.getOrThrow<string>("ZARINPAL_API_URL"),
|
||||
callBackUrl: configService.getOrThrow<string>("CALLBACK_URL"),
|
||||
ipgType: configService.getOrThrow<string>("IPG_TYPE"),
|
||||
@@ -13,7 +12,6 @@ export function zarinpalConfig() {
|
||||
}
|
||||
|
||||
export interface IZarinpalConfig {
|
||||
merchantId: string;
|
||||
gatewayApiUrl: string;
|
||||
callBackUrl: string;
|
||||
ipgType: string;
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
Logger,
|
||||
NestInterceptor,
|
||||
} from "@nestjs/common";
|
||||
import { BadRequestException, CallHandler, ExecutionContext, ForbiddenException, Injectable, Logger, NestInterceptor } from "@nestjs/common";
|
||||
import { FastifyRequest } from "fastify";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
|
||||
@@ -44,11 +44,7 @@ export class AnnouncementController {
|
||||
|
||||
@ApiProperty({ description: "Get all announcements for user " })
|
||||
@Get("user")
|
||||
getAnnouncementsByUser(
|
||||
@Query() queryDto: SearchAnnouncementQueryDto,
|
||||
@UserDec("id") userId: string,
|
||||
@BusinessDec("id") businessId: string,
|
||||
) {
|
||||
getAnnouncementsByUser(@Query() queryDto: SearchAnnouncementQueryDto, @UserDec("id") userId: string, @BusinessDec("id") businessId: string) {
|
||||
return this.announcementService.getAnnouncementsByUser(queryDto, userId, businessId);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ export class Business extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 255, nullable: true })
|
||||
logoUrl?: string;
|
||||
|
||||
@Property({ type: "varchar", length: 255, nullable: true })
|
||||
zarinpalMerchantId?: string;
|
||||
|
||||
//=========================
|
||||
|
||||
@OneToMany(() => Company, (company) => company.business, { cascade: [Cascade.ALL] })
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
import {
|
||||
Cascade,
|
||||
Collection,
|
||||
Entity,
|
||||
EntityRepositoryType,
|
||||
Enum,
|
||||
Formula,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
Opt,
|
||||
Property,
|
||||
} from "@mikro-orm/core";
|
||||
import { Cascade, Collection, Entity, EntityRepositoryType, Enum, Formula, ManyToOne, OneToMany, OneToOne, Opt, Property } from "@mikro-orm/core";
|
||||
|
||||
import { CompanyProduct } from "./company-product.entity";
|
||||
import { CompanyService } from "./company-service.entity";
|
||||
|
||||
@@ -366,8 +366,7 @@ export class CompaniesService {
|
||||
services,
|
||||
user,
|
||||
business,
|
||||
chiefExecutiveOfficer:
|
||||
"chiefExecutiveOfficer" in createDto ? createDto.chiefExecutiveOfficer : `${createDto.firstName} ${createDto.lastName}`,
|
||||
chiefExecutiveOfficer: "chiefExecutiveOfficer" in createDto ? createDto.chiefExecutiveOfficer : `${createDto.firstName} ${createDto.lastName}`,
|
||||
email: createDto.email,
|
||||
phone: createDto.phone,
|
||||
});
|
||||
|
||||
@@ -46,12 +46,7 @@ export class InvoicesController {
|
||||
|
||||
@ApiOperation({ summary: "get single invoice by Id " })
|
||||
@Get(":id")
|
||||
getInvoiceById(
|
||||
@Param() paramDto: ParamDto,
|
||||
@UserDec("role") role: RoleEnum,
|
||||
@UserDec("id") userId: string,
|
||||
@BusinessDec("id") businessId: string,
|
||||
) {
|
||||
getInvoiceById(@Param() paramDto: ParamDto, @UserDec("role") role: RoleEnum, @UserDec("id") userId: string, @BusinessDec("id") businessId: string) {
|
||||
return this.invoiceService.getInvoiceById(paramDto.id, role, userId, businessId);
|
||||
}
|
||||
|
||||
|
||||
@@ -199,13 +199,7 @@ export class InvoicesService {
|
||||
const otpCode = await this.otpService.generateAndSetInCache(user.phone, "INVOICE_VERIFY");
|
||||
const items = invoice.items.map((item) => item.name).join(", ");
|
||||
|
||||
await this.smsService.sendInvoiceVerifyCode(
|
||||
user.phone,
|
||||
otpCode,
|
||||
invoice.numericId,
|
||||
new Decimal(invoice.totalPrice).toNumber(),
|
||||
items,
|
||||
);
|
||||
await this.smsService.sendInvoiceVerifyCode(user.phone, otpCode, invoice.numericId, new Decimal(invoice.totalPrice).toNumber(), items);
|
||||
|
||||
await em.commit();
|
||||
|
||||
@@ -318,10 +312,7 @@ export class InvoicesService {
|
||||
{ populate: ["items", "company"] },
|
||||
);
|
||||
} else {
|
||||
invoice = await this.invoiceRepository.findOne(
|
||||
{ id: invoiceId, business: { id: businessId } },
|
||||
{ populate: ["items", "company", "business"] },
|
||||
);
|
||||
invoice = await this.invoiceRepository.findOne({ id: invoiceId, business: { id: businessId } }, { populate: ["items", "company", "business"] });
|
||||
}
|
||||
|
||||
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
|
||||
@@ -360,88 +351,48 @@ export class InvoicesService {
|
||||
}
|
||||
//*********************************** */
|
||||
|
||||
// async payInvoice(invoiceId: string, userId: string, em = this.em.fork()) {
|
||||
// let transactionStarted = false;
|
||||
async payInvoice(invoiceId: string, userId: string, em = this.em.fork()) {
|
||||
let transactionStarted = false;
|
||||
|
||||
// try {
|
||||
// if (!em.isInTransaction) {
|
||||
// await em.begin();
|
||||
// transactionStarted = true;
|
||||
// }
|
||||
try {
|
||||
if (!em.isInTransaction) {
|
||||
await em.begin();
|
||||
transactionStarted = true;
|
||||
}
|
||||
|
||||
// const user = await this.usersService.findOneByIdWithEntityManager(userId, em);
|
||||
const user = await this.usersService.findOneByIdWithEntityManager(userId, em);
|
||||
|
||||
// const invoice = await this.getPendingInvoiceByIdWithQueryRunner(invoiceId, user.id, em);
|
||||
const invoice = await this.getPendingInvoiceByIdWithQueryRunner(invoiceId, user.id, em);
|
||||
|
||||
// if (invoice.status !== InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_PAID);
|
||||
if (invoice.status !== InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_PAID);
|
||||
|
||||
// const userWallet = await this.walletsService.getWalletByUserId(user.id, em);
|
||||
invoice.status = InvoiceStatus.PAID;
|
||||
invoice.paidAt = dayjs().toDate();
|
||||
|
||||
// if (userWallet.balance < invoice.totalPrice) throw new BadRequestException(WalletMessage.INSUFFICIENT_BALANCE);
|
||||
await em.persistAndFlush(invoice);
|
||||
|
||||
// userWallet.balance = new Decimal(userWallet.balance).sub(invoice.totalPrice);
|
||||
await this.notificationQueue.addBillInvoiceNotification(userId, {
|
||||
invoiceId: invoice.numericId.toString(),
|
||||
dueDate: invoice.dueDate,
|
||||
createDate: invoice.createdAt,
|
||||
price: new Decimal(invoice.totalPrice).toNumber(),
|
||||
userPhone: user.phone,
|
||||
userEmail: user.email,
|
||||
items: invoice.items.map((item) => item.name).join(", "),
|
||||
paidAt: invoice.paidAt,
|
||||
});
|
||||
|
||||
// await em.persistAndFlush(userWallet);
|
||||
if (transactionStarted) await em.commit();
|
||||
|
||||
// if (invoice.items[0]?.subscriptionPlan) {
|
||||
// const userSubscription = invoice.items[0].subscriptionPlan;
|
||||
// userSubscription.status = SubscriptionStatus.ACTIVE;
|
||||
|
||||
// //
|
||||
// await queryRunner.manager.save(UserSubscription, userSubscription);
|
||||
// await this.walletsService.createSubscriptionTransaction(invoice.totalPrice, userWallet.id, queryRunner);
|
||||
// await this.addNotifyForWalletDeduction(invoice, user, userWallet, WalletMessage.SUBSCRIPTION_WALLET_TRANSFER);
|
||||
// await this.addNotifyAdminForSubscriptionPaymentToQueue(invoice);
|
||||
// //
|
||||
// } else if (invoice.items[0]?.supportPlan) {
|
||||
// const oldUserSupportPlan = await queryRunner.manager.findOne(UserSupportPlan, {
|
||||
// where: { user: { id: user.id }, status: UserSupportPlanStatus.ACTIVE },
|
||||
// });
|
||||
|
||||
// if (oldUserSupportPlan) {
|
||||
// await queryRunner.manager.update(UserSupportPlan, oldUserSupportPlan.id, { status: UserSupportPlanStatus.INACTIVE });
|
||||
// }
|
||||
|
||||
// const userSupportPlan = invoice.items[0].supportPlan;
|
||||
// userSupportPlan.status = UserSupportPlanStatus.ACTIVE;
|
||||
|
||||
// await queryRunner.manager.save(UserSupportPlan, userSupportPlan);
|
||||
// await this.walletsService.createSupportPlanTransaction(invoice.totalPrice, userWallet.id, queryRunner);
|
||||
// await this.addNotifyForWalletDeduction(invoice, user, userWallet, WalletMessage.SUPPORT_PLAN_WALLET_TRANSFER);
|
||||
// //
|
||||
// } else {
|
||||
// await this.walletsService.createInvoiceTransaction(invoice.totalPrice, userWallet.id, queryRunner);
|
||||
// await this.addNotifyForWalletDeduction(invoice, user, userWallet, WalletMessage.INVOICE_WALLET_TRANSFER);
|
||||
// }
|
||||
// invoice.status = InvoiceStatus.PAID;
|
||||
// invoice.paidAt = dayjs().toDate();
|
||||
|
||||
// await queryRunner.manager.save(Invoice, invoice);
|
||||
|
||||
// await this.notificationQueue.addBillInvoiceNotification(userId, {
|
||||
// invoiceId: invoice.numericId.toString(),
|
||||
// dueDate: invoice.dueDate,
|
||||
// createDate: invoice.createdAt,
|
||||
// price: new Decimal(invoice.totalPrice).toNumber(),
|
||||
// userPhone: user.phone,
|
||||
// userEmail: user.email,
|
||||
// items: invoice.items.map((item) => item.name).join(", "),
|
||||
// paidAt: invoice.paidAt,
|
||||
// });
|
||||
|
||||
// if (transactionStarted) await queryRunner.commitTransaction();
|
||||
|
||||
// return {
|
||||
// message: InvoiceMessage.INVOICE_PAID,
|
||||
// invoice,
|
||||
// };
|
||||
// } catch (error) {
|
||||
// if (transactionStarted) await queryRunner.rollbackTransaction();
|
||||
// throw error;
|
||||
// } finally {
|
||||
// if (transactionStarted) await queryRunner.release();
|
||||
// }
|
||||
// }
|
||||
return {
|
||||
message: InvoiceMessage.INVOICE_PAID,
|
||||
invoice,
|
||||
};
|
||||
} catch (error) {
|
||||
if (transactionStarted) await em.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//*********************************** */
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Controller, Get, Param, Patch, Query } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { ApiOperation } from "@nestjs/swagger";
|
||||
|
||||
import { SearchNotificationQueryDto } from "./DTO/search-notification-query.dto";
|
||||
import { NotificationsService } from "./providers/notifications.service";
|
||||
@@ -9,7 +9,6 @@ import { UserDec } from "../../common/decorators/user.decorator";
|
||||
import { ParamDto } from "../../common/DTO/param.dto";
|
||||
|
||||
@Controller("notifications")
|
||||
@ApiTags("Notifications")
|
||||
@AuthGuards()
|
||||
export class NotificationController {
|
||||
constructor(private readonly notificationService: NotificationsService) {}
|
||||
|
||||
@@ -142,10 +142,7 @@ export class NotificationsService {
|
||||
);
|
||||
if (data.userEmail) await this.emailService.sendInvoiceEmail(data);
|
||||
|
||||
return this.createNotification(
|
||||
{ title: NotificationMessage.INVOICE_CREATION, type: NotifType.CREATE_INVOICE, message, recipientId },
|
||||
em,
|
||||
);
|
||||
return this.createNotification({ title: NotificationMessage.INVOICE_CREATION, type: NotifType.CREATE_INVOICE, message, recipientId }, em);
|
||||
}
|
||||
//************************ */
|
||||
|
||||
@@ -238,10 +235,7 @@ export class NotificationsService {
|
||||
await this.smsService.sendInvoiceOverdueSms(data.userPhone, data.invoiceId, data.price, data.lateFee || 0, dateFormat(data.dueDate));
|
||||
if (data.userEmail) await this.emailService.sendInvoiceEmail(data);
|
||||
|
||||
return this.createNotification(
|
||||
{ title: NotificationMessage.INVOICE_OVERDUE, type: NotifType.INVOICE_OVERDUE, message, recipientId },
|
||||
em,
|
||||
);
|
||||
return this.createNotification({ title: NotificationMessage.INVOICE_OVERDUE, type: NotifType.INVOICE_OVERDUE, message, recipientId }, em);
|
||||
}
|
||||
|
||||
//************************ */
|
||||
@@ -252,10 +246,7 @@ export class NotificationsService {
|
||||
|
||||
await this.smsService.sendRecurringInvoiceDraftSms(data.userPhone, data.invoiceId, data.price);
|
||||
if (data.userEmail) await this.emailService.sendInvoiceEmail(data);
|
||||
return this.createNotification(
|
||||
{ title: NotificationMessage.RECURRING_INVOICE, type: NotifType.RECURRING_INVOICE, message, recipientId },
|
||||
em,
|
||||
);
|
||||
return this.createNotification({ title: NotificationMessage.RECURRING_INVOICE, type: NotifType.RECURRING_INVOICE, message, recipientId }, em);
|
||||
}
|
||||
|
||||
// //************************ */
|
||||
|
||||
@@ -18,12 +18,7 @@ import { NotificationsService } from "../providers/notifications.service";
|
||||
type NotificationJobData = {
|
||||
type: NotifType;
|
||||
recipientId: string;
|
||||
data:
|
||||
| ITicketNotificationData
|
||||
| IInvoiceNotificationData
|
||||
| IAnnouncementNotificationData
|
||||
| IPaymentNotificationData
|
||||
| IOtpNotificationData;
|
||||
data: ITicketNotificationData | IInvoiceNotificationData | IAnnouncementNotificationData | IPaymentNotificationData | IOtpNotificationData;
|
||||
};
|
||||
|
||||
@Processor(NOTIFICATION.QUEUE_NAME, { concurrency: 5 })
|
||||
@@ -68,11 +63,7 @@ export class NotificationProcessor extends WorkerProcessor {
|
||||
await this.notificationsService.createTicketNotification(recipientId, data as ITicketNotificationData, entityManager);
|
||||
break;
|
||||
case NotifType.ASSIGN_TICKET:
|
||||
await this.notificationsService.createAssignTicketNotificationForAdmin(
|
||||
recipientId,
|
||||
data as ITicketNotificationData,
|
||||
entityManager,
|
||||
);
|
||||
await this.notificationsService.createAssignTicketNotificationForAdmin(recipientId, data as ITicketNotificationData, entityManager);
|
||||
break;
|
||||
|
||||
// Invoice Notifications
|
||||
@@ -80,11 +71,7 @@ export class NotificationProcessor extends WorkerProcessor {
|
||||
await this.notificationsService.createInvoiceCreationNotification(recipientId, data as IInvoiceNotificationData, entityManager);
|
||||
break;
|
||||
case NotifType.BILL_INVOICE_REMINDER:
|
||||
await this.notificationsService.createBillInvoiceReminderNotification(
|
||||
recipientId,
|
||||
data as IInvoiceNotificationData,
|
||||
entityManager,
|
||||
);
|
||||
await this.notificationsService.createBillInvoiceReminderNotification(recipientId, data as IInvoiceNotificationData, entityManager);
|
||||
break;
|
||||
case NotifType.BILL_INVOICE:
|
||||
await this.notificationsService.createBillInvoiceNotification(recipientId, data as IInvoiceNotificationData, entityManager);
|
||||
@@ -104,11 +91,7 @@ export class NotificationProcessor extends WorkerProcessor {
|
||||
await this.notificationsService.createPaymentReminderNotification(recipientId, data as IPaymentNotificationData, entityManager);
|
||||
break;
|
||||
case NotifType.PAYMENT_CANCELLATION:
|
||||
await this.notificationsService.createPaymentCancellationNotification(
|
||||
recipientId,
|
||||
data as IPaymentNotificationData,
|
||||
entityManager,
|
||||
);
|
||||
await this.notificationsService.createPaymentCancellationNotification(recipientId, data as IPaymentNotificationData, entityManager);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsUUID } from "class-validator";
|
||||
|
||||
import { PaymentMessage } from "../../../common/enums/message.enum";
|
||||
|
||||
export class CheckoutPaymentDto {
|
||||
@IsNotEmpty({ message: PaymentMessage.GATEWAY_REQUIRED })
|
||||
@IsUUID("4", { message: PaymentMessage.GATEWAY_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "Gateway id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
|
||||
gatewayId: string;
|
||||
|
||||
@IsNotEmpty({ message: PaymentMessage.INVOICE_ID_REQUIRED })
|
||||
@IsUUID("4", { message: PaymentMessage.INVOICE_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "Invoice id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
|
||||
invoiceId: string;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum, IsInt, IsNotEmpty, IsUUID, IsUrl, Min } from "class-validator";
|
||||
|
||||
import { WalletMessage } from "../../../common/enums/message.enum";
|
||||
import { TransferType } from "../enums/payment-type.enum";
|
||||
|
||||
export class DepositDto {
|
||||
@IsNotEmpty({ message: WalletMessage.AMOUNT_REQUIRED })
|
||||
@IsInt({ message: WalletMessage.AMOUNT_MUST_BE_INTEGER })
|
||||
@Min(100_000, { message: WalletMessage.AMOUNT_MINIMUM })
|
||||
@ApiProperty({ description: "Amount to charge", example: 100_000 })
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export class GatewayDepositDto extends DepositDto {
|
||||
// @IsNotEmpty({ message: WalletMessage.GATEWAY_REQUIRED })
|
||||
// @IsEnum(GatewayEnum)
|
||||
// @ApiProperty({ description: "Gateway to use", example: "zarinpal" })
|
||||
// gateway: GatewayEnum;
|
||||
|
||||
@IsNotEmpty({ message: WalletMessage.GATEWAY_REQUIRED })
|
||||
@IsUUID("4", { message: WalletMessage.GATEWAY_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "Gateway id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
|
||||
gatewayId: string;
|
||||
}
|
||||
|
||||
export class TransferDepositDto extends DepositDto {
|
||||
@IsNotEmpty({ message: WalletMessage.RECEIPT_URL_REQUIRED })
|
||||
@IsUrl({ protocols: ["http", "https"], require_protocol: true }, { message: WalletMessage.RECEIPT_URL_SHOULD_BE_URL })
|
||||
@ApiProperty({ description: "the transferReceipt url", example: "https://example.com/image.png" })
|
||||
transferReceiptUrl: string;
|
||||
|
||||
@IsNotEmpty({ message: WalletMessage.BANK_ACCOUNT_ID_REQUIRED })
|
||||
@IsUUID("4", { message: WalletMessage.BANK_ACCOUNT_ID_SHOULD_BE_UUID })
|
||||
@ApiProperty({ description: "bank account id", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
|
||||
bankAccountId: string;
|
||||
|
||||
@IsNotEmpty({ message: WalletMessage.TRANSFER_METHOD_REQUIRED })
|
||||
@IsEnum(TransferType)
|
||||
@ApiProperty({ description: "transfer method", example: TransferType.CARD_TO_CARD, enum: TransferType })
|
||||
method: TransferType;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum, IsNotEmpty } from "class-validator";
|
||||
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
import { CommonMessage } from "../../../common/enums/message.enum";
|
||||
import { TransferType } from "../enums/payment-type.enum";
|
||||
|
||||
export class PaymentTransactionQueryDto extends PaginationDto {
|
||||
@IsNotEmpty({ message: CommonMessage.PaymentTypeQueryNotEmpty })
|
||||
@IsEnum(TransferType)
|
||||
@ApiProperty({
|
||||
description: `payment type ${TransferType.CARD_TO_CARD} ${TransferType.SHEBA} `,
|
||||
enum: TransferType,
|
||||
example: TransferType.CARD_TO_CARD,
|
||||
})
|
||||
type: TransferType;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IsEnum, IsNotEmpty, IsOptional } from "class-validator";
|
||||
import { IsEnum, IsNotEmpty } from "class-validator";
|
||||
|
||||
import { GatewayEnum } from "../enums/gateway.enum";
|
||||
import { GatewayType } from "../types/gateway.type";
|
||||
@@ -8,10 +8,6 @@ export class VerifyParamDto {
|
||||
@IsNotEmpty()
|
||||
@IsEnum(GatewayEnum)
|
||||
gateway: GatewayType;
|
||||
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
invoiceId?: string;
|
||||
}
|
||||
|
||||
export class VerifyQueryDto {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export enum TransferType {
|
||||
CARD_TO_CARD = "CARD_TO_CARD",
|
||||
SHEBA = "SHEBA",
|
||||
// GATEWAY = "GATEWAY",
|
||||
}
|
||||
@@ -32,9 +32,9 @@ export class ZarinpalGateway implements IPaymentGateway {
|
||||
async processPayment(processParams: IProcessPaymentParams) {
|
||||
try {
|
||||
const purchaseData: ZarinPalPGNewArgs = {
|
||||
merchant_id: this.config.merchantId,
|
||||
merchant_id: processParams.merchantId,
|
||||
amount: processParams.amount,
|
||||
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}${processParams.invoiceId ? "/" + processParams.invoiceId : ""}`,
|
||||
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}`,
|
||||
description: processParams.description,
|
||||
currency: "IRT",
|
||||
metadata: { email: processParams.email, mobile: processParams.mobile },
|
||||
@@ -68,17 +68,15 @@ export class ZarinpalGateway implements IPaymentGateway {
|
||||
const verifyData = {
|
||||
authority: verifyParams.reference,
|
||||
amount: verifyParams.amount,
|
||||
merchant_id: this.config.merchantId,
|
||||
merchant_id: verifyParams.merchantId,
|
||||
};
|
||||
const { data } = await firstValueFrom(
|
||||
this.httpService
|
||||
.post<ZarinPalPGVerifyData>(`${this.gatewayApiUrl}/v4/payment/verify.json`, verifyData, { headers: this.requestHeader })
|
||||
.pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error(err);
|
||||
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT);
|
||||
}),
|
||||
),
|
||||
this.httpService.post<ZarinPalPGVerifyData>(`${this.gatewayApiUrl}/v4/payment/verify.json`, verifyData, { headers: this.requestHeader }).pipe(
|
||||
catchError((err: AxiosError) => {
|
||||
this.logger.error(err);
|
||||
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -10,10 +10,10 @@ import { GatewayType } from "../types/gateway.type";
|
||||
export interface IProcessPaymentParams {
|
||||
amount: number;
|
||||
description: string;
|
||||
// callBackPath: string;
|
||||
callBackPath: string;
|
||||
merchantId: string;
|
||||
email?: string;
|
||||
mobile?: string;
|
||||
invoiceId?: string;
|
||||
}
|
||||
|
||||
export interface IProcessPaymentData {
|
||||
@@ -32,6 +32,7 @@ export interface IVerifyPayment {
|
||||
export interface IPaymentVerifyParams {
|
||||
amount: Decimal;
|
||||
reference: string;
|
||||
merchantId: string;
|
||||
// status: GatewayPaymentStatus;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,42 @@
|
||||
import { Controller } from "@nestjs/common";
|
||||
import { Body, Controller, Get, Param, Post, Query, Res } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { FastifyReply } from "fastify";
|
||||
|
||||
import { CheckoutPaymentDto } from "./DTO/checkout-payment.dto";
|
||||
import { VerifyParamDto, VerifyQueryDto } from "./DTO/verify-payment.dto";
|
||||
import { PaymentsService } from "./services/payments.service";
|
||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||
import { UserDec } from "../../common/decorators/user.decorator";
|
||||
import { PaginationDto } from "../../common/DTO/pagination.dto";
|
||||
|
||||
@Controller("payments")
|
||||
export class PaymentsController {}
|
||||
@ApiTags("Payments")
|
||||
export class PaymentsController {
|
||||
constructor(private readonly paymentsService: PaymentsService) {}
|
||||
|
||||
@AuthGuards()
|
||||
@ApiOperation({ summary: "get all available gateways" })
|
||||
@Get("gateways")
|
||||
getAvailableGateways() {
|
||||
return this.paymentsService.getAvailableGateways();
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "Charge wallet ==> user route" })
|
||||
@AuthGuards()
|
||||
@Post("checkout")
|
||||
checkoutPayment(@Body() checkoutDto: CheckoutPaymentDto, @UserDec("id") userId: string) {
|
||||
return this.paymentsService.checkoutPayment(checkoutDto, userId);
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get deposit gateway payment ==> admin route" })
|
||||
@AuthGuards()
|
||||
@Get("checkout/gateway")
|
||||
getDepositGatewayPayment(@Query() queryDto: PaginationDto) {
|
||||
return this.paymentsService.getDepositGatewayPayment(queryDto);
|
||||
}
|
||||
|
||||
@Get("verify/:gateway")
|
||||
verifyPayments(@Param() paramDto: VerifyParamDto, @Query() queryDto: VerifyQueryDto, @Res({ passthrough: true }) rep: FastifyReply) {
|
||||
return this.paymentsService.verifyPayment(paramDto, queryDto, rep);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import { PaymentsController } from "./payments.controller";
|
||||
import { PaymentProcessor } from "./queue/payment.processor";
|
||||
import { PaymentsService } from "./services/payments.service";
|
||||
import { zarinpalConfig } from "../../configs/zarinpal.config";
|
||||
import { InvoicesModule } from "../invoices/invoices.module";
|
||||
import { NotificationModule } from "../notifications/notifications.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MikroOrmModule.forFeature([Payment, PaymentGateway]),
|
||||
@@ -24,6 +24,7 @@ import { NotificationModule } from "../notifications/notifications.module";
|
||||
},
|
||||
}),
|
||||
NotificationModule,
|
||||
InvoicesModule,
|
||||
],
|
||||
controllers: [PaymentsController],
|
||||
providers: [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EntityManager } from "@mikro-orm/postgresql";
|
||||
import { InjectQueue, Processor } from "@nestjs/bullmq";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { Job, Queue } from "bullmq";
|
||||
import Decimal from "decimal.js";
|
||||
|
||||
@@ -16,15 +17,18 @@ import { PaymentJobData } from "../types/payment-job.type";
|
||||
@Processor(PAYMENT.QUEUE_NAME)
|
||||
export class PaymentProcessor extends WorkerProcessor {
|
||||
protected readonly logger = new Logger(PaymentProcessor.name);
|
||||
private readonly zarinpalMerchantId;
|
||||
|
||||
constructor(
|
||||
@InjectQueue(PAYMENT.QUEUE_NAME) private readonly paymentQueue: Queue,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly gatewayFactory: PaymentGatewayFactory,
|
||||
private readonly em: EntityManager,
|
||||
private readonly notificationQueue: NotificationQueue,
|
||||
private readonly paymentsService: PaymentsService,
|
||||
) {
|
||||
super();
|
||||
this.zarinpalMerchantId = this.configService.getOrThrow<string>("ZARINPAL_MERCHANT_ID");
|
||||
}
|
||||
//===============================================
|
||||
async process(job: Job<PaymentJobData>) {
|
||||
@@ -177,7 +181,7 @@ export class PaymentProcessor extends WorkerProcessor {
|
||||
id: paymentId,
|
||||
},
|
||||
{
|
||||
populate: ["user", "paymentGateway"],
|
||||
populate: ["user", "paymentGateway", "business", "invoice"],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -198,7 +202,11 @@ export class PaymentProcessor extends WorkerProcessor {
|
||||
|
||||
try {
|
||||
const gateway = this.gatewayFactory.getPaymentGateway(payment.paymentGateway.name);
|
||||
const verifyData = await gateway.verifyPayment({ reference: payment.reference, amount: payment.amount });
|
||||
const verifyData = await gateway.verifyPayment({
|
||||
reference: payment.reference,
|
||||
amount: payment.amount,
|
||||
merchantId: payment.business.zarinpalMerchantId || this.zarinpalMerchantId,
|
||||
});
|
||||
|
||||
const result = await this.paymentsService.processVerificationResult(verifyData, payment, em);
|
||||
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { LockMode } from "@mikro-orm/core";
|
||||
import { EntityManager } from "@mikro-orm/postgresql";
|
||||
import { EntityManager, LockMode } from "@mikro-orm/postgresql";
|
||||
import { InjectQueue } from "@nestjs/bullmq";
|
||||
import { BadRequestException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import { BadRequestException, HttpException, HttpStatus, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { Queue } from "bullmq";
|
||||
import { Decimal } from "decimal.js";
|
||||
import Decimal from "decimal.js";
|
||||
import { FastifyReply as FRply } from "fastify";
|
||||
|
||||
import { PaymentMessage } from "../../../common/enums/message.enum";
|
||||
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||
import { InvoiceMessage, PaymentMessage, UserMessage } from "../../../common/enums/message.enum";
|
||||
import { Business } from "../../businesses/entities/business.entity";
|
||||
import { Invoice } from "../../invoices/entities/invoice.entity";
|
||||
import { InvoicesService } from "../../invoices/providers/invoices.service";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||
import { PAYMENT } from "../constants";
|
||||
import { CheckoutPaymentDto } from "../DTO/checkout-payment.dto";
|
||||
import { VerifyParamDto, VerifyQueryDto } from "../DTO/verify-payment.dto";
|
||||
import { PaymentGateway } from "../entities/payment-gateway.entity";
|
||||
import { Payment } from "../entities/payment.entity";
|
||||
@@ -25,15 +28,65 @@ import { GatewayType } from "../types/gateway.type";
|
||||
@Injectable()
|
||||
export class PaymentsService {
|
||||
private readonly logger = new Logger(PaymentsService.name);
|
||||
|
||||
private readonly zarinpalMerchantId;
|
||||
constructor(
|
||||
@InjectQueue(PAYMENT.QUEUE_NAME) private readonly paymentQueue: Queue,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly gatewayFactory: PaymentGatewayFactory,
|
||||
private readonly paymentGatewaysRepository: PaymentGatewaysRepository,
|
||||
private readonly paymentsRepository: PaymentRepository,
|
||||
private readonly invoiceService: InvoicesService,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
) {
|
||||
this.zarinpalMerchantId = this.configService.getOrThrow<string>("ZARINPAL_MERCHANT_ID");
|
||||
}
|
||||
|
||||
//===============================================
|
||||
async checkoutPayment(chargeDto: CheckoutPaymentDto, userId: string) {
|
||||
const em = this.em.fork();
|
||||
try {
|
||||
em.begin();
|
||||
|
||||
const { gatewayId, invoiceId } = chargeDto;
|
||||
|
||||
const user = await this.getUserById(userId, em);
|
||||
const paymentGateway = await this.getPaymentGatewayById(gatewayId, em);
|
||||
|
||||
const invoice = await em.findOne(Invoice, { id: invoiceId }, { populate: ["company", "business", "company.user"] });
|
||||
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
|
||||
|
||||
if (invoice.company.user.id !== user.id) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID_OR_NOT_BELONG_TO_USER);
|
||||
|
||||
const gatewayData = await this.processPayment(paymentGateway.name, {
|
||||
amount: invoice.totalPrice.toNumber(),
|
||||
description: PaymentMessage.CHECKOUT_INVOICE_MESSAGE.replace("[invoiceNumber]", invoice.numericId.toString()),
|
||||
email: user.email,
|
||||
mobile: user.phone,
|
||||
callBackPath: invoice.business.slug,
|
||||
merchantId: invoice.business.zarinpalMerchantId || this.zarinpalMerchantId,
|
||||
});
|
||||
|
||||
const payment = await this.createGatewayPaymentForUser(
|
||||
user,
|
||||
invoice.totalPrice.toNumber(),
|
||||
gatewayData.reference,
|
||||
paymentGateway.id,
|
||||
invoice.business.id,
|
||||
invoice.id,
|
||||
em,
|
||||
);
|
||||
|
||||
await em.commit();
|
||||
return {
|
||||
payment,
|
||||
...gatewayData,
|
||||
};
|
||||
} catch (error) {
|
||||
await em.rollback();
|
||||
if (error instanceof HttpException) throw error;
|
||||
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_PAYMENT);
|
||||
}
|
||||
}
|
||||
|
||||
//===============================================
|
||||
|
||||
@@ -44,8 +97,6 @@ export class PaymentsService {
|
||||
//===============================================
|
||||
|
||||
async verifyPayment(verifyParamDto: VerifyParamDto, queryDto: VerifyQueryDto, rep: FRply) {
|
||||
const frontUrl = this.buildFrontendRedirectUrl(verifyParamDto.invoiceId);
|
||||
|
||||
const em = this.em.fork();
|
||||
|
||||
try {
|
||||
@@ -53,6 +104,7 @@ export class PaymentsService {
|
||||
|
||||
const paymentGateway = this.gatewayFactory.getPaymentGateway(verifyParamDto.gateway);
|
||||
const payment = await this.getPaymentByReference(queryDto.Authority, em);
|
||||
const frontUrl = this.buildFrontendRedirectUrl(payment.business.slug);
|
||||
|
||||
if (payment.status === PaymentStatus.COMPLETED) throw new BadRequestException(PaymentMessage.VALIDATED_BEFORE);
|
||||
|
||||
@@ -61,6 +113,7 @@ export class PaymentsService {
|
||||
const verifyData = await paymentGateway.verifyPayment({
|
||||
reference: queryDto.Authority,
|
||||
amount: payment.amount,
|
||||
merchantId: payment.business.zarinpalMerchantId || this.zarinpalMerchantId,
|
||||
});
|
||||
|
||||
return await this.handlePaymentVerificationWithResponse(verifyData, payment, rep, frontUrl, em);
|
||||
@@ -111,33 +164,26 @@ export class PaymentsService {
|
||||
//===============================================
|
||||
private async processSuccessfulVerification(payment: Payment, refId: number, em: EntityManager) {
|
||||
// Regular wallet charge
|
||||
await this.handleSuccessfulPayment(payment, refId, em);
|
||||
const { invoice } = await this.handleSuccessfulPayment(payment, refId, em);
|
||||
|
||||
const additionalParams: Record<string, string> = {};
|
||||
|
||||
return {
|
||||
status: PaymentStatus.COMPLETED,
|
||||
payment,
|
||||
invoice,
|
||||
additionalParams,
|
||||
};
|
||||
}
|
||||
//===============================================
|
||||
|
||||
async createGatewayPaymentForUser(
|
||||
usr: User,
|
||||
amt: number,
|
||||
ref_Id: string,
|
||||
gatewayId: string,
|
||||
businessId: string,
|
||||
invoiceId: string,
|
||||
em: EntityManager,
|
||||
) {
|
||||
async createGatewayPaymentForUser(usr: User, amt: number, ref_Id: string, gatewayId: string, bunsId: string, invoiceId: string, em: EntityManager) {
|
||||
const payment = em.create(Payment, {
|
||||
amount: new Decimal(amt),
|
||||
reference: ref_Id,
|
||||
user: usr,
|
||||
paymentGateway: em.getReference(PaymentGateway, gatewayId),
|
||||
business: em.getReference(Business, businessId),
|
||||
business: em.getReference(Business, bunsId),
|
||||
invoice: em.getReference(Invoice, invoiceId),
|
||||
});
|
||||
|
||||
@@ -172,19 +218,38 @@ export class PaymentsService {
|
||||
await em.nativeUpdate(Payment, { id: payment.id }, { status: PaymentStatus.COMPLETED, transactionId: transactionId.toString() });
|
||||
|
||||
await this.removePaymentReminderJobs(payment.id);
|
||||
return await this.invoiceService.payInvoice(payment.invoice.id, payment.user.id, em);
|
||||
}
|
||||
|
||||
//===============================================
|
||||
|
||||
async getDepositGatewayPayment(queryDto: PaginationDto) {
|
||||
const { limit, skip } = PaginationUtils(queryDto);
|
||||
|
||||
const [payments, count] = await this.paymentsRepository.findAndCount(
|
||||
{ deletedAt: null },
|
||||
{
|
||||
populate: ["user", "paymentGateway"],
|
||||
orderBy: { createdAt: "DESC" },
|
||||
offset: skip,
|
||||
limit,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
payments,
|
||||
count,
|
||||
paginate: true,
|
||||
};
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
//-private methods
|
||||
//----------------------------------------
|
||||
|
||||
private buildFrontendRedirectUrl(invoiceId?: string): URL {
|
||||
private buildFrontendRedirectUrl(businessSlug: string): URL {
|
||||
const frontUrl = new URL(this.configService.getOrThrow<string>("SITE_URL"));
|
||||
if (invoiceId) {
|
||||
frontUrl.pathname = "/receipts/" + invoiceId;
|
||||
} else {
|
||||
frontUrl.pathname = "/payment";
|
||||
}
|
||||
frontUrl.pathname = `/${businessSlug}/payment`;
|
||||
return frontUrl;
|
||||
}
|
||||
|
||||
@@ -206,11 +271,25 @@ export class PaymentsService {
|
||||
url.searchParams.append("date", payment.createdAt.toISOString());
|
||||
url.searchParams.append("amount", payment.amount.toString());
|
||||
}
|
||||
//===============================================
|
||||
|
||||
private async getUserById(userId: string, em: EntityManager): Promise<User> {
|
||||
const user = await em.findOne(User, { id: userId }, { populate: ["company"] });
|
||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||
return user;
|
||||
}
|
||||
//===============================================
|
||||
|
||||
private async getPaymentGatewayById(gatewayId: string, em: EntityManager): Promise<PaymentGateway> {
|
||||
const paymentGateway = await em.findOne(PaymentGateway, { id: gatewayId });
|
||||
if (!paymentGateway) throw new BadRequestException(PaymentMessage.PAYMENT_GATEWAY_NOT_FOUND);
|
||||
return paymentGateway;
|
||||
}
|
||||
|
||||
//===============================================
|
||||
|
||||
private async getPaymentByReference(reference: string, em: EntityManager): Promise<Payment> {
|
||||
const payment = await em.findOne(Payment, { reference }, { populate: ["user"], lockMode: LockMode.PESSIMISTIC_WRITE });
|
||||
const payment = await em.findOne(Payment, { reference }, { populate: ["user", "invoice", "business"], lockMode: LockMode.PESSIMISTIC_WRITE });
|
||||
if (!payment) throw new BadRequestException(PaymentMessage.PAYMENT_NOT_FOUND_WITH_REF);
|
||||
return payment;
|
||||
}
|
||||
|
||||
@@ -206,8 +206,7 @@ export class TicketsService {
|
||||
const ticket = await this.findTicket(em, ticketId, businessId, userId, role === RoleEnum.ADMIN);
|
||||
if (ticket.status === TicketStatus.CLOSED) throw new BadRequestException(TicketMessageEnum.TICKET_CLOSED);
|
||||
|
||||
if (role === RoleEnum.ADMIN && ticket.assignedTo?.id !== userId)
|
||||
throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER);
|
||||
if (role === RoleEnum.ADMIN && ticket.assignedTo?.id !== userId) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_ASSIGNED_TO_USER);
|
||||
|
||||
const user = await em.findOne(User, { id: userId });
|
||||
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
|
||||
@@ -380,11 +379,7 @@ export class TicketsService {
|
||||
private async findTicket(em: EntityManager, ticketId: string, businessId: string, userId: string, isAdmin: boolean): Promise<Ticket> {
|
||||
const ticket = isAdmin
|
||||
? await em.findOne(Ticket, { id: ticketId, user: { id: userId }, business: { id: businessId } }, { populate: ["user", "assignedTo"] })
|
||||
: await em.findOne(
|
||||
Ticket,
|
||||
{ id: ticketId, user: { id: userId }, business: { id: businessId } },
|
||||
{ populate: ["user", "assignedTo"] },
|
||||
);
|
||||
: await em.findOne(Ticket, { id: ticketId, user: { id: userId }, business: { id: businessId } }, { populate: ["user", "assignedTo"] });
|
||||
|
||||
if (!ticket) throw new BadRequestException(TicketMessageEnum.TICKET_NOT_FOUND);
|
||||
return ticket;
|
||||
|
||||
@@ -125,12 +125,7 @@ export class TicketsController {
|
||||
|
||||
@ApiOperation({ summary: "referer ticket ==> admin route" })
|
||||
@Post(":id/refer")
|
||||
referTicket(
|
||||
@Param() paramDto: ParamDto,
|
||||
@UserDec("id") userId: string,
|
||||
@Body() referDto: ReferTicketDto,
|
||||
@BusinessDec("id") businessId: string,
|
||||
) {
|
||||
referTicket(@Param() paramDto: ParamDto, @UserDec("id") userId: string, @Body() referDto: ReferTicketDto, @BusinessDec("id") businessId: string) {
|
||||
return this.ticketsService.referTicket(paramDto.id, userId, referDto, businessId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user