This commit is contained in:
2026-02-22 11:15:16 +03:30
parent 396c50cc52
commit bfa152fdd8
5 changed files with 255 additions and 251 deletions
@@ -12,6 +12,7 @@ import { IAttachment } from '../interface/order.interface';
export class CreateOrderAsAdminDto {
@ApiPropertyOptional({ example: '' })
@IsString()
@IsOptional()
userId?: string;
@ApiPropertyOptional({ example: '' })
@@ -42,17 +42,17 @@ export class ZarinpalGateway implements IPaymentGateway {
return { status: error.response.status, data: error.response.data };
}
async requestPayment({ amount, orderId }: IRequestPaymentParams): Promise<IRequestPaymentData> {
async requestPayment({ amount, invoiceId }: IRequestPaymentParams): Promise<IRequestPaymentData> {
// Transform camelCase to snake_case for Zarinpal API v4
const callbackUrl = `${this.websiteUrl}/verify/${orderId}`;
const callbackUrl = `${this.websiteUrl}/verify/${invoiceId}`;
const zarinpalRequest: IZarinpalRequestPayment = {
amount,
merchant_id: this.zarinpalMerchantId,
description: `Payment for order #${orderId}`,
description: `Payment for order #${invoiceId}`,
callback_url: callbackUrl,
currency: 'IRT',
metadata: {
order_id: orderId,
order_id: invoiceId,
},
};
@@ -81,7 +81,7 @@ export class ZarinpalGateway implements IPaymentGateway {
errors,
callbackUrl,
amount,
orderId,
invoiceId,
}),
);
throw new BadRequestException(message ?? PaymentMessage.ZARINPAL_PAYMENT_REQUEST_ERROR);
@@ -102,7 +102,7 @@ export class ZarinpalGateway implements IPaymentGateway {
data: axiosErr.data,
callbackUrl,
amount,
orderId,
invoiceId,
}),
);
throw new BadRequestException(PaymentMessage.ZARINPAL_PAYMENT_REQUEST_ERROR);
+1 -1
View File
@@ -5,7 +5,7 @@ export interface IPaymentGateway {
export interface IRequestPaymentParams {
amount: number;
orderId: string;
invoiceId: string;
}
export interface IRequestPaymentData {
+246 -243
View File
@@ -1,301 +1,304 @@
// import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
// import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
// import { EntityManager } from '@mikro-orm/postgresql';
// import { Logger } from '@nestjs/common';
// import { GatewayManager } from '../gateways/gateway.manager';
// import { OrderPaymentContext } from '../interface/payment';
// import { PaymentMessage, OrderMessage } from 'src/common/enums/message.enum';
// import { EventEmitter2 } from '@nestjs/event-emitter';
// import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events';
// import { OrderService } from 'src/modules/order/providers/order.service';
// import { PayOrderDto } from '../dto/pay-order.dto';
// import { PaymentRepository } from '../repositories/payment.repository';
// import { CreditRepository } from 'src/modules/user/repositories/credit.repository';
// import { CreditService } from 'src/modules/user/providers/credit.service';
// import { CreditTransactionType } from 'src/modules/user/interface/user';
// import { Payment } from '../entities/payment.entity';
// import { FindPaymentsDto } from '../dto/find-payments.dto';
// import { Admin } from 'src/modules/admin/entities/admin.entity';
// import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
// import { CreditTransaction } from 'src/modules/user/entities/credit-transaction.entity';
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
import { EntityManager } from '@mikro-orm/postgresql';
import { Logger } from '@nestjs/common';
import { GatewayManager } from '../gateways/gateway.manager';
import { OrderPaymentContext } from '../interface/payment';
import { PaymentMessage, OrderMessage } from 'src/common/enums/message.enum';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events';
import { OrderService } from 'src/modules/order/providers/order.service';
import { PayOrderDto } from '../dto/pay-order.dto';
import { PaymentRepository } from '../repositories/payment.repository';
import { CreditRepository } from 'src/modules/user/repositories/credit.repository';
import { CreditService } from 'src/modules/user/providers/credit.service';
import { CreditTransactionType } from 'src/modules/user/interface/user';
import { Payment } from '../entities/payment.entity';
import { FindPaymentsDto } from '../dto/find-payments.dto';
import { Admin } from 'src/modules/admin/entities/admin.entity';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { CreditTransaction } from 'src/modules/user/entities/credit-transaction.entity';
import { InvoiceService } from 'src/modules/invoice/invoice.service';
// @Injectable()
// export class PaymentService {
// private readonly logger = new Logger(PaymentService.name);
@Injectable()
export class PaymentService {
private readonly logger = new Logger(PaymentService.name);
// constructor(
// private readonly em: EntityManager,
// private readonly orderService: OrderService,
// private readonly paymentRepository: PaymentRepository,
// private readonly gatewayManager: GatewayManager,
// private readonly eventEmitter: EventEmitter2,
// private readonly creditService: CreditService,
// private readonly creditRepository: CreditRepository,
// private readonly adminRepository: AdminRepository,
// ) { }
constructor(
private readonly em: EntityManager,
private readonly orderService: OrderService,
private readonly paymentRepository: PaymentRepository,
private readonly gatewayManager: GatewayManager,
private readonly eventEmitter: EventEmitter2,
private readonly creditService: CreditService,
private readonly creditRepository: CreditRepository,
private readonly adminRepository: AdminRepository,
private readonly invoiceService: InvoiceService,
) { }
// async payOrder(orderId: string, dto: PayOrderDto, adminId?: string): Promise<{ paymentUrl: string | null }> {
async payOrder(invoiceId: string, dto: PayOrderDto, adminId?: string): Promise<{ paymentUrl: string | null }> {
// const ctx = await this.loadAndValidateOrder(orderId, dto, adminId)
const ctx = await this.loadAndValidateOrder(invoiceId, dto, adminId)
// switch (dto.method) {
// case PaymentMethodEnum.Cash:
// await this.handleCashPayment(ctx);
// return { paymentUrl: null };
switch (dto.method) {
case PaymentMethodEnum.Cash:
await this.handleCashPayment(ctx);
return { paymentUrl: null };
// case PaymentMethodEnum.Credit:
// await this.handleCreditPayment(ctx);
// return { paymentUrl: null };
case PaymentMethodEnum.Credit:
await this.handleCreditPayment(ctx);
return { paymentUrl: null };
// case PaymentMethodEnum.Online:
// return this.handleOnlinePayment(ctx);
case PaymentMethodEnum.Online:
return this.handleOnlinePayment(ctx);
// default:
// throw new BadRequestException(PaymentMessage.UNSUPPORTED_PAYMENT_METHOD);
// }
// }
default:
throw new BadRequestException(PaymentMessage.UNSUPPORTED_PAYMENT_METHOD);
}
}
// private async loadAndValidateOrder(orderId: string, dto: PayOrderDto, adminId?: string): Promise<OrderPaymentContext> {
// const { amount, method, attachments, description, gateway } = dto
private async loadAndValidateOrder(invoiceId: string, dto: PayOrderDto, adminId?: string): Promise<OrderPaymentContext> {
const { amount, method, attachments, description, gateway } = dto
// const order = await this.orderService.findOrderOrFail(orderId)
// const order = await this.orderService.findOrderOrFail(invoiceId)
const invoice=await this.invoiceService.findOneOrFail(invoiceId)
// if (!order.balance) {
// throw new NotFoundException('Balance is not set');
// }
if (!invoice.balance) {
throw new NotFoundException('Balance is not set');
}
// let admin: null | Admin = null
// if (adminId) {
// admin = await this.adminRepository.findOne({ id: adminId })
// if (!admin) {
// throw new BadRequestException("Admin not found")
// }
// }
let admin: null | Admin = null
if (adminId) {
admin = await this.adminRepository.findOne({ id: adminId })
if (!admin) {
throw new BadRequestException("Admin not found")
}
}
// if (amount <= 0) {
// throw new BadRequestException(PaymentMessage.AMOUNT_MUST_BE_GREATER_THAN_ZERO);
// }
if (amount <= 0) {
throw new BadRequestException(PaymentMessage.AMOUNT_MUST_BE_GREATER_THAN_ZERO);
}
// if (amount > order.balance) {
// throw new BadRequestException(`You cant pay more than ${order.balance}`);
// }
if (amount > invoice.balance) {
throw new BadRequestException(`You cant pay more than ${invoice.balance}`);
}
// return {
// admin,
// user: order.user,
// order,
// amount,
// attachments,
// description,
// gateway,
// method
// };
// }
return {
admin,
user: invoice.user,
invoice,
amount,
attachments,
description,
gateway,
method
};
}
// private async handleCashPayment(ctx: OrderPaymentContext): Promise<void> {
// const { order, amount, method, attachments, description, admin } = ctx
// const payment = this.paymentRepository.create({
// creator: admin,
// order,
// amount,
// method,
// status: PaymentStatusEnum.Pending,
// attachments,
// description
// })
// await this.em.persistAndFlush(payment)
// return
// }
private async handleCashPayment(ctx: OrderPaymentContext): Promise<void> {
const { invoice, amount, method, attachments, description, admin } = ctx
const payment = this.paymentRepository.create({
creator: admin,
invoice,
amount,
method,
status: PaymentStatusEnum.Pending,
attachments,
description
})
await this.em.persistAndFlush(payment)
return
}
// private async handleCreditPayment(ctx: OrderPaymentContext): Promise<void> {
// const { order, amount, user, admin } = ctx
private async handleCreditPayment(ctx: OrderPaymentContext): Promise<void> {
const { invoice, amount, user, admin } = ctx
// await this.em.transactional(async em => {
// // 1. get User remained Credit
// const remainedCredit = await this.creditService.getCurrentCredit(user.id)
await this.em.transactional(async em => {
// 1. get User remained Credit
const remainedCredit = await this.creditService.getCurrentCredit(user.id)
// if (remainedCredit < amount) {
// throw new BadRequestException("You Dont have enough Credit")
// }
if (remainedCredit < amount) {
throw new BadRequestException("You Dont have enough Credit")
}
// const newBalance = remainedCredit - amount;
// // 2. Create payment record
// const payment = em.create(Payment, {
// creator: admin,
// amount: ctx.amount,
// method: PaymentMethodEnum.Credit,
// gateway: null,
// order,
// status: PaymentStatusEnum.Paid,
// paidAt: new Date(),
// });
// // 3. Create Credit transaction record
// const newCreditTransaction = em.create(CreditTransaction, {
// user: order.user,
// amount,
// type: CreditTransactionType.WITHDRAW,
// balance: newBalance,
// orderId: order.id,
// });
const newBalance = remainedCredit - amount;
// 2. Create payment record
const payment = em.create(Payment, {
creator: admin,
amount: ctx.amount,
method: PaymentMethodEnum.Credit,
gateway: null,
invoice,
status: PaymentStatusEnum.Paid,
paidAt: new Date(),
});
// 3. Create Credit transaction record
const newCreditTransaction = em.create(CreditTransaction, {
user: invoice.user,
amount,
type: CreditTransactionType.WITHDRAW,
balance: newBalance,
invoiceId: invoice.id,
});
// await em.persistAndFlush([payment, newCreditTransaction]);
// });
await em.persistAndFlush([payment, newCreditTransaction]);
});
// this.eventEmitter.emit(
// paymentSucceedEvent.name,
// new paymentSucceedEvent(ctx.order.id, String(ctx.order?.orderNumber) || '', ctx.method, ctx.amount),
// );
// }
this.eventEmitter.emit(
paymentSucceedEvent.name,
new paymentSucceedEvent(ctx.invoice.id, String(ctx.invoice?.invoiceNumber) || '', ctx.method, ctx.amount),
);
}
// private async handleOnlinePayment(ctx: OrderPaymentContext): Promise<{ paymentUrl: string }> {
// const { amount, order, gateway: requestedGateway, admin } = ctx
private async handleOnlinePayment(ctx: OrderPaymentContext): Promise<{ paymentUrl: string }> {
const { amount, invoice, gateway: requestedGateway, admin } = ctx
// const gateway = this.gatewayManager.get(requestedGateway!);
const gateway = this.gatewayManager.get(requestedGateway!);
// const { token, paymentUrl } = await gateway.requestPayment({
// amount: ctx.amount,
// orderId: ctx.order.id,
// });
const { token, paymentUrl } = await gateway.requestPayment({
amount: ctx.amount,
invoiceId: ctx.invoice.id,
});
// const payment = this.paymentRepository.create({
// creator: admin,
// order,
// amount,
// method: PaymentMethodEnum.Online,
// status: PaymentStatusEnum.Pending,
// gateway: requestedGateway,
// token,
// })
const payment = this.paymentRepository.create({
creator: admin,
invoice,
amount,
method: PaymentMethodEnum.Online,
status: PaymentStatusEnum.Pending,
gateway: requestedGateway,
token,
})
// await this.em.persistAndFlush(payment);
await this.em.persistAndFlush(payment);
// return {
// paymentUrl,
// };
// }
return {
paymentUrl,
};
}
// async verifyOnlinePayment(token: string): Promise<Payment> {
// const payment = await this.em.transactional(async em => {
// const payment = await this.paymentRepository.findOne(
// { token },
// { populate: ['order'] },
// );
async verifyOnlinePayment(token: string): Promise<Payment> {
const payment = await this.em.transactional(async em => {
const payment = await this.paymentRepository.findOne(
{ token },
{ populate: ['invoice'] },
);
// if (!payment) {
// throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
// }
if (!payment) {
throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
}
// if (payment.status === PaymentStatusEnum.Paid) {
// return payment;
// }
if (payment.status === PaymentStatusEnum.Paid) {
return payment;
}
// if (!payment.gateway) {
// throw new NotFoundException("Payment gateway not found");
// }
if (!payment.gateway) {
throw new NotFoundException("Payment gateway not found");
}
// const gateway = this.gatewayManager.get(payment.gateway);
const gateway = this.gatewayManager.get(payment.gateway);
// const { raw, referenceId, success } = await gateway.verifyPayment({
// amount: payment.amount,
// token: payment.token!,
// });
const { raw, referenceId, success } = await gateway.verifyPayment({
amount: payment.amount,
token: payment.token!,
});
// payment.verifyResponse = raw;
payment.verifyResponse = raw;
// if (!success) {
// payment.status = PaymentStatusEnum.Failed;
// payment.failedAt = new Date();
// return payment;
// }
if (!success) {
payment.status = PaymentStatusEnum.Failed;
payment.failedAt = new Date();
return payment;
}
// payment.status = PaymentStatusEnum.Paid;
// payment.paidAt = new Date();
// payment.transactionId = referenceId;
// // TODO : use decimal js
// // does need persist or not
// // payment.order.paidAmount! += payment.amount
// // payment.order.balance! -= payment.amount
payment.status = PaymentStatusEnum.Paid;
payment.paidAt = new Date();
payment.transactionId = referenceId;
// TODO : use decimal js
// does need persist or not
// payment.order.paidAmount! += payment.amount
// payment.order.balance! -= payment.amount
// await em.flush();
// return payment;
// });
// this.eventEmitter.emit(
// onlinePaymentSucceedEvent.name,
// new onlinePaymentSucceedEvent(payment.id, payment.order.id, String(payment.order?.orderNumber) || '', payment.amount),
// );
// return payment;
// }
await em.flush();
return payment;
});
this.eventEmitter.emit(
onlinePaymentSucceedEvent.name,
new onlinePaymentSucceedEvent(payment.id, payment.invoice.id, String(payment.invoice?.invoiceNumber) || '', payment.amount),
);
return payment;
}
// findAll(dto: FindPaymentsDto) {
// return this.paymentRepository.findAllPaginated(dto)
// }
findAll(dto: FindPaymentsDto) {
return this.paymentRepository.findAllPaginated(dto)
}
// findAllByUser(dto: FindPaymentsDto, userId: string) {
// return this.paymentRepository.findAllPaginated({ ...dto, userId })
// }
findAllByUser(dto: FindPaymentsDto, userId: string) {
return this.paymentRepository.findAllPaginated({ ...dto, userId })
}
// async confirmCashPayment(paymentId: string): Promise<Payment> {
// const payment = await this.em.transactional(async em => {
// const payment = await em.findOne(Payment, paymentId, { populate: ['order'] });
// if (!payment) {
// throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
// }
// if (payment.method !== PaymentMethodEnum.Cash) {
// throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
// }
// if (payment.status === PaymentStatusEnum.Paid) {
// throw new BadRequestException(PaymentMessage.PAYMENT_ALREADY_PAID);
// }
async confirmCashPayment(paymentId: string): Promise<Payment> {
const payment = await this.em.transactional(async em => {
const payment = await em.findOne(Payment, paymentId, { populate: ['invoice'] });
if (!payment) {
throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
}
if (payment.method !== PaymentMethodEnum.Cash) {
throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
}
if (payment.status === PaymentStatusEnum.Paid) {
throw new BadRequestException(PaymentMessage.PAYMENT_ALREADY_PAID);
}
// payment.status = PaymentStatusEnum.Paid;
// payment.paidAt = new Date();
// payment.failedAt = null;
payment.status = PaymentStatusEnum.Paid;
payment.paidAt = new Date();
payment.failedAt = null;
// // TODO : use decimal js
// // payment.order.paidAmount! += payment.amount
// // payment.order.balance! -= payment.amount
// TODO : use decimal js
// payment.order.paidAmount! += payment.amount
// payment.order.balance! -= payment.amount
// em.persist(payment);
// em.persist(payment.order);
// await em.flush();
// return payment;
// });
// return payment;
// }
em.persist(payment);
em.persist(payment.invoice);
await em.flush();
return payment;
});
return payment;
}
// async denyCashPayment(paymentId: string): Promise<Payment> {
// const payment = await this.em.transactional(async em => {
async denyCashPayment(paymentId: string): Promise<Payment> {
const payment = await this.em.transactional(async em => {
// const payment = await em.findOne(Payment, paymentId, { populate: ['order'] });
const payment = await em.findOne(Payment, paymentId, { populate: ['invoice'] });
// if (!payment) {
// throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
// }
if (!payment) {
throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND);
}
// if (payment.method !== PaymentMethodEnum.Cash) {
// throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
// }
if (payment.method !== PaymentMethodEnum.Cash) {
throw new BadRequestException(PaymentMessage.PAYMENT_METHOD_NOT_CASH);
}
// if (payment.status == PaymentStatusEnum.Paid) {
// // payment.order.paidAmount! -= payment.amount
// // payment.order.balance! += payment.amount
if (payment.status == PaymentStatusEnum.Paid) {
// payment.order.paidAmount! -= payment.amount
// payment.order.balance! += payment.amount
// em.persistAndFlush(payment.order)
// }
em.persistAndFlush(payment.invoice)
}
// payment.status = PaymentStatusEnum.Failed;
// payment.failedAt = new Date();
// payment.paidAt = null
payment.status = PaymentStatusEnum.Failed;
payment.failedAt = new Date();
payment.paidAt = null
// em.persistAndFlush(payment);
em.persistAndFlush(payment);
// return payment;
// });
// return payment;
// }
return payment;
});
return payment;
}
// }
}
@@ -12,7 +12,7 @@ export class CreditTransaction extends BaseEntity {
user!: User;
@Property()
orderId: string
invoiceId: string
@Property({ type: 'decimal', precision: 10, scale: 0 })
amount!: number;