payment on deleivery
Build and Deploy / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-09 10:44:26 +03:30
parent f5a803d86f
commit 8e5e20320d
9 changed files with 64 additions and 17 deletions
+3
View File
@@ -8,6 +8,9 @@ class PriceDetailDTO {
@Expose() @Expose()
shipping_cost: number; shipping_cost: number;
@Expose()
shipping_cost_on_delivery: number;
@Expose() @Expose()
process_cost: number; process_cost: number;
@@ -16,6 +16,7 @@ export interface ICartPayment {
export interface IPriceDetails { export interface IPriceDetails {
shipping_cost: number; shipping_cost: number;
shipping_cost_on_delivery: number;
process_cost: number; process_cost: number;
total_retail_price: number; total_retail_price: number;
total_payable_price: number; total_payable_price: number;
@@ -6,6 +6,7 @@ import { PaymentStatus } from "../../../common/enums/order.enum";
const PaymentPriceDetails = new Schema<IPriceDetails>( const PaymentPriceDetails = new Schema<IPriceDetails>(
{ {
shipping_cost: { type: Number, required: true }, shipping_cost: { type: Number, required: true },
shipping_cost_on_delivery: { type: Number, default: 0 },
process_cost: { type: Number, required: true }, process_cost: { type: Number, required: true },
total_retail_price: { type: Number, required: true }, total_retail_price: { type: Number, required: true },
total_discount: { type: Number, default: 0 }, total_discount: { type: Number, default: 0 },
@@ -21,6 +21,7 @@ import { OrderService } from "../../order/order.service";
import { PricingRepository } from "../../pricing/pricing.repository"; import { PricingRepository } from "../../pricing/pricing.repository";
import { ProductService } from "../../product/providers/product.service"; import { ProductService } from "../../product/providers/product.service";
import { ShopRepo } from "../../shop/shop.repository"; import { ShopRepo } from "../../shop/shop.repository";
import { ShipmentRepository } from "../../shipment/shipment.repository";
import { IUser } from "../../user/models/Abstraction/IUser"; import { IUser } from "../../user/models/Abstraction/IUser";
import { WalletService } from "../../wallet/wallet.service"; import { WalletService } from "../../wallet/wallet.service";
import { CartCheckoutDTO } from "../DTO/cartCheckout.dto"; import { CartCheckoutDTO } from "../DTO/cartCheckout.dto";
@@ -46,6 +47,7 @@ class PaymentService {
@inject(IOCTYPES.WalletService) walletService: WalletService; @inject(IOCTYPES.WalletService) walletService: WalletService;
@inject(IOCTYPES.NotificationService) notificationService: NotificationService; @inject(IOCTYPES.NotificationService) notificationService: NotificationService;
@inject(IOCTYPES.ShopRepo) shopRepo: ShopRepo; @inject(IOCTYPES.ShopRepo) shopRepo: ShopRepo;
@inject(IOCTYPES.ShipmentRepository) shipmentRepo: ShipmentRepository;
//################################################################## //##################################################################
//################################################################## //##################################################################
@@ -98,7 +100,7 @@ class PaymentService {
if (!paymentMethods) throw new BadRequestError(PaymentMessage.PaymentMethodsNotFound); if (!paymentMethods) throw new BadRequestError(PaymentMessage.PaymentMethodsNotFound);
const { price } = this.calculateTotalPrice(cart, cartShipmentItems); const { price } = await this.calculateTotalPrice(cart, cartShipmentItems);
return { return {
cart, cart,
cartShipmentItems, cartShipmentItems,
@@ -135,7 +137,7 @@ class PaymentService {
const paymentMethod = await this.paymentMethodRepo.findById(cartCheckoutDto.payment_method_id); const paymentMethod = await this.paymentMethodRepo.findById(cartCheckoutDto.payment_method_id);
if (!paymentMethod) throw new BadRequestError(PaymentMessage.PaymentMethodNotFound); if (!paymentMethod) throw new BadRequestError(PaymentMessage.PaymentMethodNotFound);
const { price, description } = this.calculateTotalPrice(cart, cartShipmentItems); const { price, description } = await this.calculateTotalPrice(cart, cartShipmentItems);
// extracted to handle payment creation // extracted to handle payment creation
const { payment, paymentData } = await this.handlePaymentCreation(paymentMethod, price, user, description, session); const { payment, paymentData } = await this.handlePaymentCreation(paymentMethod, price, user, description, session);
@@ -238,26 +240,33 @@ class PaymentService {
//################################################################## //##################################################################
//################################################################## //##################################################################
// Helper method to calculate the total price of cart items // Helper method to calculate the total price of cart items
private calculateTotalPrice(cart: CartDTO, cartShipItem: ICartShipmentItem[]) { private async calculateTotalPrice(cart: CartDTO, cartShipItem: ICartShipmentItem[]) {
const totalShippingCost = cartShipItem.reduce( const totalShippingCost = cartShipItem.reduce(
(sum, item) => sum + item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0), (sum, item) => sum + item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0),
0, 0,
); );
// const totalCouponDiscount = cartShipItem.reduce((sum, item) => sum + item.totalCouponDiscount, 0); const shipperIds = [...new Set(cartShipItem.map((item) => item.shipper))];
// const totalShippingCost = cartShipItem.reduce((sum, item) => sum + item.shipmentCost, 0); const shippers = await this.shipmentRepo.model.find({ _id: { $in: shipperIds } }).select("_id payDeliveryFeeOnDelivery").lean();
const payOnDeliveryShipperIds = new Set(
shippers.filter((shipper) => shipper.payDeliveryFeeOnDelivery).map((shipper) => shipper._id),
);
const onlineShippingCost = cartShipItem.reduce((sum, item) => {
const itemShippingCost = item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0);
return sum + (payOnDeliveryShipperIds.has(item.shipper) ? 0 : itemShippingCost);
}, 0);
const shippingCostOnDelivery = totalShippingCost - onlineShippingCost;
const price = { const price = {
shipping_cost: totalShippingCost, shipping_cost: onlineShippingCost,
// process_cost: totalShippingCost, shipping_cost_on_delivery: shippingCostOnDelivery,
process_cost: 0, process_cost: 0,
cart_retail_price: cart.retail_price, cart_retail_price: cart.retail_price,
// cart_payable_price: cart.payable_price - cart.coupon_discount,
cart_payable_price: cart.payable_price, cart_payable_price: cart.payable_price,
total_discount: cart.total_discount, total_discount: cart.total_discount,
coupon_discount: cart.coupon_discount, coupon_discount: cart.coupon_discount,
// total_payable_price: cart.payable_price + totalShippingCost * 2, total_payable_price: cart.isWholeSale ? cart.retail_price + onlineShippingCost : cart.payable_price + onlineShippingCost,
total_payable_price: cart.isWholeSale ? cart.retail_price + totalShippingCost : cart.payable_price + totalShippingCost,
}; };
const items = cart.items; const items = cart.items;
@@ -357,6 +366,7 @@ class PaymentService {
const priceDetails: Partial<IPriceDetails> = { const priceDetails: Partial<IPriceDetails> = {
shipping_cost: price.shipping_cost, shipping_cost: price.shipping_cost,
shipping_cost_on_delivery: price.shipping_cost_on_delivery,
process_cost: price.process_cost, process_cost: price.process_cost,
total_retail_price: price.cart_retail_price, total_retail_price: price.cart_retail_price,
total_discount: price.total_discount, total_discount: price.total_discount,
@@ -1,5 +1,5 @@
import { Expose, Type } from "class-transformer"; import { Expose, Type } from "class-transformer";
import { ArrayMinSize, IsArray, IsNotEmpty, IsNumber, IsString, ValidateNested } from "class-validator"; import { ArrayMinSize, IsArray, IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs"; import { ApiProperty } from "../../../common/decorator/swggerDocs";
@@ -71,4 +71,14 @@ export class CreateShipProvidersDTO {
@IsNumber() @IsNumber()
@ApiProperty({ type: "number", description: "Delivery time in days", example: 3 }) @ApiProperty({ type: "number", description: "Delivery time in days", example: 3 })
deliveryTime: number; deliveryTime: number;
@Expose()
@IsOptional()
@IsBoolean()
@ApiProperty({
type: "boolean",
description: "If true, delivery fee must be paid at delivery time instead of checkout",
example: false,
})
payDeliveryFeeOnDelivery?: boolean;
} }
+6
View File
@@ -41,6 +41,9 @@ class ShipperInfoDTO {
@Expose() @Expose()
totalShippingCost: number; totalShippingCost: number;
@Expose()
payDeliveryFeeOnDelivery: boolean;
@Expose() @Expose()
@Type(() => ShipperItemsDTO) @Type(() => ShipperItemsDTO)
items: ShipperItemsDTO[]; items: ShipperItemsDTO[];
@@ -89,4 +92,7 @@ export class ShipmentMethodDTO {
@Expose() @Expose()
deliveryType: string; deliveryType: string;
@Expose()
payDeliveryFeeOnDelivery: boolean;
} }
@@ -7,6 +7,7 @@ export interface IShipmentProviders {
costs: IShipmentCost[]; costs: IShipmentCost[];
deliveryType: DeliveryType; deliveryType: DeliveryType;
deliveryTime: number; deliveryTime: number;
payDeliveryFeeOnDelivery: boolean;
deleted: boolean; deleted: boolean;
} }
export interface IShipmentCost { export interface IShipmentCost {
@@ -27,6 +27,7 @@ const ShipmentSchema = new Schema<IShipmentProviders>(
costs: [ShipmentCostSchema], costs: [ShipmentCostSchema],
deliveryType: { type: String, enum: DeliveryType, required: true }, deliveryType: { type: String, enum: DeliveryType, required: true },
deliveryTime: { type: Number, required: true }, deliveryTime: { type: Number, required: true },
payDeliveryFeeOnDelivery: { type: Boolean, default: false },
deleted: { type: Boolean, default: false }, deleted: { type: Boolean, default: false },
}, },
{ timestamps: true, toJSON: { versionKey: false }, _id: false, id: false }, { timestamps: true, toJSON: { versionKey: false }, _id: false, id: false },
+20 -6
View File
@@ -35,7 +35,7 @@ class ShipmentService {
//################################################################### //###################################################################
//################################################################### //###################################################################
async createShipmentProviderS(createDto: CreateShipProvidersDTO) { async createShipmentProviderS(createDto: CreateShipProvidersDTO) {
const existShipper = await this.shipmentRepo.model.exists({ name: createDto.name }); const existShipper = await this.shipmentRepo.model.exists({ name: createDto.name, deleted: false });
if (existShipper) throw new BadRequestError(CommonMessage.DuplicateName); if (existShipper) throw new BadRequestError(CommonMessage.DuplicateName);
const deliveryType = this.getDeliveryType(createDto.deliveryTime); const deliveryType = this.getDeliveryType(createDto.deliveryTime);
@@ -48,7 +48,7 @@ class ShipmentService {
//################################################################### //###################################################################
async updateShipmentProvider(shipmentId: number, updateDto: UpdateShipmentProviderDTO) { async updateShipmentProvider(shipmentId: number, updateDto: UpdateShipmentProviderDTO) {
const existShipper = await this.shipmentRepo.model.exists({ name: updateDto.name, _id: { $ne: shipmentId } }); const existShipper = await this.shipmentRepo.model.exists({ name: updateDto.name, _id: { $ne: shipmentId }, deleted: false });
if (existShipper) throw new BadRequestError(CommonMessage.DuplicateName); if (existShipper) throw new BadRequestError(CommonMessage.DuplicateName);
if (updateDto.deliveryTime) { if (updateDto.deliveryTime) {
@@ -157,6 +157,7 @@ class ShipmentService {
shipperName: shipmentProvider.name, shipperName: shipmentProvider.name,
shippingDaysRange: this.calculateShippingDays(shipmentProvider.deliveryType, shipmentProvider.deliveryTime), shippingDaysRange: this.calculateShippingDays(shipmentProvider.deliveryType, shipmentProvider.deliveryTime),
totalShippingCost, totalShippingCost,
payDeliveryFeeOnDelivery: shipmentProvider.payDeliveryFeeOnDelivery ?? false,
items: shipperItems, items: shipperItems,
}); });
} }
@@ -230,15 +231,25 @@ class ShipmentService {
})); }));
if (existingCartShipmentItem) { if (existingCartShipmentItem) {
// update the existing CartShipmentItem const totalSellingPrice = shipmentItems.reduce((acc, item) => acc + item.selling_price * item.quantity, 0);
const totalRetailPrice = shipmentItems.reduce((acc, item) => acc + item.retail_price * item.quantity, 0);
const totalShipmentCost = shipmentItems.reduce((acc, item) => acc + item.shipmentCost, 0);
const onlineShipmentCost = selectedShipper.payDeliveryFeeOnDelivery ? 0 : totalShipmentCost;
existingCartShipmentItem.shipmentItems = shipmentItems; existingCartShipmentItem.shipmentItems = shipmentItems;
existingCartShipmentItem.shipper = selectedShipper.shipperId; existingCartShipmentItem.shipper = selectedShipper.shipperId;
existingCartShipmentItem.totalSellingPrice = totalSellingPrice;
existingCartShipmentItem.totalRetailPrice = totalRetailPrice;
existingCartShipmentItem.totalShipmentCost = totalShipmentCost;
existingCartShipmentItem.totalPaymentPrice = totalSellingPrice + onlineShipmentCost;
existingCartShipmentItem.itemsCount = shipmentItems.reduce((acc, item) => acc + item.quantity, 0);
await existingCartShipmentItem.save(); await existingCartShipmentItem.save();
} else { } else {
const totalSellingPrice = shipmentItems.reduce((acc, item) => acc + item.selling_price * item.quantity, 0); const totalSellingPrice = shipmentItems.reduce((acc, item) => acc + item.selling_price * item.quantity, 0);
const totalRetailPrice = shipmentItems.reduce((acc, item) => acc + item.retail_price * item.quantity, 0); const totalRetailPrice = shipmentItems.reduce((acc, item) => acc + item.retail_price * item.quantity, 0);
const totalShipmentCost = shipmentItems.reduce((acc, item) => acc + item.shipmentCost, 0); const totalShipmentCost = shipmentItems.reduce((acc, item) => acc + item.shipmentCost, 0);
const onlineShipmentCost = selectedShipper.payDeliveryFeeOnDelivery ? 0 : totalShipmentCost;
// create a new CartShipmentItem // create a new CartShipmentItem
await this.cartShipItemRepo.model.create({ await this.cartShipItemRepo.model.create({
@@ -249,7 +260,7 @@ class ShipmentService {
totalSellingPrice, totalSellingPrice,
totalRetailPrice, totalRetailPrice,
totalShipmentCost, totalShipmentCost,
totalPaymentPrice: totalShipmentCost + totalSellingPrice, totalPaymentPrice: totalSellingPrice + onlineShipmentCost,
itemsCount: shipmentItems.reduce((acc, item) => acc + item.quantity, 0), itemsCount: shipmentItems.reduce((acc, item) => acc + item.quantity, 0),
}); });
} }
@@ -261,12 +272,15 @@ class ShipmentService {
(sum, item) => sum + item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0), (sum, item) => sum + item.shipmentItems.reduce((subSum, shipmentItem) => subSum + shipmentItem.shipmentCost, 0),
0, 0,
); );
const onlineShippingCost = cartShipmentItems.reduce((sum, item) => sum + (item.totalPaymentPrice - item.totalSellingPrice), 0);
return { return {
cartShipmentItems: cartShipmentItems, cartShipmentItems: cartShipmentItems,
shippingCost: totalShippingCost, shippingCost: totalShippingCost,
processCost: totalShippingCost, onlineShippingCost,
payable_price: cart.payable_price + totalShippingCost * 2, shippingCostOnDelivery: totalShippingCost - onlineShippingCost,
processCost: onlineShippingCost,
payable_price: cart.payable_price + onlineShippingCost,
}; };
} }