check address is inside service area
This commit is contained in:
@@ -56,13 +56,20 @@ export class CartService {
|
|||||||
throw new BadRequestException('Delivery method must be set before setting address');
|
throw new BadRequestException('Delivery method must be set before setting address');
|
||||||
}
|
}
|
||||||
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, effectiveDeliveryMethodId);
|
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, effectiveDeliveryMethodId);
|
||||||
this.assertDeliveryMethod(deliveryMethod.method, DeliveryMethodEnum.DeliveryCourier, 'Delivery method must be DeliveryCourier');
|
this.assertDeliveryMethod(
|
||||||
|
deliveryMethod.method,
|
||||||
|
DeliveryMethodEnum.DeliveryCourier,
|
||||||
|
'Delivery method must be DeliveryCourier',
|
||||||
|
);
|
||||||
|
|
||||||
const address = await this.getUserAddressOrFail(addressId);
|
const address = await this.getUserAddressOrFail(addressId);
|
||||||
if (address.user.id !== userId) {
|
if (address.user.id !== userId) {
|
||||||
throw new BadRequestException('Address does not belong to the current user');
|
throw new BadRequestException('Address does not belong to the current user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure address is within restaurant service area
|
||||||
|
await this.assertAddressInsideServiceArea(restaurantId, address.latitude, address.longitude);
|
||||||
|
|
||||||
// ensure cart fields are compatible and set address
|
// ensure cart fields are compatible and set address
|
||||||
this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCourier);
|
this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCourier);
|
||||||
cart.userAddress = {
|
cart.userAddress = {
|
||||||
@@ -84,7 +91,11 @@ export class CartService {
|
|||||||
throw new BadRequestException('Delivery method must be set before setting car delivery');
|
throw new BadRequestException('Delivery method must be set before setting car delivery');
|
||||||
}
|
}
|
||||||
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, effectiveDeliveryMethodId);
|
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, effectiveDeliveryMethodId);
|
||||||
this.assertDeliveryMethod(deliveryMethod.method, DeliveryMethodEnum.DeliveryCar, 'Delivery method must be DeliveryCar');
|
this.assertDeliveryMethod(
|
||||||
|
deliveryMethod.method,
|
||||||
|
DeliveryMethodEnum.DeliveryCar,
|
||||||
|
'Delivery method must be DeliveryCar',
|
||||||
|
);
|
||||||
|
|
||||||
const user = await this.getUserOrFail(userId);
|
const user = await this.getUserOrFail(userId);
|
||||||
|
|
||||||
@@ -270,12 +281,13 @@ export class CartService {
|
|||||||
async setAddress(userId: string, restaurantId: string, setAddressDto: SetAddressDto): Promise<Cart> {
|
async setAddress(userId: string, restaurantId: string, setAddressDto: SetAddressDto): Promise<Cart> {
|
||||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||||
|
|
||||||
const deliveryMethodId = this.requireDeliveryMethodId(
|
const deliveryMethodId = this.requireDeliveryMethodId(cart, 'Delivery method must be set before setting address');
|
||||||
cart,
|
|
||||||
'Delivery method must be set before setting address',
|
|
||||||
);
|
|
||||||
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, deliveryMethodId);
|
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, deliveryMethodId);
|
||||||
this.assertDeliveryMethod(deliveryMethod.method, DeliveryMethodEnum.DeliveryCourier, 'Delivery method must be DeliveryCourier');
|
this.assertDeliveryMethod(
|
||||||
|
deliveryMethod.method,
|
||||||
|
DeliveryMethodEnum.DeliveryCourier,
|
||||||
|
'Delivery method must be DeliveryCourier',
|
||||||
|
);
|
||||||
|
|
||||||
// Find and validate address belongs to user
|
// Find and validate address belongs to user
|
||||||
const address = await this.getUserAddressOrFail(setAddressDto.addressId);
|
const address = await this.getUserAddressOrFail(setAddressDto.addressId);
|
||||||
@@ -285,6 +297,9 @@ export class CartService {
|
|||||||
throw new BadRequestException('Address does not belong to the current user');
|
throw new BadRequestException('Address does not belong to the current user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure address is within restaurant service area
|
||||||
|
await this.assertAddressInsideServiceArea(restaurantId, address.latitude, address.longitude);
|
||||||
|
|
||||||
this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCourier);
|
this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCourier);
|
||||||
cart.userAddress = {
|
cart.userAddress = {
|
||||||
address: address.address,
|
address: address.address,
|
||||||
@@ -299,6 +314,57 @@ export class CartService {
|
|||||||
return this.saveTouchedCart(cart);
|
return this.saveTouchedCart(cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async assertAddressInsideServiceArea(
|
||||||
|
restaurantId: string,
|
||||||
|
latitude?: number | null,
|
||||||
|
longitude?: number | null,
|
||||||
|
): Promise<void> {
|
||||||
|
if (latitude === undefined || latitude === null || longitude === undefined || longitude === null) {
|
||||||
|
throw new BadRequestException('Address does not have coordinates');
|
||||||
|
}
|
||||||
|
|
||||||
|
const restaurant = await this.em.findOne(Restaurant, { id: restaurantId });
|
||||||
|
if (!restaurant) {
|
||||||
|
throw new NotFoundException('Restaurant not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceArea = restaurant.serviceArea;
|
||||||
|
// If no service area is defined, assume service is available everywhere
|
||||||
|
if (!serviceArea || !serviceArea.coordinates || !Array.isArray(serviceArea.coordinates)) return;
|
||||||
|
|
||||||
|
// GeoJSON coordinates: [ [ [lng, lat], ... ] ] -> take first ring
|
||||||
|
const rings = serviceArea.coordinates;
|
||||||
|
if (!rings || rings.length === 0 || !Array.isArray(rings[0])) return;
|
||||||
|
|
||||||
|
const ring = rings[0];
|
||||||
|
const point: [number, number] = [Number(longitude), Number(latitude)];
|
||||||
|
|
||||||
|
// Ensure polygon has the correct tuple typing ([lng, lat] pairs)
|
||||||
|
const polygon: [number, number][] = ring.map(
|
||||||
|
coord => [Number(coord[0] ?? 0), Number(coord[1] ?? 0)] as [number, number],
|
||||||
|
);
|
||||||
|
if (!this.isPointInPolygon(point, polygon)) {
|
||||||
|
throw new BadRequestException('Address is outside the restaurant service area');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray-casting algorithm for point in polygon. Expects polygon as array of [lng, lat]
|
||||||
|
private isPointInPolygon(point: [number, number], polygon: [number, number][]): boolean {
|
||||||
|
const x = point[0];
|
||||||
|
const y = point[1];
|
||||||
|
let inside = false;
|
||||||
|
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||||
|
const xi = polygon[i][0],
|
||||||
|
yi = polygon[i][1];
|
||||||
|
const xj = polygon[j][0],
|
||||||
|
yj = polygon[j][1];
|
||||||
|
|
||||||
|
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi + Number.EPSILON) + xi;
|
||||||
|
if (intersect) inside = !inside;
|
||||||
|
}
|
||||||
|
return inside;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set payment method for cart
|
* Set payment method for cart
|
||||||
*/
|
*/
|
||||||
@@ -317,11 +383,7 @@ export class CartService {
|
|||||||
return this.recalculateAndSaveCart(cart);
|
return this.recalculateAndSaveCart(cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async assertWalletHasEnoughBalance(
|
private async assertWalletHasEnoughBalance(userId: string, restaurantId: string, amount: number): Promise<void> {
|
||||||
userId: string,
|
|
||||||
restaurantId: string,
|
|
||||||
amount: number,
|
|
||||||
): Promise<void> {
|
|
||||||
const wallet = await this.em.findOne(UserWallet, {
|
const wallet = await this.em.findOne(UserWallet, {
|
||||||
user: { id: userId },
|
user: { id: userId },
|
||||||
restaurant: { id: restaurantId },
|
restaurant: { id: restaurantId },
|
||||||
@@ -346,7 +408,10 @@ export class CartService {
|
|||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||||
|
|
||||||
const deliveryMethod = await this.getEnabledDeliveryMethodOrFail(restaurantId, setDeliveryMethodDto.deliveryMethodId);
|
const deliveryMethod = await this.getEnabledDeliveryMethodOrFail(
|
||||||
|
restaurantId,
|
||||||
|
setDeliveryMethodDto.deliveryMethodId,
|
||||||
|
);
|
||||||
|
|
||||||
cart.deliveryMethodId = setDeliveryMethodDto.deliveryMethodId;
|
cart.deliveryMethodId = setDeliveryMethodDto.deliveryMethodId;
|
||||||
this.clearFieldsIncompatibleWithDeliveryMethod(cart, deliveryMethod.method);
|
this.clearFieldsIncompatibleWithDeliveryMethod(cart, deliveryMethod.method);
|
||||||
@@ -393,7 +458,11 @@ export class CartService {
|
|||||||
'Delivery method must be set before setting car delivery',
|
'Delivery method must be set before setting car delivery',
|
||||||
);
|
);
|
||||||
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, deliveryMethodId);
|
const deliveryMethod = await this.getDeliveryMethodForRestaurantOrFail(restaurantId, deliveryMethodId);
|
||||||
this.assertDeliveryMethod(deliveryMethod.method, DeliveryMethodEnum.DeliveryCar, 'Delivery method must be DeliveryCar');
|
this.assertDeliveryMethod(
|
||||||
|
deliveryMethod.method,
|
||||||
|
DeliveryMethodEnum.DeliveryCar,
|
||||||
|
'Delivery method must be DeliveryCar',
|
||||||
|
);
|
||||||
const user = await this.getUserOrFail(userId);
|
const user = await this.getUserOrFail(userId);
|
||||||
|
|
||||||
this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCar);
|
this.clearFieldsIncompatibleWithDeliveryMethod(cart, DeliveryMethodEnum.DeliveryCar);
|
||||||
@@ -689,24 +758,25 @@ export class CartService {
|
|||||||
return cart.deliveryMethodId;
|
return cart.deliveryMethodId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getDeliveryMethodForRestaurantOrFail(restaurantId: string, deliveryMethodId: string): Promise<Delivery> {
|
private async getDeliveryMethodForRestaurantOrFail(
|
||||||
|
restaurantId: string,
|
||||||
|
deliveryMethodId: string,
|
||||||
|
): Promise<Delivery> {
|
||||||
const deliveryMethod = await this.em.findOne(Delivery, {
|
const deliveryMethod = await this.em.findOne(Delivery, {
|
||||||
id: deliveryMethodId,
|
id: deliveryMethodId,
|
||||||
restaurant: { id: restaurantId },
|
restaurant: { id: restaurantId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!deliveryMethod) {
|
if (!deliveryMethod) {
|
||||||
throw new NotFoundException(`Delivery method with ID ${deliveryMethodId} not found for restaurant ${restaurantId}`);
|
throw new NotFoundException(
|
||||||
|
`Delivery method with ID ${deliveryMethodId} not found for restaurant ${restaurantId}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return deliveryMethod;
|
return deliveryMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
private assertDeliveryMethod(
|
private assertDeliveryMethod(actual: DeliveryMethodEnum, expected: DeliveryMethodEnum, message: string): void {
|
||||||
actual: DeliveryMethodEnum,
|
|
||||||
expected: DeliveryMethodEnum,
|
|
||||||
message: string,
|
|
||||||
): void {
|
|
||||||
if (actual !== expected) {
|
if (actual !== expected) {
|
||||||
throw new BadRequestException(message);
|
throw new BadRequestException(message);
|
||||||
}
|
}
|
||||||
@@ -734,7 +804,9 @@ export class CartService {
|
|||||||
{ populate: ['restaurant'] },
|
{ populate: ['restaurant'] },
|
||||||
);
|
);
|
||||||
if (!deliveryMethod) {
|
if (!deliveryMethod) {
|
||||||
throw new NotFoundException(`Delivery method with ID ${deliveryMethodId} not found for restaurant ${restaurantId}`);
|
throw new NotFoundException(
|
||||||
|
`Delivery method with ID ${deliveryMethodId} not found for restaurant ${restaurantId}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!deliveryMethod.enabled) {
|
if (!deliveryMethod.enabled) {
|
||||||
throw new BadRequestException('Delivery method is not enabled for this restaurant');
|
throw new BadRequestException('Delivery method is not enabled for this restaurant');
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class OrderListeners {
|
|||||||
async handleOrderCreated(event: paymentSucceedEvent) {
|
async handleOrderCreated(event: paymentSucceedEvent) {
|
||||||
try {
|
try {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Order created event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
|
`Payment paid event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`,
|
||||||
);
|
);
|
||||||
// get admnin os restuaraant that have order permissuins
|
// get admnin os restuaraant that have order permissuins
|
||||||
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS);
|
const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS);
|
||||||
@@ -36,7 +36,7 @@ export class OrderListeners {
|
|||||||
restaurantId: event.restaurantId,
|
restaurantId: event.restaurantId,
|
||||||
message: {
|
message: {
|
||||||
title: NotifTitleEnum.ORDER_CREATED,
|
title: NotifTitleEnum.ORDER_CREATED,
|
||||||
content: `Order ${event.orderNumber} has been created successfully`,
|
content: `payment order #${event.orderNumber} has been paid successfully`,
|
||||||
sms: {
|
sms: {
|
||||||
templateId: this.orderCreatedSmsTemplateId,
|
templateId: this.orderCreatedSmsTemplateId,
|
||||||
parameters: {
|
parameters: {
|
||||||
@@ -44,12 +44,12 @@ export class OrderListeners {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pushNotif: {
|
pushNotif: {
|
||||||
title: `Order ${event.orderNumber} has been created successfully`,
|
title: `Payment Order ${event.orderNumber} has been paid successfully`,
|
||||||
content: `Order ${event.orderNumber} has been created successfully`,
|
content: `Payment Order ${event.orderNumber} has been paid successfully`,
|
||||||
icon: `Order ${event.orderNumber} has been created successfully`,
|
icon: `Payment Order ${event.orderNumber} has been paid successfully`,
|
||||||
action: {
|
action: {
|
||||||
type: `Order ${event.orderNumber} has been created successfully`,
|
type: `Payment Order ${event.orderNumber} has been paid successfully`,
|
||||||
url: `Order ${event.orderNumber} has been created successfully`,
|
url: `Payment Order ${event.orderNumber} has been paid successfully`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user