credircard payment
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20260615130000_remove_payment_details_from_orders extends Migration {
|
||||||
|
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
this.addSql(`alter table "orders" drop column if exists "payment_description";`);
|
||||||
|
this.addSql(`alter table "orders" drop column if exists "payment_attachments";`);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async down(): Promise<void> {
|
||||||
|
this.addSql(`alter table "orders" add column "payment_description" text null;`);
|
||||||
|
this.addSql(`alter table "orders" add column "payment_attachments" jsonb null;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -694,6 +694,7 @@ export const enum OrderMessage {
|
|||||||
PAYMENT_METHOD_NOT_ENABLED = 'روش پرداخت برای این رستوران فعال نیست',
|
PAYMENT_METHOD_NOT_ENABLED = 'روش پرداخت برای این رستوران فعال نیست',
|
||||||
FOOD_NOT_FOUND = 'غذا یافت نشد',
|
FOOD_NOT_FOUND = 'غذا یافت نشد',
|
||||||
FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد',
|
FOOD_NOT_BELONGS_TO_RESTAURANT = 'غذا به این رستوران تعلق ندارد',
|
||||||
|
CREDIT_CARD_PAYMENT_DETAILS_REQUIRED = 'حداقل یکی از توضیحات پرداخت یا پیوست رسید کارت به کارت الزامی است',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum CartMessage {
|
export const enum CartMessage {
|
||||||
@@ -726,6 +727,7 @@ export const enum CartMessage {
|
|||||||
FOOD_ONLY_AVAILABLE_DURING_MEAL_TIMES = 'غذا [foodTitle] فقط در ساعات وعدههای غذایی (صبحانه، ناهار، عصرانه یا شام) در دسترس است. زمان فعلی خارج از ساعات وعدههای غذایی است',
|
FOOD_ONLY_AVAILABLE_DURING_MEAL_TIMES = 'غذا [foodTitle] فقط در ساعات وعدههای غذایی (صبحانه، ناهار، عصرانه یا شام) در دسترس است. زمان فعلی خارج از ساعات وعدههای غذایی است',
|
||||||
FOOD_ONLY_AVAILABLE_FOR_MEAL_TYPES = 'غذا [foodTitle] فقط برای [allowedMealTypes] در دسترس است. زمان فعلی [mealType] است که با این غذا سازگار نیست',
|
FOOD_ONLY_AVAILABLE_FOR_MEAL_TYPES = 'غذا [foodTitle] فقط برای [allowedMealTypes] در دسترس است. زمان فعلی [mealType] است که با این غذا سازگار نیست',
|
||||||
FOOD_ONLY_AVAILABLE_ON_DAYS = 'غذا [foodTitle] فقط در روزهای [allowedDays] در دسترس است. امروز [currentDay] است که این غذا در دسترس نیست',
|
FOOD_ONLY_AVAILABLE_ON_DAYS = 'غذا [foodTitle] فقط در روزهای [allowedDays] در دسترس است. امروز [currentDay] است که این غذا در دسترس نیست',
|
||||||
|
CREDIT_CARD_PAYMENT_DETAILS_REQUIRED = 'حداقل یکی از توضیحات پرداخت یا پیوست رسید کارت به کارت الزامی است',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum PaymentMessage {
|
export const enum PaymentMessage {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class SetAllCartParmsDto {
|
|||||||
carAddress?: SetCarDeliveryDto;
|
carAddress?: SetCarDeliveryDto;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Payment receipt attachments (URLs)',
|
description: 'Payment receipt attachments (URLs). At least one of paymentDesc or attachments is required for credit card.',
|
||||||
required: false,
|
required: false,
|
||||||
type: [String],
|
type: [String],
|
||||||
example: ['https://example.com/receipt.jpg'],
|
example: ['https://example.com/receipt.jpg'],
|
||||||
@@ -50,7 +50,7 @@ export class SetAllCartParmsDto {
|
|||||||
attachments?: string[];
|
attachments?: string[];
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Payment description or note',
|
description: 'Payment description or note. At least one of paymentDesc or attachments is required for credit card.',
|
||||||
required: false,
|
required: false,
|
||||||
example: 'Transfer from account ending 1234',
|
example: 'Transfer from account ending 1234',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
|||||||
import { UserAddress } from '../../users/entities/user-address.entity';
|
import { UserAddress } from '../../users/entities/user-address.entity';
|
||||||
import { User } from '../../users/entities/user.entity';
|
import { User } from '../../users/entities/user.entity';
|
||||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||||
|
import { PaymentMethodEnum } from '../../payments/interface/payment';
|
||||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||||
import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
|
import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
|
||||||
import { PointTransaction } from '../../users/entities/point-transaction.entity';
|
import { PointTransaction } from '../../users/entities/point-transaction.entity';
|
||||||
@@ -216,6 +217,23 @@ export class CartValidationService {
|
|||||||
return paymentMethod;
|
return paymentMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertCreditCardPaymentDetails(
|
||||||
|
paymentMethod: PaymentMethod,
|
||||||
|
paymentDesc?: string | null,
|
||||||
|
attachments?: string[] | null,
|
||||||
|
): void {
|
||||||
|
if (paymentMethod.method !== PaymentMethodEnum.CreditCard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasDescription = Boolean(paymentDesc?.trim());
|
||||||
|
const hasAttachments = Boolean(attachments?.length);
|
||||||
|
|
||||||
|
if (!hasDescription && !hasAttachments) {
|
||||||
|
throw new BadRequestException(CartMessage.CREDIT_CARD_PAYMENT_DETAILS_REQUIRED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert wallet has enough balance
|
* Assert wallet has enough balance
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -115,17 +115,17 @@ export class CartService {
|
|||||||
|
|
||||||
cart.paymentMethodId = paymentMethodId;
|
cart.paymentMethodId = paymentMethodId;
|
||||||
|
|
||||||
// Set description (if provided)
|
|
||||||
if (description !== undefined) {
|
if (description !== undefined) {
|
||||||
cart.description = description;
|
cart.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachments !== undefined) {
|
if (paymentMethod.method === PaymentMethodEnum.CreditCard) {
|
||||||
cart.attachments = attachments;
|
this.validationService.assertCreditCardPaymentDetails(paymentMethod, paymentDesc, attachments);
|
||||||
}
|
cart.paymentDesc = paymentDesc?.trim() || undefined;
|
||||||
|
cart.attachments = attachments?.length ? attachments : undefined;
|
||||||
if (paymentDesc !== undefined) {
|
} else {
|
||||||
cart.paymentDesc = paymentDesc;
|
delete cart.paymentDesc;
|
||||||
|
delete cart.attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final validation: if effective delivery method is courier, ensure all foods have pickupServe
|
// Final validation: if effective delivery method is courier, ensure all foods have pickupServe
|
||||||
|
|||||||
@@ -94,4 +94,22 @@ export class AdminCreateOrderDto {
|
|||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
carAddress?: OrderCarAddress;
|
carAddress?: OrderCarAddress;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Payment description. At least one of paymentDesc or paymentAttachments is required for credit card.',
|
||||||
|
example: 'Transfer from account ending 1234',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
paymentDesc?: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Payment receipt attachments (URLs). At least one of paymentDesc or paymentAttachments is required for credit card.',
|
||||||
|
type: [String],
|
||||||
|
example: ['https://example.com/receipt.jpg'],
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
paymentAttachments?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export class OrdersService {
|
|||||||
async checkout(userId: string, restaurantId: string) {
|
async checkout(userId: string, restaurantId: string) {
|
||||||
const cart = await this.cartService.findOneOrFail(userId, restaurantId);
|
const cart = await this.cartService.findOneOrFail(userId, restaurantId);
|
||||||
const validated = await this.validateCartForOrder(userId, restaurantId, cart);
|
const validated = await this.validateCartForOrder(userId, restaurantId, cart);
|
||||||
|
console.log(cart);
|
||||||
|
|
||||||
const order = await this.em.transactional(async em => {
|
const order = await this.em.transactional(async em => {
|
||||||
const order = em.create(Order, {
|
const order = em.create(Order, {
|
||||||
@@ -115,8 +116,7 @@ export class OrdersService {
|
|||||||
status: PaymentStatusEnum.Pending,
|
status: PaymentStatusEnum.Pending,
|
||||||
method: order.paymentMethod.method,
|
method: order.paymentMethod.method,
|
||||||
gateway: order.paymentMethod.gateway ?? null,
|
gateway: order.paymentMethod.gateway ?? null,
|
||||||
...(cart.attachments?.length ? { attachments: cart.attachments } : {}),
|
...this.buildCreditCardPaymentFields(validated.paymentMethod, cart.paymentDesc, cart.attachments),
|
||||||
...(cart.paymentDesc ? { description: cart.paymentDesc } : {}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
em.persist(payment);
|
em.persist(payment);
|
||||||
@@ -164,6 +164,7 @@ export class OrdersService {
|
|||||||
|
|
||||||
const paymentMethod = await this.getPaymentMethodOrFail(dto.paymentMethodId, restaurantId);
|
const paymentMethod = await this.getPaymentMethodOrFail(dto.paymentMethodId, restaurantId);
|
||||||
this.assertPaymentMethodEnabled(paymentMethod);
|
this.assertPaymentMethodEnabled(paymentMethod);
|
||||||
|
this.assertCreditCardPaymentDetails(paymentMethod, dto.paymentDesc, dto.paymentAttachments);
|
||||||
|
|
||||||
this.assertAdminDeliveryRequirements(dto, delivery);
|
this.assertAdminDeliveryRequirements(dto, delivery);
|
||||||
|
|
||||||
@@ -215,6 +216,7 @@ export class OrdersService {
|
|||||||
status: PaymentStatusEnum.Pending,
|
status: PaymentStatusEnum.Pending,
|
||||||
method: order.paymentMethod.method,
|
method: order.paymentMethod.method,
|
||||||
gateway: order.paymentMethod.gateway ?? null,
|
gateway: order.paymentMethod.gateway ?? null,
|
||||||
|
...this.buildCreditCardPaymentFields(paymentMethod, dto.paymentDesc, dto.paymentAttachments),
|
||||||
});
|
});
|
||||||
em.persist(payment);
|
em.persist(payment);
|
||||||
|
|
||||||
@@ -255,6 +257,7 @@ export class OrdersService {
|
|||||||
|
|
||||||
const paymentMethod = await this.getPaymentMethodOrFail(cart.paymentMethodId!, restaurantId);
|
const paymentMethod = await this.getPaymentMethodOrFail(cart.paymentMethodId!, restaurantId);
|
||||||
this.assertPaymentMethodEnabled(paymentMethod);
|
this.assertPaymentMethodEnabled(paymentMethod);
|
||||||
|
this.assertCreditCardPaymentDetails(paymentMethod, cart.paymentDesc, cart.attachments);
|
||||||
|
|
||||||
const orderItemsData = await this.buildOrderItemsData(cart, restaurantId);
|
const orderItemsData = await this.buildOrderItemsData(cart, restaurantId);
|
||||||
|
|
||||||
@@ -479,6 +482,35 @@ export class OrdersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildCreditCardPaymentFields(
|
||||||
|
paymentMethod: PaymentMethod,
|
||||||
|
paymentDesc?: string | null,
|
||||||
|
attachments?: string[] | null,
|
||||||
|
): Pick<Payment, 'description' | 'attachments'> {
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(attachments?.length ? { attachments } : {}),
|
||||||
|
...(paymentDesc?.trim() ? { description: paymentDesc.trim() } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private assertCreditCardPaymentDetails(
|
||||||
|
paymentMethod: PaymentMethod,
|
||||||
|
paymentDesc?: string | null,
|
||||||
|
attachments?: string[] | null,
|
||||||
|
): void {
|
||||||
|
if (paymentMethod.method !== PaymentMethodEnum.CreditCard) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasDescription = Boolean(paymentDesc?.trim());
|
||||||
|
const hasAttachments = Boolean(attachments?.length);
|
||||||
|
|
||||||
|
if (!hasDescription && !hasAttachments) {
|
||||||
|
throw new BadRequestException(OrderMessage.CREDIT_CARD_PAYMENT_DETAILS_REQUIRED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private assertDeliveryMethodRequirements(cart: Cart, delivery: Delivery) {
|
private assertDeliveryMethodRequirements(cart: Cart, delivery: Delivery) {
|
||||||
if (delivery.method === DeliveryMethodEnum.DineIn) {
|
if (delivery.method === DeliveryMethodEnum.DineIn) {
|
||||||
if (!cart.tableNumber || cart.tableNumber.trim() === '') {
|
if (!cart.tableNumber || cart.tableNumber.trim() === '') {
|
||||||
|
|||||||
Reference in New Issue
Block a user