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