diff --git a/src/common/utils/money.utils.ts b/src/common/utils/money.utils.ts new file mode 100644 index 0000000..32b32fb --- /dev/null +++ b/src/common/utils/money.utils.ts @@ -0,0 +1,17 @@ +/** + * Round monetary amounts to whole currency units (DB stores scale 0). + */ +export function roundMoney(value: number | string | null | undefined): number { + const numeric = Number(value); + if (!Number.isFinite(numeric)) { + return 0; + } + return Math.round(numeric); +} + +/** + * Format monetary amounts for display/SMS without floating-point artifacts. + */ +export function formatMoneyAmount(value: number | string | null | undefined): string { + return roundMoney(value).toString(); +} diff --git a/src/modules/cart/controllers/cart.controller.ts b/src/modules/cart/controllers/cart.controller.ts index b8afc3f..28fb61f 100644 --- a/src/modules/cart/controllers/cart.controller.ts +++ b/src/modules/cart/controllers/cart.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Post, Body, Patch, Delete, UseGuards, Param } from '@nestjs/common'; +import { Controller, Get, Post, Body, Patch, Delete, UseGuards, Param, ValidationPipe } from '@nestjs/common'; import { CartService } from '../providers/cart.service'; import { BulkAddItemsToCartDto } from '../dto/bulk-add-items.dto'; import { ApplyCouponDto } from '../dto/apply-coupon.dto'; @@ -38,12 +38,12 @@ export class CartController { } @Post('variant/bulk') - @ApiOperation({ summary: 'Bulk add items to cart (increments quantity if items exist)' }) + @ApiOperation({ summary: 'Bulk set item quantities in cart (replaces quantity if item exists)' }) @ApiHeader(API_HEADER_SLUG) bulkAddItems( @UserId() userId: string, @ShopId() shopId: string, - @Body() bulkAddItemsDto: BulkAddItemsToCartDto, + @Body(new ValidationPipe({ transform: true, whitelist: true })) bulkAddItemsDto: BulkAddItemsToCartDto, ) { return this.cartService.bulkAddItems(userId, shopId, bulkAddItemsDto); } diff --git a/src/modules/cart/providers/cart-calculation.service.ts b/src/modules/cart/providers/cart-calculation.service.ts index b211d5a..9d1c885 100644 --- a/src/modules/cart/providers/cart-calculation.service.ts +++ b/src/modules/cart/providers/cart-calculation.service.ts @@ -8,6 +8,8 @@ import { Product } from '../../products/entities/product.entity'; import { Cart } from '../interfaces/cart.interface'; import { CouponService } from 'src/modules/coupons/providers/coupon.service'; import { GeographicUtils } from '../utils/geographic.utils'; +import { roundQuantity } from 'src/modules/products/utils/quantity.utils'; +import { roundMoney } from 'src/common/utils/money.utils'; @Injectable() export class CartCalculationService { @@ -24,7 +26,7 @@ export class CartCalculationService { const safeUnitDiscount = Math.min(Number(unitDiscount) || 0, safeUnitPrice); const itemTotalPrice = safeUnitPrice * quantity; const itemTotalDiscount = safeUnitDiscount * quantity; - return itemTotalPrice - itemTotalDiscount; + return roundMoney(itemTotalPrice - itemTotalDiscount); } /** @@ -36,17 +38,23 @@ export class CartCalculationService { let totalItems = 0; for (const item of cart.items) { + const quantity = roundQuantity(item.quantity); const unitPrice = Number(item.price) || 0; const unitDiscount = Math.min(Number(item.discount) || 0, unitPrice); - const itemPrice = unitPrice * item.quantity; - const itemDiscount = unitDiscount * item.quantity; + const itemPrice = unitPrice * quantity; + const itemDiscount = unitDiscount * quantity; subTotal += itemPrice; itemsDiscount += itemDiscount; - totalItems += item.quantity; - item.totalPrice = itemPrice - itemDiscount; + totalItems += quantity; + item.quantity = quantity; + item.totalPrice = roundMoney(itemPrice - itemDiscount); } - return { subTotal, itemsDiscount, totalItems }; + return { + subTotal: roundMoney(subTotal), + itemsDiscount: roundMoney(itemsDiscount), + totalItems, + }; } /** @@ -94,10 +102,10 @@ export class CartCalculationService { if (cart.coupon.maxDiscount && discount > cart.coupon.maxDiscount) { discount = cart.coupon.maxDiscount; } - return discount; + return roundMoney(discount); } - return Math.min(cart.coupon.value, priceAfterItemDiscount); + return roundMoney(Math.min(cart.coupon.value, priceAfterItemDiscount)); } /** @@ -107,7 +115,7 @@ export class CartCalculationService { const shop = await this.em.findOne(Shop, { id: shopId }); const vat = shop?.vat ? Number(shop.vat) : 0; if (!vat || vat <= 0) return 0; - return (Math.max(0, amountAfterDiscounts) * vat) / 100; + return roundMoney((Math.max(0, amountAfterDiscounts) * vat) / 100); } /** @@ -122,7 +130,7 @@ export class CartCalculationService { // If not distance based, return fixed delivery fee if (deliveryMethod.deliveryFeeType !== DeliveryFeeTypeEnum.DISTANCE_BASED) { - return Number(deliveryMethod.deliveryFee) || 0; + return roundMoney(Number(deliveryMethod.deliveryFee) || 0); } // For distance based calculation we need shop and user coordinates @@ -131,11 +139,11 @@ export class CartCalculationService { if (!shop || shop.latitude == null || shop.longitude == null) { // fallback to configured fixed fee when coordinates are missing - return Number(deliveryMethod.deliveryFee) || 0; + return roundMoney(Number(deliveryMethod.deliveryFee) || 0); } if (!userAddr || userAddr.latitude == null || userAddr.longitude == null) { - return Number(deliveryMethod.deliveryFee) || 0; + return roundMoney(Number(deliveryMethod.deliveryFee) || 0); } const restLat = Number(shop.latitude); @@ -158,7 +166,7 @@ export class CartCalculationService { if (minFee > 0 && fee < minFee) fee = minFee; - return Math.max(0, Number(fee)); + return roundMoney(Math.max(0, Number(fee))); } /** @@ -171,13 +179,13 @@ export class CartCalculationService { const couponDiscount = await this.calculateCouponDiscount(cart, subTotal, itemsDiscount); cart.couponDiscount = couponDiscount; - cart.totalDiscount = couponDiscount + itemsDiscount; + cart.totalDiscount = roundMoney(couponDiscount + itemsDiscount); cart.tax = await this.calculateTax(cart.shopId, Math.max(0, subTotal - cart.totalDiscount)); cart.deliveryFee = await this.calculateDeliveryFee(cart); // total = subtotal – totalDiscount + tax + deliveryFee - cart.total = Math.max(0, subTotal - cart.totalDiscount) + cart.tax + cart.deliveryFee; + cart.total = roundMoney(Math.max(0, subTotal - cart.totalDiscount) + cart.tax + cart.deliveryFee); cart.totalItems = totalItems; cart.updatedAt = this.nowIso(); } diff --git a/src/modules/cart/providers/cart-item.service.ts b/src/modules/cart/providers/cart-item.service.ts index f23b60b..e12c66c 100644 --- a/src/modules/cart/providers/cart-item.service.ts +++ b/src/modules/cart/providers/cart-item.service.ts @@ -69,28 +69,54 @@ export class CartItemService { * Add or increment item in cart (validates stock against existing + new quantity) */ async addOrIncrementItem(cart: Cart, variantId: string, shopId: string, quantityToAdd: number): Promise { + const normalizedQuantityToAdd = roundQuantity(quantityToAdd); const index = this.getItemIndex(cart, variantId); - const existingQuantityInCart = index >= 0 ? cart.items[index].quantity : 0; + const existingQuantityInCart = index >= 0 ? roundQuantity(cart.items[index].quantity) : 0; const variant = await this.validationService.validateAndGetVariant( variantId, shopId, - quantityToAdd, + normalizedQuantityToAdd, existingQuantityInCart, cart, ); if (index < 0) { - cart.items.push(this.createCartItem(variant, quantityToAdd)); + cart.items.push(this.createCartItem(variant, normalizedQuantityToAdd)); return; } const existingItem = cart.items[index]; - const newQuantity = roundQuantity(existingItem.quantity + quantityToAdd); + const newQuantity = roundQuantity(existingQuantityInCart + normalizedQuantityToAdd); cart.items[index] = this.buildCartItemFromFood(variant, newQuantity, existingItem); } + /** + * Set item quantity in cart (adds item if it does not exist) + */ + async setItemQuantity(cart: Cart, variantId: string, shopId: string, quantity: number): Promise { + const normalizedQuantity = roundQuantity(quantity); + const index = this.getItemIndex(cart, variantId); + const existingQuantityInCart = index >= 0 ? roundQuantity(cart.items[index].quantity) : 0; + + const variant = await this.validationService.validateAndGetVariant( + variantId, + shopId, + normalizedQuantity, + existingQuantityInCart, + cart, + { mode: 'set' }, + ); + + if (index < 0) { + cart.items.push(this.createCartItem(variant, normalizedQuantity)); + return; + } + + cart.items[index] = this.buildCartItemFromFood(variant, normalizedQuantity, cart.items[index]); + } + /** * Increment item by product purchase step (1 for fixed, purchasePitch for variable) */ @@ -123,7 +149,7 @@ export class CartItemService { const existingItem = cart.items[itemIndex]; const variant = await this.productService.findOrFailVariant(variantId); const step = getQuantityStep(variant.product); - const newQuantity = roundQuantity(existingItem.quantity - step); + const newQuantity = roundQuantity(roundQuantity(existingItem.quantity) - step); if (newQuantity <= 0) { cart.items.splice(itemIndex, 1); diff --git a/src/modules/cart/providers/cart-validation.service.ts b/src/modules/cart/providers/cart-validation.service.ts index 8d7874f..191f1cf 100644 --- a/src/modules/cart/providers/cart-validation.service.ts +++ b/src/modules/cart/providers/cart-validation.service.ts @@ -37,34 +37,42 @@ export class CartValidationService { async validateAndGetVariant( variantId: string, shopId: string, - quantityToAdd: number, + quantity: number, existingQuantityInCart: number = 0, cart?: { items: { variantId: string; quantity: number }[] }, + options?: { mode?: 'increment' | 'set' }, ): Promise { + const mode = options?.mode ?? 'increment'; + const normalizedQuantity = roundQuantity(quantity); + const normalizedExistingQuantity = roundQuantity(existingQuantityInCart); const variant = await this.productService.findOrFailVariant(variantId); if (variant.product.shop.id !== shopId) { throw new BadRequestException(CartMessage.PRODUCT_NOT_BELONGS_TO_SHOP); } + const totalRequested = + mode === 'set' ? normalizedQuantity : normalizedExistingQuantity + normalizedQuantity; + if (variant.stock != null) { - const totalRequested = existingQuantityInCart + quantityToAdd; - if (totalRequested > variant.stock) { + if (totalRequested > roundQuantity(variant.stock)) { throw new BadRequestException(CartMessage.INSUFFICIENT_STOCK); } } - this.assertValidQuantity(variant, existingQuantityInCart + quantityToAdd); + this.assertValidQuantity(variant, totalRequested); const maxPurchase = variant.product.maxPurchaseQuantity; if (maxPurchase != null && maxPurchase > 0) { const productVariantIds = variant.product.variants.getItems().map((v) => v.id); const totalProductQuantityInCart = cart?.items?.reduce( - (sum, item) => (productVariantIds.includes(item.variantId) ? sum + item.quantity : sum), + (sum, item) => + productVariantIds.includes(item.variantId) ? sum + roundQuantity(item.quantity) : sum, 0, ) ?? 0; - const newTotalProductQuantity = totalProductQuantityInCart - existingQuantityInCart + quantityToAdd; + const newTotalProductQuantity = + totalProductQuantityInCart - normalizedExistingQuantity + totalRequested; if (newTotalProductQuantity > maxPurchase) { throw new BadRequestException(CartMessage.MAX_PURCHASE_EXCEEDED); } diff --git a/src/modules/cart/providers/cart.service.ts b/src/modules/cart/providers/cart.service.ts index 2fd6018..189c937 100644 --- a/src/modules/cart/providers/cart.service.ts +++ b/src/modules/cart/providers/cart.service.ts @@ -181,13 +181,13 @@ export class CartService { } /** - * Bulk add items to cart (increments quantity if items exist) + * Bulk set item quantities in cart (replaces quantity if item exists) */ async bulkAddItems(userId: string, shopId: string, bulkAddItemsDto: BulkAddItemsToCartDto): Promise { const cart = await this.getOrCreateCart(userId, shopId); for (const addItemDto of bulkAddItemsDto.items) { - await this.itemService.addOrIncrementItem(cart, addItemDto.variantId, shopId, addItemDto.quantity); + await this.itemService.setItemQuantity(cart, addItemDto.variantId, shopId, addItemDto.quantity); } return this.recalculateAndSaveCart(cart); diff --git a/src/modules/cart/repositories/cart.repository.ts b/src/modules/cart/repositories/cart.repository.ts index b5e714a..30dce2a 100644 --- a/src/modules/cart/repositories/cart.repository.ts +++ b/src/modules/cart/repositories/cart.repository.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { CacheService } from 'src/modules/utils/cache.service'; +import { roundQuantity } from 'src/modules/products/utils/quantity.utils'; import { Cart } from '../interfaces/cart.interface'; @Injectable() @@ -20,7 +21,7 @@ export class CartRepository { try { const parsed: unknown = JSON.parse(cachedCart); if (this.isCart(parsed) && parsed.userId === userId && parsed.shopId === shopId) { - return parsed; + return this.normalizeCartQuantities(parsed); } } catch { // If parsing fails, return null @@ -53,6 +54,17 @@ export class CartRepository { return `${this.CART_KEY_PREFIX}:${userId}:${shopId}`; } + /** + * Coerce cached item quantities to numbers (guards against string concat bugs). + */ + private normalizeCartQuantities(cart: Cart): Cart { + for (const item of cart.items) { + item.quantity = roundQuantity(item.quantity); + } + cart.totalItems = roundQuantity(cart.totalItems); + return cart; + } + /** * Type guard to check if an object is a Cart */ diff --git a/src/modules/orders/listeners/order.listeners.ts b/src/modules/orders/listeners/order.listeners.ts index 6e39fc8..d7f0abf 100644 --- a/src/modules/orders/listeners/order.listeners.ts +++ b/src/modules/orders/listeners/order.listeners.ts @@ -11,6 +11,7 @@ import { OrderStatus } from '../interface/order.interface'; import { PaymentMethodEnum } from 'src/modules/payments/interface/payment'; import { UserService } from 'src/modules/users/providers/user.service'; import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet'; +import { formatMoneyAmount } from 'src/common/utils/money.utils'; @Injectable() export class OrderListeners { @@ -69,17 +70,17 @@ export class OrderListeners { shopId: event.shopId, message: { title: NotifTitleEnum.ORDER_CREATED, - content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`, + content: `سفارش شماره ${event.orderNumber} با مبلغ ${formatMoneyAmount(event.total)} تومان با موفقیت ایجاد شد`, sms: { templateId: this.orderCreatedSmsTemplateId, parameters: { orderNumber: event.orderNumber, - total: event.total.toString(), + total: formatMoneyAmount(event.total), }, }, pushNotif: { title: `سفارش جدید`, - content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`, + content: `سفارش شماره ${event.orderNumber} با مبلغ ${formatMoneyAmount(event.total)} تومان با موفقیت ایجاد شد`, icon: `/`, action: { type: NotifTitleEnum.ORDER_CREATED, diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index 003979d..f35bbb1 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -27,6 +27,7 @@ import { OrderItemStatus } from '../enum/order-item.enum'; import { getMinQuantity, isValidProductQuantity, roundQuantity } from '../../products/utils/quantity.utils'; import { CouponService } from 'src/modules/coupons/providers/coupon.service'; import { CouponType } from 'src/modules/coupons/interface/coupon'; +import { roundMoney } from 'src/common/utils/money.utils'; type OrderItemData = { variant: Variant; quantity: number; unitPrice: number; discount: number }; @@ -101,7 +102,7 @@ export class OrdersService { for (const itemData of validated.orderItemsData) { const { variant, quantity, unitPrice, discount } = itemData; - const totalPrice = (unitPrice - discount) * quantity; + const totalPrice = roundMoney((unitPrice - discount) * quantity); const orderItem = em.create(OrderItem, { order, @@ -127,7 +128,7 @@ export class OrdersService { this.eventEmitter.emit( OrderCreatedEvent.name, - new OrderCreatedEvent(order.id, shopId, String(order.orderNumber), order.total), + new OrderCreatedEvent(order.id, shopId, String(order.orderNumber), roundMoney(order.total)), ); return { order }; @@ -212,6 +213,7 @@ export class OrdersService { 'payments', 'items', 'items.variant', + 'items.variant.product', ], }, ); @@ -335,19 +337,19 @@ export class OrdersService { subTotal += unitPrice * item.quantity; itemsDiscount += unitDiscount * item.quantity; totalItems += item.quantity; - item.totalPrice = (unitPrice - unitDiscount) * item.quantity; + item.totalPrice = roundMoney((unitPrice - unitDiscount) * item.quantity); } - order.subTotal = subTotal; - order.itemsDiscount = itemsDiscount; + order.subTotal = roundMoney(subTotal); + order.itemsDiscount = roundMoney(itemsDiscount); order.totalItems = totalItems; order.couponDiscount = await this.calculateOrderCouponDiscount(order); - order.totalDiscount = order.couponDiscount + order.itemsDiscount; + order.totalDiscount = roundMoney(order.couponDiscount + order.itemsDiscount); - const amountAfterDiscounts = Math.max(0, subTotal - order.totalDiscount); + const amountAfterDiscounts = Math.max(0, order.subTotal - order.totalDiscount); const vat = order.shop?.vat ? Number(order.shop.vat) : 0; - order.tax = vat > 0 ? (amountAfterDiscounts * vat) / 100 : 0; - order.total = amountAfterDiscounts + order.tax + Number(order.deliveryFee || 0); + order.tax = vat > 0 ? roundMoney((amountAfterDiscounts * vat) / 100) : 0; + order.total = roundMoney(amountAfterDiscounts + order.tax + Number(order.deliveryFee || 0)); } private async calculateOrderCouponDiscount(order: Order): Promise { @@ -395,10 +397,10 @@ export class OrdersService { if (couponDetail.maxDiscount && discount > couponDetail.maxDiscount) { discount = couponDetail.maxDiscount; } - return discount; + return roundMoney(discount); } - return Math.min(couponDetail.value, priceAfterItemDiscount); + return roundMoney(Math.min(couponDetail.value, priceAfterItemDiscount)); } private assertStatusTransitionAllowed(order: Order, to: OrderStatus, ref: StatusTransitionRef) { diff --git a/src/modules/payments/listeners/payment.listeners.ts b/src/modules/payments/listeners/payment.listeners.ts index 9e9c9e0..2d17b73 100644 --- a/src/modules/payments/listeners/payment.listeners.ts +++ b/src/modules/payments/listeners/payment.listeners.ts @@ -7,6 +7,7 @@ import { NotifTitleEnum } from 'src/modules/notifications/interfaces/notificatio import { NotificationService } from 'src/modules/notifications/services/notification.service'; import { ConfigService } from '@nestjs/config'; import { OrdersService } from 'src/modules/orders/providers/orders.service'; +import { formatMoneyAmount } from 'src/common/utils/money.utils'; @Injectable() export class PaymentListeners { @@ -48,7 +49,7 @@ export class PaymentListeners { shopId: event.shopId, message: { title: NotifTitleEnum.PAYMENT_SUCCESS, - content: `پرداخت سفارش شماره ${event.orderNumber} با روش ${this.getPaymentMethodFarsi(event.paymentMethod)} و مبلغ ${event.total} تومان با موفقیت انجام شد`, + content: `پرداخت سفارش شماره ${event.orderNumber} با روش ${this.getPaymentMethodFarsi(event.paymentMethod)} و مبلغ ${formatMoneyAmount(event.total)} تومان با موفقیت انجام شد`, sms: { templateId: this.orderCreatedSmsTemplateId, parameters: { @@ -58,7 +59,7 @@ export class PaymentListeners { }, pushNotif: { title: `پرداخت موفق`, - content: `پرداخت سفارش شماره ${event.orderNumber} با روش ${this.getPaymentMethodFarsi(event.paymentMethod)} و مبلغ ${event.total} تومان با موفقیت انجام شد`, + content: `پرداخت سفارش شماره ${event.orderNumber} با روش ${this.getPaymentMethodFarsi(event.paymentMethod)} و مبلغ ${formatMoneyAmount(event.total)} تومان با موفقیت انجام شد`, icon: ``, action: { type: NotifTitleEnum.PAYMENT_SUCCESS, @@ -96,17 +97,17 @@ export class PaymentListeners { shopId: event.shopId, message: { title: NotifTitleEnum.ORDER_CREATED, - content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`, + content: `سفارش شماره ${event.orderNumber} با مبلغ ${formatMoneyAmount(event.total)} تومان با موفقیت ایجاد شد`, sms: { templateId: this.orderCreatedSmsTemplateId, parameters: { orderNumber: event.orderNumber, - total: event.total.toString(), + total: formatMoneyAmount(event.total), }, }, pushNotif: { title: `سفارش جدید`, - content: `سفارش شماره ${event.orderNumber} با مبلغ ${event.total} تومان با موفقیت ایجاد شد`, + content: `سفارش شماره ${event.orderNumber} با مبلغ ${formatMoneyAmount(event.total)} تومان با موفقیت ایجاد شد`, icon: `/`, action: { type: NotifTitleEnum.ORDER_CREATED, @@ -137,17 +138,17 @@ export class PaymentListeners { shopId: event.shopId, message: { title: NotifTitleEnum.PAYMENT_SUCCESS, - content: `پرداخت سفارش شماره ${order.orderNumber} با روش ${this.getPaymentMethodFarsi(order.paymentMethod.method)} و مبلغ ${order.total} تومان با موفقیت انجام شد`, + content: `پرداخت سفارش شماره ${order.orderNumber} با روش ${this.getPaymentMethodFarsi(order.paymentMethod.method)} و مبلغ ${formatMoneyAmount(order.total)} تومان با موفقیت انجام شد`, sms: { templateId: this.paymentSucceedSmsTemplateId, parameters: { orderNumber: event.orderNumber, - total: event.total.toString(), + total: formatMoneyAmount(event.total), }, }, pushNotif: { title: `پرداخت موفق`, - content: `پرداخت سفارش شماره ${order.orderNumber} با روش ${this.getPaymentMethodFarsi(order.paymentMethod.method)} و مبلغ ${order.total} تومان با موفقیت انجام شد`, + content: `پرداخت سفارش شماره ${order.orderNumber} با روش ${this.getPaymentMethodFarsi(order.paymentMethod.method)} و مبلغ ${formatMoneyAmount(order.total)} تومان با موفقیت انجام شد`, icon: `/`, action: { type: NotifTitleEnum.PAYMENT_SUCCESS, diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index 5dc73d2..5a6f563 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -14,6 +14,7 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { onlinePaymentSucceedEvent, paymentSucceedEvent } from '../events/payment.events'; import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet'; import { PaymentRepository } from '../repositories/payment.repository'; +import { roundMoney } from 'src/common/utils/money.utils'; @Injectable() export class PaymentsService { @@ -83,7 +84,7 @@ export class PaymentsService { return { order, - amount: order.total, + amount: roundMoney(order.total), method: pm.method, gateway: pm.gateway ?? null, merchantId: pm.merchantId ?? null, @@ -152,7 +153,7 @@ export class PaymentsService { }); this.eventEmitter.emit( paymentSucceedEvent.name, - new paymentSucceedEvent(ctx.order.id, ctx.order.shop.id, String(ctx.order?.orderNumber) || '', ctx.method, ctx.amount), + new paymentSucceedEvent(ctx.order.id, ctx.order.shop.id, String(ctx.order?.orderNumber) || '', ctx.method, roundMoney(ctx.amount)), ); } @@ -234,7 +235,7 @@ export class PaymentsService { }); this.eventEmitter.emit( onlinePaymentSucceedEvent.name, - new onlinePaymentSucceedEvent(payment.id, payment.order.id, payment.order.shop.id, String(payment.order?.orderNumber) || '', payment.amount), + new onlinePaymentSucceedEvent(payment.id, payment.order.id, payment.order.shop.id, String(payment.order?.orderNumber) || '', roundMoney(payment.amount)), ); return payment; } diff --git a/src/modules/products/utils/quantity.utils.ts b/src/modules/products/utils/quantity.utils.ts index c38a521..3f52753 100644 --- a/src/modules/products/utils/quantity.utils.ts +++ b/src/modules/products/utils/quantity.utils.ts @@ -18,8 +18,12 @@ export function getMinQuantity(product: Product): number { return product.pricingType === PricingType.VARIABLE ? getQuantityStep(product) : 1; } -export function roundQuantity(value: number): number { - return Math.round(value * SCALE_FACTOR) / SCALE_FACTOR; +export function roundQuantity(value: number | string): number { + const numeric = Number(value); + if (!Number.isFinite(numeric)) { + return 0; + } + return Math.round(numeric * SCALE_FACTOR) / SCALE_FACTOR; } export function isQuantityAlignedToPitch(quantity: number, pitch: number): boolean {