From fb7671338a57f1de3aa0f15394cccd52ac6e6180 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Mon, 15 Jun 2026 12:55:51 +0330 Subject: [PATCH] add card owner --- ...60615140000_add_card_owner_to_payment_methods.ts | 13 +++++++++++++ src/common/enums/message.enum.ts | 1 + .../payments/dto/create-payment-method.dto.ts | 8 +++++++- .../payments/entities/payment-method.entity.ts | 3 +++ .../payments/services/payment-method.service.ts | 13 +++++++++++-- src/modules/payments/services/payments.service.ts | 4 ++++ src/seeders/data/payment-methods.data.ts | 2 ++ src/seeders/payment-methods.seeder.ts | 7 ++++++- 8 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 database/migrations/Migration20260615140000_add_card_owner_to_payment_methods.ts diff --git a/database/migrations/Migration20260615140000_add_card_owner_to_payment_methods.ts b/database/migrations/Migration20260615140000_add_card_owner_to_payment_methods.ts new file mode 100644 index 0000000..d62434c --- /dev/null +++ b/database/migrations/Migration20260615140000_add_card_owner_to_payment_methods.ts @@ -0,0 +1,13 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20260615140000_add_card_owner_to_payment_methods extends Migration { + + override async up(): Promise { + this.addSql(`alter table "payment_methods" add column "card_owner" varchar(255) null;`); + } + + override async down(): Promise { + this.addSql(`alter table "payment_methods" drop column "card_owner";`); + } + +} diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index e7e5d25..086fb79 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -745,6 +745,7 @@ export const enum PaymentMessage { PAYMENT_METHOD_NOT_CASH = 'روش پرداخت نقدی نیست', PAYMENT_METHOD_NOT_CREDIT_CARD = 'روش پرداخت کارت به کارت نیست', CARD_NUMBER_REQUIRED = 'شماره کارت برای پرداخت کارت به کارت الزامی است', + CARD_OWNER_REQUIRED = 'نام صاحب کارت برای پرداخت کارت به کارت الزامی است', PAYMENT_ALREADY_PAID = 'پرداخت قبلا انجام شده است', EXISTING_PENDING_PAYMENT_MISMATCH = 'پرداخت در انتظار موجود با روش پرداخت سفارش مطابقت ندارد', ZARINPAL_INVALID_RESPONSE = 'پاسخ نامعتبر از API زرین‌پال', diff --git a/src/modules/payments/dto/create-payment-method.dto.ts b/src/modules/payments/dto/create-payment-method.dto.ts index d943f6f..ce9b5df 100644 --- a/src/modules/payments/dto/create-payment-method.dto.ts +++ b/src/modules/payments/dto/create-payment-method.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsOptional, IsBoolean, IsNumber, IsEnum } from 'class-validator'; +import { IsString, IsOptional, IsBoolean, IsNumber, IsEnum, ValidateIf, IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { PaymentMethodEnum, PaymentGatewayEnum } from '../interface/payment'; @@ -23,6 +23,12 @@ export class CreatePaymentMethodDto { @IsString() cardNumber?: string; + @ApiProperty({ description: 'Card owner name for card-to-card payments', required: false }) + @ValidateIf((dto: CreatePaymentMethodDto) => dto.method === PaymentMethodEnum.CreditCard) + @IsNotEmpty() + @IsString() + cardOwner?: string; + @ApiProperty({ description: 'Is payment method enabled', required: false, default: true }) @IsOptional() @IsBoolean() diff --git a/src/modules/payments/entities/payment-method.entity.ts b/src/modules/payments/entities/payment-method.entity.ts index 61365ac..4b25be5 100644 --- a/src/modules/payments/entities/payment-method.entity.ts +++ b/src/modules/payments/entities/payment-method.entity.ts @@ -23,6 +23,9 @@ export class PaymentMethod extends BaseEntity { @Property({ nullable: true }) cardNumber?: string; + @Property({ nullable: true }) + cardOwner?: string; + @Property({ default: true }) enabled: boolean = true; diff --git a/src/modules/payments/services/payment-method.service.ts b/src/modules/payments/services/payment-method.service.ts index 73a6848..8d671e0 100644 --- a/src/modules/payments/services/payment-method.service.ts +++ b/src/modules/payments/services/payment-method.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; import { PaymentMethod } from '../entities/payment-method.entity'; import { PaymentMethodRepository } from '../repositories/payment-method.repository'; @@ -7,6 +7,7 @@ import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto'; import { RequiredEntityData } from '@mikro-orm/core'; import { PaymentMessage } from 'src/common/enums/message.enum'; import { RestaurantsService } from 'src/modules/restaurants/providers/restaurants.service'; +import { PaymentMethodEnum } from '../interface/payment'; @Injectable() export class PaymentMethodService { @@ -18,7 +19,8 @@ export class PaymentMethodService { async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise { // Check if restaurant exists - const restaurant = await this.restaurantsService.findOneOrFail(restId) + const restaurant = await this.restaurantsService.findOneOrFail(restId); + this.assertCreditCardOwner(createPaymentMethodDto.method, createPaymentMethodDto.cardOwner); const paymentMethod = this.paymentMethodRepository.create({ restaurant, @@ -43,10 +45,17 @@ export class PaymentMethodService { async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise { const paymentMethod = await this.findOneOrFail(id); this.em.assign(paymentMethod, updatePaymentMethodDto); + this.assertCreditCardOwner(paymentMethod.method, paymentMethod.cardOwner); await this.em.flush(); return paymentMethod; } + private assertCreditCardOwner(method: PaymentMethodEnum, cardOwner?: string): void { + if (method === PaymentMethodEnum.CreditCard && !cardOwner?.trim()) { + throw new BadRequestException(PaymentMessage.CARD_OWNER_REQUIRED); + } + } + async remove(id: string): Promise { const paymentMethod = await this.findOneOrFail(id); await this.em.removeAndFlush(paymentMethod); diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index 7948852..dcb20c8 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -83,6 +83,10 @@ export class PaymentsService { throw new BadRequestException(PaymentMessage.CARD_NUMBER_REQUIRED); } + if (pm.method === PaymentMethodEnum.CreditCard && !pm.cardOwner?.trim()) { + throw new BadRequestException(PaymentMessage.CARD_OWNER_REQUIRED); + } + return { order, amount: order.total, diff --git a/src/seeders/data/payment-methods.data.ts b/src/seeders/data/payment-methods.data.ts index 9ccfd91..878144c 100644 --- a/src/seeders/data/payment-methods.data.ts +++ b/src/seeders/data/payment-methods.data.ts @@ -7,6 +7,7 @@ export interface PaymentMethodData { order: number; gateway: PaymentGatewayEnum | null; cardNumber?: string; + cardOwner?: string; merchantId?: string; } @@ -40,5 +41,6 @@ export const paymentMethodsData: PaymentMethodData[] = [ order: 4, gateway: null, cardNumber: '6037991234567890', + cardOwner: 'صاحب کارت', }, ]; diff --git a/src/seeders/payment-methods.seeder.ts b/src/seeders/payment-methods.seeder.ts index c3ce182..278cdd1 100644 --- a/src/seeders/payment-methods.seeder.ts +++ b/src/seeders/payment-methods.seeder.ts @@ -23,6 +23,7 @@ export class PaymentMethodsSeeder { order: methodData.order, gateway: methodData.gateway, cardNumber: methodData.cardNumber, + cardOwner: methodData.cardOwner, merchantId: methodData.merchantId, }); em.persist(paymentMethod); @@ -32,7 +33,8 @@ export class PaymentMethodsSeeder { existing.description !== methodData.description || existing.enabled !== methodData.enabled || existing.order !== methodData.order || - existing.cardNumber !== methodData.cardNumber + existing.cardNumber !== methodData.cardNumber || + existing.cardOwner !== methodData.cardOwner ) { existing.description = methodData.description; existing.enabled = methodData.enabled; @@ -43,6 +45,9 @@ export class PaymentMethodsSeeder { if (methodData.cardNumber !== undefined) { existing.cardNumber = methodData.cardNumber; } + if (methodData.cardOwner !== undefined) { + existing.cardOwner = methodData.cardOwner; + } if (methodData.merchantId !== undefined) { existing.merchantId = methodData.merchantId; }