This commit is contained in:
shafi54 2026-02-06 19:35:23 +05:30
parent 09e1067b69
commit 3d44171f06
11 changed files with 210 additions and 249 deletions

View file

@ -1,6 +1,7 @@
# DATABASE_URL=postgresql://postgres:postgres_shafi_password@57.128.212.174:6432/meatfarmer #technocracy ENV_MODE=PROD
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/ PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090 PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
PHONE_PE_CLIENT_VERSION=1 PHONE_PE_CLIENT_VERSION=1
@ -20,8 +21,8 @@ S3_URL=https://da9b1aa7c1951c23e2c0c3246ba68a58.r2.cloudflarestorage.com
S3_BUCKET_NAME=meatfarmer S3_BUCKET_NAME=meatfarmer
EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK- EXPO_ACCESS_TOKEN=Asvpy8cByRh6T4ksnWScO6PLcio2n35-BwES5zK-
JWT_SECRET=my_meatfarmer_jwt_secret_key JWT_SECRET=my_meatfarmer_jwt_secret_key
# REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379 # REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379
REDIS_URL=redis://default:redis_shafi_password@5.223.55.14:6379 REDIS_URL=redis://default:redis_shafi_password@57.128.212.174:6379
APP_URL=http://localhost:4000 APP_URL=http://localhost:4000
RAZORPAY_KEY=rzp_test_RdCBBUJ56NLaJK RAZORPAY_KEY=rzp_test_RdCBBUJ56NLaJK
RAZORPAY_SECRET=namEwKBE1ypWxH0QDVg6fWOe RAZORPAY_SECRET=namEwKBE1ypWxH0QDVg6fWOe
@ -32,6 +33,6 @@ DELIVERY_CHARGE=20
# Telegram Configuration # Telegram Configuration
TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
TELEGRAM_CHAT_IDS= TELEGRAM_CHAT_IDS=5147760058
# TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U # TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
# TELEGRAM_CHAT_IDS=-5075171894 # TELEGRAM_CHAT_IDS=-5075171894

File diff suppressed because one or more lines are too long

View file

@ -12,7 +12,7 @@ interface PendingPaymentRecord {
export const createPaymentNotification = (record: PendingPaymentRecord) => { export const createPaymentNotification = (record: PendingPaymentRecord) => {
// Construct message from record data // Construct message from record data
const message = `Payment pending for order ORD${record.order.readableId.toString().padStart(3, '0')}. Please complete before orders close time.`; const message = `Payment pending for order ORD${record.order.id}. Please complete before orders close time.`;
// TODO: Implement notification sending logic using record.order.userId, record.order.id, message // TODO: Implement notification sending logic using record.order.userId, record.order.id, message
console.log(`Sending notification to user ${record.order.userId} for order ${record.order.id}: ${message}`); console.log(`Sending notification to user ${record.order.userId} for order ${record.order.id}: ${message}`);

View file

@ -45,3 +45,5 @@ export const deliveryCharge = Number(process.env.DELIVERY_CHARGE as string);
export const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN as string; export const telegramBotToken = process.env.TELEGRAM_BOT_TOKEN as string;
export const telegramChatIds = (process.env.TELEGRAM_CHAT_IDS as string)?.split(',').map(id => id.trim()) || []; export const telegramChatIds = (process.env.TELEGRAM_CHAT_IDS as string)?.split(',').map(id => id.trim()) || [];
export const isDevMode = (process.env.ENV_MODE as string) === 'dev';

View file

@ -1,47 +1,52 @@
import { db } from '../db/db_index';
import { orders } from '../db/schema';
import redisClient from './redis-client'; import redisClient from './redis-client';
import { sendTelegramMessage } from './telegram-service'; import { sendTelegramMessage } from './telegram-service';
import { inArray } from 'drizzle-orm';
const ORDER_CHANNEL = 'orders:placed'; const ORDER_CHANNEL = 'orders:placed';
interface SimplifiedOrderItem { interface OrderIdMessage {
productName: string; orderIds: number[];
quantity: number;
} }
interface SimplifiedOrder { const formatDateTime = (dateStr: string | null | undefined): string => {
deliveryTime: string; if (!dateStr) return 'N/A';
orderPlaceTime: string; return new Date(dateStr).toLocaleString('en-IN', {
totalAmount: number; dateStyle: 'medium',
orderItems: SimplifiedOrderItem[]; timeStyle: 'short',
} });
};
interface FormattedOrderData { const formatOrderMessageWithFullData = (ordersData: any[]): string => {
orders: SimplifiedOrder[];
}
/**
* Format order details for Telegram message
* @param orderData The order data to format
* @returns Formatted message string
*/
const formatOrderMessage = (orderData: FormattedOrderData): string => {
let message = '🛒 <b>New Order Placed</b>\n\n'; let message = '🛒 <b>New Order Placed</b>\n\n';
orderData.orders.forEach((order, index) => { ordersData.forEach((order, index) => {
if (orderData.orders.length > 1) { message += `<b>Order ${order.id}</b>\n`;
message += `<b>Order ${index + 1}</b>\n`;
}
message += '📦 <b>Items:</b>\n'; message += '📦 <b>Items:</b>\n';
order.orderItems.forEach(item => { order.orderItems?.forEach((item: any) => {
message += `${item.productName} x${item.quantity}\n`; message += `${item.product?.name || 'Unknown'} x${item.quantity}\n`;
}); });
message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\n`; message += `\n💰 <b>Total:</b> ₹${order.totalAmount}\n`;
message += `🚚 <b>Delivery:</b> ${new Date(order.deliveryTime).toLocaleString()}\n`;
message += `⏰ <b>Ordered:</b> ${new Date(order.orderPlaceTime).toLocaleString()}\n`;
if (index < orderData.orders.length - 1) { message += `🚚 <b>Delivery:</b> ${
order.isFlashDelivery ? 'Flash Delivery' : formatDateTime(order.slot?.deliveryTime)
}\n`;
message += `\n📍 <b>Address:</b>\n`;
message += ` ${order.address?.name || 'N/A'}\n`;
message += ` ${order.address?.addressLine1 || ''}\n`;
if (order.address?.addressLine2) {
message += ` ${order.address.addressLine2}\n`;
}
message += ` ${order.address?.city || ''}, ${order.address?.state || ''} - ${order.address?.pincode || ''}\n`;
if (order.address?.phone) {
message += ` 📞 ${order.address.phone}\n`;
}
if (index < ordersData.length - 1) {
message += '\n---\n\n'; message += '\n---\n\n';
} }
}); });
@ -59,14 +64,22 @@ export const startOrderHandler = async (): Promise<void> => {
await redisClient.subscribe(ORDER_CHANNEL, async (message: string) => { await redisClient.subscribe(ORDER_CHANNEL, async (message: string) => {
try { try {
const orderDetails = JSON.parse(message); const { orderIds }: OrderIdMessage = JSON.parse(message);
console.log('New order received, sending to Telegram...'); console.log('New order received, sending to Telegram...');
const telegramMessage = formatOrderMessage(orderDetails); const ordersData = await db.query.orders.findMany({
where: inArray(orders.id, orderIds),
with: {
address: true,
orderItems: { with: { product: true } },
slot: true,
},
});
const telegramMessage = formatOrderMessageWithFullData(ordersData);
await sendTelegramMessage(telegramMessage); await sendTelegramMessage(telegramMessage);
} catch (error) { } catch (error) {
console.error('Failed to process order message:', error); console.error('Failed to process order message:', error);
// Still try to send raw message to Telegram if parsing fails
await sendTelegramMessage(`⚠️ Error parsing order: ${message}`); await sendTelegramMessage(`⚠️ Error parsing order: ${message}`);
} }
}); });
@ -90,11 +103,7 @@ export const stopOrderHandler = async (): Promise<void> => {
} }
}; };
/** export const publishOrder = async (orderDetails: OrderIdMessage): Promise<boolean> => {
* Publish order details to the queue
* @param orderDetails Full order details to publish
*/
export const publishOrder = async (orderDetails: FormattedOrderData): Promise<boolean> => {
try { try {
const message = JSON.stringify(orderDetails); const message = JSON.stringify(orderDetails);
await redisClient.publish(ORDER_CHANNEL, message); await redisClient.publish(ORDER_CHANNEL, message);
@ -105,40 +114,13 @@ export const publishOrder = async (orderDetails: FormattedOrderData): Promise<bo
} }
}; };
/**
* Mould raw order data into simplified format and publish
* @param createdOrders Array of created orders from the database
* @param ordersBySlot Map of slotId to items with product info
* @returns Promise<boolean> indicating success or failure
*/
export const publishFormattedOrder = async ( export const publishFormattedOrder = async (
createdOrders: any[], createdOrders: any[],
ordersBySlot: Map<number | null, any[]> ordersBySlot: Map<number | null, any[]>
): Promise<boolean> => { ): Promise<boolean> => {
try { try {
const simplifiedOrders: SimplifiedOrder[] = createdOrders.map(order => { const orderIds = createdOrders.map(order => order.id);
// Get items for this order from ordersBySlot return await publishOrder({ orderIds });
const slotItems = ordersBySlot.get(order.slotId) || [];
// Map items to simplified format
const orderItems: SimplifiedOrderItem[] = slotItems.map(item => ({
productName: item.product?.name || 'Unknown Product',
quantity: item.quantity,
}));
return {
deliveryTime: order.slot?.deliveryTime?.toISOString() || order.deliveryDate?.toISOString() || new Date().toISOString(),
orderPlaceTime: order.createdAt?.toISOString() || new Date().toISOString(),
totalAmount: parseFloat(order.totalAmount?.toString() || '0'),
orderItems,
};
});
const formattedData: FormattedOrderData = {
orders: simplifiedOrders,
};
return await publishOrder(formattedData);
} catch (error) { } catch (error) {
console.error('Failed to format and publish order:', error); console.error('Failed to format and publish order:', error);
return false; return false;

View file

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { telegramBotToken, telegramChatIds } from './env-exporter'; import { isDevMode, telegramBotToken, telegramChatIds } from './env-exporter';
const BOT_TOKEN = telegramBotToken; const BOT_TOKEN = telegramBotToken;
const CHAT_IDS = telegramChatIds; const CHAT_IDS = telegramChatIds;
@ -8,9 +8,12 @@ const TELEGRAM_API_URL = `https://api.telegram.org/bot${BOT_TOKEN}`;
/** /**
* Send a message to Telegram bot * Send a message to Telegram bot
* @param message The message text to send * @param message The message text to send
* @returns Promise<boolean> indicating success or failure * @returns Promise<boolean | null> indicating success, failure, or null if dev mode
*/ */
export const sendTelegramMessage = async (message: string): Promise<boolean> => { export const sendTelegramMessage = async (message: string): Promise<boolean | null> => {
if (isDevMode) {
return null;
}
try { try {
const results = await Promise.all( const results = await Promise.all(
CHAT_IDS.map(async (chatId) => { CHAT_IDS.map(async (chatId) => {
@ -35,6 +38,9 @@ export const sendTelegramMessage = async (message: string): Promise<boolean> =>
}) })
); );
console.log('sent telegram message successfully')
// Return true if at least one message was sent successfully // Return true if at least one message was sent successfully
return results.some((result) => result); return results.some((result) => result);
} catch (error) { } catch (error) {

View file

@ -50,7 +50,7 @@ export const cancelledOrdersRouter = router({
const refund = status.order.refunds[0]; const refund = status.order.refunds[0];
return { return {
id: status.order.id, id: status.order.id,
readableId: status.order.readableId, readableId: status.order.id,
customerName: `${status.order.user.name}`, customerName: `${status.order.user.name}`,
address: `${status.order.address.addressLine1}, ${status.order.address.city}`, address: `${status.order.address.addressLine1}, ${status.order.address.city}`,
totalAmount: status.order.totalAmount, totalAmount: status.order.totalAmount,
@ -134,7 +134,7 @@ export const cancelledOrdersRouter = router({
// Format the response similar to the getAll method // Format the response similar to the getAll method
const formattedOrder = { const formattedOrder = {
id: order.id, id: order.id,
readableId: order.readableId, readableId: order.id,
customerName: order.user.name, customerName: order.user.name,
address: `${order.address.addressLine1}${order.address.addressLine2 ? ', ' + order.address.addressLine2 : ''}, ${order.address.city}, ${order.address.state} ${order.address.pincode}`, address: `${order.address.addressLine1}${order.address.addressLine2 ? ', ' + order.address.addressLine2 : ''}, ${order.address.city}, ${order.address.state} ${order.address.pincode}`,
totalAmount: order.totalAmount, totalAmount: order.totalAmount,

View file

@ -151,7 +151,7 @@ export const orderRouter = router({
return { return {
id: orderData.id, id: orderData.id,
readableId: orderData.readableId, readableId: orderData.id,
customerName: `${orderData.user.name}`, customerName: `${orderData.user.name}`,
customerEmail: orderData.user.email, customerEmail: orderData.user.email,
customerMobile: orderData.user.mobile, customerMobile: orderData.user.mobile,
@ -302,7 +302,7 @@ export const orderRouter = router({
return { return {
id: orderData.id, id: orderData.id,
readableId: orderData.readableId, readableId: orderData.id,
customerName: `${orderData.user.name}`, customerName: `${orderData.user.name}`,
customerEmail: orderData.user.email, customerEmail: orderData.user.email,
customerMobile: orderData.user.mobile, customerMobile: orderData.user.mobile,
@ -513,7 +513,7 @@ export const orderRouter = router({
return { return {
id: order.id, id: order.id,
readableId: order.readableId, readableId: order.id,
customerName: order.user.name, customerName: order.user.name,
address: `${order.address.addressLine1}${ address: `${order.address.addressLine1}${
order.address.addressLine2 ? `, ${order.address.addressLine2}` : "" order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
@ -607,7 +607,7 @@ export const orderRouter = router({
return { return {
orderId: order.id.toString(), orderId: order.id.toString(),
readableId: order.readableId, readableId: order.id,
customerName: order.user.name, customerName: order.user.name,
address: `${order.address.addressLine1}${ address: `${order.address.addressLine1}${
order.address.addressLine2 ? `, ${order.address.addressLine2}` : "" order.address.addressLine2 ? `, ${order.address.addressLine2}` : ""
@ -783,10 +783,10 @@ export const orderRouter = router({
.sort((first, second) => first.id - second.id); .sort((first, second) => first.id - second.id);
dayjs.extend(utc); dayjs.extend(utc);
return { return {
id: order.id, id: order.id,
orderId: order.id.toString(), orderId: order.id.toString(),
readableId: order.readableId, readableId: order.id,
customerName: order.user.name, customerName: order.user.name,
address: `${order.address.addressLine1}${ address: `${order.address.addressLine1}${
order.address.addressLine2 order.address.addressLine2
? `, ${order.address.addressLine2}` ? `, ${order.address.addressLine2}`

View file

@ -275,7 +275,7 @@ export const vendorSnippetsRouter = router({
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0); const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
return { return {
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`, orderId: `ORD${order.id}`,
orderDate: order.createdAt.toISOString(), orderDate: order.createdAt.toISOString(),
customerName: order.user.name, customerName: order.user.name,
totalAmount: orderTotal, totalAmount: orderTotal,
@ -434,7 +434,7 @@ export const vendorSnippetsRouter = router({
const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0); const orderTotal = products.reduce((sum, p) => sum + p.subtotal, 0);
return { return {
orderId: `ORD${order.readableId.toString().padStart(3, '0')}`, orderId: `ORD${order.id}`,
orderDate: order.createdAt.toISOString(), orderDate: order.createdAt.toISOString(),
customerName: order.user.name, customerName: order.user.name,
totalAmount: orderTotal, totalAmount: orderTotal,

View file

@ -8,7 +8,6 @@ import {
addresses, addresses,
productInfo, productInfo,
paymentInfoTable, paymentInfoTable,
keyValStore,
coupons, coupons,
couponUsage, couponUsage,
payments, payments,
@ -67,7 +66,6 @@ const validateAndGetCoupon = async (
const applyDiscountToOrder = ( const applyDiscountToOrder = (
orderTotal: number, orderTotal: number,
totalAmount: number,
appliedCoupon: typeof coupons.$inferSelect | null, appliedCoupon: typeof coupons.$inferSelect | null,
proportion: number proportion: number
) => { ) => {
@ -136,14 +134,9 @@ const placeOrderUtil = async (params: {
const minOrderValue = constants[CONST_KEYS.minRegularOrderValue] ?? 0; const minOrderValue = constants[CONST_KEYS.minRegularOrderValue] ?? 0;
const deliveryCharge = constants[CONST_KEYS.deliveryCharge] ?? 0; const deliveryCharge = constants[CONST_KEYS.deliveryCharge] ?? 0;
const flashFreeDeliveryThreshold = constants[CONST_KEYS.flashFreeDeliveryThreshold] ?? 0;
const flashDeliveryCharge = constants[CONST_KEYS.flashDeliveryCharge] ?? 0;
const freeThreshold = params.isFlash ? flashFreeDeliveryThreshold : minOrderValue;
const charge = params.isFlash ? flashDeliveryCharge : deliveryCharge;
const orderGroupId = `${Date.now()}-${userId}`; const orderGroupId = `${Date.now()}-${userId}`;
// Validate address belongs to user
const address = await db.query.addresses.findFirst({ const address = await db.query.addresses.findFirst({
where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)), where: and(eq(addresses.userId, userId), eq(addresses.id, addressId)),
}); });
@ -151,7 +144,6 @@ const placeOrderUtil = async (params: {
throw new ApiError("Invalid address", 400); throw new ApiError("Invalid address", 400);
} }
// Group items by slotId and validate products
const ordersBySlot = new Map< const ordersBySlot = new Map<
number | null, number | null,
Array<{ Array<{
@ -176,9 +168,7 @@ const placeOrderUtil = async (params: {
ordersBySlot.get(item.slotId)!.push({ ...item, product }); ordersBySlot.get(item.slotId)!.push({ ...item, product });
} }
// Validate flash delivery product availability if isFlash is true
if (params.isFlash) { if (params.isFlash) {
// Check if all products are flash-available (flashPrice is optional - fallback to regular price)
for (const item of selectedItems) { for (const item of selectedItems) {
const product = await db.query.productInfo.findFirst({ const product = await db.query.productInfo.findFirst({
where: eq(productInfo.id, item.productId), where: eq(productInfo.id, item.productId),
@ -186,50 +176,102 @@ const placeOrderUtil = async (params: {
if (!product?.isFlashAvailable) { if (!product?.isFlashAvailable) {
throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400); throw new ApiError(`Product ${item.productId} is not available for flash delivery`, 400);
} }
// Note: flashPrice validation removed - frontend falls back to regular price if not set
} }
} }
// Calculate total amount across all orders
let totalAmount = 0; let totalAmount = 0;
for (const [slotId, items] of ordersBySlot) { for (const [slotId, items] of ordersBySlot) {
const orderTotal = items.reduce( const orderTotal = items.reduce(
(sum, item) => { (sum, item) => {
const itemPrice = params.isFlash const itemPrice = params.isFlash
? parseFloat((item.product.flashPrice || item.product.price).toString()) ? parseFloat((item.product.flashPrice || item.product.price).toString())
: parseFloat(item.product.price.toString()); : parseFloat(item.product.price.toString());
return sum + itemPrice * item.quantity; return sum + itemPrice * item.quantity;
}, },
0 0
); );
totalAmount += orderTotal; totalAmount += orderTotal;
} }
const appliedCoupon = await validateAndGetCoupon(couponId, userId, totalAmount); const appliedCoupon = await validateAndGetCoupon(couponId, userId, totalAmount);
// Calculate delivery charge
const expectedDeliveryCharge = const expectedDeliveryCharge =
totalAmount < minOrderValue ? deliveryCharge : 0; totalAmount < minOrderValue ? deliveryCharge : 0;
// Calculate total amount including delivery charge for first order
const totalWithDelivery = totalAmount + expectedDeliveryCharge; const totalWithDelivery = totalAmount + expectedDeliveryCharge;
// Create orders in transaction type OrderData = {
const createdOrders = await db.transaction(async (tx) => { order: Omit<typeof orders.$inferInsert, "id">;
// Get and increment readable order ID counter orderItems: Omit<typeof orderItems.$inferInsert, "id">[];
let currentReadableId = 1; orderStatus: Omit<typeof orderStatus.$inferInsert, "id">;
const existing = await tx.query.keyValStore.findFirst({ };
where: eq(keyValStore.key, CONST_KEYS.readableOrderId),
});
if (existing?.value != null) {
const storedValue = existing.value;
const parsedValue = typeof storedValue === 'number'
? storedValue
: parseInt(String(storedValue), 10) || 0;
currentReadableId = parsedValue + 1;
}
// Create shared payment info for all orders const ordersData: OrderData[] = [];
let isFirstOrder = true;
for (const [slotId, items] of ordersBySlot) {
const orderTotal = items.reduce(
(sum, item) => {
const itemPrice = params.isFlash
? parseFloat((item.product.flashPrice || item.product.price).toString())
: parseFloat(item.product.price.toString());
return sum + itemPrice * item.quantity;
},
0
);
const orderGroupProportion = orderTotal / totalAmount;
const orderTotalAmount = isFirstOrder ? totalWithDelivery : totalAmount;
const { finalOrderTotal: finalOrderAmount } = applyDiscountToOrder(
orderTotalAmount,
appliedCoupon,
orderGroupProportion
);
const order: Omit<typeof orders.$inferInsert, "id"> = {
userId,
addressId,
slotId: params.isFlash ? null : slotId,
isCod: paymentMethod === "cod",
isOnlinePayment: paymentMethod === "online",
paymentInfoId: null,
totalAmount: finalOrderAmount.toString(),
deliveryCharge: isFirstOrder ? expectedDeliveryCharge.toString() : "0",
readableId: -1,
userNotes: userNotes || null,
orderGroupId,
orderGroupProportion: orderGroupProportion.toString(),
isFlashDelivery: params.isFlash,
};
const orderItemsData: Omit<typeof orderItems.$inferInsert, "id">[] = items.map(
(item) => ({
orderId: 0,
productId: item.productId,
quantity: item.quantity.toString(),
price: params.isFlash
? item.product.flashPrice || item.product.price
: item.product.price,
discountedPrice: (
params.isFlash
? item.product.flashPrice || item.product.price
: item.product.price
).toString(),
})
);
const orderStatusData: Omit<typeof orderStatus.$inferInsert, "id"> = {
userId,
orderId: 0,
paymentStatus: paymentMethod === "cod" ? "cod" : "pending",
};
ordersData.push({ order, orderItems: orderItemsData, orderStatus: orderStatusData });
isFirstOrder = false;
}
const createdOrders = await db.transaction(async (tx) => {
let sharedPaymentInfoId: number | null = null; let sharedPaymentInfoId: number | null = null;
if (paymentMethod === "online") { if (paymentMethod === "online") {
const [paymentInfo] = await tx const [paymentInfo] = await tx
@ -243,97 +285,32 @@ const placeOrderUtil = async (params: {
sharedPaymentInfoId = paymentInfo.id; sharedPaymentInfoId = paymentInfo.id;
} }
const createdOrders: any[] = []; const ordersToInsert: Omit<typeof orders.$inferInsert, "id">[] = ordersData.map(
let isFirstOrder = true; (od) => ({
...od.order,
// Create separate order for each slot group paymentInfoId: sharedPaymentInfoId,
for (const [slotId, items] of ordersBySlot) {
// Calculate order-specific total
const orderTotal = items.reduce(
(sum, item) => {
const itemPrice = params.isFlash
? parseFloat((item.product.flashPrice || item.product.price).toString())
: parseFloat(item.product.price.toString());
return sum + itemPrice * item.quantity;
},
0
);
const orderGroupProportion = orderTotal / totalAmount;
const orderTotalAmount = isFirstOrder ? totalWithDelivery : totalAmount;
// const { finalOrderTotal } = applyDiscountToOrder(
// orderTotal,
// totalAmount,
// appliedCoupon,
// orderGroupProportion
// );
const { finalOrderTotal: finalOrderAmount } = applyDiscountToOrder(
orderTotal,
orderTotalAmount,
appliedCoupon,
orderGroupProportion
);
console.log({orderTotalAmount, finalOrderAmount})
// Create order record
const [order] = await tx
.insert(orders)
.values({
userId,
addressId,
slotId: params.isFlash ? null : slotId, // No slot assignment for flash delivery
isCod: paymentMethod === "cod",
isOnlinePayment: paymentMethod === "online",
paymentInfoId: sharedPaymentInfoId,
totalAmount: finalOrderAmount.toString(),
deliveryCharge: isFirstOrder ? expectedDeliveryCharge.toString() : '0',
readableId: currentReadableId++,
userNotes: userNotes || null,
orderGroupId,
orderGroupProportion: orderGroupProportion.toString(),
isFlashDelivery: params.isFlash,
})
.returning();
// Create order items
const orderItemsData = items.map((item) => ({
orderId: order.id as number,
productId: item.productId,
quantity: item.quantity.toString(),
price: params.isFlash ? (item.product.flashPrice || item.product.price) : item.product.price,
discountedPrice: (params.isFlash ? (item.product.flashPrice || item.product.price) : item.product.price).toString(),
}));
await tx.insert(orderItems).values(orderItemsData);
// Create order status
await tx.insert(orderStatus).values({
userId,
orderId: order.id as number,
paymentStatus: paymentMethod === "cod" ? "cod" : "pending",
});
createdOrders.push(order);
isFirstOrder = false;
}
// Update readable ID counter
await tx
.insert(keyValStore)
.values({
key: CONST_KEYS.readableOrderId,
value: currentReadableId,
}) })
.onConflictDoUpdate({ );
target: keyValStore.key,
set: { value: currentReadableId }, const insertedOrders = await tx.insert(orders).values(ordersToInsert).returning();
});
const allOrderItems: Omit<typeof orderItems.$inferInsert, "id">[] = [];
const allOrderStatuses: Omit<typeof orderStatus.$inferInsert, "id">[] = [];
insertedOrders.forEach((order, index) => {
const od = ordersData[index];
od.orderItems.forEach((item) => {
allOrderItems.push({ ...item, orderId: order.id as number });
});
allOrderStatuses.push({
...od.orderStatus,
orderId: order.id as number,
});
});
await tx.insert(orderItems).values(allOrderItems);
await tx.insert(orderStatus).values(allOrderStatuses);
// Create Razorpay order for online payments
if (paymentMethod === "online" && sharedPaymentInfoId) { if (paymentMethod === "online" && sharedPaymentInfoId) {
const razorpayOrder = await RazorpayPaymentService.createOrder( const razorpayOrder = await RazorpayPaymentService.createOrder(
sharedPaymentInfoId, sharedPaymentInfoId,
@ -346,37 +323,33 @@ const placeOrderUtil = async (params: {
); );
} }
// Remove ordered items from cart return insertedOrders;
await tx.delete(cartItems).where(
and(
eq(cartItems.userId, userId),
inArray(
cartItems.productId,
selectedItems.map((item) => item.productId)
)
)
);
return createdOrders;
}); });
// Record single coupon usage if applied (regardless of number of orders) await db.delete(cartItems).where(
and(
eq(cartItems.userId, userId),
inArray(
cartItems.productId,
selectedItems.map((item) => item.productId)
)
)
);
if (appliedCoupon && createdOrders.length > 0) { if (appliedCoupon && createdOrders.length > 0) {
await db.insert(couponUsage).values({ await db.insert(couponUsage).values({
userId, userId,
couponId: appliedCoupon.id, couponId: appliedCoupon.id,
orderId: createdOrders[0].id as number, // Use first order ID orderId: createdOrders[0].id as number,
orderItemId: null, orderItemId: null,
usedAt: new Date(), usedAt: new Date(),
}); });
} }
// Send notifications for each order
for (const order of createdOrders) { for (const order of createdOrders) {
sendOrderPlacedNotification(userId, order.id.toString()); sendOrderPlacedNotification(userId, order.id.toString());
} }
// Publish order details to Redis Pub/Sub
await publishFormattedOrder(createdOrders, ordersBySlot); await publishFormattedOrder(createdOrders, ordersBySlot);
return { success: true, data: createdOrders }; return { success: true, data: createdOrders };
@ -540,7 +513,7 @@ export const orderRouter = router({
return { return {
id: order.id, id: order.id,
orderId: `ORD${order.readableId.toString().padStart(3, "0")}`, orderId: `ORD${order.id}`,
orderDate: order.createdAt.toISOString(), orderDate: order.createdAt.toISOString(),
deliveryStatus, deliveryStatus,
deliveryDate: order.slot?.deliveryTime.toISOString(), deliveryDate: order.slot?.deliveryTime.toISOString(),
@ -704,7 +677,7 @@ export const orderRouter = router({
return { return {
id: order.id, id: order.id,
orderId: `ORD${order.readableId.toString().padStart(3, "0")}`, orderId: `ORD${order.id}`,
orderDate: order.createdAt.toISOString(), orderDate: order.createdAt.toISOString(),
deliveryStatus, deliveryStatus,
deliveryDate: order.slot?.deliveryTime.toISOString(), deliveryDate: order.slot?.deliveryTime.toISOString(),
@ -739,11 +712,8 @@ export const orderRouter = router({
const userId = ctx.user.userId; const userId = ctx.user.userId;
const { id, reason } = input; const { id, reason } = input;
const readableId = Number(id);
console.log({id, reason}) console.log({id, reason})
// Check if order exists and belongs to user // Check if order exists and belongs to user
const order = await db.query.orders.findFirst({ const order = await db.query.orders.findFirst({
where: eq(orders.id, Number(id)), where: eq(orders.id, Number(id)),

View file

@ -63,8 +63,8 @@ const isDevMode = Constants.executionEnvironment !== "standalone";
// const BASE_API_URL = API_URL; // const BASE_API_URL = API_URL;
// const BASE_API_URL = 'http://10.0.2.2:4000'; // const BASE_API_URL = 'http://10.0.2.2:4000';
// const BASE_API_URL = 'http://192.168.100.101:4000'; // const BASE_API_URL = 'http://192.168.100.101:4000';
// const BASE_API_URL = 'http://192.168.1.7:4000'; const BASE_API_URL = 'http://192.168.1.3:4000';
let BASE_API_URL = "https://mf.freshyo.in"; // let BASE_API_URL = "https://mf.freshyo.in";
// let BASE_API_URL = 'http://192.168.100.104:4000'; // let BASE_API_URL = 'http://192.168.100.104:4000';
// let BASE_API_URL = 'http://192.168.29.176:4000'; // let BASE_API_URL = 'http://192.168.29.176:4000';